Commit Graph

3809 Commits

Author SHA1 Message Date
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
Drew T 371a18b6de docs(cookbook): §461 — laundering can be the defect; Residual A is single-op only
From the S76 func_80039B20 agent (79 ins, prior best 16 -> 10). Two findings.

A volatile-asm launder on the WRONG loop invariant displaced the address
chain and cost an entire cluster (16 -> 81 with it present); the matched
sibling func_8003A0E4 uses the plain idiom. Another invariant in the same
loop genuinely needs its launder. So the lever is per-invariant, not
per-loop, and it can go backwards.

Scope correction to Residual A (L875): the first-dying-operand / source-order
fix works on a SINGLE binary op and does NOT transfer to a PLUS chain —
measured byte-identical output when swapping operands on a 3-term chain,
because fold.c canonicalizes associative PLUS before combine sees it. Worth
recording as a negative result so nobody re-derives it.
2026-09-03 15:50:31 -06:00
Drew T 2eac966cc5 fix(manifest): six §179-C fragments are PERMANENT-VERBATIM, not decompilable
Reverts my six src/800c.c stub conversions from commit:3772 and corrects the
manifest to match the evidence. main still builds 143dbb89.

Each of the six has NO `jr $ra` of its own: it ends mid-basic-block or
tail-jumps into a sibling's label, and the shared lw $ra / addiu $sp / jr $ra
tail lives in the NEXT symbol. gcc-2.7.2 has no sibcall pass and appends an
epilogue to every C function it compiles, so no C spelling can ever match —
cookbook §179-C, which already NAMED func_8005C1C0 as a follow-up.

I converted them anyway on a `rows == 1` filter that meant "the manifest
listed one row", not "this is an independent function", ignoring the
DECOMPILE-AS-PARENT disposition whose whole meaning is "this row is a
FRAGMENT". Three drafting agents then rediscovered §179-C independently, one
citing the very cookbook line naming its own target, before a mechanical
no-jr-$ra sweep confirmed all six at once.

Also corrects func_8017D810 and func_80181828 from UNCERTAIN: both are
handwritten GTE (SQR lane), per agents that transcribed the .s 1:1.

The guard that prevents a repeat shipped in commit:3773.
2026-09-03 15:42:56 -06:00
Drew T dc4412b1de fix(verbatim_to_stub): refuse a §179-C epilogue-less fragment
A function with no `jr $ra` of its own falls into a sibling's shared
epilogue. gcc-2.7.2 has no sibcall/tail-merge pass and appends an epilogue to
every C function it compiles, so no C spelling can ever match — converting one
to an INCLUDE_ASM stub just puts an unbankable target into the drawable
frontier.

I did exactly that to six functions in src/800c.c, on a `rows == 1` filter
that meant "the manifest listed one row", not "this is an independent
function" — ignoring the DECOMPILE-AS-PARENT disposition whose entire meaning
is "this row is a FRAGMENT". Three drafting agents then rediscovered §179-C
from scratch, one citing the cookbook line that names its own target.

The symptom is one grep, so nobody should pay an agent to find it again.

TWO THINGS THIS COST, both caught only by testing a known-true case:
  * the first version read the function's .s — but splat stops emitting <fn>.s
    for a verbatim body, so it had nothing to read and returned False: inert
    for precisely the case it guards. It now reads the verbatim block itself.
  * my first negative control was CloseEvent, a libapi trampoline that
    genuinely has no `jr $ra` — a "false positive" that was the correct
    answer. Re-controlled on VectorNormal (verbatim, has jr $ra, guard stays
    silent) vs func_80047E58 (verbatim, no jr $ra, guard fires).

Census of main's verbatim blocks: 37 have jr $ra, 100 do not.
2026-09-03 15:02:50 -06:00
Drew T 37beb6420b refactor(fleet): convert 11 self-contained verbatim units to stubs
The second tranche: every unit in config/verbatim_manifest.json whose
disposition says decompile-it and whose unit is SELF-CONTAINED (one row, so
the unit_entry is the function itself, not a fragment). 6 in main's src/800c.c
plus func_80185810 (ov_SC03_105, 489 ins), func_8017DC80 (ov_SC07_002, 346),
func_80181E04 (ov_SC01_001, 269), func_80181828 (ov_SC05_005) and
func_8017D810 (ov_SC06_032).

Byte-neutral, verified per binary: main 143dbb89, ov_SC03_105 d305ff6d,
ov_SC07_002 fad71342, ov_SC01_001 a8e49bc0, ov_SC05_005 452897fc,
ov_SC06_032 af117efb.

Checked FIRST that none sits in a LINKED subseg — several carry SDK-shaped
names and a draft written into a linked subseg gates GREEN while wrong.

NOT converted: the 21 multi-row DECOMPILE-AS-PARENT units. Their rows are
FRAGMENTS of a larger unit, and converting a fragment to its own stub would
invite drafting something that is not an independent function. That needs a
parent-unit tool, not a per-function one.

Still skipped: ov_SC03_107:func_8017D878, a deliberate §265 bank per the
cookbook addendum (address-taken use forces a void(void) declaration the real
body contradicts).
2026-09-03 14:54:56 -06:00
Drew T c982efada3 docs(playbook): record the S76 draw and oracle fixes as procedure
The playbook IS the procedure, so the five instrument fixes have to land in
it or the next session repeats them: --main drawing zero main functions,
the ledger reporting an empty frontier, the reorder-island oracle
manufacturing a §188 wall, and verbatim-asm drafts refused at three points.

Each entry carries the check to run rather than the fix that was made — the
'main: N stub(s) reached the pool' line, the ledger NOTE, and the rule that a
draw disagreeing with corpus.stubs is the thing that is wrong.
2026-09-03 14:49:29 -06:00
Drew T 1dbbdbbcd2 docs(decision-log): S76 — five more instrument defects, one shape
Records the session's through-line while the evidence is live (R31): every
wall examined was the measuring apparatus. The verbatim trap behind three
doors, the reorder oracle behind two, and draw_waves --main never iterating
main at all.

Keeps the measurements a fresh session cannot reconstruct: 1,099 of 704,375
draft files are verbatim-asm; 0 false positives across 45,898 controls;
closeness 5/36 vs 2/35 on the same draft under the two oracles; 0 -> 55 main
stubs in the pool. And the cost that is not in any count — a large part of
the 800c3 cluster's recorded wall history is instrument error, and the
journal has been feeding those false walls forward into new waves.
2026-09-03 14:48:47 -06:00
Drew T 505a50a9b6 fix(draw_waves): --main was a no-op; every mixed draw saw ZERO main functions
bins is built from src/* DIRECTORIES, and main has no src/main/ — its TUs are
top-level src/*.c. So "main" was never in the list, and the filter that keeps
it could only ever preserve a "main" already present. --only-main worked
solely because it overwrote the list; --main contributed nothing, in every
mixed draw this project has ever run.

The tool meanwhile printed "main: refusing 49 LINKED subseg(s)" whenever
--main was passed, so it announced it was handling main while main was never
iterated. A flag that changes nothing is worse than a missing flag: it
answers the question you asked.

Measured: 0 -> 55 main stubs reach the pool. This is why S76y's 47 main
targets had to be assembled by hand from corpus.stubs — the draw could not
see the actual frontier. Coverage is now ASSERTED (R32): --main with zero
main stubs exits 4 and names itself a defect rather than reporting an empty
population as a fact.
2026-09-03 14:47:46 -06:00
Drew T 096fe153cc feat(decomp): parallel gate — 10 fns across 4 binaries (8 workers)
ov_SC02_005    func_8018DFC4
  md_MAIN_003    func_800D0204 func_800D0440 func_800D05B4 func_800D0664 func_800D09A0 func_800D0A7C func_800D0B1C
  ov_SC03_105    func_80180EC0
  ov_SC02_003    func_80187B40
2026-09-03 14:40:01 -06:00
Drew T b4b2400d65 docs(cookbook): §460 — read the scheduler's ready list with -dS
From the S76 func_80180B3C agent (297 ins, 82 -> 23). Three prior attempts
steered sched1 by reordering source and inferring the cost model from .sched
RTL order; cc1 -dS prints the ready list WITH priorities, so it can be read
instead of reconstructed.

Two reusable findings: register pins beat schedule-chasing when the diff
walks a register chain (four pins carried 44 -> 23 after three attempts had
treated the chain as downstream of the schedule) — and statement order was
inert BEFORE the pins and live after, so an 'order does nothing' measurement
is only valid for the allocation it was taken under. Second, sched1's
birthing boost was proven to be the dial and is still unturnable here:
every spelling making the mask single-set lets combine fold the subreg and
lose four instructions. A dial you can prove and cannot turn is permuter
fuel, not a wall.
2026-09-03 14:34:54 -06:00
Drew T 1778556b6d fix(draw_waves): a ledgered stub that is still OPEN is still work
After two S76 draws the tool reported 'population: 0 open stubs' with 51
open stubs on disk. True, and about a scope far narrower than the reader
believes — the session's dominant defect class. The draw ledger records what
was ATTEMPTED, not a property of the function, so a stub still open after
being drawn (the draft was never gated, or the blocker has since been fixed)
was filtered forever while the work remained.

This is the S72 exclude-list lesson in a second place, and the fix is the
same shape: --redraw-open includes them, and the population line now always
names how many were filtered for that reason alone, saying explicitly when
an empty pool means 'the ledger has seen them all', not 'the frontier is
empty' (R41 — a number ships with its denominator).
2026-09-03 14:26:57 -06:00
Drew T 63d9a36f8d fix(rtu_match): route the reorder-island TUs through as -O2, like match_one
The fourth copy of one defect. The Makefile pipes REORDER_TUS through
reorder_passthrough.py into as -O2; rtu_match hardcoded maspsx + as -O1, so
for those TUs it reported a phantom +1 epilogue instruction and
recover_integration --probe-only booked it as a real DIFF.

Found by a drafting agent on func_8005D4B8: the already-fixed match_one said
MATCH 14/14 while rtu_match said 15/14, and the agent correctly identified
its own oracle as the liar rather than the draft. Derived from the Makefile,
never copied (R51).
2026-09-03 14:25:26 -06:00
Drew T 9df4ae32f6 fix(api_agent): never warm-start a pack from the target's own assembly
Third door of one defect, and the one that mattered. A §265 verbatim body is
stored as <fn>.c like any draft, so prior_draft offered it under 'a previous
attempt left this body behind, keep what matches' — an invitation to
resubmit it. match_one then says MATCH, the gate goes green, nothing is
decompiled.

Measured today: gate_main banked 9 such bodies with progress.py moving by
exactly zero; harvest_verify had no guard at all; and with BOTH gates fixed,
two relaunched agents (func_8005E79C, func_8005EAC8) STILL returned verbatim,
because the pack handed it to them and they reasonably reported 'the prior
draft is already MATCH closeness 0'. It is — that is the problem. Fixing the
consumers is not the same as fixing the supply.

Verified on func_8005EAC8: 2 verbatim candidates now rejected with a named
reason (R32, never a silent drop) and the warm start falls back to a real C
body from wave_m05/shard31. Shared by claude_wave_packs, so every future
Claude wave gets it too.
2026-09-03 14:20:44 -06:00
Drew T 186a8b1548 fix(match_one): model the reorder island, not maspsx, for its four TUs
REORDER_TUS := 800c2 800c2_2 800c2_3 800c3 are piped through
reorder_passthrough.py into as -O2 by the Makefile — the mode that fills
delay slots and emits the jr/addiu epilogue. That island landed 2026-09-01
and banked 20 functions. match_one, the oracle every drafting agent scores
against, still compiled those TUs through maspsx + as -O1, so it reported a
phantom LENGTH-DRIFT in the epilogue and an extra instruction.

Measured on one plain-C draft of func_8005ECC0:
  maspsx + as -O1   closeness 5, 36 ins vs 35   'the §188 wall'
  reorder + as -O2  closeness 2, 35 ins vs 35   epilogue identical

Cost, in the S76w wave alone: seven of eleven main agents produced correct C,
saw the phantom tail, correctly identified the §182/§188 shape, consulted
oracle_reorder.py — which told them 'file IMMOVABLE, no C-level work can ever
close it' — and each submitted a §265 verbatim-asm body instead. They all
reasoned correctly from a false premise the knowledge base gave them.

The TU list is DERIVED from the Makefile, never a second copy (R51 — a
derived property stored as config goes stale, which is this defect exactly).
oracle_reorder.py's docstring is corrected and the cookbook carries the
§182/§188 correction with the byte evidence.
2026-09-03 14:17:53 -06:00
Drew T dcbcb04bf1 fix(harvest_verify): refuse a verbatim-asm draft, same as gate_main
One defect, two doors. gate_main gained this refusal earlier today after 9
main functions round-tripped verbatim -> stub -> verbatim and 'banked' with
progress.py moving by exactly zero. The S76w wave then produced verbatim
submissions for md_MAIN_003 and ov_SC06_010 — which reach the tree through
harvest_verify, not gate_main, so the guard I added would never have fired
on them.

This is the §442/S74 sibling-provisioner lesson again: a fix made in one of
two paths is a fix in neither. Both gates now call the same
draft_prechecks.is_verbatim_asm_draft, and harvest_verify SKIPs with a named
reason rather than silently dropping (R32/R43).
2026-09-03 14:14:11 -06:00
Drew T d7a9f471b4 refactor(fleet): convert 26 DECOMPILE-NOW verbatim bodies to stubs
Every remaining DECOMPILE-NOW row in config/verbatim_manifest.json that was
still a §265 verbatim __asm__ body: main 13 (incl. `main` itself, 509 ins,
in src/boot.c), md_MAIN_003 11, md_MAIN_020 1, ov_SC06_010 1. They were
byte-identical by construction and completely undecompiled, and no gate or
draw could see them — draw_waves reported only 26 drawable stubs fleet-wide
while 27 more sat locked in this form.

Byte-neutral, verified per binary: main 143dbb89, md_MAIN_003 dd1b32ec,
md_MAIN_020 0990e041, ov_SC06_010 05c2d8c4.

SKIPPED ov_SC03_107:func_8017D878. The manifest marks it DECOMPILE-NOW but
the cookbook's §265 addendum documents it as a DELIBERATE verbatim bank: its
only use in the TU is address-taken, forcing a `void f(void)` declaration
the real body contradicts, and no C spelling reconciles them. Two sources
disagree; the one with the byte evidence wins.

md_MAIN_020 and ov_SC06_010 needed --asm-subdir: both are single-TU overlays
with zero INCLUDE_ASM lines left, so there is no prefix in the binary to
derive from. Spelling confirmed against a sibling overlay's own stubs.
2026-09-03 14:02:09 -06:00