The one wave core that did not close still paid for itself:
- §50-A the exact §47 priority encoding: pri = floor_log2(refs)*refs*size/(death-birth), birth/death = 2*insn_number,
DEATH IS 2*M NOT 2*M+1; ties break by ascending qty = BIRTH ORDER. A tie you can compute is a tie you can break.
- §50-B ** BOUNDS §48-A1/A4 **: 'cross_jump refunds the bytes' is only true for tails >= 2 insns, or when one path
FALLS THROUGH. jump.c:1993 calls find_cross_jump(minimum=2) and does not count the jumps themselves, so two j's
with a 1-insn common tail will NOT merge. Check the tail length before using A1/A4.
- §50-C an s16 param + 'x|1' manufactures a poison temp (ior->T; sll; sra); s32 does not (combine reuses i2dest).
Widening a parameter can DELETE an allocno.
- §50-D copy preferences beat plain preferences and need a BLOCK BOUNDARY (combine's LOG_LINKS never cross blocks).
- §50-E maspsx/gas MERGES lui $at for two stores to the same 64K page — which is why the original interleaves its
global stores. Never 'tidy up' the store order of a matched function.
- §50-F the documented wall: a local-alloc qty_compare_1 race needing a reload-deleted no-op copy in a specific range.
The sched.c analogue of §47's live-length slider. A close=2 with IDENTICAL registers is not a regalloc
residual — it is sched2's rank_for_schedule falling through to its final tiebreak,
'return INSN_LUID(tmp) - INSN_LUID(tmp2)', i.e. position in the .greg stream. Root cause is upstream in
sched1: adjust_priority/birthing_insn_p gives every register-DEFINING insn LAUNCH_PRIORITY 0x7f000001
(sched.c:2574), which sinks the un-boosted insn past its rivals and inverts the LUID order.
THE DIAL: materialize a call argument's sign-extension into an explicit s32 temp, placed AFTER the
intervening statement (adjacent to the load, combine fuses lhu+sll+sra into one lh and you LOSE 3 insns;
the intervening store blocks the fusion). Widen the prototype to (s32,s32) so the call adds no conversion.
Same instructions emitted, earlier INSN_LUID -> the tie flips.
Zero-byte dial family is now three: §47 live-length (global.c), §48-A1/A4 sink-init/sink-call
(global.c/local-alloc), §49 LUID (sched.c). Method: -dS -dR dumps the ready lists + priorities; equal
priorities => you are on a LUID tiebreak => the fix is PLACEMENT, not registers.
11 of 12 wave cores now MATCH.
The inverse of A1: A1 sinks an INIT to shorten a live range; this sinks the CONSUMER to delete the
allocno outright. A value defined in both if/else arms and consumed only by a call at the join becomes
a cross-block global allocno whose copy-prefs include the ARG register — and find_reg's copy-pref
override scans ascending regno (NOT reg_alloc_order), so $5 deterministically beats $16. The only
escape is allocno_calls_crossed>0 (global.c:906 strips caller-saved prefs), which a pseudo defined
after one call and dead before the next does not get. Duplicating the consumer call into the arms
demotes it to a call-crossing BLOCK-LOCAL -> local-alloc gives it a callee-saved reg, preserving the
§48-A2 $s0 occupant; the identical post-reload tails are re-merged by cross_jump, so the duplication
costs ZERO bytes. 10 of the 12 wave cores now MATCH.
- §48-A allocno-PRICING DIALS (global.c:594): sink an init into the if/else arms to collapse a
live-range and RAISE priority byte-neutrally (cross_jump re-merges the tails after regalloc);
the local-alloc $s0 occupant that pushes arg0 to $s1; block-scoped per-case temps as a
local-alloc tie gate (local-alloc.c:1765 refuses to tie a multi-block pseudo).
- §48-B THE EBB RULE, the general form of §46-L2: anything that must survive cse needs its def and
uses in different extended basic blocks — reg-reg copies, held global addresses (la $sN in a loop
preheader, def at loop top + use inside a jtbl-reached case), pointers-to-global across calls.
Corollary: a pointer-to-global survives only if EVERY use is at offset 0 (fold_rtx folds sym+k).
- §48-C the C TYPE selects the addressing mode: scalar global -> lui/%lo, struct global -> la+offset;
lwl/lwr block copy == a plain assign of a 2-byte-aligned struct (mips.c:output_block_move needs
align>=4 for the lw/sw arm); the dead-sibling-scalar trap (cost 108 ins — use a real array).
- §48-D the CROSS-JUMP RATCHET: two cases needing opposite branch senses cannot be a mirrored
if/else — cross_jump + jump.c's invert-over-uncond-jump collapse them into one. Use gotos into
labels inside the other case.
- 9/12 first-pass MATCH by ordinary agents applying the map. Fable5 discovers a class; everyone
else applies it.
The largest unmatched core in the game, walled at close=2 for the permuter (25 min, no close) and
queued for a gdb-on-cc1 read. Closed WITHOUT gdb — the RTL dumps were the oracle:
- THE TIE, byte-measured (.lreg): &g.sz1 pseudo 228 refs 13 / live_length 783; &g.sz2 pseudo 230
refs 13 / 782 -> pri = int(390000/L) = 498 == 498, an exact int-truncation tie in global.c:594
allocno_compare. Tie-break = creation order -> allocation follows emission; the target needs them
to DIFFER (allocation sz2-first, emission sz1-first). The shipped operand-permutation workaround
could only pick one (close=2 vs close=10).
- THE FIX (§47): restore NATURAL operand order (emission correct) + ONE zero-byte
`__asm__ volatile ("")` placed BETWEEN two existing GTE volatile asms (no new cse/sched barrier —
one is already there) -> +1 static insn at global-alloc time -> L 784/783 -> pri 497 vs 498 ->
the tie SPLITS toward the shorter-lived (later-created) pseudo, which is ALWAYS the direction
"allocation != creation" requires. All 10 grants cascade; MATCH 952/952 first try; the slider
emits only #APP/#NO_APP (zero bytes). PIN-FREE, ×113 template-safe.
- BANKED ×1 in ov_SC01_000 through the WHOLE-BINARY gate (jr fn — match_one is not the arbiter,
§8a): lazy isolation -> new region ov_SC01_000_jr_8017BEBC + 9-piece jtbl interleave -> splice ->
BYTE-IDENTICAL. One TU-visible decl reconcile en route (D_800B9A02: declare the TU's `short`,
force the unsigned halfword at use `(*(u16*)&D_800B9A02)` — §8d sub-class (b)).
- R22 clean-fleet 136/136 BYTE-IDENTICAL; 0 NON_MATCHING (G4). The ×113 sibling sweep is IMM-class
(scattered addresses) -> Task-8 mechanical work via the imm engine.
- cookbook §47 (the slider method + the placement rule + the direction law); decision-log (R31).
The extract_unit fix (commit:0552) revealed 82 families with a genuinely-matched exemplar and UNSWEPT
siblings (~2.03M templatable bytes) — mostly exemplars cracked AFTER the session-2/3 mechanical band
sweeps ran (the giant campaign + recent cores), so the sweep had simply never seen them.
- re-ran `family_sweep --hseq --band substantial` on a regenerated manifest: 29 matched-exemplar
families, 1046 member drafts staged, 1643 correctly skipped as pinned-exemplar.
- BANKED 266 member-matches / 780 gate-rejected. The whole-binary byte-gate (G3/P9) arbitrated every
one; R22 clean-fleet 136/136 BYTE-IDENTICAL from `make clean`.
- metrics: instr-weighted 63.6 -> 63.8%; distinct-code 40.5 -> 40.7%; fn-count 82.39%.
The 780 gate-rejections are the next lever: family_sweep's h_seq path does NOT yet carry the §8d
`scoped` stage (it prepends carried data externs at FILE scope, the exact class that blocked the jr
sweeps), so a large share are expected to be the same decl-environment conflict. Investigated next.
The heaviest core in the game (890 ins, reach 134 = 477 KB) closed from close=39 to MATCH 890/890,
PIN-FREE, by cheap-Opus reading loop.c/jump.c/cse.c. All 39 residuals were in ONE case body and every
one was STRUCTURAL — the permuter could not have reached any of them. Four new general levers:
- L1 PEEL: a loop's `break` must not land on the loop's own fall-through label — that leaves
NOTE_INSN_LOOP_BEG + an unconditional jump, firing duplicate_loop_exit_test (jump.c:2131), which
rotates the loop and peels iteration 1 (const-folding `i++` and dragging an extra address
re-materialization block). Write `goto <label>;` — same destination, different construct.
- L2 SURVIVING COPY: a source-level `fp = q;` ALWAYS dies (cse canon_reg + qty_first_reg, then flow).
To make it survive, split def and uses across extended basic blocks — cse resets its hash table at a
label with >1 predecessor. Test the memory, assign inside the guard branch.
- L3 MERGED STORE: write the store INSIDE the branch that reaches the shared tail, so jump2 tail-merges
it and reorg steals the `li` into the delay slot. An unconditional store before the `if` blocks it.
- L4 UN-COALESCED LOOP COPY = a non-replaceable giv, needing all three of: an index giv `&A[i]`; a use
OUTSIDE the loop (record_giv, loop.c:4437 -> emit_insn_after at loop.c:3945); and the biv increment
LAST, so the reduced giv's addiu lands in the loop-back delay slot (i++ at the top costs +1 insn).
- L5 KEEP TAILS APART: two structurally identical loops must differ in a REGISTER or cross_jump merges
their tails — give each its own pointer pseudo. (§8's cross-jump lever, inverted.)
Confirms the tier doctrine: cheap-Opus applying the documented §31 map cracked the game's heaviest core;
Fable5 was not needed. R17 held — "wrong BYTES" -> read the real gcc-2.7.2 passes.
- BUG: the guard `not ln.rstrip().endswith(";")` misses m2c's declaration form
`M2C_UNK func_80178D40(s32, s32); /* extern */` — the raw line ends in `*/`, not `;`, so a
DECLARATION was accepted as a DEFINITION and the forward brace-scan swallowed the NEXT
function's body, handing remap_hseq a garbage unit.
- BLAST RADIUS (measured): 15 of 35 substantial-family exemplars were phantom "matches" — all
still INCLUDE_ASM stubs (incl. func_80178D40 and the carried-queue func_801670E4); 3 more
anchored on the Phase-17 canonical-sig layer's `extern … /* match-first, arity N */` decls and
templated garbage, leaving those families SILENTLY UNBANKABLE. The whole-binary byte-gate
rejected every one — no wrong match was ever banked (G3/P9 held) — but the engine burned a
build per sibling and every extract_unit-based readiness analysis was wrong.
- FIX: strip trailing comments before the `;` test.
- REGRESSION-GATED over the whole corpus (6,286 family exemplars, .run/_eu_before.json):
15 phantom exemplars now correctly refused; 3 garbage units corrected to the REAL definition
(found in the right region file); 0 real definitions lost; 0 unit contents otherwise changed.
src/ and config/ untouched, so the committed build is unaffected.
- cookbook §40: the trap + the general lesson — this is the phase's FOURTH silent-skip bug
(find_site braces, overlay_files splits, reconcile_decls fn-ptr regex, now this). A tool that
silently no-ops on input it cannot parse is indistinguishable from one that had nothing to do.
- ROOT CAUSE (R14 — the session-7 diagnosis was half right): the isolated region builds [ OK ]
WITHOUT the body, so §8b isolation was never implicated. `family_remap.gather_externs` prepends
carried decls at FILE scope; D_801812A4 is a fn-ptr dispatch table the sibling declares FOUR
incompatible ways at BLOCK scope inside its own later functions, so the carried file-scope decl
ESTABLISHES A GLOBAL THE TU NEVER HAD and every later block-scope extern must now agree with it.
Byte-proven asymmetry: BLOCK(int)->BLOCK(struct*)->FILE(void*) builds; FILE(void*)->BLOCK(int)
errors. It was the ONLY hard error in the build — all 27 carried function externs were fine raw.
- THE FIX (demote, don't reconcile): tools/scope_data_externs.py emits a carried D_ extern at BLOCK
scope inside the function body when the TU has no file-scope decl of it above the insertion point.
Byte-neutral (an extern emits no code; type + access opcodes unchanged) and never worse than raw,
so it needs no oracle, no type comparator, no fn-ptr parser. Restores fidelity — the original
declares these symbols at block scope in exactly this way. Wired into jtbl_family_bank as the
`scoped` stage: raw -> scoped -> recovered -> reconciled (scoped is the base for the later stages).
- reconcile_decls is the WRONG instrument for this class, twice: its oracle answers "what does the
FLEET call this symbol" when the question is "what can THIS TU see", and its DATA_DECL_LINE_RE
cannot parse `extern void (*D_x[])(void *);` — silently skipping the very symbols that were
failing (the phase's third silent-skip bug, after find_site braces + overlay_files splits).
- R17 TRIAGE RULE, first real test, held: `conflicting types` = the compiler REFUSED TO COMPILE =
a C front-end diagnostic = our Python. Reading cse.c/global.c would have taught nothing.
- RESULT: func_8015AE2C (562 ins, reach 134) swept 133/133 siblings, 0 failures. R22 clean-fleet
136/136 BYTE-IDENTICAL (534 changed src files); dedup-check 1813 validated / 0 failed; 0
NON_MATCHING (G4). instr-weighted 63.0 -> 63.6%; distinct-code 39.1 -> 40.5% (+256 unique fns /
+79,957 ins) — one core, ~0 agent tokens.
- knowledge captured during the producing session (R30/R31/R21): cookbook §8d, decision-log
2026-07-13 session 8, SETUP tool-inventory row; CURRENT_PHASE session-8 checkpoint.
Drew asked whether the x133 sweep blocker warrants a gcc-2.7.2 source read. It does not,
and the distinction is worth pinning down because it routes every future residual:
- The sweep blocker is a C FRONT-END diagnostic (conflicting types: two incompatible
file-scope decls of one identifier in one TU). gcc is correctly rejecting plain C89.
The bug is in reconcile_decls (fleet-majority oracle vs the TU's visible decl).
Reading cse.c/loop.c/global.c would tell you nothing.
- func_8017BEBC (close=2) is the opposite: it compiles fine and emits the wrong bytes, and
the cause is localized to global.c's allocno-priority tie. THAT is the R17/§45-B target
(gdb-on-cc1 read of allocno_live_length) — 2 instructions from a 107K-ins bank.
Rule: 'wrong BYTES' -> read the compiler (R17). 'won't COMPILE' -> read our Python.
cookbook §31-triage + the CURRENT_PHASE NEXT block annotated with the routing.
Second silent no-op of the §G class, found while permuting func_8017BEBC (close=2):
- hide_asm() is built for __asm__ STATEMENTS and `register __asm__("$sN")` pins inside a
function body (it scans back to the previous ;{} and forward to the next top-level ;).
A draft whose GTE ops are #defines CONTAINING __asm__ (the PsyQ inline_c.h convention,
i.e. most renderer code) therefore had its macro DEFINITIONS chewed up, swallowing the
function itself -> pycparser 'Function <fn> not found in base.c' -> decomp-permuter
no-op'd in 0s. base.c contained ZERO occurrences of the target function.
FIX: cpp_expand_macros() pre-expands with `cpp -P` so each GTE op becomes an inline
__asm__ statement hide_asm can carry via the b64 pragma. Applied ONLY when a
'#define ... __asm__' is present -> macro-free drafts byte-untouched.
- p16_permute was hardcoded to OV=ov_SC01_077's MAIN object, so no core in another overlay
or split object could be permuted at all. Added --asm-subdir (threaded explicitly: a
def-time default arg cannot see a mutated global).
LESSON (cookbook): permuter 'no match (0s)' is a TOOLING failure signature, never a real
search result. Verify workers actually ran.
Verified: base.c now holds the function; 16 workers searching on func_8017BEBC.
A trailing `.word 0x00000000` under a jtbl dlabel is the ORIGINAL TU's intra-rdata
.align 3 padding, NOT a table entry (0x00000000 is not a jump target). The true entry
count is the fn's `sltiu <n>` bound: func_8015AE2C has sltiu 0x7 = 7 entries yet its
raw dlabel spans 8 words.
maspsx drops all .align, so a C-emitted jump table can never reproduce the pad. Carving
to the next dlabel would reserve 8 words while the compiled object supplies 7 ->
.rodata under-fills by 4 B -> every later symbol shifts +4 (the same image-corruption
class as §41d). jtbl_range now trims trailing zero words, leaving the pad in the raw
post-carve data piece.
Retroactively explains the §8a func_80159C84 '5 words vs the real 6' false-MATCH.
Existing carves are parsed from CONFIG, not re-derived, so committed banks are
unaffected (verified: the 3 carved jtbls are absent from the raw data asm). The build
never invokes jtbl_carve, so the fleet is inert to this change until the next bank.
Found by the Fable5 crack of func_8015AE2C (562 ins x134, MATCH, pin-free).
End-to-end proof of the §8b lazy bank composition on a real cracked jr core:
lazy isolate -> jtbl_carve into the isolated subseg -> C body -> whole-binary gate
-> d19c9580 BYTE-IDENTICAL; R22 clean-fleet 136/136.
- func_80182268 (31-ins jr, ov_SC01_077_after) MATCHED first try: shared-tail
fallthrough (jtbl cases 3+7 enter case 4's tail) + the u16-shift sign-extend idiom
((s8)(*(u16*)(p+0x70) >> 8) -> lhu/sll16/sra24). Carve collided with the committed
func_801734BC carve -> lazy isolation fired exactly as designed.
- R14 FINDING (cookbook §41d): the Phase-17 canonical convention "void->s32 return is
byte-neutral (§3a-1)" is FALSE for a void body with no `return` — it costs ONE extra
instruction. canon_sig_reconcile applies it unconditionally, so it turned a perfect
31-ins MATCH into 32 ins. That extra word made the isolated object's .text 4 B long,
shifting EVERY data symbol +4 -> ~271k differing bytes, image +5 B. match_one said
MATCH; only the whole-binary gate caught it (G3/P9).
- FIX (generalizes the §19 sig_unify lesson): every recovery pass is a FALLBACK, never
unconditional. jtbl_family_bank now gates RAW first, reconciled only on failure.
- 136/136 byte-identical from a clean tree (R22); 0 NON_MATCHING (G4).
The full 54-jr isolate-all on ov_SC01_077 now builds d19c9580 BYTE-IDENTICAL
(R22 clean-fleet 136/136) — the configuration session 5 could not build. The
heavy-jr harvest (191 cores / 5.53M templatable ins) is unblocked.
- R14 CORRECTION: session-5's "gcc-2.7.2 block-scope-extern TU-persistence" root
cause was WRONG. There is no gcc quirk — DEFINE_func_* macros expand at FILE
scope, so their leading externs are genuine file-scope decls that merely live in
engine_core.h, invisible to any col-0 .c scan (1377 macros / 3929 lines / 1462 syms).
- REJECTED the approved "global symbol->type map + shadow set" design: the engine is
loosely typed (func_80173544 is DEFINED `s32 f(void*)` yet declared `extern void
f(void);` inside func_801734BC's body), so declaring every USED symbol hoists that
block-scope shadow to file scope and CREATES the conflict a shadow-set then dodges.
Instead reconstruct the original TU's file-scope decl environment and carry it
strictly FORWARD — conflict-free by construction (every carried decl already
coexisted with every definition in the one original TU; compatibility is
order-symmetric; shadows stay in bodies and travel with their item).
- The byte-gate found two MORE lost decl sources, not predicted: (a) a definition is
itself a declaration for everything below it in its TU (func_8012B2CC undeclared);
(b) file-local typedefs used by a carried prototype (parse error, Vec3s). K&R defs
must render `extern T f();` (unprototyped), never f(void).
- LAZY per-core isolation wired into jtbl_family_bank (Drew's call — upfront-x134 =
~7,200 region files): jtbl_carve NON-CONTIGUOUS fail-loud -> jr_isolate_all --only
<core> -> re-extract -> re-carve. Proven on func_80178D40 (890x134, heaviest core):
carve blocked -> isolated (byte-neutral d19c9580) -> carve in its own subseg.
- TWO LATENT BUGS fixed (both would have corrupted the heavy sweeps):
* jtbl_carve.func_subseg derived the owning subseg from the ASM TREE, which `make
extract` never prunes -> after an isolation it returned the STALE owner and
silently re-created the very collision the isolation removed. Now config-derived.
* jtbl_family_bank/jtbl_carve revert() DELETED the shared overlays.mk carve var
unconditionally -> would destroy a COMMITTED carve (all 134 overlays have one) on
any failed sibling. Now restored to its committed value; only region files created
by this attempt are removed; dirty-tree preflight refuses to start a sweep.
- docs: cookbook §8b RESOLVED + new §8c "splitting a TU means rebuilding its
DECLARATION ENVIRONMENT, not moving text"; decision-log 2026-07-13 (R30/R31).
- parser selftest 404/404; R22 clean-fleet 136/136; 0 NON_MATCHING (G4).
Session-4 same-subseg handling (the de-risk preamble's harder half; byte-proof of
the merged build + isolation deferred to Stage 2 with concrete cores):
- jtbl_carve.py: MERGE adjacent same-subseg carves into one spanning .rodata piece
(a code object emits its jtbls contiguous, so two matched jr-fns in one subseg are
byte-correct iff their jtbls abut). BOUND-FIX: a new jtbl's end is bounded by the
next raw dlabel OR the next existing carve start (an already-carved adjacent jtbl
is gone from the data asm -> raw dlabels over-extend it -> false "non-contiguous").
Config-proven (func_80171B4C 801D8C48 merges with func_801734BC 801D8C68). NO-OP
for family-1/cross-subseg (single carve per subseg) -> committed configs unaffected.
- jr_isolate.py (scaffold, NOT yet functional): the non-contiguous case — split a fn
into its own code subseg (whale _o0b precedent) so its jtbl carves independently.
BLOCKED on split_src_region, which can't partition the overlay .c (global canonical-
sig extern layer + per-fn callee-externs + DEFINE_func macros + @class annotations,
~922 non-address items). Stage-2 build item (overlay-.c-aware source split).
- cookbook §8b (the --order sandwich + the two same-subseg cases + the blocker);
CURRENT_PHASE session-4 checkpoint updated with the Stage-2 unblock decision.
- Drew's sequencing (agreed): do the 45 small jr families FIRST — not for byte-weight (~+1% instr,
129K ins) but to de-risk + harden the §8 x134 pipeline before the heavy Fable5 cores bet on it.
- decisive technical reason: jtbl_carve only built the single-jtbl carve; func_8012ACE0 is now
matched in all 133 siblings, so family #2 forces the multi-jtbl address-ordered `ld_interleave
--order` carve -> build & prove it on cheap 30-ins targets first. Also needs no Fable5.
- guardrail kept explicit: small tier = MEANS (harden pipeline + build multi-jtbl), NOT the
objective; the 191 heavy jr families (5.53M ins) remain THE byte-weight target -> pivot after.
- CURRENT_PHASE.md SESSION-3 checkpoint updated to Stage 1 (small + build multi-jtbl) -> Stage 2
(heavy 191, Fable5 un-paused). decision-log addendum with the forcing-function wiki lesson.
- decision-log (R31): §8 unblocked the SINGLE heaviest byte-weight chunk of the game — 9 of
the 10 heaviest unmatched family cores are switch (jr) functions (func_80178D40 890x134 =
477K ins alone); jr substantial = 191 fams / 5.53M templatable ins. My "45 small jr families"
recommendation (129K ins) was a light-tail trap — Drew caught it against the endgame plan
(heaviest-byte-weight-first). Corrected next play: Fable5 crack the heavy jr cores -> §8 x134
bank -> parallel R22 verify; needs Task 7 (Fable5) un-paused (§8 makes that worth it now).
- CURRENT_PHASE.md: SESSION-3 checkpoint as the fresh-session resume point (4 commits this
session: tiny-band commit:0531, §8 PoC commit:0532, §8 x134 commit:0533, R22 parallel commit:0534;
distinct-code 30.3->39.1%, instr-weighted 58.2->63.0%, R22 now ~50s)
- the jr-function ×134 harvest pipeline, proven end-to-end: per family sibling,
jtbl_carve (per-sibling jtbl-rodata carve, computed from THAT sibling's own jtbl
address — the fn is at the same vram across overlays but its jtbl floats) -> make
extract (auto ld_interleave) -> remap_hseq + canon_sig_reconcile -> whole-binary gate
- tools/jtbl_carve.py: per-overlay §8 carve generator (config data-tail split +
<ov>_JTBL_INTERLEAVE var)
- tools/jtbl_family_bank.py: the sibling sweep driver (idempotent, revert-on-fail, byte-gated)
- tools/family_remap.py: extract_unit now carries single-line typedefs (jr-function bodies
define local `typedef struct{} Foo_<addr>;` that must template with the body — the
propagation cap for these; additive, byte-gate-protected)
- func_8012ACE0 family: 133/133 siblings BANKED, 0 failures; R22 clean-fleet 136/136
byte-identical; 0 NON_MATCHING (G4)
- metrics: distinct-code 39.1% (50,698 unique fns), instr-weighted 63.0%
- opportunity (has_mid_jr families): 237 total (5,805 members) = 46 small mid/tiny
(771 members, same mechanical pipeline) + 191 substantial (the Fable5 cores, Task 7 paused)
- NEXT: R22 profiling/parallelization; then the other 45 small jr families
- overlay jr-functions can now bank as C: gcc switch jump tables form a .rodata island at
the overlay TAIL; carve a matched fn's jtbl into a dotted [.rodata, <code-subseg>] subseg
+ ld_interleave (data->rodata->data sandwich) places it byte-exact. cookbook §8a + SETUP.
- tools/ld_interleave.py: --section .<binary> param (derives the <binary>_TEXT/DATA/RODATA/
DATA2/BSS symbol prefix); default .main = the EXE, byte-identical (backward-compat proven)
- Makefile + config/overlays.mk: <bin>_JTBL_INTERLEAVE hook + a $(strip)-guarded extract
branch (gotcha caught: a trailing #comment on the := left whitespace -> non-empty -> the
branch misfired on resident with the EXE defaults)
- PoC: func_8012ACE0 (25-ins jr-fn in ov_SC01_077) reconciled (canon_sig_reconcile) + banked
BYTE-IDENTICAL d19c9580 -- the first overlay jr-function matched through the C pipeline
- R22 FULL-FLEET clean rebuild: 136 passed, 0 failed (main 143dbb89 unaffected by the
ld_interleave change); 0 NON_MATCHING in any default build (G4)
- P9 findings: func_80159C84/func_8015444C (the 2 carried Fable5 jr bodies) are rtu_match
FALSE-matches (incomplete jtbls: 52B vs 56B -> never bank); the maspsx "hang" scare was a
truncated experimental-file artifact (real pipeline builds in ~1s)
- metrics: distinct-code 39.1% (50,572 unique fns), instr-weighted 62.9%
- NEXT: the ×134 automation (generate the per-overlay carve + template the reconciled body)
Comprehensive CURRENT_PHASE handoff for a fresh session: committed baseline commit:0528 (729 banks, R22
136/136); the h_seq engine + reconcile-raw tooling map; the mid-band harvest in flight (~2477+ banked,
uncommitted -> R22 + commit next); the two harvest gaps (§8 jtbl-rodata [Drew-approved] + reconcile
fn-ptr-extern); the Fable5 rtu_match-vs-whole-binary finding (no more Fable5 until re-approval); and the
mechanical next-steps priority order. R30 knowledge capture before context handoff.
- +266 member-matches: func_8015CD20/func_8015C128 templated x133 via --reconcile-raw (each SHA-gated
per-overlay vs config/check.<ov>.sha = byte-identical, G3). Full R22 deferred until func_80176218
releases asm/ (established per-overlay-gate + deferred-R22 pattern, as the committed 463 which R22'd 136/136).
- family_sweep: --reconcile-raw now also covers draft-ov077 (unbanked) cracks (template from the RAW seed).
- P9 CORRECTION + decision-log 2026-07-12: the 2 Fable5 cracks rtu_match-MATCH but FAIL the whole-binary
gate (both jr-functions; rtu_match masks relocs + excludes neutralized INCLUDE_ASM rodata, so it never
verifies the §8 jtbl rodata). TWO harvest gaps: §8 jtbl-rodata (blocks all jr cracks) + reconcile
data-extern (D_801891B8-class, blocks ~15/21 no-jtbl triage cracks). 6 no-jtbl reconcile-clean cracks
bank whole-binary (729 members). rtu_match is NOT a sufficient arbiter for jr-functions.
- BUILT the per-sibling reconcile: family_remap.remap_hseq_body (h_seq-remap a RAW crack draft: symbol +
immediate + cross-address self-rename) + family_sweep.reconcile_remap_hseq + --reconcile-raw. Per sibling,
remap the RAW crack then canon_sig_reconcile against that sibling's own TU (the h_seq port of the h_norm
M2 path) — because a reconciled body is TU-specific and can't template plainly (validation: 0/4).
- HARVEST: the 4 triage isolation-cracks (func_80155800/80167540/801506A4/8016A73C) templated 463/0 x~133
via --reconcile-raw (0 failures). Metrics: instr 58.5->58.9%, distinct 30.9->31.1%.
- each overlay SHA-gated by harvest_verify vs config/check.<ov>.sha (byte-identical = the match def, G3).
FULL R22 clean-fleet DEFERRED until the concurrent Fable5 crack agents release asm/ (their m2c needs it);
R22 fleet-confirm to follow post-window.
- cookbook §40c (the h_seq per-sibling reconcile technique, R30).
- pre-Fable5-window de-risk (Drew): validate reconcile→gate→template on the triage cracks before the window.
- reconcile→bank WORKS: raw 0/23 (§41 def-side wall) -> canon_sig_reconcile v3.2 -> 4/15 banked into ov077
(func_801506A4/8016A73C/80167540/80155800), byte-identical, R22 clean-fleet 136/136.
- templating a RECONCILED body x133 FAILS 0/4: reconciled bodies are ov077-TU-specific (canonical-sig casts
+ collision-renames) -> need per-sibling re-reconcile (§41c). Task-8 prerequisite: port the h_norm
--reconcile M2 path into hseq_sweep so the type-using families (triage cracks + the 61 Fable5 cores) can
template x134. PURE families already template plainly (Task 5: 399 banked).
- decision-log 2026-07-11: the slice paid for itself — found the templating gap BEFORE spending the window.
Paused before building the wiring per Drew.
- full triage complete: cheap 29 (23 closeness-0 isolation-MATCH) / permuter 29 / fable5 61 (1.71M ins).
- attempted to bank the 23 cracked wins into ov077 -> 0/23: the match_one isolation-MATCHes are genuine
function matches but carry standalone struct/scalar typedefs + Ghidra-typed sigs that conflict with the
real ov077 TU (redefinition of struct Obj / conflicting types) = the §41 def-side wall. Banking needs
the Task-8 --reconcile / canon_sig_reconcile pass (not run — paused before Task 8 per Drew).
- docs/phase26-triage.md carries the crack curriculum + the caveat; seeds in .run/phase26-seeds/.
- src pristine, ov077 byte-identical.
- remap_hseq.gather_externs: carry file-scope externs for body-referenced symbols (extract_unit only
grabbed adjacent ones) — the decl class that blocked per-location bodies indexing a global. func_8015F118
gate-fail -> BYTE-IDENTICAL; the 3 tracker-miss PURE families then bank 133/133 each.
- ran the real whole-binary byte-gate on the 29 substantial matched-exemplar families:
532 members BANKED (byte-gated). Per-family: 3 tracker-miss PURE (0x8015d5e8/0x8015f118/0x801407f4)
bank 100% x133 = 399 byte-perfect (the tracker-fix free win); 1 cross-addr family 50%; 9 zero-bank
families are type-using (Work8016/Prim/...) -> the existing --reconcile/type-lift follow-on (Task 8);
16 families pinned -> Task 7 pin-free re-crack.
- VERDICT: the h_seq machinery (tracker + imm + cross-address + extern-carry) is byte-proven 100% correct
on clean families. GO to scale.
- R22 clean-fleet: make clean + extract-all-136 + check-all = 136 passed, 0 failed. 0 NON_MATCHING (G4).
Metrics: distinct-code 30.3->30.9% (+375 fns), instr-weighted 58.2->58.5%.
- decision-log 2026-07-11 (R31: stratify a mechanical-harvest rate by family/class before judging it).
- Fable5 subagent cracked func_80166994 (trail/afterimage ring recorder, 369 ins) — FULLY
STRUCTURAL, zero register pins -> swept ×134 CLEAN (exemplar + 133 siblings byte-identical).
R22 clean-fleet 136/136; instr-weighted 56.8% -> 57.2%; distinct-code 27.3% -> 28.2%
- NEW IDIOM cookbook §43: a K&R s16-param DEFINITION dissolves the §17/§29 "narrow-param wall".
On MIPS K&R promotes s16->int (ABI-identical to the canon-sig s32), body keeps the in-place
sll aN,16 narrow/extend the (s16)cast form can't reproduce. void->s32 return-flip pair:
split //@EDIT (self-fn, ov077-specific) + engine_core.h ec_edit ×5 (byte-neutral, callers discard)
- family_sweep --edit-remap: split-edits now OPTIONAL (apply where present, never skip; the
whole-binary byte-gate is the sole arbiter, G3/P9) — a sibling lacking the ov077 canon-sig decl
still banks via ec_edit + body. edit-absent tracked, not skipped
- R14: the prior wave's "@stuck: none — MATCH" note on func_80166994 was STALE/FALSE (re-ran DIFF
366/369). Verify a MATCH claim vs the bytes, never a stale note
- structural cracks are the ×134-SAFE ones (contrast §42e pin-heavy families that cc1-SIGABRT in
sibling TUs). Other 6 giants -> cheap-Opus applying §43+§31, Fable5 only on new-class evidence
- weighted_metrics() from .run/sig.*.jsonl + src stubs (executable code only, resident + 134
overlays; main EXE excluded). Two framings: fleet instr-weighted (per-overlay, the decomp.dev
-display number) + dedup distinct-code (each unique h_exact once, the distinct-RE number)
- --fleet now emits THREE labeled metrics into docs/progress.fleet.md: fn-count 74.48% (×134-
inflated), instr-weighted 56.8% (shipped .text), distinct-code 27.3% (of 84,996 unique fns)
- --weighted prints the two weighted numbers standalone; degrades gracefully if sigs absent
- corrects the stale "~30-35% byte-weighted" estimate: the giant campaign since Phase 19 raised
the fleet instr-weighted number to 56.8%; the distinct-code 27.3% is the unique-monster-tail truth
- SETUP §tooling row updated (R21)
- family_sweep.py: new --edit-remap MANIFEST mode (§42e) — per family, symbol-remap the
split-scope //@EDIT old||new per sibling + apply once-global engine_core.h ec_edits
(byte-neutral), stage the family_remap body, gate via harvest_verify (the sole arbiter)
- BANKED 266/266 (0 failed): func_80136824 + func_80136334 (array-decay ptr-flip) ×133
siblings each — full ×134. R22 clean-fleet 136/136, fleet 74.40% -> 74.48%, dedup 1813/0
- R14 FINDING (cookbook §42e addendum + decision-log): the other 4 byte-drift families
(func_80133AB0 zero-reg pin, func_8016DF5C/8013D9B0 GTE-pin, func_80156044 trampoline)
cc1-SIGABRT (Error 134) in the SIBLING TU — hand pins are ov077-TU-context-specific,
NOT mechanically ×134-recoverable; backlogged as ×1/permuter fuel. rtu_match/match_one
are blind here (neutralized/isolation compiles crash too); only make build is truth
- 0 NON_MATCHING in any default build (G4)
- R14 CORRECTION of the prior "family_remap limitation" call: it was a MISDIAGNOSIS. family_remap
succeeds on all droppers; the "remap-fail" family_sweep reports was a mislabeled canon_sig_reconcile
throw ("no definition of func_X found in draft") — the def-finder regex required a leading \n, so a
//@EDIT-stripped raw draft with the fn definition on line 1 was not found.
- FIX: def-finder regex \n -> (?:^|\n) (also match a def at draft start; strictly additive, low-risk).
- Recovered func_8014FE60 fully: 133/133 siblings banked (fix + engine_core.h DEFINE_func_8014FDF4
extern void->s32 global flip, byte-neutral fleet-wide; caller discards return).
- Residual (the genuine, small --edit-remap): func_8016DF5C/80136334/8013D9B0/80156044 reconcile but
byte-drift per sibling (out-of-body fixes: pointer //@EDIT, no-proto, return-flip not carried per sibling).
- cookbook §42e (the two-layer diagnosis + the forward ×134-leverage-realism rule); decision-log corrected.
- R22 clean-fleet 136/136 BYTE-IDENTICAL from a fully clean tree; NON_MATCHING 7 (0 in default build, G4).
- func_80164930 (81 ins) CRACKED + swept x134 = 134 fns (133/0 siblings, family_sweep --reconcile).
The crack = the read-global fix: flip the file-scope decl s16->u16 (byte-neutral to the store-only
caller func_801647A4) + reference the global directly, so the read lowers to direct-addressed `lhu`.
- TWO DURABLE FINDINGS (cookbook §42b):
(1) THE STALE-OBJECT GATE TRAP: a piped `make build >/dev/null` that FAILS leaves a stale .o, and
`asm-differ -o` then reports a phantom score-0. This invalidated wave-2's "iso-drift" labels --
a rigorous re-check (rm .o + build exit-code + real whole-binary SHA) shows all 4 remaining
iso-drift drafts NOCOMPILE (unreconciled callee externs vs the TU canonical-sig layer). Every
gate MUST rm the split .o + check the exit code (compounds the §42a --out gotcha).
(2) canon_sig_reconcile `*(T*)&D_sym` READ-global drift: &sym forces the address into a held register
(kills direct %hi/%lo -> schedule drift); write-only globals unaffected. Fix = file-scope exact-type
decl + direct ref (a block-scoped `extern u16` vs ambient s16 is a hard cc1 conflicting-types error).
- Frontier reassessed: the 9 remaining wave-3 targets each need real-TU reconcile-cracking (NOT gating the
broken wave-2 drafts); each cracks -> ~134 fns (all x134 families). ~1,200 fleet potential.
- R22 clean-fleet 136/136 BYTE-IDENTICAL from a fully clean tree; NON_MATCHING 7 (0 in default build, G4).