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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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).
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.
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.
§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).
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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).
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).
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.
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.
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).
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.
I converted 9 main SDK functions from §265 verbatim bodies to INCLUDE_ASM
stubs so they could be decompiled, then 'banked' all 9 from stored drafts
that were those same verbatim asm blocks. match_one printed closeness 0 nine
times and the whole-binary gate went BYTE-IDENTICAL — both truthfully, since
a raw asm blob assembles to the bytes it was copied from. Nothing was
decompiled. progress.py caught it by not moving: REAL 882, VERBATIM 164,
INCLUDE_ASM 37, identical before and after. The banks are reverted.
The cookbook's closing paragraph, written last session, describes this exact
trap. I read it and hit it anyway ~4 hours later, because the rule was
addressed to 'any burst over this class' and I was hand-picking stored
drafts, and because 'no byte gate can catch it' reads as unpreventable. The
byte CHECK cannot; a slate-load refusal can.
draft_prechecks.is_verbatim_asm_draft: a file-scope __asm__ naming the fn via
.ent/.globl/label AND no C definition of it. Both spellings of .ent handled
(inside a C string it is a backslash-t, not a tab — five censuses of this
class disagreed until that was fixed). gate_main refuses such a slate beside
its existing INCLUDE_ASM no-op refusal (R43).
Census of the draft store: 1,099 of 704,375 .c files are verbatim-asm drafts
under ordinary <fn>.c names. Negative control: 0 false positives across
45,898 drafts carrying both a C definition and an inline __asm__ (R39).
The 9 SDK functions the verbatim manifest marks DECOMPILE-NOW in src/800c3.c
and src/800c2_2.c were §265 verbatim __asm__ blocks: byte-identical by
construction, undecompiled, and unreachable by every gate in the project,
which splices a draft in place of an INCLUDE_ASM line these did not have.
splat also stops emitting <fn>.s for them, so they had no target asm to
match against either. Byte-neutral: main still builds 143dbb89.
verbatim_to_stub refused three of them — src/800c2_2.c has no sibling
INCLUDE_ASM to copy the subdir spelling from, and all three of its remaining
functions are verbatim, so the file can never grow the sibling the rule
wants. The tool that exists to reach unreachable functions could not reach
them. It now DERIVES the spelling and proves it: the prefix from this
binary's other TUs, the last component from the file stem, which must appear
as a "c" segment in the binary's own splat config — the same file that
decides where splat writes the .s. Still refuses when either half is
unproven; --asm-subdir is the explicit override.
The S75 checkpoint recorded this group as "20 of 21 banked, one bisection";
counted from src/, it is 9 outstanding, corroborated by an independent count
from config/verbatim_manifest.json.
gate_main is the only trustworthy EXE verifier and is strictly serial: one
flock, one tree, a full clean rebuild per bisect step. The serialization is
an artifact of the SHARED TREE, not of the verification, so this runs the
REAL gate_main inside N git worktrees and hands the union of what they prove
to ONE authoritative gate_main in the real tree. Workers discover; only the
final serial pass banks. Two chunks that each pass alone can still fail
together, which is exactly why that pass exists (G3 — the arbiter never moved).
The non-obvious hazard is asm/: parallel_gate symlinks all 442 MB because an
overlay gate only reads it, but main's verification RUNS make extract, which
writes it. main owns 6.2 MB of that, so this copies main's subtree per worker
and symlinks the other 214 binaries read-only.
Two defects the negative control caught, both mine:
* .run/obj40 (11 MB of SDK objects) was never provisioned. The Makefile says
a tree without them 'builds byte-identically via the stubs'; that is no
longer true for main, whose decompiled src/800_c.c CALLS CdReadyCallback —
the link failed outright with an empty build/psyq/.
* make_worktree reads parallel_gate's module-level WT_ROOT, so the first run
put its worktree in .run/pgate/wt0 — the slots parallel_gate force-removes.
Measured: one gate cycle is 16s in both trees, so MAX_STEPS=24 is ~6.4 min
serial and ~90s across four workers. The docstring's original '1-2 min per
step' was my assertion, not a measurement, and is corrected in the file.
try_batch is stateless and the bisect loop held `good` only in memory,
writing .run/gate_main_banked.json once at the very end. A 34-minute
bisection killed by a timeout, a Ctrl-C or a supervisor therefore lost
every match it had already PROVEN — and each of those proofs cost a full
clean EXE rebuild. The S75 checkpoint named this the single highest-value
gate improvement available.
Adds an atomic .run/gate_main_progress.json written after every verdict,
and a resume that reuses it. Three guards, each a way it could silently
lie: the journal must belong to this slate; entries are re-keyed against
`kept` so a draft dropped by resolve_conflicts cannot sneak back; and the
draft's content hash must still match (R56 — a verdict measures those
bytes). Resumed sets are re-verified as one batch anyway, so a wrong reuse
costs one rebuild and can never bank anything unproven. --no-resume opts out.
Negative-controlled on six cases incl. a changed draft, a foreign slate and
a half-written journal.
SaveLoadRoutine is `case 0:` inside func_8002B0B4, not a function of its
own (S75). The lingering `= 0x8002B154; // func` declaration kept splat
emitting asm/nonmatchings/800_b/SaveLoadRoutine.s, which progress.py
reported as the single UNPLACED parse hole. The two config/wave_exclude.txt
WALL entries described the same misconception.
UNPLACED 1 -> 0. Build stays byte-identical at 143dbb89.
Verified at close rather than asserted: 25 commits, src/config/tools clean, main
green at 143dbb89..., and HEAD genuinely carries the func_8002B0B4 C (0
INCLUDE_ASM for SaveLoadRoutine, 1 real definition). A 47-minute bisection left
several mid-run readings that looked like regressions and were not, so the
figures are now stated from a settled tree.
Adds START HERE item 0: the UNPLACED parse hole is one line --
config/symbols.us.txt:27 still declares SaveLoadRoutine = 0x8002B154 // func, so
splat keeps emitting a .s for a symbol that is now case 0: inside func_8002B0B4.
Delete, re-extract, rebuild. config/wave_exclude.txt lines 14-15 are stale for
the same reason. Left undone only because a gate held main's tree at close.
T10 checklist now carries the S75 line.
Written for a fresh session. Headline: every codegen wall examined this session
was an instrument defect, and the two largest results came from deleting a
belief rather than writing better C -- SaveLoadRoutine's §434 wall was a splat
symbol boundary (it is case 0 of func_8002B0B4, one function on one frame), and
the '§332/§188 wall' was the reorder island, which banked 20 functions whose
drafts had been on disk since waves m04-m16.
Records what I got wrong so it is not inherited: five regex censuses
(116/112/108/178/199), contiguity mistaken for fragmentation, a build-config gap
called compiler-inexpressible, and two bursts drawn at fragments because the
triage came after the draw instead of before it.
R22 clean-fleet NOT run; the checkpoint says so at the top.
BANKED 20 of 21 after bisection in 11 rebuild(s)
143dbb89f34491258bbc27810d0a12ec8b43a8dd BYTE-IDENTICAL
rejected: ['func_8005E13C']
src/800c3.c INCLUDE_ASM stubs 36 -> 16. These are libapi/libcard C functions
that had been banked as §265 verbatim __asm__ blocks and were unreachable by
every gate (a verbatim body has no INCLUDE_ASM to substitute).
The chain that unblocked them, all this session:
* the triage identified the class and showed the "§332/§188 wall" is the §332b
-O2 reorder island, which landed 2026-09-01 -- one day BEFORE four of these
were banked as assembly, with "6 of 53 at closeness 0" drafts in hand;
* REORDER_TUS was extended to 800c2_2/800c2_3 ($(filter) is an exact stem
match, so 800c2 never covered them), proven byte-neutral by --assert-baseline;
* verbatim_to_stub converted 20 bodies back to stubs, byte-neutral;
* gate_main was fixed twice -- it destroyed uncommitted work, and my own first
guard sat inside the bisection loop where it tripped on the gate's own
substitution.
Drafts were already on disk from waves m04-m16 and s67m1; not one needed
redrafting. This is the §451 lesson paying out: the evidence was recorded, and
what was missing was a tool able to reach it.
R33, "the best outcome is a DELETED SCANNER, not a fixed regex". asm_in_c.py
existed to DISCOVER the §265 verbatim class by parsing __asm__ blocks. That job
is done, and regex was the wrong instrument: five successive censuses returned
116 -> 112 -> 108 -> 178 -> 199, and the classification was worse than the count
-- it called 154 rows "game code" where the authoritative answer is 24.
The real answers came from evidence a regex cannot see:
* the <OBJ>_OBJ_<hex> naming key -- every one is placed_object.text_start +
hex, so those symbols are OFFSETS INTO LIBRARY OBJECTS, not functions;
* the PsyQ archive symbol tables in .run/obj40/, which keep statics as W
symbols, so for a byte-identical object the archive IS the function map
(checkRECT = SYS.o+0x52C = func_80059760, and NONE of the 44 SYS_OBJ_*
symbols in SYS.o is a function).
So:
config/verbatim_manifest.json (NEW, committed) -- the authoritative census.
200 rows, derived once from the ROM image + archives + naming key, each with a
class and a DISPOSITION:
PERMANENT-VERBATIM 69 rows / 57 units hand asm; never decompilable
DECOMPILE-AS-PARENT 57 rows / 23 units a FRAGMENT; decompile unit_entry,
never the fragment itself
DECOMPILE-NOW 41 rows / 41 units
DECOMPILE-LOW-VALUE 20 rows / 4 units
UNCERTAIN 5 / NOT-VERBATIM 7 / NOT-CODE 1
tools/verbatim_check.py (NEW) -- a GUARD, not a census. Detects verbatim bodies
(the cheap part, and the only part regex is good at), diffs the NAMES against the
manifest, and reports NEW / GONE / MOVED. A NEW row means someone banked assembly
and it is about to become invisible work; it is never allowed to inherit a
disposition by default. It deliberately does not classify or count units.
Compares case-insensitively on the hex, because an address is a NUMBER (R48).
tools/verbatim_target_s.py -- put on the MANIFEST LEASH. It used to enumerate
every verbatim SYMBOL, and 62 of those are not functions (fragments, bare
epilogue tails, padding, trampolines). Emitting per-symbol targets for them is
what sent two drafting bursts at things no C function can express. It now takes
only DRAFTABLE dispositions: 66 targets emitted, 134 skipped and SAID SO.
tools/verbatim_to_stub.py -- repointed to verbatim_check for detection, so there
is ONE detector in the tree rather than three copies.
tools/asm_in_c.py -- REMOVED.
I added the guard to try_batch() an hour ago. try_batch runs REPEATEDLY during
bisection, and its own first substitution makes main's TUs dirty -- so on
iteration two the guard could not tell the operator's unsaved work from the
gate's own in-flight edit, and aborted the run:
M src/800c3.c
gate_main: aborting with an UNVERIFIED substitution in main's TUs — reverting
It failed safely (reverted, no bank lost, and said so), but it made the gate
unusable for any batch larger than one.
Hoisted to assert_main_tus_clean(), called ONCE from main() before any
substitution. The lesson is worth the line it costs: A GUARD MUST BE ABLE TO
DISTINGUISH THE STATE IT PROTECTS FROM THE STATE IT CREATES. Placed inside the
loop it was checking its own footprints.
Negative-controlled both directions: a genuinely dirty src/800c3.c is refused by
name before anything is substituted, and a clean tree now proceeds into the
bisection (currently running 21 drafts).
These are libapi/libcard C functions in src/800c3.c that were banked as §265
verbatim __asm__ blocks. The triage (.run/S75/triage/report.md) established they
carry the §188 shape (`jr $ra` with `addiu $sp,$sp,+N` in the slot) and that the
"§332/§188 wall" is the §332b -O2 reorder island, which landed 2026-09-01 --
one day BEFORE four of them were banked as assembly, with the commit itself
recording "6 of 53 at closeness 0" stored drafts.
Converting them to stubs makes them reachable by every gate again (a verbatim
body has no INCLUDE_ASM to substitute, so gate_main drops it as "resolved to NO
stub") and is the honest accounting: a stub counts as OUTSTANDING WORK, a
verbatim body counted as banked.
BYTE-NEUTRAL, PROVEN: make extract + make build BINARY=main ->
143dbb89f34491258bbc27810d0a12ec8b43a8dd BYTE-IDENTICAL. INCLUDE_ASM pastes the
same assembly the block transcribed, so it must be -- but "must" is a claim and
this was gated, not assumed.
Metric moves honestly: VERBATIM __asm__ bodies 173 -> 148, INCLUDE_ASM stubs
34 -> 53.
20 of 24 converted. The 4 refusals are reported, not silent: func_800623A4 /
func_80062434 / func_8006252C (800c2_2) and func_8005D8A0 -- the last being the
row all three of asm_in_c's detectors missed, which is its own finding.
Also fixes verbatim_to_stub to CASE-NORMALISE the address. splat's convention is
func_%08X but analysis artifacts carry lowercase (the triage taxonomy does), and
asking for func_8005ed4c found 0 of 24 blocks that were all sitting right there.
An address is a NUMBER; matching it as a case-sensitive string is R48 in its
case-sensitivity form.
`$(filter $*,$(REORDER_TUS))` matches the TU stem EXACTLY, so `800c2` never
covered `800c2_2` or `800c2_3`. Those two TUs went through maspsx while their
siblings went through reorder_passthrough | as -O2 (the §332b island, landed
2026-09-01).
That gap is why func_80062388's `lui at / jr ra / sw a0,lo(at)` was written up
as COMPILER-INEXPRESSIBLE in cookbook §452: a probe (`void f(int v){D=v;}` ->
cc1 -> reorder_passthrough | as -O2) emits exactly that sequence. It was a
build-config gap, not a gcc-2.7.2 define_delay limit. §452 corrected.
Byte-neutrality PROVEN the right way -- gate_main --assert-baseline builds the
committed tree with NO draft substituted:
BASELINE GREEN — 143dbb89f34491258bbc27810d0a12ec8b43a8dd BYTE-IDENTICAL
This unblocks the 24 SDK-C-REORDER units, four of which were banked as verbatim
assembly on 2026-09-02 off a wall list that predated the fix by one day, with
closeness-0 drafts already in hand.
TWO DEFECTS, both found by the SaveLoadRoutine decompile and both of which made
this gate unable to bank a whole class of function.
1. try_batch() opens with `git checkout -- <main TUs>`. Correct for the normal
flow (restore stubs, re-extract, substitute drafts) and CATASTROPHIC for
anything uncommitted. Measured twice today: the SaveLoadRoutine decompile
(1,179 ins, byte-identical) sat uncommitted while a gate ran and survived only
because it was committed first; and a §265 verbatim body converted to a stub
is UNCOMMITTED BY CONSTRUCTION, so this line restored the __asm__ block NEXT
TO the substituted C -- 9 jump tables instead of 5, jtbl_rodata_pads refused,
and the gate REJECTED a byte-identical bank. gate_main could not bank anything
in the verbatim class, by construction.
Now refuses when main's TUs are dirty, printing the offending paths and
telling the operator to commit (R42) or stash. --allow-dirty /
GATE_MAIN_ALLOW_DIRTY=1 is the deliberate override. A destructive step that
cannot be undone must ASK, not assume.
2. The failure analysis looks for error/undefined/conflict/..., and
jtbl_rodata_pads aborts via sys.exit with a message containing none of them --
so a carve REFUSAL surfaced as "only warnings" and the real cause of a
rejected bank was invisible. Refusals from jtbl_rodata_pads / jtbl_carve /
corpus / jr_isolate_all are now named explicitly as the cause.
Negative-controlled both directions: the guard fires on a dirty src/800_b.c
naming the file, and --assert-baseline on a clean tree still reports
BASELINE GREEN 143dbb89... BYTE-IDENTICAL.
The largest open function in the project, carried as the §434 WALL since Phase
31 opened, is now real C. main SHA1 143dbb89... BYTE-IDENTICAL.
THE WALL WAS NOT A PROPERTY OF THE CODE. A whole-binary census of every .s for
the interior labels and raw addresses 0x8002B154..0x8002C31C found EXACTLY TWO
sources, and both are func_8002B0B4 itself: its own beq/j to .L8002C2A8 /
.L8002C2AC / .L8002BFE4, and its own jtbl_80072E44 (36 entries, entry 0 =
0x8002B154). Nothing else in the binary references the range.
So func_8002B0B4 (40 ins, prologue + dispatch) and SaveLoadRoutine (1,139 ins,
epilogue) are ONE function sharing one 0x40 frame -- entry func_8002B0B4, five
overlay callers, all s32 f(s32, s32, void *). SaveLoadRoutine is `case 0:` of
its state switch; .L8002C2A8/.L8002C2AC are the switch exit and .L8002BFE4 the
outer `case 1:` body. The "no epilogue of its own / must stay file-scope
__asm__" note that parked this for a phase was describing a SPLAT SYMBOL
BOUNDARY, not a code structure. The predicted "middle path" (decompile one while
siblings stay asm) was moot: there are no siblings.
The three "save/load handler code pointers at saveHeaderTemplate+0x54" in
docs/memory-map.md are jtbl_80072E44[0..2] -- confirmed against
dumps/ram_savescreen.bin (0x80072E44/48/4C = 0x8002B154/1AC/BEA4). The S73
correction to that row is right; no further RAM capture was needed.
Verified three ways: match_one MATCH (1179 ins) on a merged target .s; `make
extract && make build BINARY=main` -> 143dbb89...; and gate_main's own
clean_build() sequence driven from Python -> "sha1 143dbb89... == check.us.sha
(BYTE-IDENTICAL)". Independently re-checked here: tools/asm_in_c.py reports
NEITHER symbol as assembly-posing-as-C, so this is genuine C (the 94 __asm__
occurrences in the TU are §3a zero-byte cross-jump barriers, which are C).
TWO NEW TOOL DEFECTS, logged not fixed (main is busy; next session):
* gate_main.py:710 runs `git checkout -- <main TUs>` immediately BEFORE
substitute(). For a function whose current form is a hand-written __asm__
block that restores the block NEXT TO the C, the TU then carries 9 jump
tables instead of 5, and gate_main REJECTS a byte-identical bank. It cannot,
by construction, bank anything in the verbatim class.
* gate_main's error filter (error|undefined|conflict|...) does not match
jtbl_rodata_pads' sys.exit refusal, so that failure shows only warnings.
HAZARD, and why this is committed immediately (R42): any gate_main run on main
wipes this bank via that same line 710.
Ten measured levers for the body are in .run/S75/slr_c/cookbook_448.md pending a
cookbook merge, headed by: when a frame check says "no prologue / no epilogue",
build the MERGED .s and decompile the pair as one function before excluding
anything.
`build/asm/%.o: asm/%.s` globs the ENTIRE asm/ tree, so the 146 regenerated
target .s files I emitted to asm/verbatim/ were picked up as BUILD OBJECTS and
main went red:
make: *** [Makefile:696: build/asm/verbatim/main/func_80052430.o] Error 1
Default --out moved to .run/verbatim_targets/, which is outside every build
glob. main verified green again: 143dbb89... BYTE-IDENTICAL.
The lesson belongs with the others from this session: a generator's OUTPUT
LOCATION is part of its contract, and asm/ is not a scratch directory. The
failure was invisible until a full build ran -- the tool itself succeeded, the
targets were correct, and nothing about them was wrong except where they sat.
§450 — regenerating a target .s for a function that is no longer a stub. The
source must be the ROM IMAGE, never the __asm__ block: the block is the thing
under test, and a target derived from it agrees with the candidate by
construction. Two silent defects caught by ONE known-true cross-check: splat
writes BYTE-order hex where objdump prints the VALUE (reversing double-swaps --
91/1139 words agreed, and the LENGTH was perfect so only a word-level compare
could catch it), and objdump ELIDES runs of zero bytes so every MIPS nop
vanished (-z is load-bearing; there the length assertion did catch it). Plus
verbatim_to_stub: to gate this class, put the function back into the form every
tool already understands rather than writing a parallel gate.
§451 — your evidence has more than one source, and the one you query is probably
the worse one. BEST not LAST from the append-only backlog (a last row is
evidence about that lane's seed, not about the function); journal_notes as a
second, DISAGREEING oracle (37 functions reclassified, G-DRAFTED-UNKNOWN 47->10,
and func_8017DB98's 122 ins banked from a one-word declaration fix the journal
had recorded all along); and a regex that consumes an unbounded body cannot
enumerate the items after the first -- a 400-char window swallowed the next
attempt's header and hid BOTH of that function's MATCH records.
§452 — CORRECTION to §448's headline. A burst against the ten smallest verbatim
bodies returned 0 banks and refuted the "154 functions of real decompilation
work" framing. Four classes are legitimately verbatim: fragments of a SPLIT
function sharing one stack frame (SYS_OBJ_604/640/func_80059760 are the compiled
output of ONE original C function; a bare epilogue tail cannot be decompiled
alone), hand-written GTE assembly from 1998, compiler-inexpressible forms (a
symbolic store in a jr-ra delay slot, which gcc-2.7.2's define_delay cannot
emit), and no-return tails. 154 is an UPPER BOUND, not a work queue, and the
four tells are cheap to check.
Also banked: one agent submitted the verbatim __asm__ block itself as its
"decompile", and match_one truthfully printed MATCH -- a raw asm blob
byte-matches its own source by construction. The adversarial verifier refuted
it. Any burst over this class MUST carry that check: the trivially-passing draft
is not hypothetical here, it is the default thing to produce, and a byte gate
cannot tell the difference.
Provenance stated per row (CONFIRMED = banked through the whole-binary gate;
CLAIMED = the agent's own measurement on a function that did not bank), because
one of these came from a function that was adversarially upheld and then FAILED
the real gate.
A. reg_n_sets is a one-line scheduling dial (CONFIRMED, func_80180ABC 257 ins).
sched1 schedules backward; a pseudo set exactly once gets the birthing_insn_p
launch boost (priority = 7f000001 in cc1 -dS), which drags its load LATE.
Splitting the RMW as 't = t + 1; *p = t;' makes reg_n_sets 2, suppresses the
boost, and floats the load to the block head -- the block-local dual of §350's
shared temp, WITHOUT the global-allocno penalty that costs the in-place addiu.
Companions: 180 legal statement permutations all scored identically while one
cc1 -dS dump named the cause (diagnose, don't permute); a pin-free fix for
paired-register inversion; and gcc frame slot order is NOT declaration order
(BLKmode aggregates go in order at expand_decl, an addressable scalar is
forced to the stack later -- declare 's32 x[2]' to place a slot between two
aggregates).
B. A single-set local's VALUE is visible at a switch join and erases a
zero-extension (CONFIRMED, func_801806F8 241 ins). combine.c:10035 lets
get_last_value bypass the label_tick guard when reg_n_sets == 1, so all seven
narrowing spellings emit nothing. Diagnostic: a visible extension in the
target means the variable has MORE THAN ONE SET in the original source.
Verified against a matched sibling: andi is the multi-set zero-extend and
sll;srl is NEVER reachable from a single expression.
C. CORRECTION to §439 -- the sll 16; srl 16 pair lands AFTER the jal, not before
it (sched1 sinks the ashift past the call), and it works even for a KNOWN
CONSTANT, because the call-split defeats folding structurally rather than by
hiding the value.
D. An offline jtbl-rodata placement audit (CLAIMED, func_800CB00C -- did not
bank, which is the point: both matchers compare .text only, so a jtbl
function's MATCH says nothing about its table).
THE GAP. Every gate in this project substitutes a draft in place of an
INCLUDE_ASM line. A §265 verbatim __asm__ body has none, so:
gate_main.substitute() resolves each entry through the STUB map; a verbatim
function is reported "resolved to NO stub" and dropped
gate_stage/harvest_verify same splice, same gap
splat stops emitting <fn>.s once a function is not a stub
So all 147 were undecompilable AND ungateable -- not for want of information,
but because the information was in a form nothing consumes.
The fix is not a parallel gate (R33 -- one implementation). It is to put the
function back into the form every existing tool already understands: replace the
__asm__ block with INCLUDE_ASM. That is also the HONEST representation --
INCLUDE_ASM pastes the very same assembly the block transcribes, so the bytes
are identical either way, but a stub counts as OUTSTANDING WORK in progress.py
while a verbatim body counted as banked. The conversion moves a function from
"silently done" to "visibly to do".
Two refusals rather than guesses (R43), because both failure modes are silent
and destructive:
* the block is located by brace/paren MATCHING via asm_in_c.asm_blocks, never
regex-sliced -- these blocks are full of braces and parens inside string
literals and an approximate cut corrupts a file that currently builds;
* the asm subdir for the new INCLUDE_ASM is copied from a sibling stub IN THE
SAME FILE. Subsegs are per-file, so a neighbouring file's spelling names a
different subseg -- a stub with the wrong subdir compiles happily and
includes ANOTHER FUNCTION'S ASSEMBLY. With no sibling to copy, it refuses.
--gate rebuilds and asserts the SHA is unchanged, restoring the file if not:
byte-neutrality here is a claim, and this tool exists to enable a byte gate, so
it declines to be the one link that goes unchecked.
Dry-run verified on main:func_80047E58 -> src/800b.c, 9-line block, subdir
correctly derived as asm/nonmatchings/800b. The --gate proof is deferred only
because another agent is mid-build on main right now.