R22 CLEAN-FLEET: make clean -> extract 136 -> build 136 -> check-all = 136 PASSED, 0 FAILED.
make audit-corpus: 0 PHANTOM + 0 TRUNCATED (was 193).
=== A4: a CORPUS defect the byte-gate could never have caught ===
config/symbols.us.txt:981 declared `listCdBuffer = 0x80180000` — a correct Phase-3 name for MAIN's
LIST.CD RAM buffer. But that address is OUTSIDE main's image and INSIDE the overlay slot, and every
overlay's splat config stacks symbols.us.txt. High RAM is REUSED: an address that is a buffer to main
is live CODE to an overlay. So splat saw a symbol boundary mid-code and, across 97 of 134 overlays:
* CUT 97 REAL FUNCTIONS IN HALF (a head ending on a `lui`, no return), and
* INVENTED 96 PHANTOM ONES (a tail beginning by reading the assembler temp $at).
193 slices NOBODY COULD EVER MATCH — not "hard", not "a compiler wall": unmatchable by construction.
They sat in the harvest queue as ordinary work, so agents would burn on them forever and the failures
would be filed as intrinsic compiler residuals.
The phantom listCdBuffer.s in ov_SC01_005 literally begins:
lw $ra, 0x10($sp) / addiu $sp, $sp, 0x18 / jr $ra
splat cut a function immediately before its EPILOGUE and called the epilogue a function.
AND IT HAD ALREADY CONTAMINATED REAL WORK: in ov_SC03_031 the cut landed where the epilogue was
exactly `jr $ra; nop`, so the Phase-26 x134 sweep innocently BANKED the phantom as
`void listCdBuffer(void) {}` — byte-correct, gate-green, entirely fictitious — while leaving
func_8017FFC4 permanently unmatchable. Removed.
WHY NO GATE CAUGHT IT, AND WHY THAT IS THE POINT: INCLUDE_ASM pastes the two .s halves back VERBATIM
in original order, so the image is byte-identical either way. The byte-gate was green the whole time
and always would have been. It is a perfect CORRECTNESS oracle and a NULL COVERAGE oracle. No
assertion added INSIDE it could ever have found this. What found it was a SECOND, INDEPENDENT oracle:
tools/sig_image.py derives boundaries from the ORIGINAL bytes without splat, and DISAGREED with the
corpus (58,524/58,621 agreement with spimdisasm; correct on all 97 disagreements).
=> When one oracle is structurally blind to a class of error, the answer is not a better assertion
inside it. It is a SECOND ORACLE THAT CAN DISAGREE WITH IT. (`make audit-corpus` is now that.)
THE RULE (the mirror of R13/R15, never written down): a symbol whose address falls inside ANOTHER
binary's vram window must never enter that binary's symbol stack.
FIX: config/symbols.us.ram.txt — main-scoped symbols outside main's image — stacked ONLY by
config/splat.us.exe.yaml. Main keeps the name it needs (10 %hi / 11 %lo refs; 143dbb89 byte-identical);
the overlays never see it. Exactly one symbol was in scope fleet-wide; the resident window was clean.
AND A REAL FUNCTION THE ACCIDENT WAS HIDING: in ov_SC01_084 / ov_SC02_041 / ov_SC03_094 / ov_SC06_008
there IS a genuine function at 0x80180000 (111 / 35 / 28 / 74 ins), reachable ONLY via a fn-pointer
table (.word func_80180000) and never by `jal` — so splat cannot find it and needs the boundary
DECLARED. listCdBuffer had been supplying it by luck. Now declared honestly, per-overlay, in
config/symbols.<ov>.txt — exactly where R13/R15 says an overlay-scoped symbol belongs.
=== A5: the closeness oracle every crack agent trusts was lying on 155 functions ===
masked_diff._reloc_kind() knew 26/HI16/LO16. An over-approximating sweep of every reloc objdump emits
across all 3,367 build objects found FOUR: R_MIPS_26, HI16, LO16 — and R_MIPS_PC16 (211). PC16 fell
through to a FULL-WORD compare, but the object holds an UNRESOLVED PLACEHOLDER in the branch
displacement, so that compare can NEVER succeed.
DECISIVE TEST (derived from the invariant, not from reading the regex): INCLUDE_ASM pastes the
ORIGINAL asm, so for every stub diff_object_s() MUST be 0. Measured, coverage-asserted:
2,741 functions scored — old mask: 150 LIES; PC16 masked: 4 LIES.
(The 4 survivors are the separate length-delta defect.) A phantom non-zero sends an agent to grind at
a wall that is not there, and the wasted attempt is then booked as a MATCHING failure, feeding
reserved_walls() and PERMANENTLY BLACKLISTING a function that was never broken.
=== NEW FINDING (found by cutting the R22 corner): a STALE OBJECT CAN PRODUCE A FALSE PASS ===
`.o <- .s` is not a dependency make can see: assembly arrives via INCLUDE_ASM, expanded to a `.include`
consumed by maspsx/as AFTER cpp, while -MMD tracks headers only. Re-extract, build incrementally, and
make links a STALE object. This is not merely slow — INCLUDE_ASM pastes the ORIGINAL bytes, so a stale
object still yields the original image: SHA1 GOES GREEN while the split just changed is never exercised.
A broken config change can be "verified" by an incremental build. Live proof: 8 of 136 binaries linked
stale objects here; they failed LOUDLY ONLY BY LUCK (the dead symbol was an undefined reference) — a
merely-different-but-valid split would have gone green on all 136.
R22/H3 already legislate this, and I broke them. But a rule that needs a human to remember it is not a
gate. FIX: `extract` now invalidates the objects that include what it just rewrote (main's are top-level,
so -maxdepth 1 — verified it cannot clobber the other 1,605 objects). Structural, not advisory.
R14 self-catch, recorded: my first A5 test passed `fn=` to diff_object_s(), which takes two args; the
TypeError was swallowed by my own `except Exception: continue` and it reported 0 scored / 0 lies. I
wrote the exact bug I was auditing, inside the test for it. Caught only because 0 looked wrong. The
test now asserts its own coverage.
docs/family-manifest.md is the document the whole Phase-25/26 structural-family endgame was planned
from. Its matched-set oracle asked ov_SC01_077 ALONE: matched := {h_exact of that one overlay's
non-stub fns} | dedup hashes. So a function ABSENT from that overlay — or stubbed there but matched
in the other 133 — came out "unmatched" and was ranked as live work.
advertised real (derived)
multi-member families 2,758 -> 1,495
"hidden leverage" 11.0 MB -> 3.9 MB
matched-free lever 235/5.9 MB -> 57/1.0 MB
7.1 MB of the advertised leverage was DEAD WORK. And because `instances` counted every overlay
carrying a function — including the ones where it was already banked — the byte-weight RANKING (the
file's entire purpose: "draft these first") was sorted mostly on already-finished code, with the
real targets buried underneath. The A2 audit predicted "true frontier: 1,475 families / 3.9 MB";
derived independently here it is 1,495 / 3.9 MB.
R33: the invariant answers this with no oracle at all —
an h_exact class is WORK iff at least ONE of its instances is still an INCLUDE_ASM stub.
That also makes the dedup-hash union redundant (a dedup-shared member is by definition not a stub),
so the `hash:` regex over config/dedup.us.yaml is DELETED. `instances` now counts only the members
still to bank, so the leverage is the real x-N.
family_hseq: stub scan -> corpus (+100 curated-name stubs the func_-only regex could not see; they
had made 3 still-stubbed functions look like MATCHED exemplars, which every sweep then re-nominates,
produces nothing from, and books as a silent skip). Its hardcoded "expect ~663/~186/~1.85M" self-check
was a stale 2026-07-11 snapshot — 38 banking commits have landed since — and is now labelled a
point-in-time reference, not an invariant. (Verified my change can only GROW the frontier: +100 stubs.)
census_conflict_callees: scoped to src/<ov>/<ov>.c alone, so it saw 13 of 264 stubs and reported
"wave scope: 2 still-stub" when the truth is 57 — every downstream percentage computed against a
denominator 96% too small. Now 0/57 (the audit's exact figure). Its 0-conflict answer was right BY
LUCK; it is now right for a reason. MARKED FOR DELETION (R33): it re-derives from C text what
reconcile_tu.py answers from the build, and its parse holes fail in the UNSAFE direction (an unknown
callee is silently bucketed "conflict-free"). Delete once reconcile_tu is wired into its only consumer.
R14 near-miss, recorded: my first census patch handed collect_stubs() a set of NAMES where it wanted
ADDRESSES, so the membership test was always false and it printed 0/0. Caught only because 0
contradicted the audit's expected 57. A scanner that returns 0 is indistinguishable from a scanner
that found nothing — which is the entire thesis of this audit, and it very nearly bit me while
fixing it.
harvest_verify is the sole arbiter (G3/P9) and has never accepted a wrong match. It also could not
REACH most of the work: it scanned the single file the caller passed as --src and silently dropped
every draft whose stub lived elsewhere. An overlay's source spans up to 14 .c files, so:
open overlay stubs it could not see : 56,742 of 58,717 (96.6%)
ov_SC01_077 reachable by the gate : 13 of 264 (4.9%) -> 264 of 264 (100%)
THREE of the six callers passed no --src at all (orchestrator.py, grinder.py, idiom_hunt.py) and so
inherited gate_stage's `src = src or f"src/{binary}/{binary}.c"` default. For grinder.py that means
1,290 of its own 1,298 QUEUED FUNCTIONS COULD NEVER BANK, however good the permuter's output was.
=> Phase-22's "the permuter's fuel is exhausted" was never a safe conclusion. Re-test (A12).
gate_stage knew the right answer and then handed the gate the wrong file: its negative control
ALREADY globs every split .c to build bin_stubs. The default is now removed; --src is passed only
when a caller deliberately restricts the gate to one TU.
WHAT CHANGED, PRECISELY: only the SPLICE LOCATION. Each draft is now spliced into whichever TU
actually holds its stub, derived from tools/corpus.py. Every TU links into the same image, so ONE
`make build BINARY=<bin>` still gates them all — correct AND strictly fewer builds than the
per-split re-gate it replaces.
SAFETY (this is the byte-gate, so the argument is explicit): the VERDICT is untouched — `make build`
+ SHA1 == the locked hash. INCLUDE_ASM pastes the ORIGINAL assembly, so a wrong draft always changes
the bytes and always fails SHA1. A bug in the splice can therefore make the tool FAIL TO BANK; it
CANNOT make it falsely bank. The failure mode is conservative by construction.
VERIFIED end-to-end (2 real builds, tree clean before and after):
* discovery: 264 live stubs across 12 TUs (was: only those in the single --src file)
* IDENTITY known-answer test: 3 drafts whose stubs live in THREE DIFFERENT split TUs
(_jr_801734BC, _after, _jr_8012ACE0) — all discovered, spliced into their own files, built,
SHA-matched, committed, restored. Final SHA d19c9580 BYTE-IDENTICAL. Under the old code all
three were silently dropped as "not stubbed".
* `git checkout -- src/` recovers, exactly as the docstring promises.
Also derived rather than defaulted: --good-sha now reads config/check.<bin>.sha (a caller that
passed --binary but forgot --good-sha used to gate an overlay against RESIDENT's SHA), and
match_one_closeness resolves the asm subdir PER FUNCTION — one subdir for a whole batch is the same
single-TU bug, and pointing match_one at the wrong one scores a draft against a DIFFERENT function's
asm, producing a phantom non-zero closeness that lands in the backlog and feeds reserved_walls().
No committed source or config changed, so no build artifact can have moved; the byte-gate was
exercised twice and returned BYTE-IDENTICAL both times.
The audit's CRITICAL finding, fixed at the root. Both tools now derive the corpus from
tools/corpus.py instead of keeping their own decaying copy of the tree layout.
build_fuel_manifest.live_stubs() — a hardcoded 3-file dict {<ov>.c, _a.c, _o0.c}. ov_SC01_077 has
FOURTEEN .c files, so it saw 30 of 264 stubs AND REPORTED SUCCESS. Everything downstream consumes
this manifest — worklist.py (100% of its rows), wave_targets.py (100% of its pools) — so:
targets 30 -> 263
reach-134 targets 10 -> 127 (the ENTIRE high-ROI band was invisible)
remaining gain 83,305 -> 994,633 instructions
994,633 is the A2 audit's predicted figure TO THE UNIT — a fourth independent confirmation
(auditor -> skeptic -> corpus.py -> this). Four of the five highest-leverage functions in the whole
project sit in split regions no tool could see; the top one, func_80178004 (165 ins x reach 134 =
22,110), had never been nominated by anything.
It rotted SILENTLY: .run/fuel_manifest.json (Jul 8) recorded 130 stubs; the same code today returns
30, because the Phase-26 jr splits moved ~100 stubs out from under a dict literal last edited in
Phase 22. Nobody noticed, because a target that is never nominated produces SILENCE, not an error.
wave_targets.REGION_SUB / asm_for() — a 3-entry dict with a silent fallback to the main subdir.
ov_SC01_077 has TWELVE asm subdirs, so 78 of the 87 targets any --class wave emitted handed a
drafter an asm path THAT DOES NOT EXIST. The drafter then drafts against nothing, and the wasted
attempt is booked in the backlog as a *matching* failure — which feeds reserved_walls() and
PERMANENTLY BLACKLISTS a function that was never actually attempted. A silent skip compounding into
a false wall. Now 263/263 asm paths resolve, 0 missing; asm_for() raises rather than guess.
Also: --region's 3-value whitelist defaulted to 'main', which sees 13 of 264 stubs even with a
correct manifest -> default 'any', free-form.
R33 throughout: the INCLUDE_ASM line is SELF-DESCRIBING (its first argument IS the asm subdir,
because splat wrote it there), so both dicts were second copies of a fact the tree already states.
A dict literal is strictly worse than the filesystem AND it fails OPEN. Never re-introduce one.
No build impact (selection/report tools only); docs/worklist.md regenerated with the honest numbers.
The 28 surviving audit findings collapse to ONE bug repeated ~10 times: a hand-maintained model of
the corpus layout (a file allowlist, a single-.c assumption, a func_-only symbol regex, a REGION_SUB
dict) sitting on top of a filesystem that already answers the question. The fix is not ten repaired
regexes — it is one DERIVED oracle and ten deleted scanners (R33).
WHAT IT DERIVES FROM
1. THE FILESYSTEM. Which .c files make up a binary, and where a function's .s lives, are FACTS OF
THE TREE THAT SPLAT ITSELF WROTE. The INCLUDE_ASM line is SELF-DESCRIBING — its first argument
IS the asm subdir — so there is nothing to guess and no dict to rot. A dict literal is strictly
worse than the filesystem AND it fails OPEN (silently yields a wrong path) instead of closed.
2. THE PROVEN INVARIANT. INCLUDE_ASM pastes the ORIGINAL asm and the build is byte-identical, so a
function NOT wrapped in it is byte-exact. `matched` is DERIVED as sig - stubs, never re-parsed
from C text. (progress.py learned this the hard way: weighted_metrics() derived and was right;
classify() re-parsed C and inherited a bug.)
VALIDATED against the real corpus:
* ov_SC01_077: 264 stubs across 14 files. The old 3-file allowlist saw 30.
* Fleet: 58,717 stubs vs the allowlist's 1,992 — 56,725 (96.6%) were INVISIBLE.
* Coverage-asserted (R32): every INCLUDE_ASM line must parse, every symbol must resolve (ANY C
identifier — a func_-only regex silently misses the 100 curated listCdBuffer stubs), every stub
must have a .s. A silent skip is a DEFECT, not a no-op.
THE SECOND ORACLE (`make audit-corpus`) — the real lesson of this audit.
The byte-gate is structurally BLIND to a bad function boundary: the .s halves are pasted back
verbatim in original order, so the image stays byte-identical and green. Only an oracle that can
DISAGREE can see it. sig_image is that oracle — Ghidra-free, derived from the ORIGINAL bytes,
independent of splat. corpus.audit() cross-checks the two and reports:
PHANTOM — a stub address the sig does not know: splat INVENTED a function.
TRUNCATED — a stub whose .s length != the sig's: splat MIS-SLICED one.
It reports 193 (96 + 97) — reproducing the A2 audit's number EXACTLY, from an independently written
tool. That is a third confirmation of the listCdBuffer defect (auditor -> skeptic -> this).
AND AN R14 SELF-CATCH, recorded because the near-miss is the lesson.
Run naively over all 136 binaries the same check reports 914 slices — 4.7x the truth. It is noise:
main/resident are signed by the GHIDRA dumper, whose boundaries are shorter than splat's by design
(and which never analysed the linked PsyQ subsegs at all), so the comparison measures GHIDRA'S limits,
not splat's errors. Only the overlays are signed by sig_image, the oracle actually validated at
58,524/58,621. sig_is_independent() now encodes that domain, with the reasoning, so nobody repeats it.
A check applied outside its valid domain does not become more thorough — it becomes noise.
`make audit-corpus` is RED by design until A4 removes the bad symbol line; then it becomes a gate.
38 agents / 2.24M tok / 0 err. 32 findings raised -> 28 SURVIVED adversarial verification
(4 REFUTED, 16 downgraded). 40 scanners measured CLEAN. Full write-up: docs/tooling-audit.md ROUND 2.
THE ROOT CAUSE — one bug, ~10 times: a hand-maintained model of the corpus layout (a file
allowlist, a single-.c assumption, a func_-only regex, a REGION_SUB dict) sitting on top of a
filesystem that already answers the question. Every TU split silently widened it.
DECAY PROVEN: .run/fuel_manifest.json (Jul 8) recorded 130 stubs; the same tool today returns 30.
The Phase-26 splits moved ~100 stubs out from under a dict literal last edited in Phase 22 — and
nobody noticed, because an un-nominated target produces SILENCE, not an error.
MEASURED: 91.6% of ALL remaining project gain is invisible to target selection (true 994,633 ins;
the manifest sees 83,305). 117 of 127 reach-134 fns never nominated. harvest_verify cannot see
56,742 of 58,717 (96.6%) open stubs. wave_targets hands 78 of 87 targets a nonexistent asm path.
THREE RESULTS OVERTURN SETTLED CONCLUSIONS:
1. Phase-22's 'the permuter's fuel is exhausted' is UNSAFE. grinder banks through harvest_verify,
which sees ONE TU — 1,290 of its own 1,298 queued fns live in another. 99% could never have
banked. '0 banks since Phase 21' is equally consistent with 'the tool could not bank'.
2. The Phase-25/26 endgame plan is MAJORITY-FICTION. family-manifest.md advertises 2,758
multi-member families / 11.0 MB; 1,071 of them / 6.80 MB (62% of the byte-weight) are ALREADY
FULLY MATCHED. The ranking — the file's whole purpose — is sorted mostly on dead work.
3. A CORPUS defect the byte-gate is structurally blind to: symbols.us.txt:981 puts a main-EXE DATA
symbol (listCdBuffer = 0x80180000) into every overlay's symbol stack, but in overlay space that
address is CODE. splat cuts 97 real functions in half and invents 96 phantom ones = 193 slices
NOBODY CAN EVER MATCH, in 97 of 134 overlays — and the build stays byte-identical and green,
because the .s halves are pasted back verbatim. A perfect correctness oracle, a null coverage
oracle. What saved us: sig_image was RIGHT (58,524/58,621 vs spimdisasm; correct on all 97
disagreements). A SECOND INDEPENDENT ORACLE is the only reason it was visible at all.
FIX RESTRUCTURED around the root cause: ONE derived corpus oracle (A3) + ~10 DELETED scanners —
not ten fixed regexes. Plus the listCdBuffer corpus fix (A4) and the closeness oracle (A5, which
lies on 155 functions, feeding false walls into reserved_walls()).
decision-log (R31): the why, and the design lesson — a derived fact cannot rot; a hand-maintained
copy of it is a liability that grows with every structural change. We had no instrument that could
report ABSENCE: every gate we owned answered 'is this right?', none answered 'is this all?'
Drew, mid-session: 'I thought the last session said there were some 15 tools we need to audit.'
He was right, and my ordering was wrong.
I had put the 18-tool audit near the END (as A9). docs/tooling-audit.md prescribes the opposite:
dedup_integrate -> jtbl_family_bank -> the SELECTION tools -> masked_diff/match_one -> THEN the
40 measured findings. The reason is the one that matters:
A hole in a SELECTION tool makes work invisible to PLANNING — the worst kind, because you
never know to look.
Fixing on top of unaudited selection tooling means re-running every fix when the audit later
finds the hole. So: A2 is now the full audit; A3-A9 (the fix campaign) are blocked on it.
Tool coverage, stated plainly: A1 (1) + A2 (18) + the fix campaign (~17 already-measured) = ~36
tools — not 82. The filter, from the audit doc: does it PARSE something, and does it GATE or
SELECT work? The remaining ~46 are dead LLM-tier scripts.
A1's result recorded in-file (the three false greens, the causal chain, the null-result blast
radius that confirms R33).
The audit's priority #1: a fail-closed byte-honesty validator whose silent skips nothing
downstream can catch. Three false-green paths, all measured, all now fail-closed with
negative controls.
R33 FIRST (derive, don't re-derive). The registry makes two claims; the tool only ever
checked one, and mis-described that one:
C1 EQUIVALENCE ("these vrams hold the same code in the ORIGINAL") — checked against the
sigs, which sign the ORIGINAL bytes. KEPT. But the docstring claimed it also caught
SOURCE drift: it cannot. A sig is a property of the ROM, immutable w.r.t. src/. Source
drift is caught by the BUILD. Docstring corrected (P9).
C2 BANK ("matched once in the source header, instantiated at every member") — NEVER
CHECKED. Now DERIVED from the build invariant: INCLUDE_ASM pastes the ORIGINAL asm, so
a member NOT wrapped in it is byte-exact, and one that IS wrapped is not banked —
whatever the registry says. C2a: the group's macro token must occur in its source file.
C2b: no member may still be an INCLUDE_ASM stub.
THE THREE FALSE GREENS
1. 1808 groups claimed a DEFINE_func_* macro; only 1801 exist. The 7 ghosts printed [ OK ] —
hiding 532 member-instances / 22,344 instructions of REAL, UNBANKED work (4 fns matched in
ov_SC01_077, still INCLUDE_ASM in the other 133 overlays).
2. An absent .run/sig.<bin>.jsonl degraded to "0 validated, 0 failed" and EXIT 0. On a fresh
clone the gate validated NOTHING and passed. Now fails; --allow-unsigned is the escape.
3. The bank claim was never checked at all.
THE CAUSAL CHAIN (the audit's thesis in one example). 3 of the 4 hidden fns are defined in
ov_SC01_077_jr_8012ACE0.c — a _jr_* split file. dedup_propagate.overlay_files allowlists only
("_a","_o0","_o0b","_after"), so the propagator could not SEE them; the group was registered
anyway; dedup_integrate greenlit the lie. TWO silent-skip bugs compounding: one created the
hole, the other hid it. Harvest fuel -> .run/audit/a1_harvest_fuel.json, banked in A5.
BLAST RADIUS, MEASURED NOT PREDICTED (R14). Headline metrics UNCHANGED to the decimal
(instr-weighted 66.5%, distinct-code 46.8%) — weighted_metrics() derives from the invariant and
was structurally immune to the lying registry. FLEET REAL substantive unchanged (282,466):
progress.py had already been taught to distrust it (commit:0574). Only dedup_integrate still
believed it. A null result that CONFIRMS R33: the tool that refused to re-derive was the one
that was right.
- registry repaired: 1813 -> 1806 groups (7 ghosts removed; instances 223,725 -> 222,787)
- make report GREEN end-to-end: 1806 validated, 0 failed | C1 coverage 222,787/222,787 signed
- negative controls: stubbed member -> exit 1; missing sig -> exit 1; --allow-unsigned -> exit 0
- report-only tool: no compiled artifact depends on it, so no R22 clean-fleet is owed here
- Drew (2026-07-14, gate 1): run the audit inside Phase 26, then resume at Task 7.
Declined the alternative (close Phase 26 early on an unmet milestone -> Phase 27):
the audit is a PREREQUISITE to structural completion, not a successor to it — the
tooling that MEASURES the milestone is the thing at fault. Phase-3.5 precedent.
- CURRENT_PHASE.md: the Phase 26-A block (A0-A11), built FROM docs/tooling-audit.md
(40 measured findings), R33-before-R32 ordering — the best outcome is a DELETED
scanner, not a fixed regex.
- decision-log (R31): the why, the structural blind spot (a scanner extracts N, the
true count is M > N, and nobody ever compared N to M — the byte-gate is a perfect
CORRECTNESS oracle and a NULL COVERAGE oracle), and A1's first finding.
- harness task list built (R28).
The 40 measured findings were living only in an ephemeral workflow journal outside the repo; the
checkpoint carried my SUMMARY of the audit, not the audit. Now the fresh session is routed to the
evidence, with the priority order (dedup_integrate FIRST — it can print a false green), the R33-before-R32
method (the best outcome is a DELETED scanner), and the real prize: re-test the walls that were diagnosed
on top of the broken 10% callee oracle (the def-side loose-typing wall, the 159 arity conflicts, the
type-heavy tail).
The audit's evidence (6 agents + 6 skeptics, 1.2M tokens, 40 findings with file:line proof and measured
candidate/parsed/skip counts) existed ONLY in a workflow journal OUTSIDE the repo. A fresh session would
have inherited my SUMMARY of the audit, not the audit — exactly the R30 failure mode (capture
context-dependent artifacts DURING the session that produced them). Drew caught it.
Now committed as the plannable input to the audit phase, with:
- the method (measure found-vs-candidates against an OVER-approximating detector; never "review the regex"
— that is the failure mode that wrote these bugs);
- why it gates the matching work (the byte-gate is a perfect CORRECTNESS oracle and a NULL COVERAGE oracle:
green since Phase 5 at 0% decompiled, so it is compatible with ANY decomp %);
- THE QUESTION IT ANSWERS: how many walls we have already "byte-proven" across 26 phases were lookup misses
wearing a wall's clothes? (the def-side loose-typing wall, the 159 arity conflicts, the type-heavy tail
were ALL diagnosed on top of the 10% callee-oracle hole);
- R32 (coverage assertion) + R33 (derive, don't re-derive — apply FIRST: the best outcome is a DELETED
scanner, not a fixed regex);
- the priority order (dedup_integrate FIRST — a fail-closed validator that can print a FALSE GREEN);
- the 7 bugs already fixed (do not redo) and the 63 tools not yet audited, with the filter for which matter.
RESULTS. Fleet instr-weighted 63.0 -> 66.5%, distinct-code 39.1 -> 46.8%, fn-count 82.61%.
FINAL R22: make clean + extract-all + check-all -> 136/136 BYTE-IDENTICAL, 0 coverage defects.
dedup 1813/0. 0 NON_MATCHING (G4). 31 commits.
13 CORES CRACKED incl. the four heaviest functions in the game (952/890/562/536 ins). The 12-agent
Ultracode wave returned 11/12 first-pass MATCH, each adversarially verified (a skeptic re-ran match_one
+ the §8a jump-table check). Banked x134 this session: func_8015AE2C, func_80178D40, func_8015A3C8,
func_8013FFD8, func_8016AB6C, func_8015444C, func_801380E0 (+ func_8017BEBC x1).
THE TOOLKIT CROSSED A LINE — three ZERO-BYTE DIALS now cover the three passes that produce essentially
every "irreducible" residual, each with a diagnostic signature a cheap agent recognises on sight:
registers rotated -> global.c allocno priority -> §47 slider / §48-A pricing dials
two insns swapped, SAME regs -> sched.c rank_for_schedule LUID tiebreak -> §49 LUID dial
structure right, count wrong -> loop peel / cross-jump -> §46 / §48-D
That is why 9/12 fell first-pass to ORDINARY agents. Fable5 DISCOVERS a class; everyone else APPLIES it.
New: §46 §47 §48(+A4) §49 §50. Read §50-B before using §48-A1/A4 — it BOUNDS them (the "cross_jump
refunds the bytes" claim is FALSE for a 1-insn tail reached by two jumps; jump.c:1993 minimum=2).
DREW'S DIRECTIVE (binding): the TOOLING-INTEGRITY AUDIT comes BEFORE any further matching work, and is
NOT part of this phase. First act of the fresh session is a Tier-1 phase-boundary call (close Phase 26
early, or run the audit as an inserted phase — Drew decides).
WHY: seven silent-skip tool bugs in one session, and they are a STRUCTURAL blind spot — a scanner
extracts N items, the truth is M > N, and nobody ever compared N to M. The byte-gate is a perfect
CORRECTNESS oracle and a NULL COVERAGE oracle: it has been green since Phase 5 at 0% decompiled (
INCLUDE_ASM pastes the ORIGINAL asm), so a green gate is compatible with ANY decomp %. One 10% hole in
the callee oracle made NINE byte-exact functions look like an intrinsic compiler wall. The real question
the audit answers: how many walls we have already "byte-proven" across 26 phases were lookup misses
wearing a wall's clothes? (The def-side loose-typing wall, the 159 arity conflicts, the type-heavy tail
were ALL diagnosed on top of that hole.) Audit scope so far is 19 of 82 tools (23%), by risk — NOT
comprehensive; dedup_integrate.py is unaudited and can print a FALSE GREEN.
RULE CANDIDATES (P10, Drew ratifies at PhaseEnd):
R32 Coverage assertion — a corpus scanner must assert its own coverage and fail loud on unparsed input.
R33 Derive, don't re-derive — where a proven invariant answers the question, derive from it. The best
audit outcome is not a fixed regex; it is a DELETED scanner.
SELF-CORRECTION ON THE RECORD (P9/R14): I told Drew the headline numbers under-reported by ~190k
instructions. WRONG. weighted_metrics() never calls classify(), so it was structurally immune; the
published numbers were correct all along. I verified the DEFECT but not its BLAST RADIUS. A null result
against a strong prediction is a refutation — chase it.
WRITTEN + VALIDATED, DELIBERATELY NOT WIRED IN (inert; nothing imports it). Wiring + byte-gating is the
first item of the integration fix pass, AFTER the tooling-integrity audit Drew gated it behind.
The successor to reconcile_decls.py for the templating/banking path. Two things are wrong with that tool,
and the second is structural, not a typo (Phase-26 scanner audit):
1. BLIND TO FUNCTION POINTERS. DATA_DECL_LINE_RE wants `extern <type-words> D_x[];`, so the `(` in
extern void (*D_801DA75C)(void); <- fn-ptr scalar
extern void (*D_801812A4[])(void *); <- fn-ptr array (a dispatch table)
breaks its type run; the line never matches; the tool SILENTLY SKIPS exactly the symbols that are
failing and reports success. Blocking func_8017A4AC (536 ins x134 = 287 KB) today.
2. ITS ORACLE ASKS THE WRONG QUESTION. It elects a canonical decl by FLEET MAJORITY. But 34.4% of fleet
symbols carry >=2 mutually incompatible spellings, so a single fleet-wide answer is PROVABLY WRONG FOR
SOME TU BY CONSTRUCTION — and it is worse than a skip: it returns an ACTIVELY WRONG decl (measured:
3,717 symbols) that then collides with the very macro it was meant to conform to.
The only question that matters is what gcc compares the draft against: WHAT CAN THIS TU SEE. So reconcile_tu
reconstructs the TU's visible file-scope decl environment from BOTH §8c sources — col-0 decls AND the externs
INJECTED BY engine_core.h MACRO INVOCATIONS (a DEFINE_func_*() expands at file scope, so its leading externs
are genuine file-scope decls of the invoking TU, invisible to any col-0 scan: 544 visible syms from 1801
macros) — then conforms the draft's decl to it and CASTS AT EVERY USE (gcc folds a compile-time cast of a
known symbol, so the emitted bytes are unchanged; the whole-binary byte-gate remains the sole arbiter).
Carries a COVERAGE ASSERTION (R32): every line that LOOKS like an extern of a D_ symbol must parse, or it is
reported LOUDLY (--strict exits non-zero). A silent skip is a defect, not a no-op.
Validated read-only on func_8017A4AC: resolves D_801DA75C (fn-ptr -> `extern s32` + call-site cast),
D_80126B58 (struct), D_801DA734 (ptr).
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.
TWO REAL BUGS in classify(), and an HONEST CORRECTION of their blast radius (P9/R14).
- BUG 1 (under-count). classify() decides definition-vs-declaration by scanning to the first `{` or
`;`. A K&R definition puts its parameter declarations BEFORE the brace:
s32 func_8015AE2C(arg0)
s32 arg0; <- a `;` before the `{`
{ ... }
so it was read as a forward declaration and dropped into NO bucket — not REAL, not a stub,
invisible. And a K&R def is MANDATORY whenever a zero-arg engine_core.h thunk calls the function,
i.e. exactly the heavy-jr cores our own banking recipe produces: func_8015AE2C (562x134),
func_8015A3C8 (493x132), func_80166994 (369x134) were all compiled, linked and BYTE-IDENTICAL in
the shipped build while counting as zero. Fix: skip over K&R parameter declarations (a bare
`<type> <name>;` carrying no parens — that is what distinguishes it from a wrapped ANSI
prototype's continuation line, which always carries the `)`).
- BUG 2 (over-count). `real |= dedup_members(BINARY)` folded in EVERY registered dedup member without
checking it is actually instantiated. A member still sitting as an INCLUDE_ASM stub was counted
REAL *and* stayed in `stubs` — double-counting into `matchable` and inflating `byteident`
(532 phantom instances, per the scanner audit). Fix: subtract `stubs`. The registry is advisory;
the source tree is authoritative.
- COVERAGE ASSERTION (the rule ratified 2026-07-14): ground truth = every function splat emitted a
.s for. Anything classify() cannot place in ANY bucket is now reported LOUDLY (stderr + the .md),
because a silent skip is a defect, not a no-op. Currently: 0 unplaced.
- CORRECTION (this is the part that matters — I over-claimed and the bytes refuted me). The scanner
audit reported ~243k instructions "counted as nothing", and I repeated it. WRONG. weighted_metrics()
— which produces the HEADLINE instr-weighted and distinct-code numbers — does NOT call classify()
at all. It tests `func not in src_stubs(binary)`: since the fleet is 136/136 byte-identical,
anything not wrapped in INCLUDE_ASM must be compiled C emitting the exact original bytes. That test
never parses a definition, so it is IMMUNE to this bug. Verified: old-vs-new on the same tree gives
identical weighted numbers. The published 65.6% / 44.9% were CORRECT ALL ALONG; only the secondary
REAL count and fn-count % were wrong.
THE LESSON, sharper than the one we started with: a metric DERIVED FROM A PROVEN INVARIANT beats a
metric that RE-PARSES THE WORLD. weighted_metrics() leans on the byte-gate and inherits its
correctness; classify() re-derives the same fact by parsing C and inherited a bug instead. Prefer
the former wherever an invariant exists.
Residue of the same isolation-revert bug fixed for ov_SC01_000 in commit:0558: a failed bank left its
isolation's config in place, the retry re-isolated on top, and a duplicate
- [0x4b364, c, ov_SC01_077_jr_801734BC]
line rode into a commit. It is HARMLESS to splat (a zero-length subseg), so R22 stayed green and the
correctness gate never saw it — but it BLOCKED every subsequent isolation, which is what failed 5 of
the 9 crack-wave banks. Caught only by the fail-loud validation added in commit:0558 (a tool that refuses
to proceed on input it does not understand), never by the byte-gate. Fleet audit: ov_SC01_077 was the
ONLY affected config of 137. ov_SC01_077 rebuilds d19c9580 BYTE-IDENTICAL.
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.
- BUG: gen_harvest_targets.SIG_IN_BODY_RE required `)\s*{` between a DEFINE_func_* macro's signature
and its opening brace. When the brace sits on its OWN continuation line there is a line-continuation
BACKSLASH between them:
s32 func_80148824(void *arg0) \
{ \
and `\s` does not match `\`. So the regex silently dropped every own-line-brace macro.
- BLAST RADIUS (measured): 186 of 1801 engine_core.h shared signatures — 10% of the oracle — were
MISSING from the canonical-callee map that cast_call_sites / sig_unify / gen_harvest_targets resolve
against. A draft calling one of them kept its own guessed signature, hit `conflicting types` against
the TU's real definition, and the recovery pass reported nothing to fix — the failure looked like a
hard wall. This is why the crack wave's byte-exact cores would not bank.
- FIX: `[\s\\]*` instead of `\s*`. Oracle 2122 -> 2308 entries.
- PROOF: func_8015A3C8 (493 ins, MATCH standalone) went from "28 conflicting types, unbankable" to
BANKED ×1 BYTE-IDENTICAL at the `recovered` stage, with zero hand edits. R22 clean-fleet 136/136.
- This is the phase's SIXTH silent-skip bug and the THIRD of the same brace-placement class (§19
find_site; scope_data_externs' own-line brace; now this). Cookbook §40's standing lesson applies:
a tool that silently no-ops on input it cannot parse is indistinguishable from one that had nothing
to do — prefer fail-loud on unparsed input.
Cutting func_80178D40 out of ov_SC01_000_jr_801734BC adds the region's banked LEADER (0x801734BC)
as a cut too (the one-carve-per-object rule), making region 0 EMPTY (the object's first item IS the
first cut) — and region 1's derived name equals the object name, so emitting region 0 duplicated the
line exactly -> splat "segments out of order". Skip an empty region 0; region 1 rightly claims the
object's offset and name. First sibling then banks through the full chain (isolation validation
green -> carve -> --raw remap -> stage ladder -> whole-binary gate): ov_SC01_000 BANKED, included
here. The remaining 132 siblings sweep next.
Three-layer fix for the func_80178D40 ×133 sweep failures:
- LAYER 1 (the residue): jtbl_family_bank.revert() restored carve pieces + src/ but NOT the
isolation's CODE-subseg lines in the splat config. A failed bank attempt (BEBC's first try)
left its isolation config in place; the successful retry re-isolated on top and a DUPLICATE
`- [0x4b364, c, ov_SC01_000_jr_801734BC]` line rode into the commit (harmless to splat —
zero-length — so R22 stayed green). revert() now also restores config/splat.<ov>.yaml.
The committed duplicate is removed (ov_SC01_000 rebuilt BYTE-IDENTICAL 9052dc0e).
- LAYER 2 (the detonation): jr_isolate_all walked the duplicated object TWICE -> two
replacements -> a reversed duplicate block -> splat "segments out of order". It now VALIDATES
the generated config (code subsegs strictly ascending, names unique) and refuses to write on
violation, naming the likely cause — a corrupt input dies at the tool, not three tools later.
- LAYER 3 (the sweep template): jtbl_family_bank gains --raw <crack.c> — template from the RAW
crack via remap_hseq_body instead of the exemplar's banked source unit. REQUIRED when the
exemplar banked at the `reconciled` stage: a reconciled body is TU-SPECIFIC (§41c — uniquified
type names, TU-targeted casts), so extract_unit hands the sweep a polluted template and every
sibling gate-fails (byte-proven: 178D40 banked reconciled -> sweep 0/4; 8015AE2C banked raw ->
sweep 133/133). Same law as family_sweep --reconcile-raw.
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).
- _body_open_brace only matched a `{` on its own line (the K&R shape), so fix() SILENTLY NO-OP'D on
every ANSI draft — the same silent-skip disease as the four catalogued in §40/§8d, caught because
the h_seq re-sweep banked 0/780. Now brace-scans forward from the signature (ANSI same-line,
ANSI own-line, and K&R all work). Load-bearing for func_80178D40's upcoming ×134 bank.
- family_sweep --hseq now applies the §8d scoped stage at staging time.
- HONEST RESULT: still 0/780 — the substantial-band h_seq rejections are a DIFFERENT (sibling) class,
now fully diagnosed against the bytes:
gcc-2.7.2 decl-conflict semantics: a VISIBLE file-scope decl + a conflicting later decl (file OR
block) is a HARD ERROR; a limbo-only block decl (scope closed) + a conflicting later decl is a
warning. The h_seq drafts carry the EXEMPLAR TU's spellings; sibling TUs legitimately spell the
same symbol differently (loose typing), and the visible decl is often MACRO-INJECTED — a
DEFINE_func_* leading extern (§8c), invisible to any col-0 scan (e.g. D_80115158's `short` decl
enters ov_SC01_000.c via DEFINE_func_8014168C() @4637; the draft carries ov077's
`unsigned short` -> conflicting types at ANY scope).
Sub-class (a) no-visible-decl -> §8d demotion (the jr class, proven x133). Sub-class (b) visible
decl, different spelling -> needs reconcile-to-TU-VISIBLE (rewrite the draft decl to the TU-visible
spelling + byte-neutral access cast; oracle = col-0 decls above the stub + engine_core.h macro
externs for the DEFINE_ invocations above). Parked as a designed follow-up task; the 780 members
are mechanical-recovery fodder once the tool exists.
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.
- NEW "recovered" stage between raw and reconciled: cast_call_sites + reconcile_decls run
against THIS sibling's TU. The recovery must be redone per sibling because the conflicting
symbols are largely PER-OVERLAY (D_801812A4 in ov_SC01_000 vs D_800D4F8C in ov_SC01_077),
so the exemplar's recovered decls do not transfer through the remap.
- The stage loop no longer aborts a sibling when a stage cannot PRODUCE a candidate. It skips
to the next one. canon_sig_reconcile raises on a K&R definition (it expects an ANSI
signature), and a K&R def is MANDATORY whenever a zero-arg engine_core.h thunk calls the
function (func_8015AE2C) — so that must not kill the bank.
- gate-fail now reports the last stage error instead of an empty string.
STATUS (P9): the func_8015AE2C x133 sweep is still BLOCKED and this commit does not close it.
Remaining blocker, precisely diagnosed: the remapped body's DATA externs conflict with the
sibling's §8b carried decl layer (which carries the macro externs of earlier regions and
faithfully reproduces the original TU's declaration environment). reconcile_decls resolves
against a FLEET-MAJORITY canonical oracle, not against the TU's actually-visible decl, so it
picks a type that still conflicts. Fix direction: reconcile the body's externs against the
TU's carried layer (authoritative) — or drop body externs the layer already provides and cast
at use. The exemplar itself is banked and green.
Exemplar banked byte-identical (d19c9580); R22 clean-fleet 136/136.
Fable5 crack: MATCH 562/562, pin-free, jump table verified.
THREE REAL BUGS the bank exposed in jr_isolate_all (each byte-proven; each would have
silently corrupted every future heavy-core bank):
1. --only filtered `banked` as well as the cut set, so already-banked jr went untracked
and their carves were never followed. --only selects what to CUT; it must not erase
the record of what is already banked.
2. carve ownership was read from splat .s — but splat emits NO .s for a MATCHED function
(its .c holds real C), so the lookup found nothing. Now resolved from the extracted
IMAGE via family_remap.reloc_targets (byte-exact: func_801734BC -> 0x801d8c68 etc).
3. THE STRUCTURAL ONE: a region may host at most ONE .rodata carve, because an object's
.rodata is a single CONTIGUOUS section. Cutting at func_8015AE2C (jtbl 0x801D8B54)
left the banked func_801734BC (jtbl 0x801D8C68) inside the same region, so the object
emitted a 0x34 .rodata spanning BOTH tables (image +33 B). Every already-banked jr in
a cut object is now cut too -> exactly one carve per object. Cookbook 8b's "bank
same-subseg families ASCENDING" note warned about this; it is now enforced by
construction instead of left to discipline.
Also required (per the crack's own analysis, all byte-verified):
- engine_core.h: DEFINE_func_8015BEC4's zero-arg thunk returns func_8015AE2C(), so the
extern must drop its (void) prototype and the def must stay K&R/unprototyped.
Byte-neutral across all 136 (R22 green).
- recovery chain: cast_call_sites (27 callees) + reconcile_decls (3 data syms). The raw
body declares callees with types that conflict with their real engine_core.h defs; the
original never redeclares them, it CASTS at the call site (cookbook 20).
Layout now exact: .rodata 0x801d8b54/0x1c (7 entries, pad trimmed) + 0x801d8c68/0x14 +
0x801d92a0/0x20 — one table per object, each at its true address.
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.