mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 21:36:06 -04:00
8ffcf9646eb65ed70a42a8d666daa244f8cfb99f
292 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8ffcf9646e |
feat(phase-26a): A3f — 33 functions banked that the project had written off as compiler walls
The payoff of A3e, byte-verified. These 33 sat in the backlog at closeness==0 -- match_one said
their bodies were BYTE-EXACT -- and the whole-binary gate rejected them, so they were logged as
`near`/`failed`, i.e. AS MATCHING PROBLEMS, and filed as intrinsic compiler residuals.
They were not hard. They were UNREACHABLE. gate_stage passed `--src src/<ov>/<ov>.c`
unconditionally, which restricts the byte-gate to ONE translation unit -- and every one of these
functions has its stub in a SPLIT TU. Look at where they landed:
src/ov_SC01_077/ov_SC01_077_a.c
src/ov_SC01_077/ov_SC01_077_after.c
src/ov_SC01_077/ov_SC01_077_jr_8012ACE0.c
src/ov_SC01_077/ov_SC01_077_jr_8015AE2C.c
src/ov_SC01_077/ov_SC01_077_jr_8016AB6C.c
src/ov_SC01_077/ov_SC01_077_jr_801734BC.c
src/ov_SC01_077/ov_SC01_077_jr_80178D40.c
src/ov_SC01_077/ov_SC01_077_jr_80182268.c
8 files. SEVEN of them are _jr_/_a/_after carves. NOT ONE is the main .c -- the only file the gate
was ever allowed to look at.
gate: 63 drafts -> banked 33, near 30, FAILED 0 (--no-propagate; the gate and the propagation
are different jobs, and letting an experiment tow an unbounded fleet-wide propagation is
what broke the tree an hour ago)
R22 CLEAN-FLEET: make clean + extract-all + check-all -> 136 passed, 0 failed of 136
dedup-check: 1823 validated, 0 failed | C1 coverage 224933/224933
METRICS, ×1, HONESTLY (no propagation yet -- the multiplier is still ahead):
functions byte-identical 284,526 -> 284,559 (+33)
instructions 8,470,381 -> 8,471,912 (+1,531)
fn-count % 82.79% -> 82.80%
instr-weighted % 66.7% -> 66.7% (flat: ×1 banks do not move the headline)
WHAT THIS MEASURES, beyond the 33: the backlog holds 1,588 entries at closeness==0. 1,215 have been
banked since by other paths. 373 ARE STILL OPEN STUBS WITH BYTE-EXACT BODIES. 63 of them were in
ov_SC01_077 and 33 banked -- a 52% rate on functions the ledger calls unrecoverable. The other 310
are spread across the remaining overlays: same class, same fix, not yet attempted.
Cookbook §51g LAW 11 -- a fix is not landed until its caller stops overriding it. And the reason
this hid for 26 phases, which belongs in the posterity doc: A TOOL THAT CANNOT BANK A FUNCTION IS
INDISTINGUISHABLE, IN EVERY LOG THIS PROJECT KEEPS, FROM A FUNCTION THAT CANNOT BE BANKED.
|
||
|
|
b89fcc2edc |
fix(phase-26a): A3e — gate_stage pinned the byte-gate back to 4.9%, OF A3'S OWN FIX
THE WORST DEFECT IN THE AUDIT IS NOT IN A SCANNER. It is one default argument in the CALLER of a
scanner we had already fixed.
# tools/gate_stage.py:315
summary = run_gate(a.drafts, binary=b, src=a.src or f"src/{b}/{b}.c", ...) # ALWAYS the main .c
`src` RESTRICTS the byte-gate to ONE translation unit, and _gate1 does `if src: cmd += ["--src", src]`
-- always truthy. A3 had just taught harvest_verify to DERIVE each draft's home TU *when --src is
omitted*, lifting the byte-gate's reach from 4.9% to 100%. gate_stage NEVER OMITS IT. The fix was
neutralised by its own caller's default, and the PRIMARY BANKING PATH -- every wave, the grinder, the
orchestrator, bulk_harvest -- remained structurally unable to bank 250 of ov_SC01_077's 263 stubs.
WHY IT SURVIVED 26 PHASES: harvest_verify cannot splice a draft whose stub is not in the TU it was
pointed at, so the draft never verifies -- and is then logged as near/failed, i.e. AS A MATCHING
PROBLEM. The wave reports a poor close-rate; the function goes to the backlog as a compiler residual.
A tool that CANNOT bank a function is indistinguishable, in every log this project keeps,
from a function that CANNOT BE banked.
PROOF, same draft / same gate / same second: gate_stage rejected func_80129C40; harvest_verify run
directly (no --src) VERIFIED it byte-identical and banked it.
AND A COUNTING BUG THAT HID THE HIDING (gate_stage:261): when match_one says MATCH but the whole-binary
gate rejects, the record is logged status="near" and THE COUNTER IS NEVER INCREMENTED. A 63-draft run
printed `banked 0, near 0, failed 0` -- three zeros that do not sum to 63 -- for phases. Nobody ever
added them up. (The number was not wrong. It was ABSENT.)
ALSO FIXED, sig_unify (the same disease, one level down): it SILENTLY DROPPED 190 of 196 drafts (97%).
`cur_stubs` was read from the main .c (13 of 263 stubs), so any draft whose stub lives in a _jr_ carve
hit `if fn not in cur_stubs: continue` -- dropped BEFORE THE WRITE: never copied to --out, never gated,
never logged, while the summary printed "drafts unified: 6" and read like success. THIS IS GATE_STAGE'S
STAGE-2 RECOVERY -- the pass whose whole job is to rescue the stage-1 failures -- and it has been a
no-op for nearly every draft it was meant to save. Now: TU derived per draft (corpus.stubs), canon
derived from cdecl.tu_scope (cpp -- macro-injected decls finally visible), and _keep() so an
already-acceptable decl is left alone (the §19 "sig_unify regresses canonical drafts" failure mode).
Reach: 6 -> 196 drafts; callee-externs rewritten 2 -> 90; own def-sig 2 -> 86.
MEASURED, all three consumers migrated (196 never-banked drafts):
near 5 -> 116 failed 190 -> 17
=> 173 of 190 "failures" were PLUMBING, not codegen: now compiling and SCORED instead of invisible.
THE PRIZE (measured, not claimed): the backlog holds 1,588 entries at closeness==0 -- body byte-exact
per match_one, whole-binary gate rejected. 1,215 have been banked since by other paths. 373 ARE STILL
OPEN STUBS WHOSE BODIES ARE ALREADY BYTE-EXACT, sitting in a ledger that calls them unrecoverable.
⚠ THE HARVEST ITSELF IS NOT IN THIS COMMIT, AND IS NOT CLAIMED (P9). Gating the 63 ov_SC01_077 ones
dragged `dedup_propagate --auto-from --recover` behind it; it ran >1h and hit its timeout -- its
first-ever run over the FULL corpus (A6/A7 unblocked the 407 files it could never see). It MUTATES THE
TREE BEFORE IT GATES, so the kill left 859 files + engine_core.h (+544 lines) written and UN-GATED with
the registry never updated. R22 on that tree: 44 passed / 92 FAILED -> `git checkout -- src/ config/`,
fleet restored to 136/136. Nothing lost (H4: the tree was clean, so the revert was one command).
Two real lessons, recorded: dedup_propagate is NOT crash-safe and must never run under a timeout it can
hit; and a 63-draft experiment must not drag an unbounded fleet-wide propagation behind it.
R22 clean-fleet after revert: 136 passed, 0 failed of 136. src/ and config/ clean.
cookbook §51g LAW 11: A FIX IS NOT LANDED UNTIL ITS CALLER STOPS OVERRIDING IT. After fixing a
scanner, grep every call site and ask whether a caller's default re-disables it. An audit that stops
at the callee is half an audit.
|
||
|
|
f4502f11bb |
fix(phase-26a): A3c — the recovery passes were reconciling 95% of drafts against the WRONG TU
FIRST CONSUMER MIGRATION onto the cdecl oracle — and the compiler taught me two things I had
wrong, one of which reopens a wall that has been closed since Phase 15.
1. cdecl.compatible() — "will cc1 accept these two declarations of one name?"
The predicate four tools each half-implement and get wrong: norm_sig / _norm_type collapse the
int family to ONE token, so a SIGNEDNESS change reads as "already compatible" and gets no
rewrite -- while cc1 REJECTS that redeclaration. Right about codegen, wrong about the front end,
which never reaches codegen.
2. THE ADJUDICATOR MUST BE THE COMPILER THAT COMPILES YOUR CODE (cookbook §51g LAW 9).
I wrote the rules from the C standard, then let a compiler judge. It contradicted me -- and then
the RIGHT compiler contradicted the first one. Three different answers:
declarations in one TU | standard | modern gcc | gcc-2.7.2 cc1
typedef int X; twice | error | ACCEPTS | ERROR
extern u16 X; + volatile u16 X| error | error | ACCEPTS
void X(s16); then void X(); | error | error | ACCEPTS
void X(); then void X(s16)| error | error | ERROR
--compat now adjudicates with tools/bin/gcc-2.7.2-psx/cc1, the front end that actually
arbitrates the build: 1,485/1,485 live corpus pairs agree, 0 disagree, 0 skipped.
3. THE PRIZE: the Phase-15 narrow-param wall rests on a false premise.
The no-prototype rule is ORDER-DEPENDENT. `void X(s16); void X();` COMPILES; only the reverse
fails. Phase 15 closed "the 159 arity/narrow-param conflicts" as "no clean deterministic fix --
it is simply C's default-promotion rule". cc1 does not enforce that rule in the direction the
wall assumed. Four three-line probes, 90 seconds, zero tokens. -> A10 RE-TEST TARGET.
Probe the compiler for FACTS; read its source only for LEVERS; byte-validate both. (We read
gcc-papermario for five phases believing it was 2.7.2. It was 2.8.1.)
4. THE MIGRATION: cast_call_sites canonicalized 95.1% of drafts against a TU that would never
compile them. `--src-file` is an OPTIONAL HAND-PASSED flag defaulting to src/<ov>/<ov>.c, and no
caller knows about the Phase-26 _jr_<ADDR> carves: ov_SC01_077 has 263 open stubs across 12 TUs
and only 13 are in the main .c -- while harvest_verify (A3) correctly splices into the real one.
Now DERIVED from corpus.stubs() (the INCLUDE_ASM line is self-describing), with the canonical map
derived from cdecl.tu_scope() (cpp -- so macro-injected DEFINE_func_* decls are finally visible).
Callee-conflict repair reach: 8 -> 58 of 196 drafts (7x).
5. AND THE NULL RESULT, REPORTED AS SUCH (P9/R14). Those 58 banked ZERO functions. The historical
draft tail fails on CODEGEN, not plumbing -- func_801387B8, which the audit blames on a single
unparsed `[4]`, is really 67/100 instructions off with a $s0/$s1 swap (that claim does not
reproduce on today's tree). The real gain is narrower and still worth having: 52 drafts moved
from "won't compile" to "compiles, N instructions off" -- from an INVISIBLE failure that reads as
a compiler wall into a SCORED near-miss the permuter and the §47/§48 dials can act on. That is
the audit's thesis, not a bank. THREE times in one session a confirmed mechanism produced a null
consequence.
Also: my own new audit printed "ALL ORACLES GREEN" while silently skipping 100% of its corpus (a
missing -Isrc). The exact bug class, in the tool written to hunt it. An unadjudicable check is not
a passed check.
R22 clean-fleet: make clean + extract-all + check-all -> 136 passed, 0 failed of 136
src/ untouched (0 changes) make audit-cdecl: green --compat: 1485/1485
NEXT: sig_unify + reconcile_decls carry the SAME wrong-TU bug (same --src-file flag).
|
||
|
|
f9742cf9c0 |
feat(phase-26a): A3b — cdecl.py, THE C-declaration oracle: one grammar, fifteen deleted models
Fifteen tools each carried their own regex model of "what is a C declaration", and they
disagreed — two tools in ONE pipeline disagree today about whether `extern s32 D_a, D_b;`
is a declaration at all. All fifteen shared one character class,
extern\s+([A-Za-z_][\w\s\*]*?\bD_[0-9A-Fa-f]+\s*(?:\[\s*\])?)\s*;
which cannot hold '(', ',', or a non-empty [N] — so three whole shapes were invisible to
every one of them: fn-ptr/jump-table arrays, sized arrays (one unparsed `[4]` has blocked
func_801387B8 in 134 TUs), and multi-declarators (the WHOLE line dropped, not just #2..N).
REJECTED the audit's own prescription (a shape-aware alternation per tool, ~15 coordinated
regex edits) on R33 grounds: fifteen hand-maintained models are exactly what diverged, and
an alternation only ever covers the shapes somebody remembered. The thing being scanned HAS
A GRAMMAR. C's declarator grammar is small, closed and TOTAL — it describes fn-ptr arrays,
sized/2-D arrays, multi-declarators, fn-ptr params and K&R identifier-lists without being
told they exist. ~250 lines of recursive descent: LESS code than the regexes it deletes, and
exhaustive by construction rather than by memory. (decision-log 2026-07-14.)
Two statement paths, because the inputs genuinely differ:
* tu_statements() - a TU's file scope, derived from cpp. A decl inside a DEFINE_func_*
macro body declares NOTHING until the macro is invoked (the §8c law);
a raw scan is wrong in both directions. cpp answers it exactly, in
54 ms/TU (~20 s for the fleet, cacheable).
* split_statements() - span-preserving raw split, for drafts (which get rewritten).
THREE ORACLES, whole corpus — a measurement, not a belief:
* coverage 2,952,246 depth-0 statements -> 2,731,521 declarators, 0 PARSER DEFECTS
* the real gcc 50,405 distinct declarations compiled beside this parser's reconstruction
of each one -> 0 REJECTED
* differential 0 file-scope symbols the incumbents see that cdecl misses; 26 in
engine_core.h they cannot see; 6 they wrongly promote from BLOCK scope
Two ideas worth keeping (cookbook §51g, LAWS 4-8):
* THE CANDIDATE SET IS DERIVED TOO (R33 applied to R32). At file scope C admits nothing but
declarations, so R32's over-approximating detector is *every depth-0 statement* — supplied
by the grammar, with no hand-maintained candidate regex to rot.
* GCC ADJUDICATES MY OWN COVERAGE GAP. Deciding for myself which failures "don't count" is
grading my own homework — the habit that wrote the fifteen bugs. A statement gcc ALSO
rejects is not C (my rejection is correct, the INPUT is corrupt); one gcc ACCEPTS and I do
not is MY defect. All 33 residual: NOT-C, all dead .run/drafts* scratch, none in src/.
NEW findings (docs/tooling-audit.md):
* reconcile_decls.DATA_DECL_LINE_RE finds ZERO decls in engine_core.h — it is line-anchored
and every decl there ends in a '\'. Its "authoritative tier" has ALWAYS been empty.
* gen_harvest_targets + sig_unify count BLOCK-SCOPE externs (6, byte-proven inside a macro's
function body) as file-scope canonicals — the §8d `conflicting types` confusion.
* tu_ambient's func regex ([^()]* params) drops ANY callee with a fn-ptr parameter.
* R14 near-miss: 33 drafts contain `extern if ((func_80029178(0x119) & 0xFF) != 0);`, written
by a RECOVERY TOOL — but the source bug was already fixed in Phase 19 (0 garbage / 300 sigs
today). Mechanism confirmed, consequence nil. Note what it cost while live: a draft that
cannot compile fails the byte-gate and reads downstream as an INTRINSIC COMPILER WALL.
Bugs the oracles caught in ME (and would otherwise have shipped): `extern s32 (*D_801274D0)(s32);`
parsed the BASE TYPE as the name; a K&R declaration-list flushes as SEVERAL spans, so the body
attached to the wrong one and leaked the K&R parameter names into file scope as fake globals.
SCOPE, deliberate: NO consumer is migrated here, so this cannot move a byte. The audit warns
that making the parser see more ARMS dormant transforms (reconcile_decls.data_access_subs would
mangle `D_1[i]()` -> `((u8 *)D_1)[i]()` the moment fn-ptr decls become visible to it). Migration
is one tool at a time, each byte-gated.
R22 clean-fleet: make clean + extract-all + check-all -> 136 passed, 0 failed of 136
make audit-corpus: 0 PHANTOM + 0 TRUNCATED make audit-cdecl: ALL ORACLES GREEN (new gate)
|
||
|
|
c7772bc452 |
docs(phase-26a): SESSION-9 CLOSE — cookbook §51 (the tooling-integrity laws) + handoff
R30/R16: the context-dependent artifacts, written while the context is live.
cookbook §51 — the SILENT SKIP: the bug class, why the byte-gate cannot see it, the
over-approximating-detector method, and FOUR LAWS:
1. Derive, don't re-derive — the best outcome is a DELETED SCANNER (28 findings -> one
derived oracle + ~10 deleted scanners). A derived fact cannot rot; a hand-maintained
copy of it is a liability that grows with every structural change.
2. Assert your COVERAGE, not merely your correctness. *** A LOUD FAILURE THAT NOBODY
COUNTS IS EXACTLY AS INVISIBLE AS A SILENT ONE *** — build_engine_types printed
'[overlap] handle manually' every single time for four phases while dead on 81% of its
own corpus. This CORRECTS the first draft of R32 ('fail loud'), which was not enough.
3. When an oracle is structurally blind to a class of error, add a SECOND ORACLE THAT CAN
DISAGREE WITH IT — not a better assertion inside it. We had two all along and never made
them argue. (And scope the comparison to where the second oracle is genuinely independent:
the same check run outside its domain reports 914 slices when the truth is 193.)
4. A rule that needs a human to remember it is not a gate. Make it structural.
+ the FALSE-WALL PIPELINE (a silent skip -> a wasted draft -> a backlog 'matching failure'
-> reserved_walls() PERMANENTLY blacklists a function that was never attempted), and a
checklist for any new corpus-scanning tool.
CURRENT_PHASE: session-9 handoff — what is done, what remains (each with its spec on disk),
and the R32-corrected / R33 / R34-new rule candidates for P10 ratification.
|
||
|
|
82d79e7a32 |
fix(phase-26a): A6/A7 — the family engine could not see half its corpus; 17 fns banked x134 free
R22: check-all 136 PASSED / 0 FAILED. dedup-check 1823 validated / 0 failed (C1 coverage 224,933/224,933).
Fleet instr-weighted 66.5% -> 66.7%.
=== dedup_propagate: it was blind to HALF the corpus ===
overlay_files() used a hardcoded suffix allowlist ("_a","_o0","_o0b","_after") that predated the
Phase-26 jr carves -> 404 of the fleet's 811 overlay .c. The 407-file gap held 36,135 INCLUDE_ASM stubs
and ~32,000 inline defs, and overlay_files gates ALL of dedup_propagate (source_text / find_site /
apply_plan / struct_check / reconcile_caller_extern). Now a GLOB — never an allowlist, because the NEXT
split family would re-open it. The asm_subdir is always the file stem, an invariant the old four entries
already satisfied.
find_site's def-detector required the signature line to END in ')' and the next non-blank line to START
with '{'. It therefore silently dropped THREE shapes: K&R definitions (`s32 f(arg0)` / `s32 arg0;` / `{`),
multi-line signatures, and single-line bodies. K&R is the project's house style for exactly the biggest,
highest-reach functions — func_8015AE2C (562 ins), func_80166994, func_80133CD4, func_8015A3C8 — and they
live in the _jr_* files overlay_files could not even open. Fixing either alone would have been useless:
the glob exposes the files, and find_site would still drop their biggest prizes. Both fixed together.
* The signature's closing paren is now found by a real paren-walk, not line.count() or split(')')[-1]:
a single-line body containing a call (`void f(int a){ g(a); }`) has balanced parens of its own, so
both shortcuts land on the WRONG paren and then misread the body's ';' as a prototype terminator.
* AGREEMENT ASSERTION (the audit's): find_site vs family_remap.extract_unit -> 701 agree / 0 disagree.
Negative controls hold (a prototype+call is rejected; a 1-line body with a call is a def).
=== THE HARVEST (free work, byte-gated) ===
--auto-from ov_SC01_077 now nominates what it could never see: 20 planned, 17 propagated x134, 3 dropped
as cross-overlay stragglers. 134 overlays rebuilt BYTE-IDENTICAL; 17 new dedup groups.
Includes ALL FOUR functions A1 caught the registry lying about (func_80128ED8 / 8012C098 / 8012C0EC /
8012C750): 0 stubs remaining, real shared macros. THE LOOP CLOSES — A1 found the lie, and THIS is the
bug that had made it true (3 of the 4 are defined in ov_SC01_077_jr_8012ACE0.c, which the allowlist could
not open, so the propagation never ran and dedup_integrate greenlit the result).
=== family_remap: 96 PHANTOM exemplars -> 0 ===
extract_unit globbed only src/<ov>/<ov>*.c, so a function matched via a SHARED body had no source form
and read as NOT MATCHED. 93-96 of 218 h_seq "matched" exemplars were phantom, carrying 2,157 candidate
members of which 1,834 are still-stubbed, PURE/IMM-clean, symbol_map-clean and unpinned — staged and
gated today, dropped before the first build then. It is now TOTAL over BOTH shared-body mechanisms:
(1) the DEFINE_func_<ADDR>() macro — reconstructed as the exact INVERSE of dedup_propagate.make_macro
(derived from the generator, not re-guessed from the text);
(2) a DIRECT definition in a shared header, #included per overlay — the whale (func_80144B9C, 770 ins,
-O0), which the registry explicitly records as "NOT a DEFINE_ macro".
CENSUS: 216 matched exemplars, 216 real, 0 PHANTOM.
symbol_map named the symbol by HOW IT WAS LOADED, not by WHAT IT IS: reloc_targets labels every lui/%lo
pair "data", and a FUNCTION's address taken via lui/%lo (an address-taken callback) is exactly that shape
(splat's own .s: %lo(func_8017E1D4), 7 occurrences). The map got a D_<ADDR> key while the C writes
func_<ADDR>, so the word-bounded substitution matched NOTHING and silently no-op'd — the sibling kept the
EXEMPLAR's function pointer and the loss was booked as a BYTE failure, indistinguishable from a compiler
wall. Now emits both keys (addresses are unique; the pass is simultaneous, so the extra key is free).
gather_externs was line-oriented, so a WRAPPED comma extern was invisible in both directions (the first
line has no ';', the continuation has no `extern`). ov_SC01_077.c:271-272 declares NINE symbols that way,
and the exemplar referencing them (func_8013D178) is a 133-member family — every sibling was staged with
NO declaration, failed to compile, and bisect-stormed its whole gate group. Now statement-oriented, and
an unresolved symbol is REPORTED, never silently dropped.
=== family_sweep.stub_map / build_engine_types ===
stub_map: func_-only -> a curated-name stub read as "already matched" -> phantom exemplar. Now corpus-derived.
build_engine_types hard-exited on 1,070 of 1,470 type-bearing overlay .c (73%; the audit measured 573/709
= 81% on its narrower set) because 1,929 TAGGED-struct typedefs tripped a guard whose own comment asserts
"our source has only ANONYMOUS-struct typedefs" — true in Phase 20, false since the harvest agents started
writing tagged structs. inject_capped_externs routes every type-bearing body HERE as the type-heavy tail's
ONLY sanctioned unblocker, so the tail's unblocker could not run on the corpus the tail lives in.
A contained def (the typedef's span encloses the body) is liftable — it just must not be counted twice;
only a PARTIAL overlap is malformed. Verified on a file that used to hard-exit: 5 tagged typedefs folded +
forward-declared, 46 types written, exit 0.
** AND THE SHARPEST LESSON IN THE AUDIT: this one was never silent. It printed "[overlap] ... handle
manually" every single time. But the message reads like a rare edge case rather than a four-fifths
coverage failure, so nobody ever COUNTED it. A loud failure that nobody counts is exactly as
invisible as a silent one. R32 must be "assert your coverage", not merely "fail loud". **
R14 self-catches, recorded because I hit both while fixing them: my first shared-header scan read a macro
body's `extern void f(void); \` as a DEFINITION (the trailing continuation means the line does not end in
';', so the decl guard never fired) — the exact bug fixed at commit:0552, reintroduced by me and caught only
because the whale resolved from the WRONG file. Column-0 anchoring fixes it by construction. And my
phantom census returned 0/0 twice because I guessed the manifest schema instead of reading it.
|
||
|
|
af2f40d153 |
fix(phase-26a): A4/A5 — 193 unmatchable slices dissolved; the closeness oracle stops lying
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.
|
||
|
|
9794b13ed2 |
fix(phase-26a): A3 — the endgame plan was 2.8x too big; the matched set is now DERIVED
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.
|
||
|
|
a302908f24 |
fix(phase-26a): A3 — target selection was blind to 91.6% of the remaining work; now derived
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.
|
||
|
|
ffb6f1a40f |
docs(phase-26a): A2 — the full audit; 28 findings survive; the endgame plan was majority-fiction
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?'
|
||
|
|
bb65d36341 |
fix(phase-26a): A1 — dedup_integrate was a gate that could print a FALSE GREEN
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 |
||
|
|
978ef703ac |
docs(phase-26a): A0 — the tooling-integrity audit, as an INSERTED HALF-PHASE (Drew's call)
- 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). |
||
|
|
9a94e9ba46 |
docs(phase-26): docs/tooling-audit.md — the 40 measured findings, made DURABLE (R30)
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. |
||
|
|
6e8c459ade |
docs(phase-26): SESSION-8 CLOSE — checkpoint for a fresh session; the tooling-integrity audit gates what comes next
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.
|
||
|
|
4e1ec84028 | docs(phase-26): decision-log — Drew: the tooling-integrity audit GATES further matching work and gets its own phase (fix the instrument before taking more readings) | ||
|
|
8079240216 |
docs(phase-26): cookbook §50 — refinements that BOUND §47/§48 (from the func_80135EB0 wall)
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. |
||
|
|
7e7c80659b | docs(phase-26): decision-log — the audit's finding was real, my reading of it was wrong; derive metrics from invariants, don't re-parse the world (R14/P9 self-correction) | ||
|
|
a30500eecf |
docs(phase-26): cookbook §49 — the LUID DIAL (func_8017A4AC MATCH, 536 ins ×134, the biggest remaining fn)
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. |
||
|
|
0bf0313a6d |
docs(phase-26): cookbook §48-A4 — SINK THE CONSUMER CALL INTO THE ARMS (func_8016AB6C 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. |
||
|
|
5aea9b1112 | docs(phase-26): decision-log — promote the silent-skip lesson from a rule to a MECHANISM (coverage oracles); flag the verdicts reached on top of the broken oracle (Drew approved) | ||
|
|
29478ab9b0 |
docs(phase-26): cookbook §48 — the 12-core jr crack wave (9/12 MATCH): allocno-pricing dials, the EBB rule, type-driven codegen, the cross-jump ratchet
- §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. |
||
|
|
cc08601ae7 |
feat(phase-26): func_80178D40 swept ×134 — the heaviest core in the game, fleet-wide
- 132/132 siblings banked (0 failures) via jtbl_family_bank --raw + the lazy-isolation chain. Each sibling: isolate -> jtbl carve -> remap from the raw crack -> stage ladder (raw -> scoped §8d -> recovered -> reconciled) -> WHOLE-BINARY byte-gate. - R22 clean-fleet 136/136 BYTE-IDENTICAL from `make clean`; 0 NON_MATCHING (G4). - METRICS: instr-weighted 63.8 -> 64.7%; distinct-code 40.7 -> 42.8% (+2.1 points from ONE core — 890 ins x 133 overlays = ~118K instructions of unique engine code); fn-count 82.43%. - tools/bank_exemplar.py promoted from scratch: bank a cracked EXEMPLAR ×1 through the same stage ladder jtbl_family_bank uses for siblings (carve/lazy-isolate -> raw/scoped/recovered/reconciled -> whole-binary gate). The exemplar path was previously hand-run each time. |
||
|
|
3a67dd609f |
feat(phase-26): func_8017BEBC (952 ins, ×113) CLOSED + banked ×1 — the §47 live-length slider
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).
|
||
|
|
b8a525bb98 |
feat(phase-26): h_seq substantial-band re-sweep — 266 free member-matches (~0 agent tokens)
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. |
||
|
|
7b970653ff |
docs(phase-26): cookbook §46 — func_80178D40 MATCH (890 ins ×134): four loop-structure levers
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. |
||
|
|
cc6220eba4 |
fix(phase-26): extract_unit mistook m2c declarations for definitions (15 phantom exemplars)
- 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.
|
||
|
|
1ab9905368 |
feat(phase-26): §8d scope_data_externs — the ×133 sweep blocker fixed; func_8015AE2C banked ×134
- 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. |
||
|
|
12631df74a |
docs(phase-26): R17 triage rule — 'wrong bytes' -> read gcc; 'won't compile' -> read our Python
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. |
||
|
|
754ac3428f |
fix(phase-26): permuter silently no-op'd on every GTE draft (+ --asm-subdir)
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.
|
||
|
|
b0691f4bd9 |
fix(phase-26): jtbl_carve trims trailing .align pad words (§8a-pad)
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). |
||
|
|
e79d030499 |
feat(phase-26): func_80182268 banked via the LAZY isolation path + the void->s32 gate-cap fix
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). |
||
|
|
38ac5659aa |
feat(phase-26): §8b scoping wall BROKEN — decl-environment reconstruction + lazy per-core isolation
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).
|
||
|
|
234788dfc4 |
feat(phase-26): §8b overlay-src parser (404/404) + jr isolation tool + the gcc-scoping wall finding
- tools/overlay_src_split.py: overlay-.c-aware partition (header = includes + Phase-17 canonical-sig layer; per-address items = preamble + body; robust def/decl/K&R/DEFINE_func/ SETTER/RETCONST classification). Fleet-validated 404/404 overlay .c, 341,902 items — round-trip exact / 0 unresolved / 0 non-monotonic. The Stage-2 isolation unblock. - tools/jr_isolate_all.py: multi-cut jr resegment (config split at jr boundaries, source repartition + INCLUDE_ASM path repoint, banked-jr carve repoint, -O0 skip, ambient decl carry). SINGLE-cut isolation byte-identical (func_8013FFD8 -> d19c9580, R22). - FINDING (decision-log 2026-07-13): full 54-jr isolation of the dense _after object hits gcc-2.7.2 block-scope-extern TU-persistence (func_801734BC/D_80126B3E declared only in engine_core.h DEFINE_func macros); mechanical TU-split breaks it. Fix = declaration- completion from a global symbol->type map (Drew-approved next step; lazy per-core). - baseline intact (ov_SC01_077 rebuilds d19c9580); no config/src/binary change committed. CURRENT_PHASE session-5 checkpoint + decision-log R31. db.*.gbf = R23 noise, not staged. |
||
|
|
d1dd29d815 |
feat(phase-26): §8b multi-jtbl same-subseg — contiguous MERGE (built) + isolation scaffold
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. |
||
|
|
79ca6b4975 |
docs(phase-26): refine session-3 checkpoint — small-jr-first as de-risk preamble, then heavy 191
- 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. |
||
|
|
4e17a7e77b |
docs(phase-26): session-3 checkpoint — heavy-byte-weight reframe (§8 unlocked the switch cores)
- 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) |
||
|
|
5a08180617 |
feat(phase-26): §8 ×134 automation — func_8012ACE0 banked fleet-wide (133/133, R22 136/136)
- 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
|
||
|
|
095a611e75 |
feat(phase-26): §8 jtbl-rodata tooling — overlay PoC proven (func_8012ACE0, R22 136/136)
- 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) |
||
|
|
025cc03f69 |
feat(phase-26): tiny-band mechanical harvest — 17,975 member-matches, R22 136/136
- family_sweep --hseq --band tiny: 180 tiny matched-exemplar families -> 17,975 member-matches BANKED / 5,617 gate-rejected (h_seq-collision false-templates — the whole-binary byte-gate refused every one; G3/P9), 266 overlay .c files touched (~76% bank rate) - R22 clean-fleet (make clean + extract-all-136 + check-all) -> 136/136, 0 failed; dedup-check 1813 validated / 0 failed; 0 NON_MATCHING in any default build (G4) - metrics: distinct-code 35.2 -> 39.1% (50,571/84,996 unique fns), instr-weighted 60.9 -> 62.9%. mechanical size-bands (substantial/mid/tiny) now harvested; remaining levers = the two harvest gaps (§8 jtbl-rodata, reconcile fn-ptr-extern) - regen docs/family-hseq.md + docs/progress.fleet.md; CURRENT_PHASE.md log |
||
|
|
3074ceb485 |
feat(phase-26): mid-band clean harvest — 6853 member-matches (320 distinct fns), R22 136/136
- plain --hseq --band mid: 6,853 whole-binary member-matches banked = 320 distinct engine functions templated across the overlays (matched-exemplar families, plain templating; the ×N leverage of the h_seq per-location reframe). 3,098 type-heavy tail + 1,743 pinned + 873 no-ov077-body skipped/failed (expected — the reconcile/§8/pin tail, not machinery failures). - R22 clean-fleet: make clean + extract-all-136 + check-all = 136 passed, 0 failed. 0 NON_MATCHING. - METRICS: distinct-code 31.1->35.2% (+6,437 byte-distinct classes), instr-weighted 58.9->60.9%. Phase-26 total: distinct 30.3->35.2%, instr 58.2->60.9%. - session-2 checkpoint (CURRENT_PHASE 'SESSION-2 CHECKPOINT') current: next = tiny band, then the §8 jtbl tooling (Drew-approved) + reconcile fn-ptr fix. Fable5 held until re-approval. |
||
|
|
b94e417edf |
docs(phase-26): session-2 checkpoint — full resume context (mid-harvest in flight, §8 approved, 2 gaps)
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. |
||
|
|
62f9533024 |
feat(phase-26): +266 reconcile-class banks (2 no-jtbl cracks x133) + Fable5 batch-1 whole-binary findings
- +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. |
||
|
|
7ccf48f2f4 |
feat(phase-26): Task-8 reconcile wiring (§41c h_seq) + 463 reconcile-class banks
- 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). |
||
|
|
8dbde10752 |
feat(phase-26): Task-8 validation slice — reconcile→bank proven (4 cracks into ov077, R22 136/136)
- 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. |
||
|
|
5f07d099e6 |
docs(phase-26): finalize Task-6 triage (119 agents) + banking caveat; pause before Task 8
- 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. |
||
|
|
80536efbea |
feat(phase-26): task 6 — Ultracode triage of 119 draftable substantial families
- .run/wf_triage*.js Workflow: per-family m2c draft (+§8 jtbl handling) -> match_one reloc-masked closeness -> classify cheap/permuter/fable5 + §31/§45 lever + seed to .run/phase26-seeds/. - 119 families triaged (2.38M templatable ins): cheap 22 (324k ins, 20 already isolation-MATCH) / permuter 23 (340k) / fable5 74 (1.72M). The cheap-Opus triage flywheel cracked 20 families to closeness-0 as a side effect (each templates x134) -> Task-8 gate+template candidates. - docs/phase26-triage.md = the crack curriculum (already-cracked seeds + top-30 Fable5 targets + permuter band). Seeds in .run/phase26-seeds/. match_one closeness is an INDICATOR; the whole-binary byte-gate (Task 8) is the sole arbiter (G3/P9). |
||
|
|
d05203b9a0 |
feat(phase-26): task 5 — h_seq zero-crack GO/NO-GO = GO; 532 members banked (R22 136/136)
- 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). |
||
|
|
462734edb2 |
feat(phase-26): task 3 — T2a immediate engine (Tier 1) + T2b cross-address, 0-DIFF verified
- family_remap: imm_value (signed/unsigned field-aware) + imm_map_tier1 (diff-driven literal swap: asm-side ambiguity guard defers values that also appear at a non-differing position; C-literal swap preserves sign + hex-case) + remap_hseq (symbol remap + imm subst + cross-address self-rename in one pass; refuses STRUCT/unresolved members -> caller skips, byte-gate would reject anyway). - Tier 2 (targeted probe) intentionally DEFERRED — build-if-needed per Task-5 measurement; only ~8 low-weight IMM families, and the whole-binary byte-gate arbitrates (plan: simpler where gate arbitrates). - VERIFIED (.run/v3_imm.py, match_one reloc-masked so immediates are checked exactly): 0 DIFF on every compilable derived draft — 3 IMM + 2 PURE cross-address MATCH; 4 remap-fails were correctly-deferred asm-ambiguous values (not wrong output); 45 compile-fail are match_one isolation limits -> whole-TU gate in Task 5. Cookbook §40b imm-engine forward-ref fixed. |
||
|
|
faedd103e4 |
feat(phase-26): task 2 — tools/family_hseq.py full-frontier survey + shared word-diff classifier
- family_remap.py: shared classifier (stream_words / reloc_indices / reg_fields / classify_member) — per-instruction diff class RELOC/IMM/IMM_SA/STRUCT, register-drift aware (h_seq ignores registers, so regalloc-drift members are STRUCT-excluded, not templatable). Reused by T1/T2a/T3. - tools/family_hseq.py: cluster ALL unmatched overlay instances by h_seq; per family classify every member vs exemplar (PURE/IMM/MIXED), tag per-location/cross-address/scattered, matched-sibling count, has_mid_jr (§8), exemplar pick (matched-ov077 > matched > draft-ov077 > modal), size band. -> .run/family_hseq.json + docs/family-hseq.md (committed digest). - VERIFIED: fleet 74.8/58.2/30.3 (= PhaseEnd_25 + progress.py exact); tail cross-check 663 families / 186 substantial / 1.847M ins (exact); classification vs Plan-agent PURE-same 62 & IMM 8 exact; 890x134/562x134 PURE per-location + 952x113 #addr21 IMM confirmed. Full frontier: 581 substantial families / 3.22M templatable ins; 345 matched-sibling PURE/IMM families / 1.14M ins = V2/V3 corpus. |
||
|
|
5b7e366648 |
feat(phase-26): task 1 — extended reloc tracker (addu-hi) + single-pass remap; V0/V1 green
- reloc_targets: propagate lui-hi through add/addu index arithmetic (gcc-2.7.2 indexed-global idiom lui;addu $idx;lw %lo($at)). The pre-26 tracker dropped it -> D[i] functions mis-normalized per overlay (inflated the 'h_norm reach-1 tail') AND lost their indexed D_ symbols in remap. norm_stream/h_norm deliberately UNTOUCHED (fleet metrics + proven sweep depend on it). - remap/symbol_map: backward-compatible to_addr=None (cross-address T2b sibling + self-rename) + imm_map hook (T2a); sequential re.sub -> single-pass simultaneous substitution (fixes latent chained-rename/value-permutation corruption). All 5 family_sweep call sites unchanged. - V0 (.run/v0_reloc.py): func_80141100 22/22 NEW==OLD (zero regression); func_801407F4 15/15 vs splat .s (pre-fix 10), recovers indexed D_80187B88/90/B0; cross-addr symbol_map clean. - V1 (.run/v1_regression.py): 160 real h_norm sibling pairs, 96 SAME, 0 lost — differences are strict indexed-reloc improvements only. - docs: cookbook §40b (the technique, R30) + decision-log 2026-07-11 (the strategic why, R31). |