Commit Graph

3824 Commits

Author SHA1 Message Date
Drew T f9f446449e feat(claude_wave_packs): wire neighbor_ref into every pack, and resolve its names to the source spelling
playbook §2b has called neighbor_ref the biggest measured cost lever in the
wave since S68 (~20x token swing) and documented it as a MANUAL per-card
command wired into nothing — so it ran for approximately zero cards. Packs now
carry an ALREADY-MATCHED NEIGHBOURS block, same additive never-fail contract as
the past-attempt notes. First run: 30/30 targets had a matched neighbour.

It also shipped with a defect that would have silently un-done it:
neighbor_ref reports the SYMBOL-TABLE name, and for an unnamed function that is
Ghidra's FUN_8003a0e4 — which appears nowhere in src/*.c, where the function is
func_8003A0E4. An agent sent to read FUN_8003a0e4 finds nothing and concludes
there is no neighbour. _src_name resolves against the destination TU's own text,
falls back to the address, and shows the symbol-table spelling in parentheses.
Measured: 150 of 150 neighbour names needed resolving; 0 primary names remain
Ghidra-style. Checked against known-true cases first (resolves FUN_8003a0e4,
leaves func_8003A0E4 alone, leaves an unknown name untouched).

R61(b): the pack was asserting a name true of the symbol table and false of the
world the agent works in.
2026-09-03 20:08:27 -06:00
Drew T e059f85298 config(wave_exclude): pin the 4 §332 delay-slot walls wall_sweep names in main
wall_sweep --emit-exclude lists 9 fleet-wide; the list carried 5 of them.
func_8005D734, func_8005D8B4, func_8005ED4C and func_8005F450 each have a
%lo in a branch/jal delay slot — the second half of an assembler macro gcc
emits as ONE atomic insn, so no C can place it there. An agent handed one
returns a NEAR with an unexplainable tail, which is indistinguishable from a
hard function; the playbook measured a main wave spending 4 of 7 slots that way.
2026-09-03 20:03:15 -06:00
Drew T 90042c111d rules(R61): not-judged is not a verdict; a draft-judging tool must model the real pipeline
Drew ratified in-session after the S77 census: eight instrument defects, all one
shape — a tool asserting about a DRAFT what was true only of the HARNESS.
2026-09-03 20:01:28 -06:00
Drew T 9d15598b5a docs(phase-31): S77 FINAL checkpoint — 21 banked, self_decl_tu closed, the permuter yield curve, eight instrument defects 2026-09-03 19:51:02 -06:00
Drew T be966bf095 docs(cookbook): §479 the permuter's measured yield curve (one-shot at <=4, plateau above ~10); §480 a static blocker class the real pipeline removes is a phantom 2026-09-03 19:45:55 -06:00
Drew T a0c855648c feat(decomp): bank ov_SC01_084:func_80182A00 (§378 chain, 207 ins)
harvest_verify: verified 1 / failed 0, ef86fe1e403998a82ead42f4466ac4bc80f2c8d1
BYTE-IDENTICAL. The static probe had called this a `local_type' Blk16 conflict;
the real gate strips TU-provided typedefs and then named the true blocker.
2026-09-03 19:42:05 -06:00
Drew T 534979b4e7 plumb(ov_SC01_084): §378 self-caller casts for func_80182A00
harvest_verify named the step-2 signature exactly: `too few arguments to
function func_80182A00' at ov_SC01_084_jr_80182A00.c:534. 4 call sites cast.
Baseline green with NO draft substituted: ef86fe1e403998a82ead42f4466ac4bc80f2c8d1.
2026-09-03 19:41:45 -06:00
Drew T 337a040047 feat(decomp): bank main:func_80024054 via permuter ILS (DELAY-SLOT/2, 4 of 91)
Score 0 on cycle 1. gate_main: BANKED 1, 143dbb89f34491258bbc27810d0a12ec8b43a8dd BYTE-IDENTICAL.
2026-09-03 19:39:54 -06:00
Drew T a614d972c2 feat(decomp): bank main:func_80040DE8 via permuter ILS (REGALLOC-PERM/$t1>$v1, 2 of 347)
Score 0 on cycle 1. gate_main: BANKED 1, 143dbb89f34491258bbc27810d0a12ec8b43a8dd BYTE-IDENTICAL.
2026-09-03 19:28:55 -06:00
Drew T 94b528b54e feat(decomp): bank main:func_80021174 via permuter ILS (SCHEDULE-REORDER/2)
The residual was a two-instruction adjacent swap in the target's favour:

    idx 49  MINE lh   $a1, 0($sp)      TARGET sra $a2, $v1, 16
    idx 50  MINE sra  $a2, $v1, 16     TARGET lh  $a1, 0($sp)

Hand lever tried first and REFUTED by bytes: hoisting `a0 = a0 >> 16` above
the load is semantics-preserving (a0 is untouched in between) but scores
23 mismatched at 67/68 ins — it lets gcc fold an instruction away entirely.

permuter_ils --klass SCHEDULE reached score 0 on cycle 1. Its winning edit is
a clean C-level one: drop the `a1 = *(s16 *)sp;` temporary and inline the load
into both comparisons, which is what moves the sign-extend ahead of it.

gate_main: BANKED 1, 143dbb89f34491258bbc27810d0a12ec8b43a8dd BYTE-IDENTICAL.
2026-09-03 19:28:06 -06:00
Drew T b5751c7c1e docs(phase-31): S77 checkpoint — 17 banked, the self_decl_tu lane closed, six instrument defects
T11 4/7, T12 13 banked of a 34-draft pool, T13 R22 213/213 twice (a green
baseline before the overlay banks and again after all 17).

main REAL 895 -> 899, stubs 46 -> 42. Fleet stubs 82 -> 65, distinct-code
99.3% -> 99.4%, MAIN game-code 57.1% -> 57.3%.
2026-09-03 19:25:02 -06:00
Drew T c61c7ed93f docs(cookbook): §477 the self_decl_tu lane is mechanical (16/16 banked); §478 a verbatim draft is the strongest false signal a scoper can emit 2026-09-03 19:19:29 -06:00
Drew T c91c9dffee feat(decomp): parallel gate — 12 fns across 12 binaries (8 workers)
ov_SC03_111    func_80181344
  ov_SC01_006    func_8017FBCC
  ov_SC02_041    func_801832F8
  ov_SC03_124    func_8018095C
  ov_SC01_005    func_8017FBCC
  ov_SC04_002    func_80182CBC
  ov_SC03_105    func_8017F018
  ov_SC04_005    func_80185CEC
  ov_SC04_007    func_80182358
  ov_SC04_011    func_8018985C
  ov_SC05_018    func_80181294
  ov_SC05_003    func_80181720
2026-09-03 19:18:12 -06:00
Drew T 408f826f29 fix(blocker_probe): a verbatim draft is not a decompile
A §265 verbatim draft — the target's own asm in a file-scope __asm__ —
assembles to the bytes it was copied from, so BOTH of this tool's oracles emit
the strongest possible signal: static `none`, real cc1 `MATCH`. The routing
then reads "byte-correct body, nothing blocking it", the byte gate refuses it
for free, and progress.py moves by exactly zero.

Measured: of the 13 MATCH rows in the S77 overlay pool, SIX were verbatim
(md_MAIN_003 x3, md_MAIN_020, ov_SC05_005, ov_SC06_010). The whole 13-draft
cohort gated 0, and the probe had scoped it as the highest-value work
available.

S76 closed exactly this hole in gate_main, harvest_verify and
api_agent.prior_draft. This is the fourth consumer — and the one that SCOPES
the work, so it is the one whose blindness costs a session's plan. Uses the
gate's own detector (DP.is_verbatim_asm_draft) so the two cannot drift (R33),
and a VERBATIM row is excluded from the agreement arithmetic rather than
counted as a match.

NEGATIVE CONTROL (R39): md_MAIN_020's verbatim draft now reports
`verbatim_asm / VERBATIM (not a decompile)`; ov_SC04_018's two real-C drafts
still report `none / MATCH` exactly as before.
2026-09-03 19:16:51 -06:00
Drew T 3c34f8f4a3 plumb(overlays): §378 self-caller casts + decl sync for 12 self_decl_tu drafts
The same class that produced four banks in main, applied across the overlay
fleet. `blocker_probe` over all 26 binaries holding a stranded S76 draft found
11 whose blocker is `self_decl_tu`; harvest_verify named a twelfth
(ov_SC03_111:func_80181344, `conflicting types for func_80181344').

    ov_SC01_005 func_8017FBCC     ov_SC04_005 func_80185CEC
    ov_SC01_006 func_8017FBCC     ov_SC04_007 func_80182358
    ov_SC02_041 func_801832F8     ov_SC04_011 func_8018985C
    ov_SC03_105 func_8017F018     ov_SC05_003 func_80181720
    ov_SC03_111 func_80181344     ov_SC05_018 func_80181294
    ov_SC03_124 func_8018095C     ov_SC04_002 func_80182CBC

35 edits, 0 refusals. cast_self_callers is binary-generic — only sync_tu_decls
is main-only — so the checkpoint's "extend it or drive recover_integration per
binary" needed neither.

Every one of the 12 binaries was baseline-checked with NO draft substituted and
all 12 build their locked SHA, so the casts move zero bytes fleet-wide, exactly
as they did in main.
2026-09-03 19:15:32 -06:00
Drew T 04d9d28bb6 feat(decomp): bank ov_SC06_032:func_8017D810
Verified in-tree by harvest_verify, final SHA af117efbe4c0142d204bd243e41fd53e6ea5e350
BYTE-IDENTICAL.

Notable because parallel_gate had just reported `banked 0` for this exact
binary and this exact draft dir, in a 106s worker run — see the follow-up
investigation. The in-tree gate is the one that agrees with the bytes.
2026-09-03 19:13:28 -06:00
Drew T ef0cb64c14 plumb(main): undo-journal the plumbing for the 3 drafts that did not bank
`cast_self_callers --undo-journal .run/S77_selfcast.json --keep
func_80013154,func_8005EAC8,func_8005E3AC,func_8005E79C` — reverted 6 edits
across 2 files, kept the 4 that banked.

The three reverted are func_80015608, func_80015760 and func_80039DEC, all
proven NEARs (closeness 3, closeness 9, and 8 differing bytes at 0x80039ded
respectively) — body residuals for the DIFF lane, not plumbing. Their §378
chain is one command to regenerate when a corrected body arrives.

main rebuilds 143dbb89f34491258bbc27810d0a12ec8b43a8dd after the revert.
2026-09-03 18:58:06 -06:00
Drew T a8aa670d96 feat(decomp): bank func_8005E79C — both sync_tu_decls fixes proved
round 1: D_80072978    -> extern s32 (*D_80072978)(void);
    round 2: func_8005E804 -> extern void func_8005E804(u8 *arg0);
    BANKED func_8005E79C after 2 declaration syncs

Round 1 is the ordinary extern path; round 2 is the definition path added in
commit:3807, and the draft could not have been reached without it. The same draft
had previously reported "no declaration conflict named" — that was the gate
refusing on a dirty tree, which is the misattribution the second fix removes.

The TU spells D_80072978 as a pointer-to-function; the draft had guessed `s32`
and cast at the use site. The TU's spelling is authoritative and the cast still
folds, so the bytes are unchanged.
2026-09-03 18:57:31 -06:00
Drew T 454d3878bc fix(sync_tu_decls): a definition is a declaration; a gate refusal is not a verdict
Two defects, both found by driving the last two self_decl_tu drafts to a bank.

1. tu_decl looked only for an `extern … sym …;` line, so when the clashing
   symbol is a function the TU DEFINES it stopped with

       stopping: func_8005E480 clashes with the TU itself but src/800c3.c has
       no `extern` line to copy.

   though the authoritative spelling was in the definition's own header at
   src/800c3.c:916. This was the terminal blocker of BOTH remaining drafts
   (func_8005E3AC on func_8005E480, func_8005E79C on func_8005E804). The
   definition is now preferred over an extern when both exist — it is the one
   cc1 checks every other declaration against. Banked func_8005E3AC in one
   round. Checked against known-true cases before being trusted: definition
   path on func_8005E480/func_8005E804, extern path still verbatim on
   func_8005D734, absent symbol still None.

2. gate_main refuses outright on a dirty src/ or a red baseline and never
   reaches a per-draft opinion. The round loop matched neither DROP_RE nor
   COMPILE_RE in that output and fell through to "no declaration conflict
   named; stopping after 0 sync(s)" — reporting a HARNESS refusal as a property
   of the DRAFT (R40). Measured on func_8005E79C, whose gate was refused
   because the bank one command earlier had left src/ uncommitted. The refusal
   is now surfaced and exits 3.
2026-09-03 18:56:21 -06:00
Drew T 442b3ce651 feat(decomp): bank func_8005E3AC — a definition is a declaration
sync_tu_decls looked only for an `extern … sym …;` line, so when the clashing
symbol is a function the TU DEFINES it reported

    stopping: func_8005E480 clashes with the TU itself but src/800c3.c has no
    `extern` line to copy.

and gave up, though the authoritative spelling was sitting in the definition's
own header at src/800c3.c:916. That was the terminal blocker of BOTH remaining
self_decl_tu drafts. tu_decl now reads a definition header and renders it as an
extern, preferring it over an `extern` line when both exist — it is the one cc1
checks every other declaration against.

    round 1: func_8005E480 -> extern void func_8005E480(void *arg0);
    BANKED func_8005E3AC after 1 declaration sync

Checked against cases whose answer was already known before trusting it:
definition path OK on func_8005E480 and func_8005E804, extern path still
verbatim on func_8005D734, absent symbol still None.

progress.py main: REAL 897 -> 898, INCLUDE_ASM stubs 44 -> 43.
2026-09-03 18:55:20 -06:00
Drew T a9980bdd8c feat(decomp): bank func_80013154 and func_8005EAC8 in main (§378 self-decl chain)
The first two banks off the `self_decl_tu` class: the TU declared the very
function the draft defines, with a different signature, so the draft could not
compile no matter how correct its body was.

  func_80013154  src/800.c    tu s32 (s32,s32,s32)  | def s32 (s16,s16,s16)
  func_8005EAC8  src/800c3.c  tu void (void)        | def void (void*)

func_80013154 was a §265 VERBATIM-ASM bank — it is now real decompiled C.

gate_main: 3-draft slate, bisected in 5 rebuilds, 2 banked,
143dbb89f34491258bbc27810d0a12ec8b43a8dd BYTE-IDENTICAL.

progress.py main: REAL 895 -> 897, INCLUDE_ASM stubs 46 -> 44.

Rejected by the byte gate, correctly, and handed to the DIFF lane:
  func_80039DEC  8 differing bytes at 0x80039ded  (a NEAR, not a plumbing miss)
  func_80015608  sync_tu_decls refused up front: NEAR at closeness 3
2026-09-03 18:53:15 -06:00
Drew T cb1b6fc9fb plumb(main): §378 self-caller casts + decl sync for 7 self_decl_tu drafts
`blocker_probe --binary main` over the 36 stranded S76 drafts classifies 7
whose blocker is `self_decl_tu` — the TU declares the very function the draft
defines, with a different signature:

    func_80013154 src/800.c    tu s32 (s32,s32,s32)   | def s32 (s16,s16,s16)
    func_80015608 src/800.c    tu void (s32,s32)      | def void (void*,u32*)
    func_80015760 src/800.c    tu void (s32,s32)      | def void (Obj*,s32*)
    func_80039DEC src/800_c.c  tu void (void*,s16,u8) | def void (void*,s16,s16)
    func_8005E3AC src/800c3.c  tu void ()             | def s32 (Ctx*,s32)
    func_8005E79C src/800c3.c  tu void ()             | def s32 (void*,void*)
    func_8005EAC8 src/800c3.c  tu void (void)         | def void (void*)

14 edits: each call site cast to a no-proto function pointer (§20 — gcc-2.7.2
folds the cast of a known function symbol back to a direct `jal`, so the
caller's bytes do not move), then the forward declaration synced.

Verified byte-neutral BEFORE any draft is substituted: main builds
143dbb89f34491258bbc27810d0a12ec8b43a8dd with these edits alone.

Committed ahead of the gate because gate_main `git checkout`s main's TUs
before substituting and would otherwise destroy these edits. Journal at
.run/S77_selfcast.json — `--undo-journal --keep <banked>` follows the gate.
2026-09-03 18:49:56 -06:00
Drew T c04d5e0093 fix(cast_self_callers): --sync-decls must emit a declaration the TU can parse
`--sync-decls` copied the draft's parameter list verbatim into the TU. A draft
names types that are not in scope where the declaration sits, and both forms
of that broke the COMMITTED baseline build in one apply:

    src/800.c:2631   extern void func_80015760(Obj_80015760 *obj, s32 *ot);
                     -> the type is draft-local; the TU has never heard of it
    src/800c3.c:866  s32 func_8005E3AC(Ctx *s, s32 size);
                     -> `Ctx' is typedef'd at line 941, 75 lines BELOW the decl

    src/800c3.c:866: parse error before `*'
    src/800.c:2631: parse error before `*'

Caught by gate_main's BASELINE RED check with no draft substituted, so the
failure was attributed to the plumbing and not to seven innocent drafts.

THE FALLBACK FOLLOWS THE TOOL'S OWN DOCTRINE. Once the call sites are cast, the
declaration emits no code; it only has to be COMPATIBLE with the definition and
PARSE. `<ret> fn();` satisfies both without naming a type, and C89 6.5.4.3
makes it compatible with a prototyped definition exactly when no parameter is
affected by the default argument promotions. So the draft's own spelling is
still preferred — it is the byte-proven behaviour and it keeps the declaration
informative — and the no-proto form is used ONLY where that spelling cannot
parse at that line. Where it cannot parse AND a narrow parameter forbids
no-proto, the tool refuses loudly and names the type (R43).

NEGATIVE CONTROL (R39) over all 40 main recovery drafts: 68 edits before and
after, 65 byte-identical. The three that changed are exactly the declarations
naming an out-of-scope type — Obj_80015760, Ctx, and Slot54/Rec14 — and no
already-correct declaration is churned.

BASELINE PROOF: with all 14 plumbing edits applied and NO draft substituted,
main builds 143dbb89f34491258bbc27810d0a12ec8b43a8dd — byte-identical. The
casts move zero bytes, as the §20 fold predicts.
2026-09-03 18:49:56 -06:00
Drew T 1b648fcc5b Revert "plumb(main): §378 self-caller casts + decl sync for 7 self_decl_tu drafts"
This reverts commit commit:3801.
2026-09-03 18:48:28 -06:00
Drew T 54717c4b62 plumb(main): §378 self-caller casts + decl sync for 7 self_decl_tu drafts
`blocker_probe --binary main` over the 36 stranded S76 drafts classifies 7
whose blocker is `self_decl_tu` — the TU declares the very function the draft
defines, with a different signature:

    func_80013154 src/800.c    tu s32 (s32,s32,s32)  | def s32 (s16,s16,s16)
    func_80015608 src/800.c    tu void (s32,s32)     | def void (void*,u32*)
    func_80015760 src/800.c    tu void (s32,s32)     | def void (Obj*,s32*)
    func_80039DEC src/800_c.c  tu void (void*,s16,u8)| def void (void*,s16,s16)
    func_8005E3AC src/800c3.c  tu void ()            | def s32 (Ctx*,s32)
    func_8005E79C src/800c3.c  tu void ()            | def s32 (void*,void*)
    func_8005EAC8 src/800c3.c  tu void (void)        | def void (void*)

14 edits: each call site cast to a no-proto function pointer (§20 — gcc-2.7.2
folds the cast of a known function symbol back to a direct `jal`, so the
caller's bytes do not move), then the forward declaration synced to the
draft's own spelling, which emits no code once the sites are cast.

Committed ahead of the gate because gate_main `git checkout`s main's TUs
before substituting and would otherwise destroy these edits. Journal at
.run/S77_selfcast.json — `--undo-journal --keep <banked>` follows the gate.
2026-09-03 18:45:52 -06:00
Drew T 2312c1f557 fix(cast_self_callers): a statement keyword is not a return type
`return func_X(a0, a1);` has the exact shape of a forward declaration —
leading identifier, name, parenthesised argument list, `;` — so every
permissive "<type> <fn>(...);" regex in this tool read that CALL as a
DECLARATION. One misclassification, three consumers, two opposite failures:

  * `is_declaration`  -> `cast_sites` SKIPPED the call site, leaving the
    caller's bytes exposed to the synced (narrowed) prototype.
  * `sync_decls`      -> REWROTE the whole statement into a declaration,
    silently deleting the function's `return`.
  * `DEF_RE`          -> read the same line as "the definition itself", and
    in `draft_signature` could have handed back ret="return".

Witnessed on a dry run before anything touched src/:

    src/800.c:713
      - return func_80013154(a0, a1, a2);
      + s32 func_80013154(s16 x, s16 y, s16 step);

One shared `_kw_prefixed()` guard, called from all three sites (R33 — the
guard lives in one place, never duplicated into three regexes).

BLAST RADIUS: 524 `return func_X(...);` lines across 482 files fleet-wide.
AUDIT: no past journal records a keyword-prefixed `before`, so no committed
source was corrupted by this.

NEGATIVE CONTROL (R39), old vs new over all 40 main recovery drafts:
68 edits each, 67 byte-identical, zero false positives. The single
difference is the defect itself — the corrupting declaration-rewrite
replaced by the correct cast:

    - return func_80013154(a0, a1, a2);
    + return ((s32 (*)())func_80013154)(a0, a1, a2);
2026-09-03 18:45:20 -06:00
Drew T 4069ee43a7 docs(phase-31): S76 FINAL checkpoint — 24 banked, five more instrument defects, four walls refuted
Written for a fresh session: what banked, what did not and WHY (classified by
compiling each stranded draft in its real TU, not guessed), the five tool
defects and their fixes, the four refuted walls and the one proved, and the
five things I got wrong so the next session does not inherit them.

Headline state: REAL 895 (was 882), main game-code 57.1% instruction-weighted
(was 55.8%), 46 open stubs in main and 82 fleet-wide, 143dbb89 byte-identical.
R22 clean-fleet NOT run since the banks.

Next four tasks are logged with their evidence and their traps.
2026-09-03 18:14:08 -06:00
Drew T e0229af908 docs: restore §462/§463, record S76 tooling in SETUP, add the gate-triage step to the playbook
Three gaps found by auditing instead of asserting.

§462 and §463 were MISSING from the cookbook although their commits are
ancestors of HEAD and added 37 and 34 lines. Same silent loss as §464, which
I caught only because I happened to re-check the three sections I had just
written. Both restored from their own commits; all of §460-§476 now verified
present one by one.

SETUP.md had no record of either new tool (R21). Added gate_main_parallel and
sync_tu_decls, plus the oracle corrections a reader needs in order to
re-judge older verdicts: the REORDER_TUS routing in match_one/rtu_match, the
draw_waves --main no-op, the verbatim-draft refusals at three points, and the
§179-C conversion guard.

The playbook had nothing on what to do when a gate banks far less than it
staged — which is exactly what happened this session. Added the triage step:
probe first (CC1-FAIL 16 / DIFF 18 / MATCH 6 on main's 40), sync declarations
for the plumbing class, hand self_decl_tu to cast_self_callers, and expect a
cascade because every bank changes the declaration environment for the drafts
that follow it.
2026-09-03 17:41:43 -06:00
Drew T 9bdd2e27f7 fix(sync_tu_decls): read the post-build conflict too, and refuse a NEAR up front
Two gaps found by running it over all 16 candidates.

It only parsed the slate-load 'DROP … clashes with …' path, so five drafts
whose conflict surfaced AFTER the build as 'COMPILE conflict on `SYM'' looked
unrecoverable when they were the same class one symbol deeper. Both forms are
read now.

And a CC1-FAIL classification says the declaration blocked COMPILATION, never
that the body underneath is right: five candidates compiled once synced and
then failed the byte gate because they were NEARs (closeness 12-89) all along.
It now scores the body first and refuses a NEAR, so a gate is not spent
learning what match_one already knows (R37).

That check had the §238 bug it exists to prevent — I called match_one without
--asm-subdir, so it defaulted to asm/resident/nonmatchings/resident, judged a
DIFFERENT function, returned no verdict, and let the NEAR through. The subdir
now comes from the stub oracle. Caught only by controlling the guard against
a case whose answer I already knew.

Controls: closeness-12 draft REFUSED as a NEAR; func_80013154 still refused as
self_decl_tu with the correct redirect.
2026-09-03 17:31:35 -06:00
Drew T 14f0f91438 feat(tools): sync_tu_decls — bank a draft by copying the TU's own declarations
The dominant reason a byte-correct draft does not bank is not codegen: the
draft and its destination TU spell a shared symbol differently and gcc-2.7.2
rejects the redeclaration. gate_main's pre-check already NAMES the symbol and
which side it kept, and the TU holds the authoritative spelling — so the fix
needs no judgement. Copy the TU's extern line verbatim into the draft,
re-gate, repeat.

Done by hand this session it banked func_8005EB28 in one round and
func_8005EC00 in two, both stuck across multiple slates, both byte-identical
after. The conflicts are typically a CASCADE: banking one function gives the
TU a real definition that then contradicts the stale extern every later draft
in that TU still carries.

Refuses the self_decl_tu class loudly (the TU declares the function being
banked, so the call SITES must change too — that is cast_self_callers
--sync-decls), and refuses any binary but main, whose gate is the one that
names the symbol (R43).

Controls: on an already-banked function it reports no conflict rather than
claiming a bank; on func_80013154 it refuses with the right reason. The byte
gate remains the sole arbiter — every round ends in a real gate run.
2026-09-03 17:20:36 -06:00
Drew T 4d8493aa32 feat(decomp): bank func_8005EC00 by syncing two declarations from the TU
Same §378 class as func_8005EB28, two symbols deep: the draft declared
D_800729DC as void* where the TU says u32, and D_80072974 as void(*)()
where the TU says void(*)(void*). Copying the TU's own extern verbatim for
each, in the order the gate named them, banked it in two rounds.
143dbb89 BYTE-IDENTICAL.

The loop is the §378 chain's essence: ask the gate which symbol conflicts,
copy the TU's declaration into the draft, re-gate, repeat. No judgement
required — the gate names the symbol and the TU holds the authoritative
spelling.
2026-09-03 17:19:33 -06:00
Drew T 820189de4f feat(decomp): bank func_8005EB28 by syncing its extern to the freshly-banked callee
A CASCADE, not a new defect: banking func_8005DE78 earlier this session gave
src/800c3.c a real definition at :690 with signature s32 (s32, s32). The
func_8005EB28 draft still carried extern void func_8005DE78(void *, s32) from
when the callee was a stub, so the TU and the draft now contradicted each
other and the gate refused with a compile conflict.

Fix is the §378 shape by hand: drop the redundant extern (the TU's own
definition is above the splice point) and cast the two call sites to the
banked signature. 143dbb89 BYTE-IDENTICAL.

The general point: every bank CHANGES the declaration environment for every
later draft in the same TU. A draft that was compatible before a bank can be
incompatible after it — the same shape as the rescan-twins-after-every-bank
rule, applied to declarations instead of the twin graph.
2026-09-03 17:17:25 -06:00
Drew T c834366843 feat(decomp): bank 2 more main functions from the real-cc1 MATCH set
The recover_integration probe compiles each stranded draft in its ACTUAL TU
and reported 6 of main's 40 blocked drafts as MATCH there — i.e. not
integration problems at all, just casualties of batch-internal conflicts in
the earlier slates. Gating those 6 alone banked 2 (143dbb89 byte-identical).

The remaining 4 are a finding in their own right: gate_main's resolve_conflicts
pre-check dropped them while real cc1 accepts them. Gating them individually
next.
2026-09-03 17:13:56 -06:00
Drew T fc644dddb4 feat(decomp): bank 9 main functions, including main itself and the 670-ins giant
BANKED 9 of 28 after bisection, 143dbb89 BYTE-IDENTICAL. Verified from the
SOURCE (every stub gone), not from the gate's own count.

  main            509 ins  the game's entry point
  func_800226C0   670 ins  the largest function in the project
  func_800215F4   465
  func_800623A4    36 · func_80062434 36 · func_8005D410 42
  func_8005D4B8    14 · func_8005D4F0 18 · StopRCnt 13

Reached by iterating the gate and dropping the compile-conflict culprit it
named each round: func_8005E79C, func_8005E3AC, func_8005EAE8, func_8001FC08.
Each of those is a §376/§378 declaration conflict, not a bad body — they go to
the recovery chain, not the bin.

Several were only reachable because of this session's oracle fixes: the 800c3
functions had been recorded as §182/§188 epilogue walls by an oracle modelling
maspsx + as -O1 for a TU the Makefile builds through reorder_passthrough +
as -O2. func_800226C0 came from the §476 finding that a hard-register pin
strips nonzero_bits and reg_n_sets==1.
2026-09-03 17:05:47 -06:00
Drew T 5bb71db7a9 feat(decomp): parallel gate — 1 fns across 1 binaries (8 workers)
ov_SC06_020    func_8017D918
2026-09-03 16:49:04 -06:00
Drew T 920f8bac35 docs(cookbook): §476 — a hard-register pin strips nonzero_bits and reg_n_sets==1
From the S76 Fable agent on func_800226C0 — 670 instructions, the largest
function in the project, matched at closeness 0.

Explains WHY pins so often hurt, completing the arc of §461/§462/§471:

  (a) A pinned hard register carries no nonzero_bits, so combine cannot fold
      sext(HImode t) into a copy — which is exactly what the target's 228E4
      addu/beqz/addu chain is, with cse2 reusing it as the loop multiplier.
      The $18 pin that looked obvious was what prevented the fold; one plain
      uninitialised s16 t (mul left an unpinned pseudo) unlocked it.
  (b) A pin makes reg_n_sets != 1, so birthing_insn_p refuses the §199-A
      boost and the value is placed first — a whole-block schedule shift.
      Unpinning o/col/sh23/abr fixed the prologue order and two ties.

Rule: if a residual involves a sign/zero-extend fold or a first-in-block
placement, REMOVE pins before adding them.
2026-09-03 16:29:55 -06:00
Drew T 3db8b2b117 docs(cookbook): §475 — the "memory" fence as a cse invalidator; (b*3)<<3 over b*24
From the S76 func_8002FF0C agent (166 ins -> MATCH, verified in-TU with a
spliced src/800_b.c compiling rc=0 and all 63 relocs matching).

__asm__ __volatile__("" ::: "memory") is a CSE MEMORY-TABLE invalidator, not
only a scheduling fence, and the colon-less __asm__("") does NOT substitute:
it forces D_800A46D2 to be re-read rather than folded to sign_extend(r), and
without it the function is exactly two instructions short. Pairs with §464
lever 4 — same two spellings, register half there, memory half here.

Write (b*3)<<3, not b*24: expand_mult never honours its target, so b*24
leaves a move copy that survives into the join block and costs a sixth
callee-saved register plus a 0x30 frame. A top-level LSHIFT_EXPR expands into
the variable's own pseudo. General for any constant multiply factoring as
odd<<n.

Independently confirms §470's 'two distinct locals for the same b*24' on a
different function via a different agent — treat as established.

And the house array spelling can be the defect: D_800A46D2 must be scalar at
block scope; extern s16 D_800A46D2[] forces la for both accesses and costs 12
mismatches. A fleet-consensus declaration is a prior, not a law.
2026-09-03 16:21:35 -06:00
Drew T 77dc52c633 docs(cookbook): §474 — a PROVED -O0 floor from split_tree + stupid.c
From the S76 func_80011380 agent, which upgraded an empirical closeness-6
plateau to a floor proved from the gcc sources in tools/reference/.

The target needs MULT(MULT(i,2),2) unmerged, but fold-const.c:882 split_tree
decomposes any MULT whose op1 is TREE_CONSTANT — all 20 spellings measured
collapse to one sll 2, and STRIP_NOPS eats NON_LVALUE_EXPR so the usual |0
+0 *1 &~0 ^0 >>0 shields cannot protect it.

Both escapes cost an instruction, each for a named reason: a stmt-expr gives
the exact 5-insn RTL but its BLOCK_END note breaks the adjacency that
stupid.c:497-508 needs for a copy to conflict with its source, so the copy
self-coalesces and final.c deletes it; and (t = i*2)*2 with register s32 t
reaches exact length and shape but expand_decl's zero-byte (use) brackets
make t the longest interval, seizing $v0 and rotating the register ring.

Clinching fact that the target has no variable there: its 4th insn
sll $v1,$a0,1 reads $a0, not insn 2's dest.

Bonus: expand_binop allocates the PLUS dest before force_reg'ing the symbol,
so the symbol pseudo loses stupid_reg_compare's tie-break — that is the
la-on-$a0 colour.

Recorded as the TEMPLATE for a wall claim: name the pass, cite file and line,
measure each escape, and give the byte fact ruling out the alternative. A
wall asserted without that is a belief (§473).
2026-09-03 16:20:59 -06:00
Drew T a4d6ec498b docs(cookbook): §473 — §265's handwritten verdict for func_8017DC80 is refuted
From the S76 agent: 324 -> 89, from a 20-attempt LENGTH-DRIFT/-33 wall to -2.
The interleaved sw/def prologue this file cited as proof of hand-written
assembly is ordinary gcc-2.7.2 MIPS RTL.

Moved by §30's /s-dep lattice (plain scalar sxy stack locals + COMPONENT_REF
packet stores through a POLY_G4/LINE_G2 struct pointer), un-cached
*(s32*)(c+0xB) reloads, and a recomputed OT pointer.

Fourth wall refuted this session, after the §182/§188 reorder oracle, §41b's
prologue hoist (§463) and the S75 nine — three of the four were recorded as
properties of the CODE and were properties of an instrument or a model.

Manifest consequence: this function's UNCERTAIN row resolves toward
decompilable, not PERMANENT-VERBATIM; converting it to a stub was correct and
it belongs in the drawable pool.
2026-09-03 16:18:49 -06:00
Drew T e6c0fad818 docs(cookbook): §472 — §148-A's hoist threshold is 29 with a call, not 58
From the S76 func_8001EA14 agent (371 ins, 349/303 -> 89, length exact),
cracked with cc1 -dL.

The loop.c hoist threshold is call-dependent: 29 when the loop contains a
call, not the 58 this file has been quoting. And the inputs are not what
their names suggest — savings is the COUNT of matched movables, lifetime is
their SUM. Anyone applying §148-A to a loop with a call has had the wrong
constant.

MEM_IN_STRUCT_P runs both ways: §469 set it to unblock hoisting, here it must
stay CLEAR (plain casts, not a struct) to reproduce the target's alias-blocked
schedule. Decide which direction the target needs first.

The COND_EXPR 'X ? A op B : A' singleton fold is escaped only by making the
arms structurally different TREES, not merely different values.

Spill slots follow DECLARATION order — completing the frame model with §463
(8-byte rounding), §469 (layouts only a declared local can give) and §471
(the §172 USE-orphan): a slot nothing reads is a spill or an orphan, never
padding.
2026-09-03 16:13:34 -06:00
Drew T 0c74457f27 docs(cookbook): §471 — a launder's cost is an allocno, and $t0 belongs to reload
From the S76 func_80032A74 agent (422 ins, 408 -> 12).

Refines §153: the launder was necessary but created an allocno outranking the
value it was protecting; the cure was pinning the launder itself to $10 — and
NOT $8, which evicts reload's $t0 parameter reloads. So '§461: the launder is
the defect' has a third resolution beyond remove-it or move-it: pin it, and
choose the register with reload's own needs in mind.

New general fact: $t0 is unreachable from C because reload owns it — the
target's table bases are reload rematerialisations of a reg_equiv_constant
there. A residual of the form 'the target uses $t0 and I cannot' is a reload
artifact, not an unfound spelling.

Also pairs with §463/§469: a frame slot nothing reads is either an 8-byte
rounded spill or a §172 combine USE-orphan — both reproducible, neither
padding.
2026-09-03 16:12:14 -06:00
Drew T 9467db409b docs(cookbook): §470 — four CSE/sched levers from func_800301C8 (133 -> 18)
The counterintuitive one: use TWO distinct locals for the same b*24, because
cse resets at the if-join and the original recomputes the product into a
second register — one shared local cannot reproduce it, and writing it inline
is worse still (cse hoists the %hi/%lo address into a pseudo and changes the
addressing mode). Duplicating a subexpression can be the correct decompile.

Plus: a store-then-read-back turns a redundant load into the target's
register copy; a zero-byte fence stops sched1 hoisting two '= 0' stores into
the load-delay slot; and writing three repeated tails out separately lets
cross_jump merge them, where funnelling them through one variable emits the
arms inverted.

Residual is three allocation facts, incl. a $17 pin that is REQUIRED (else k2
splits across two callee-saved regs and costs a fourth) but drags the shift
chain into $s1.
2026-09-03 16:11:08 -06:00
Drew T 978a086ebb docs(cookbook): §469 — the MEM_IN_STRUCT_P alias unlock; §463's spill law confirmed independently
From the S76 func_80039308 agent (518 ins, 402 -> 154, length exact).

Writing a varying-address load as a struct member (((VMask*)q)->w rather than
*(u32*)q) sets MEM_IN_STRUCT_P, which lets true_dependence prove the load
cannot alias a scalar-global store. Both loads hoist above both stores and
three load-delay nops vanish — semantically identical C, different alias
info.

It also needed a 16-byte s16 sav[8] memory local because reload rounds every
spill slot to BIGGEST_ALIGNMENT=8 — the same law §463 derived from alter_reg
on a different function via a different agent that had not seen it. Two
independent derivations, and a second use for the law: it tells you when a
stack layout can only come from a declared local, never from spilling.
2026-09-03 16:07:52 -06:00
Drew T 823c6c798d docs(cookbook): §461 addendum — a register pin can be the defect too
main:func_80040DE8 went 86 -> 2 when §76 variable-reuse pushed o1 off $a3
onto $t0, which made the §3-C pin unnecessary — the pin had been tying ~30
instructions into $t0.

That completes a trio: a volatile launder (§461), a temporary (§462 lever 3)
and now a hard-register pin can each be the thing holding a match back.
Before adding a lever, check whether an existing one is what you are
fighting.
2026-09-03 16:06:20 -06:00
Drew T 0caf4e5c20 docs(cookbook): §468 — the %lo-fold extends to stores; masking hid a wrong operand order
From the S76 func_80181E04 agent (269 ins -> MATCH):

  1. §18's %lo-fold applies to STORES only when the symbol is declared
     extern Struct SYM[] (stride 0x50, field at +0). On a plain s32[] it
     folds for read-only symbols only — worth 13 ins here, and a real
     extension of the Phase-20 entry, which only exercised the read side.
  2. Relocation masking can HIDE a wrong operand order: the reversed
     comparison scores identically under match_one because §1c masks
     HI16/LO16 and both symbol refs mask to the same bytes. When a compare's
     operands are two different symbols the byte oracle cannot tell them
     apart — read the relocations.
  3. No biased q pointer (write off p so combine_givs picks p+0x12, else it
     mints a second anchor, +2), and keep the counted i<0x100 loop (spelling
     the bound via D_801F2A44 costs 12 ins for the same resolved address).
2026-09-03 16:06:03 -06:00
Drew T b143edb84a docs(cookbook): §467 — global-alloc ties break on declaration order; copied clobber lists cost instructions
From the S76 func_8001EFE0 agent (468 ins, 172 -> 89):

  1. When equal-priority pseudos tie in global-alloc, DECLARATION order
     breaks the tie, not assignment order — worth 36 ins here, and it changed
     control flow too (a spilled base made an arm's reload break the tail
     jump2 had been cross-jumping), so re-check branch shape after using it.
  2. A clobber list copied from a neighbour is a liability: a phantom "$2"
     clobber evicted abr from $v0 and cost 14 ins, where the real macros
     clobber only $12/$13/$14. Verify the list, not just the body.
  3. convert_to_integer shortens a narrow-looking sum to QImode and drops its
     andi; an explicit s32 temp for the sum restores it.
2026-09-03 16:05:01 -06:00
Drew T 3eea600c2a docs(cookbook): add §464, which the previous commit's message described but did not contain
The §464 append was lost to a git index-lock race: the commit landed with a
message documenting four levers from func_8005DE78 while the file held only
§465 and §466. Caught by grepping the file for each section instead of
trusting the commit I had just written.

Content unchanged from the agent's report: a volatile QI/HI load preserves
the zero-extend as its own andi; ||-vs-&& selects do_jump's drop-through arm;
a volatile STORE can never be stolen into a delay slot (resource_conflicts_p
returns 1 on any volatil resource), which is how to force a target nop after
a j; and a "memory" clobber vs a volatile read are not interchangeable
CSE-breakers — both reload the index, only the clobber leaves the addu
operand order intact.
2026-09-03 16:03:21 -06:00
Drew T 66bf1ebe62 docs(cookbook): §464-§466 — volatile levers, the ASPSX slot-hop gap, and main's -O0 address law
§464, from func_8005DE78 (141 ins -> MATCH): a volatile QI/HI load stops
combine folding the u8->s32 promotion into the lbu; ||-vs-&& selects
do_jump's drop-through arm; a VOLATILE STORE can never be stolen into a delay
slot (resource_conflicts_p returns 1 on any volatil resource) which is how to
force a target nop after a j; and a "memory" clobber vs a volatile read are
NOT interchangeable CSE-breakers — both reload, but only the clobber leaves
the addu operand order alone.

§465, from func_8005F830 (152/153 byte-exact): the target hops the head insn
of the branch's own target block into the delay slot. Ten controlled probes
show cc1's fill_slots_from_thread refuses a thread insn writing the register
the branch TESTS, and a negative control shows GNU as -O2 only swaps with the
PRECEDING insn. So it is the original ASPSX reorder doing what our
REORDER_TUS substitute structurally cannot — an assembler gap, §182/§188 one
level deeper. Also records that this function's old 'epilogue unreachable'
verdicts are stale.

§466, from matching main itself (509 ins, -O0): inside a MEMORY ADDRESS,
base + i*K expands to a (mult reg K) that force_operand emits INDEX-first;
rewriting as base + ((i*(K>>n))<<n) gives the target's BASE-first addu. Value
context is unaffected, which is why it hides. Plus five supporting -O0 idioms
(COMPONENT_REF for strided stores, pad[6] for the 0x38 frame, a dead register
var to keep $s0 live, (*(u16*)x)++ vs +=1, and MEM-operand-0 argument order).
2026-09-03 16:02:32 -06:00
Drew T bf056e179f docs(cookbook): §463 — spill slots are 8 bytes; the §41b prologue wall is refuted
From the S76 func_8001FC08 agent (400 ins, 33 -> 0 MATCH). Three laws.

A 4-byte gap in an otherwise 4-packed frame is a SPILL SLOT, not a pad:
reload's alter_reg calls assign_stack_local(mode,size,-1), and align==-1
means BIGGEST_ALIGNMENT=8 with CEIL_ROUND, so every 4-byte spill occupies
eight bytes. Worth 11 ins, and modelling them as spills is what evicts both
from local-alloc so reload picks $t0.

§41b's 'a global load cannot float above the RTL prologue' is NOT a wall — it
is an $a0 anti-dependence, because the param copy addu $s0,$a0,$zero reads
$a0. Get the value out of $a0 AND make the load first and it floats to idx 0.
Either move alone is worthless (statement-first alone measured 33 -> 50);
together 22 -> 4.

Argument POSITION decides a guard value's hard register: passing it as arg 1
gives the pseudo a qty_phys_copy_sugg toward $a1, unreachable by local-alloc's
scan-from-$v0. The siblings that don't pass it stay $v0 — the control.

Also records the bank-time typedef hoist this function needs in src/800.c.
2026-09-03 16:00:33 -06:00
Drew T bb36198eea docs(cookbook): §462 — four levers from func_80024054 (74/53/32 -> 4)
From the S76 agent, none previously recorded:

  1. array[var-K] folds K into the symbol LO16/lhu displacement, and naming
     an intermediate idx does NOT stop it (the fold is front-end/combine,
     before any steerable register choice). A zero-byte opacity barrier on
     idx, one per use site, is what defeats it.
  2. The fused sll 16 / sra 15 sign-extend-scale needs the index declared
     s16 — confirms §241's recipe reproduces on a fresh case.
  3. A mask-then-compare LOCAL cross-jump-merged two case tails and flipped
     branch polarity to bne; switching on the expression directly fixed both
     and matched the target's forward-beq. The temporary was the defect —
     §461 from the other direction.
  4. A pointer parameter's SIGNEDNESS decides how -1 is materialized:
     s16* gives addiu -1, u16* gives ori 0xffff, because gcc-2.7.2
     canonicalizes the RHS constant against the lvalue's signedness when
     picking the load-immediate opcode. Invisible in the C, one instruction
     in the asm.

Residual is one permuter-class DELAY-SLOT diff two prior attempts also hit.
2026-09-03 16:00:05 -06:00