Commit Graph

1073 Commits

Author SHA1 Message Date
Drew T 13812fa50c docs(phase-29): SESSION-21 T8 — span-derivation fix + func_8012AAAC 137/137; routing rule shown both ways 2026-07-27 11:26:04 -06:00
Drew T ab3ac83a41 feat(phase-29): func_8012AAAC family swept 137/137 — the --like guard unblocked the whole class
- 134/134 BANKED on the remainder (after 3/3 on the probe) => the family is 137/137, ZERO failures.
  func_8012AAAC is now stubbed in NO overlay. R22 clean-fleet: extract-all 139/139, check-all
  140 passed / 0 failed.
- FLEET 81.9 -> 82.0% instr · 89.60 -> 89.64% fn-count · distinct-code 69.3 -> 69.5%.
- THE METRIC POINT, reproduced twice in one session and in BOTH directions: this jtbl family is
  byte-VARIANT (each overlay's table holds its own addresses), so every member is a genuinely new
  unique function and distinct-code MOVED. The h_seq PURE families swept earlier added 274 members
  and moved distinct-code by +0.0, because those members were already counted via their shared
  exemplar. SESSION-20's routing rule, now byte-demonstrated: target byte-VARIANT families to move
  RE-completeness; high-reach h_exact families move only the display number.
- cookbook §91 — "a structure-TRANSFER is only valid where the structure corresponds": the --like
  role trap, plus the three-hypothesis trail (two wrong, and instructive: the sibling call-site casts
  were a real defect that fixed nothing, and my own carve-alone test was a false lead that departed
  from the tool's real sequence). The law: any "same family => same structure" transfer must state
  which structural fact it assumes and CHECK it on both sides — and a tool that drops an error class
  it cannot act on should still SURFACE it, because a bare `gate-fail` repeated 137 times cost far
  more than printing one line would have.
2026-07-27 11:25:42 -06:00
Drew T a5e5ea45ef fix(phase-29): jtbl_carve — guard the --like role-transfer; the ×137 sweep goes 0/3 -> 3/3
ROOT CAUSE of the 0/3 (found by reading the tool's ACTUAL invocation, not by guessing):
jtbl_family_bank calls `jtbl_carve <sibling> --func <fn> --like <exemplar_ov>`, and the role-transfer
keys on the SUBSEG ROLE (`ov_SC01_077_a` -> `_a`). Its premise — "same family => same span
structure" — silently breaks when the exemplar and the sibling host the function in subsegs with
DIFFERENT roles, which happens whenever the exemplar has a split the sibling does not.

MEASURED: func_8012AAAC lives in `ov_SC01_077_a` (role `_a`) in the exemplar but in the MAIN subseg
(role ``) in every sibling. The transfer therefore looked up `ov_SC01_077` — an unrelated SEVEN-table
span belonging to entirely different functions — and stamped those starts onto a sibling span holding
one table. jtbl_rodata_pads then refused with `consumed 1 rodata .align(s) but 2 pad spec(s) given —
table-count drift`, and jtbl_family_bank deliberately does NOT treat that error as isolate-fixable,
so all 137 siblings returned a bare `gate-fail` with the cause discarded.

THE FIX: transfer only when the exemplar's subseg for THIS FUNCTION has the sibling's role; otherwise
derive the span from the sibling's own carve (which was already computing it correctly). Fail-open is
not acceptable here — a wrong table set corrupts the image, so the guard defaults to local derivation.

MEASURED RESULT: the 3-member probe goes 0/3 -> 3/3 BANKED. R22 clean-fleet: extract-all 139/139,
check-all 140 passed / 0 failed.

Two earlier hypotheses were tested and are recorded honestly in CURRENT_PHASE.md: the sibling
call-site casts (real conflict, fixed, byte-neutral — but NOT the blocker) and my own carve-alone
test (which fails by construction for this shape, because a stub object does not emit the table its
2-entry spec describes — the tool splices the body BEFORE building, so its path is the valid one).
2026-07-27 11:01:59 -06:00
Drew T 3ef1ea08c0 docs(phase-29): SESSION-21 final checkpoint — 278 banked, jtbl chain proven, sweep blocker named 2026-07-27 10:44:40 -06:00
Drew T 1f0f33b165 docs(phase-29): name the ×137 sweep blocker — exemplar span structure does not transfer (§8e table-count drift) 2026-07-27 10:44:11 -06:00
Drew T c1d2adab29 fix(phase-29): cast the 137 sibling 0-arg call sites of func_8012AAAC (byte-neutral, R22 140/140)
The ×137 member sweep returned 0/3, and §53/§86 say a 0% is a diagnosis task, not a verdict.
Diagnosed: every sibling TU has the IDENTICAL shape to ov_SC01_077 — the INCLUDE_ASM stub, then
'extern void func_8012AAAC();', then a 0-arg call LATER in the same file. Splicing the definition
in puts a prototype in scope, so gcc rejects the call with 'too few arguments' — the exact failure
the exemplar hit, reproduced 137 times.

Measured, not assumed: 137 sibling TUs hold BOTH the stub and a 0-arg call — exactly the member
count. Cast one call site per TU to ((void (*)(void))func_8012AAAC)() (§17a-1; gcc folds the cast
of a known symbol to a direct jal). The 137 'extern void func_8012AAAC();' DECLARATIONS were
deliberately left alone — an early count of '274 sites' was the calls AND the externs, and casting
an extern would have been meaningless churn.

Byte-neutrality PROVEN before committing, not asserted: R22 clean-fleet extract-all 139/139,
check-all 140 passed / 0 failed. This must be committed BEFORE the sweep because
jtbl_family_bank reverts each sibling from HEAD — an uncommitted fix would be reverted by the very
tool that needs it.
2026-07-27 10:42:32 -06:00
Drew T 7c6810ba2f docs(phase-29): SESSION-21 — func_8012AAAC chain recorded; ×137 sweep 0/3 open for diagnosis 2026-07-27 10:39:35 -06:00
Drew T 2cc49d0310 feat(phase-29): SESSION-21 — func_8012AAAC banked via the §81 carve chain (R22 140/140)
The first jtbl-routed bank of the session, and it validates the whole chain end-to-end:
 1. jtbl_carve SPLIT-TABLE repair (this session): jtbl_801D7FB0 28 -> 50 words (112 -> 200 B),
    authorized by func_8012AAAC's own `sltiu 0x32`.
 2. NEW FIX — SINGLE-TABLE PREDECESSOR: adding a second table to a subseg whose existing carve was
    single-table lost the FIRST table's start entirely (new_offs has only the new one;
    overlay_jtbl_addrs cannot see the old one because its owner is banked and extract PRUNED the
    stub .s; and single-table carves persist no tables= to rebase). The span then failed its own
    validator with "first must equal the span start" — the invariant naming the missing entry.
    A single-table carve spans exactly its one table, so ITS SPAN START *IS* THAT TABLE'S START:
    inference, not persistence, so it also works for spans carved before tables= existed. This is
    the RECOVERABLE half of the documented func_8013F350 lesson (that one was a pre-§8e merged
    DOUBLE — two tables, no record, genuinely unrecoverable).
    Result: ov_SC01_077_a JTBL_PADS := 0,0 tables=+0x0,+0x14. Carve alone byte-gated BYTE-IDENTICAL
    BEFORE the bank was attempted (§81 step 2).
 3. ARITY axis, all-or-nothing: 1,244 decl sites / 1,240 files `(void)` -> `()` + an R32 completion
    assertion (old-form remaining: 0).
 4. ONE call-site cast: the definition lands at line 811 and a 0-arg call sits at 822, so gcc sees
    the prototype and rejects it — `((void (*)(void))func_8012AAAC)()` (§17a-1; gcc folds the cast
    of a known symbol to a direct jal). Only 1 of the 1,386 fleet-wide 0-arg call sites needed it:
    the others see only the `extern ()` decl, which permits a 0-arg call.

DIAGNOSIS NOTE: the failure read CC1-FAIL with only a warning visible under make. Running the
pipeline stage-by-stage (cpp | cc1 | maspsx | jtbl_rodata_pads | as) put it on cc1 rc=33, and cc1's
own stderr named it exactly: "too few arguments to function func_8012AAAC" at line 994. Isolating
the stage was what turned an opaque Error 33 into a one-line fix.

R22 clean-fleet: extract-all 139/139, check-all 140 passed / 0 failed.
family_sweep correctly REFUSED this exemplar (§53: a jr-family must route through
jtbl_family_bank.py; "a 0% from this path would be a TOOL artifact, not a wall") — the ×137 member
sweep is the next step and needs a clean tree, which this commit provides.
2026-07-27 10:38:10 -06:00
Drew T d3a40f2aa0 docs(phase-29): SESSION-21 checkpoint — 277 banked, wave resumable, 8 jtbl drafts ready 2026-07-27 10:08:48 -06:00
Drew T 5be4c21480 feat(phase-29): SESSION-21 — the ×138 member sweep: 274 members from 3 exemplar cracks (R22 140/140)
- family_sweep --hseq over the 3 newly-banked exemplars: BANKED 274 member-matches / 137 failed
  across 137 overlays, for ~0 agent tokens. Session total: 3 exemplars + 274 members = 277 fns.
- THE STALE-MAP STEP, hit and handled: the first sweep returned "0 matched-exemplar families"
  because .run/family_hseq.json still listed the fresh cracks as draft-ov077. Regenerated
  (matched-sib families 60 -> 63) and the sweep found them — the documented bank-x1 -> regen ->
  sweep path (memory crack-wave-sweep-map-regen).
- §86 REPRODUCED CLEANLY: 2 of 3 families templated ~137/137; the third failed ~137/137. Not a
  rate — a BIMODALITY. One probe per family, then sweep or skip; never a blended pool average.
- R22 clean-fleet: extract-all 139/139, check-all 140 passed / 0 failed (second clean-tree
  verification this session). dedup 1886/0, C1 coverage 239,604/239,604, 0 NON_MATCHING (G4).
- FLEET 81.7 -> 81.9% instr · 89.52 -> 89.60% fn-count · distinct-code 69.3% UNCHANGED — correct
  and expected: these are h_seq PURE propagation-class families, and SESSION-20's routing rule says
  propagation moves only the DISPLAY metric (members were already counted once via their exemplar).
  To move RE-completeness, target byte-VARIANT families. Stated plainly so the next session picks
  targets by the metric it means to move.
- drive-by: family_sweep --help crashed (argparse %-expands help text; a literal "0%" needed "0%%").
2026-07-27 10:08:19 -06:00
Drew T 2ce6c5fade feat(phase-29): SESSION-21 — 3 family exemplars banked + the §85 return-axis widen (R22 140/140)
BANKED (whole-binary byte-gate, the sole arbiter): func_8014D2A0 (80 ins ×138) · func_80158638
(87 ×138) · func_8016B6BC (94 ×138). Stubs in ov_SC01_077: 150 -> 147, 0 new stubs.
R22 CLEAN-FLEET: extract-all 139/139, check-all 140 passed / 0 failed. dedup 1886/0,
0 NON_MATCHING (G4). Fleet 81.7% instr / 69.3% distinct-code / 89.52% fn-count.

- WAVE STOPPED at Drew's request with 15/24 agents returned, ALL 15 status=match. Only the
  completed drafts were gated; in-flight ones are still being written (§90d).
- PRE-GATE, both oracles, all 15: match_one MATCH + reloc_verify ALL RESOLVED. Routed 7 plain /
  8 to the §81 jtbl carve chain.
- THE BLOCKER, MEASURED: 7 of 7 plain drafts failed PLUMBING, 0 DIFF, 0 compiler walls — the same
  shape as SESSION-20's T0.2. §58b applies: the draft sig is byte-TRUTH (it MATCHed), the header
  decl is the stale stub-era guess, so conform the DECLS.
- §85 RETURN-AXIS WIDEN, all-or-nothing: 3,471 decl sites / 1,736 files, precondition verified
  (ZERO callers consume the return => byte-neutral by construction) + an R32 completion assertion
  (old-form decls remaining: 0). func_8014D820's s32 return is load-bearing — forcing `void` costs
  2 instructions (302 vs 304), so the decls had to move, not the draft.

TWO HONESTY ITEMS:
 1. I REPORTED "0 of 7 banked"; the true number was already 2. My diagnostic pass printed only
    lines starting with "- func_" (the failures) and hid its own successes while I read it for
    error text. A script that prints only failures cannot tell you it succeeded — the R32
    silent-skip shape aimed at my own instrumentation. Ground truth is the stub count (§55b(3)).
 2. A REAL FINDING fell out of that mistake: same drafts, same tree, minutes apart — gate_stage's
    full ladder banked 0/7 while bare harvest_verify banked 2/7. The LADDER REGRESSED two drafts
    the bare gate accepts (§19's "sig_unify regresses already-canonical drafts", one level up, and
    the exact mirror of SESSION-20's missing-ladder false 33%). Neither "always ladder" nor "never
    ladder" is right — run both, let the byte-gate arbitrate. One build per draft.

OPEN: func_8014D820 still a stub — after the widen its error moved from `conflicting types` to an
assembler-stage failure, not finished diagnosing. Recorded as open, NOT as a wall.
2026-07-27 09:58:17 -06:00
Drew T 0be781efdf docs(phase-29): cookbook §90 — five tool-integrity laws from SESSION-21 (R16/R30)
Each law is one this session paid for, and each fix CHANGED the answer its tool gave:
 §90a a comparison tool must share its reference oracle's index space exactly (objdump -dr vs -drz
      elided 6 nops -> 104 vs 110 ins -> phantom mismatches; and the .s word field is little-endian
      hex TEXT, not the instruction integer). Five false alarms preceded the first true one.
 §90b 'byte-neutral' is not 'wanted' — undo on the SUCCESS path (a 0-bank run left 123 files of
      dead diff because the backstop only fired on a NON-neutral edit). §61 applied forward.
 §90c a library-callable function must FAIL CLOSED on unconfigured module state — an empty result
      is indistinguishable from a true negative (R32's silent skip in a different hat).
 §90d do not measure a live wave's drafts — §87 staleness in real time (same function, two verdicts
      two minutes apart; its agent rewrote the file 12s earlier).
 §90e an agent's CONCLUSION and its EVIDENCE fail independently: re-derive the premise, design the
      fix from what you can prove. The agent's jtbl bug was real and confirmed 3 ways; its 'zero
      xrefs' premise was false and its proposed remedy would have deleted a referenced symbol.
      The fix instead gates on the function's own sltiu bound — the program declaring its own table
      length. Negative control (228 combinations, exactly 1 change) is half the deliverable.
2026-07-27 09:29:12 -06:00
Drew T 87b02b044f fix(phase-29): jtbl_carve — repair the SPLIT-TABLE undercount, gated on the function's own sltiu
THE BUG (real, found by a wave agent): jtbl_range() ends a carve at the next data dlabel, assuming
every dlabel is an object boundary. spimdisasm can CUT ONE JUMP TABLE IN HALF and emit the tail
under an invented D_ label — func_8012AAAC's 50-word table is jtbl_801D7FB0 (28) + D_801D8020 (22).
The carve then reserves 112 B for an object supplying 200 B of .rodata, shifting every later symbol.
§84-class: match_one is structurally blind; it surfaces only as a whole-binary DIFF.

THE AGENT'S EVIDENCE WAS WRONG (R14): it reported D_801D8020 as having "ZERO xrefs anywhere in the
tree" and proposed deleting the label. It has TWO (.word D_801D8020 and +0x2 in tail.data.s) —
almost certainly spimdisasm mis-symbolizing packed halfword data, but "almost certainly" is not a
gate, and the proposed remedy would have deleted a symbol two emitted words reference. I built the
xref census first, watched it refuse, and only then found the references.

THE GATE USED INSTEAD — the function's own `sltiu N` range check, which gcc emits right before the
indexed load, so the PROGRAM declares its own table length (func_8012AAAC: sltiu 0x32 = 50). Absorb
only when the next label is immediately adjacent, its words are all code addresses in the overlay's
text, and absorbing lands on an EXACT sltiu bound (the SET, not max() — a multi-switch function has
several and no way to say which owns this table).

Three further corrections, each caught by testing rather than assumed:
 - the absorption fired and the trailing-pad trim immediately UNDID it (re-trimming against the
   first dlabel's 28 words); the trim now sees the whole absorbed table;
 - a continuation ends at ITS OWN last .word, not the next dlabel (D_801D8020 ends 0x801D8078; the
   next dlabel is 0x801D8158, 224 B on) — using the next dlabel is the assumption being repaired;
 - the shortfall warning now fires only on an unambiguous single-bound pairing (it fired ~90 times
   across 38 tables before the guard — a warning that fires on ambiguity is noise, not a signal).

VERIFIED: the split table 28 -> 50 words (112 -> 200 B), matching the agent's 3 independent
confirmations; and across 38 jtbls x 6 functions = 228 combinations, EXACTLY ONE range changes —
that table, for its owning function only.
2026-07-27 09:28:08 -06:00
Drew T 181ba8c4e6 docs(phase-29): SESSION-21 wave 1 first half — 8/8 match_one MATCH, 6 of 8 blocked on ONE lever
- 8 of 24 agents completed before the session limit (16 errored on the limit, none technically);
  resumed from cache. 8 MATCH / 0 near / 0 fail, stake 210,726 templatable ins, 2.48M subagent tokens.
- CANDIDATES not banks (§58) — but DIAGNOSED ones: the prompt required symcheck + a named blocker,
  so instead of 8 opaque MATCHes there are 8 with their banking prerequisite stated.
- THE FINDING: 6 of 8 are blocked on the SAME jtbl/rodata carve class — one mechanical lever in
  front of ~153,596 templatable ins in this batch alone. Corroborated independently by
  tools/reloc_verify.py, which flagged the identical class on the drafts it could check (R34).
- A REAL jtbl_carve BUG found by an agent, confirmed 3 ways: jtbl_range() ends the carve at the
  next data dlabel, but splat split ONE 50-word table across jtbl_801D7FB0 (28) + D_801D8020 (22,
  zero xrefs) -> 112B carve for a 200B .rodata. §84-class: match_one is blind; it surfaces only as
  a whole-binary DIFF. Fix queued.
- Agents touched zero tracked files (write-set constraint held).
2026-07-27 09:21:21 -06:00
Drew T d0322d473c feat(phase-29): promote tools/reloc_verify.py — resolve every relocation before paying a gate cycle
The SESSION-20 carry item ("promote it — it closes 3 of the 4 blindness classes"), generalized:
base vram DERIVED from the target .s (was hard-coded to one function, R33) and the parse
coverage-asserted (R32 — a target that parses to zero instructions refuses to report a verdict
rather than reading "ALL RESOLVED"). Resolves jal callees, %hi/%lo data addresses (recovering the
implicit REL addend objdump -r never prints — the §84 trap) and internal j destinations.

IT TOOK TWO OF ITS OWN BUGS TO TRUST IT — both found by cross-checking masked_diff (R34):
 1. `objdump -dr` instead of `-drz`: without -z objdump ELIDES identical-instruction runs, so
    func_801330E0 read 104 ins vs masked_diff's 110 (6 elided nops) and every later index compared
    against the wrong instruction — 2 phantom mismatches on a clean draft. A comparison tool MUST
    share its reference oracle's index space exactly.
 2. the .s word field is little-endian HEX TEXT, not the instruction integer; masked_diff byte-swaps
    it and this did not — reporting "word differs" on three byte-IDENTICAL sites.

Now classifies instead of alarming: JTBL (gcc emits its own switch table via a local label => nothing
to relocate; the §81 routing signal — bank via jtbl_family_bank, never plain harvest_verify) ·
BAKED-LITERAL (same constant materialized inline: byte-correct here, but if the symbol is
per-overlay the exemplar matches and every SIBLING breaks — the §84 shape) · real mismatch.

Recorded: measuring a live wave's drafts is itself the §87 staleness error — a draft rewritten 12s
before the check gave a different verdict. Draft QA happens after the wave returns.
(The wave's gate driver lives at .run/s21_gate.py — gitignored scratch, §55b orchestration law
built in: --no-propagate per TU group, commit before the fleet propagate, and BANKED derived from
the stub set rather than read from gate_stage's accumulating verified-file.)
2026-07-27 00:06:01 -06:00
Drew T ccbc65e9c3 fix(phase-29): progress.linked_subsegs fails closed (R32) + the main-EXE bucket re-measured
- progress.linked_subsegs() was FAIL-OPEN: gated on the module global BINARY that set_binary()
  assigns, it returned an EMPTY SET when imported as a library without that call — i.e. "no
  linked library subsegs", which for main is confidently wrong (there are 49) and silently
  reclassifies ~960 already-byte-identical PsyQ-linked stubs as outstanding game-code work.
  Now raises when unconfigured; the CLI path is untouched (set_binary assigns before calling).
  Caught by hitting it myself while measuring bucket #2.
- ENDGAME-MAP CORRECTION (measured, zero-token): the map's "main EXE game code ~59,765 ins /
  ~1,048 stubs" conflates two populations. Correctly split: game code 1,042 stubs / 31,888
  measurable ins; LINKED PsyQ library 960 stubs / 27,877 ins (already byte-identical). Bucket #2
  is ~47% smaller than quoted. Caveat kept: 467 game-code stubs have NO sig row (the documented
  main second-oracle gap), so the true weight is above 31,888 and not currently measurable —
  re-price when the main second oracle lands, do not quote either number alone.
- .run/s21_zerocrack.json: the 60-family zero-crack pool enumerated (45 plain / 15 jr) and
  honestly discounted — its top entries (0x8013c414 -O0 wall, 0x80144090 LENGTH-DRIFT,
  0x80133ab0 pinned) are already-diagnosed refusals, so ~95k of the 208,499 is not available.
2026-07-26 23:41:40 -06:00
Drew T 2b38c68333 feat(phase-29): SESSION-21 T1-T3 — the frontier measured, the family-exemplar wave, 3 tool fixes
- T1 FRONTIER MEASURED (zero-token, R35: family map regenerated on fresh sigs first — it was
  stale by ~657 banked members): 36,020 stubs / 2,345,599 weighted ins remain, and only
  9.0% are h_exact-FREE. PROPAGATION IS TAPPED (238 distinct classes / 3,245 instances);
  22,498 distinct classes / 1,680,097 distinct ins is what is actually left. The mass is FLAT
  across all 139 binaries (~300-550 sub-500 stubs each) -> "pick the best overlay" is not a
  strategy. .run/s21_frontier.py + .run/s21_frontier.json
- T2 THE AXIS IS THE FAMILY, NOT THE LOCATION: 1,342 substantial h_seq families /
  1,298,135 templatable ins = 55% of ALL remaining weighted instructions. Routed by blocker:
  jr/§81 181 fams (33.6%) · DRAFT-with-cached-Ghidra-C 91 (28.6%) · DRAFT-modal 1,023 (27.5%)
  · zero-crack 45 (6.5%) · permanent walls 2 (3.9%). Live+cached+non-wall in ov_SC01_077 = 54
  families / 589,502 ins, value steeply concentrated (top 24 = 96%).
  .run/s21_targets.py + .run/s21_targets.json + .run/s21_draft_pool.json
- T3 WAVE 1 LAUNCHED: tools/workflows/family_core_wave.js (NEW) — 24 xHigh drafters, one per
  family exemplar, stake 575,488 templatable ins (24% of remaining). Supersedes worker_wave.js
  for family work: carries each target's family STAKE, encodes the four §58/§87 integration
  rules at source (splat D_<UPPERHEX> not Ghidra DAT_; never invent a symbol; canonical callee
  sigs; leave decl plumbing to the ladder), and requires symcheck.py on any claimed MATCH.
- T3b LADDER HYGIENE, both SESSION-20 carry items fixed — one defect, two masks: a byte-NEUTRAL
  transform was left in the tree when it banked nothing. family_sweep's --normalize-self-decls
  backstop only fired on MISMATCH (left 123 files of dead diff on a 0/123 run); gate_stage's
  ARITY undo narrowed to src/shared/ and left ~40 TUs. Both now restore the full snapshot when
  NOTHING banked (no banks to preserve => the splice hazard cannot apply). §61 on the success path.
- T3c BACKLOG addr DEFECT fixed (R32/R33): new addr_of() derives the address from `name`,
  assert_addr_coverage() fails loud on an unkeyable row, append_record fills both directions.
  Found a latent bug doing it: load_best() keyed on `addr or name`, splitting one function into
  two "best" records. Keyable rows 128/1,701 (7.5%) -> 1,701/1,701 (100%).
2026-07-26 23:37:35 -06:00
Drew T 2d2c01f046 docs(phase-29): SESSION-20 FINAL CHECKPOINT — behemoths done, the measured endgame map, §84-§89, the new throughput sequence 2026-07-26 23:15:28 -06:00
Drew T 5c422e8426 feat(phase-29): tools/sweep_parallel.py + §89 — the parallel gate farm, reachable from the family path (step 2)
bulk_harvest's Phase B has been a ProcessPoolExecutor over DISTINCT binaries (per-binary flock,
per-worker result files, compute_fleet=False) since Phase 23 — but welded to Phase A's LLM drafting.
Family sweeps stage drafts differently (family_sweep --stage-only), so the farm was UNREACHABLE from
that path, and SESSION-20 gated 389 + 268 + 104 members SERIALLY for no architectural reason (~8-16x
throughput loss on a 32-thread box). This is a thin adapter: same gate_stage.run_gate, same
per-binary lock, NO new gate logic.

Also fixes the phantom-dir bug at source: a bare .run/sweep/*/ glob matches gate_stage's own
intermediate ladder dirs (-cn/-cast/-rc/-s2in/-uni) and calls them as binaries — 24 phantom
PARTIAL 0/1 lines that inflated one run's notbanked from 0 to 56. Requires config/splat.<bin>.yaml
to exist (R33/R36: derive the binary set, never glob it). Smoke-tested: the phantom is skipped and
named, real binaries kept.

§89 records both throughput rules the project already had and was not following.
2026-07-26 23:10:06 -06:00
Drew T 6ce2e8963f chore(phase-29): preserve the behemoth close-out artifacts (R20)
39 files from the three behemoth agents: the matched drafts (s21_func_80183814_b2.c,
s21_func_8017D2DC_b1.c, s21_func_8017DC1C_b1.c), their reports with do-not-re-buy tables AND BASES
(§80), and the reusable harnesses — including s21_g21_reloc_verify.py, which resolves every
relocation (incl. the implicit MIPS-REL addend objdump -r does not print) against the target and is
the missing rung between match_one and the binary (§88f). ~735k agent tokens of work; .run/giants is
the curated allowlist.
2026-07-26 23:08:33 -06:00
Drew T 1748eabe62 feat(phase-29): tools/blast_radius.py — MEASURE the write set, enforce the §63 tier (step 1)
§63 has defined T0/T1/T2 since Phase 26 and never enforced it. Two measured consequences in
SESSION-20: ~13 full clean-fleet verifies (~15 min each) for batches that were provably T1
(over-verification), AND two cases where the write set was LARGER than the belief about it — the §85
widen believed contained to one overlay broke ov_SC01_077, and gate_stage's ARITY pre-pass silently
rewrote 40 TUs (under-verification, the dangerous half).

A tier is a CLAIM about the write set; this turns it into a MEASUREMENT. --expect t1 fails loud when
the tree disagrees. Binary list DERIVED from config/splat.*.yaml (R33, never hardcoded — R36's
incident was a hardcoded set missing 4 real binaries). Coverage ASSERTED (R32): an unclassified path
exits 2 and names itself rather than being silently skipped. overlays.mk is attributed per-binary by
parsing its diff, so a change confined to one binary's var-block stays T1.
2026-07-26 23:08:07 -06:00
Drew T 890dd08e6f docs(phase-29): §88 — the behemoth close-out laws (cross_jump/call, slti literal-position + its equality false-positive, banking order, the reloc gate) 2026-07-26 22:52:22 -06:00
Drew T d2a79deff2 feat(phase-29): ALL THREE BEHEMOTHS BANKED — func_80183814 (5,122), func_8017DC1C (1,518), func_8017D2DC (1,586)
Zero functions >1000 ins remain unmatched anywhere in the fleet.

func_80183814 (5,122 ins — the LARGEST function in the game) — round 2 closed it: length 5127->5122
exact, structural residual 36->0, register-sensitive 1201->0, frame -256 -> -0xF8 exact, saves
10 -> .mask 0x807f0000 exact. Verified independently (R14): match_one MATCH (5122 ins).
  ROUND 1's DIAGNOSIS WAS WRONG and the agent refuted it properly: the +5 length was a SYMPTOM, not
  the lever, and the §83d max_reg/cse.c:8340 story does not hold — a 15-line reproducer reproduced the
  case-0/3 CSE exactly (so it cannot be max_reg-gated), max_qty only gates extension ACROSS blocks,
  and the target leaves $s7/$fp unused (no pressure story). Confirmed from a second direction: C01 has
  the identical two groups over the identical symbols with ZERO residual, because a `break` puts a
  CODE_LABEL between them. The two biggest levers were pure DECLARATION SCOPE (§45/§76), not pins.
func_8017DC1C (1,518) — MATCH first round, pin-free, zero __asm__ dials. NOT a jr fn (0 mid-fn jr).
func_8017D2DC (1,586) — MATCH first round (banked in the previous commit).

BANKING ORDER MATTERS — a new failure mode found and worked around: banking func_8017DC1C BEFORE the
carve chain broke the build. Its draft establishes the canon for 39 previously-undeclared externs;
jr_isolate_all's re-partition (overlay_src_split) then DROPPED ALL 39 across the new split boundary
(`D_801C1EB0 undeclared`), leaving them in NEITHER file. The §77 preamble-drop class, in a third tool.
FIX = ordering, not patching: run the §81 carve chain FIRST on a clean tree (gated BYTE-IDENTICAL),
then bank. Reverted, re-sequenced, both banked clean.

R22 clean-fleet 140/140 BYTE-IDENTICAL; tools-health OK; dedup 1886/0; 0 NON_MATCHING (G4).
Fleet: instr 81.6 -> 81.7% · distinct-code 69.1 -> 69.3% · fn-count 89.52%.
2026-07-26 22:16:48 -06:00
Drew T 09b1993059 feat(phase-29): T0.7 sweep (104 members) + BEHEMOTH func_8017D2DC banked (1,586 ins)
T0.7 — the §86 one-member probe applied to the remaining FREE families: 9 LIVE / 6 DEAD / 5 unstaged.
The three highest-value families by raw size (18,084 / 11,234 / 10,880 ins) all probed DEAD — the
probe skipped them instead of burning ~400 gate cycles rediscovering it. Swept the 9 live: 104 banked,
8 of 9 families fully cleared (func_8017BEF8 has 8 stragglers).

BEHEMOTH 2 of 3: func_8017D2DC (1,586 ins, ov_SC01_001) MATCHED and BANKED — closed in ONE agent
round, pin-free. Verified independently (R14): match_one MATCH (1586 ins).
  §81 carve chain: the agent predicted step 1 unnecessary; jtbl_carve REFUSED (the subseg already
  hosts a .rodata carve and the new table's start != span start). The refusal was RIGHT and is the
  instruction to run step 1 — jr_isolate_all --only (2 fns/1 object) -> BYTE-IDENTICAL, then
  jtbl_carve -> BYTE-IDENTICAL, then the ladder banked it.

R22 clean-fleet 140/140 BYTE-IDENTICAL; tools-health OK; dedup 1886/0; 0 NON_MATCHING (G4).
Fleet: instr 81.5 -> 81.6% · distinct-code 69.0 -> 69.1% · fn-count 89.49 -> 89.52%.
2026-07-26 22:02:37 -06:00
Drew T 1eb36504f6 docs(phase-29): §87 — match_one never LINKS, so the 315 'integration' entries are not bankable
T0.6 measured: the autopsy's integration bucket (315 match_one MATCHes, 'blocked only on plumbing')
banked 0/27 through the full gate_stage ladder. Two causes, neither plumbing:
 (1) UNDEFINED DATA SYMBOLS — the gate fails at LINK on symbols defined in NO overlay's symbol file.
     match_one compiles one TU and never links, so an extern resolving nowhere is structurally
     invisible to it. (Refuted the obvious alternative: the drafts WERE authored for the right binary.)
 (2) STALE DRAFTS — 'redefinition of struct S80172C50': the struct was since lifted into
     engine_types.h, so the draft's own copy collides. A stored draft is scored against TODAY's tree.

=> FOUR match_one blindness classes now catalogued: §81 jump tables, §84 masked %lo, §87 link, §87
staleness. A match_one MATCH is 'this TU compiles to the right bytes with relocations masked' —
nothing about linking, nothing about the current tree. A stored MATCH is a CLAIM WITH A TIMESTAMP.

Consequence: with §83's 44%-misfiled finding, docs/backlog.md's headline count is NOT a work queue.
Re-gate a sample before planning against any stored-draft pool. Cheap discriminator added (grep each
D_ symbol against the binary's symbol files; any UNRESOLVABLE will fail at link regardless of ladder).

MY RECOMMENDATION WAS WRONG: I ranked this pool first on 'highest certainty of any pool we have'.
The certainty was an artifact of a tool that cannot see link errors. 0 banked, 0 tokens, tree clean.
2026-07-26 20:48:54 -06:00
Drew T 454a0d2a02 docs(phase-29): SESSION-20 closing checkpoint — ~1,070 members from five tooling root causes; 81.5/69.0/89.49 2026-07-26 20:24:01 -06:00
Drew T c2566010f2 feat(phase-29): --allow-pins sweep — 268 banked; pin templatability is PER-FAMILY (cookbook §86)
The §42e pin guard refuses any family whose exemplar carries `register __asm__` pins: 680 of the top
8 FREE families' 1,083 members (63%) were skipped BEFORE any gate ran. Re-run with --allow-pins,
letting the byte-gate arbitrate (G3/P9): 268 banked, and ZERO cc1 crashes across hundreds of pinned
compiles — confirming the SIGABRT the guard was written against was Phase 27's extract_unit
macro-drop, NOT a compiler limit. The guard is protecting against a bug that no longer exists.

THE LAW (§86): templatability is a PER-FAMILY property, not a per-member rate.
  func_801749C8  137/137 = 100%      func_80133AB0    4/136
  func_8014C6F4  137/137 = 100%      func_8014CF04    0/137
                                     func_80143D28    0/136
Two families at 100%, three at ~1%. MY REPORTED "37%" WAS AN ARTEFACT: a 19-member sample that
straddled families reported their AVERAGE and hid the bimodality. Sample PER-FAMILY, never per-pool.

=> PROCEDURE, now the default: probe ONE member per pinned family; bank -> sweep the family; fail ->
skip entirely. The blanket sweep spent ~412 futile gate cycles (60% of the run) on three families
that were never going to bank; the 1-member probe reduces that to 5 probes + 2 sweeps.

Left explicitly UNDIAGNOSED (do not guess): why two families template and three do not. Likely axis
is caller-saved pins spanning a `jal` (§74's corrupting form) vs pins fixing only a local allocno.
Diagnose BEFORE extending --allow-pins fleet-wide — the byte-gate makes a wrong guess free, but a
wrong PROCEDURE costs a sweep.

R22 clean-fleet 140/140 BYTE-IDENTICAL; tools-health OK; dedup 1886/0; 0 NON_MATCHING (G4).
Fleet: instr 81.3 -> 81.5% · distinct-code 69.0% · fn-count 89.41 -> 89.49%.
2026-07-26 20:23:28 -06:00
Drew T 52e6522f1b feat(phase-29): T0.5 — FREE-subset sweep, 3 families fully banked (389 members, +37,713 ins)
Re-derived the T0.1 decomposition post-harvest (it was stale by 395 banked members):
  zero-crack pool 76 fams / 347,892 ins  ->  73 fams / 290,850 ins   (the harvest came out of it)
  FREE (sweepable, non-jr, non-O0)       ->  58 fams / 167,368 ins

Swept the top FREE families through the gate_stage ladder (sample 8/8 first, then the rest):
389 banked; func_801463A0 / func_8017B490 / func_80156670 now stubbed in ZERO overlays.
R22 clean-fleet 140/140 BYTE-IDENTICAL; tools-health OK; dedup 1886/0; 0 NON_MATCHING (G4).
Fleet: instr 81.0 -> 81.3% (10,645,711 -> 10,683,424) · distinct-code 68.8 -> 69.0% ·
fn-count 89.30 -> 89.41%.

THE BLOCKER HAS MOVED — it is now OUR OWN PIN GUARD, not gcc and not declarations. Of the 1,083
candidate members in the top 8 FREE families, 680 (63%) were refused by the §42e pinned-exemplar
guard BEFORE any gate ran; only 403 reached staging. Two pieces of evidence say the guard may now be
over-conservative: SESSION-19 banked func_8017A4AC x134 WITH pins once the byte-gate arbitrated, and
Phase 27 dissolved the cc1 SIGABRT that motivated it (it was the extract_unit macro-drop, not a
compiler limit). Next probe: --allow-pins on a sample of 8, byte-gated.

MY OWN SCRIPT BUG, fixed + negative-controlled: the sweep loop globbed `.run/sweep/*/`, which also
matches gate_stage's INTERMEDIATE ladder dirs (-cn, -cn-cast, -cn-cast-rc, -s2in, -s2in-uni). Those
were called as if they were binaries -> 24 phantom "PARTIAL 0/1" lines inflating notbanked to 56 when
the true failure count was ZERO (stub counts 0/0/0 are the ground truth). Fixed by requiring
config/splat.<ov>.yaml to exist; negative control confirms phantoms are skipped and real binaries kept.
2026-07-26 18:32:09 -06:00
Drew T 57e63730dc docs(phase-29): SESSION-20 final checkpoint — 395 members harvested from two tooling bugs, ~56,200 ins, zero tokens 2026-07-26 16:49:29 -06:00
Drew T 015ebff536 feat(phase-29): §84 family fully harvested — 123/123 members, 0 failed (+29,280 ins, +27,840 distinct)
The derived-offset recompute swept the whole func_8013D53C family: 119 banked / 0 failed on top of
the 4 earlier; func_8013D53C is now stubbed in ZERO overlays. R22 clean-fleet 140/140 BYTE-IDENTICAL;
tools-health OK; dedup 1886/0; 0 NON_MATCHING (G4).

RECIPE (and it is NOT the return-axis recipe — sampling caught this):
  §84 derived-offset -> per-member literal recompute AND the gate_stage ladder.
  With the recompute alone the sample was 0/8; through the ladder it was 3/3, then 119/119.
  Had I reused the return-axis recipe (plain harvest_verify, which banked 272/272 there) I would
  have swept 123 members to zero banks and mis-concluded the fix was wrong. Probe-before-scale.

METRIC FINDING worth carrying: this harvest moved instr +29,280 AND distinct-code +27,840, while the
return-axis harvest moved instr +26,928 and distinct-code +0. §84-class members are byte-VARIANTS so
each is a new unique function; propagation-class members were already counted once via their shared
exemplar. => §84-class work moves the RE-COMPLETENESS number; propagation moves only the DISPLAY one.

Session fleet: 80.6 -> 81.0% instr · 68.2 -> 68.8% distinct-code · 89.18 -> 89.30% fn-count.
2026-07-26 16:48:56 -06:00
Drew T 9d9cd58028 feat(phase-29): T0.4 harvest — 272 return-axis members banked (+26,928 ins) + the §84 recompute in family_remap
THE §85 WIDEN PAID OFF AS PREDICTED. It is a ONE-TIME fleet edit, so once committed the
`conflicting types` blocker was gone for EVERY member of both families at once:
  - sample 8 first (probe-before-scale): 8/8 banked with PLAIN harvest_verify, no ladder needed
  - full sweep: 264 banked / 0 failed across 132 overlays; 10 skipped as not-stub
  - total 272 members ~= 27k ins, ZERO agent tokens
R22 clean-fleet 140/140 BYTE-IDENTICAL; dedup 1886/0; 0 NON_MATCHING (G4).
Fleet: instr 80.6 -> 80.8% (10,589,503 -> 10,616,431, +26,928) · fn-count 89.19 -> 89.26%.
distinct-code UNCHANGED at 68.3% — propagation moves the DISPLAY metric, not the RE-completeness
one (the SESSION-19 split, reconfirmed).

§84 RECOMPUTE now implemented in tools/family_remap.py (fix_derived_offsets), wired into all three
apply_remap call sites as a PRE-pass on the exemplar body (the literal is ambiguous as a substitution
token, so it cannot be a table entry):
    correct_literal = mapped(aliased_sym) - mapped(base_sym)
Verified by negative control: the hand-solved case recomputes 0x20 -> 0x18 exactly, and a site whose
target endpoint is NOT a mapped symbol is left byte-for-byte alone AND REPORTED in info
["derived_offsets"] (R32 — a silent skip is a defect, and a silent skip is how this bug survived).
2026-07-26 15:36:06 -06:00
Drew T c6b17f3056 docs(phase-29): SESSION-20 checkpoint — 4 causes diagnosed, 3 banked, the unlock identified but not yet harvested 2026-07-26 15:22:57 -06:00
Drew T 772b5c4e02 feat(phase-29): the RETURN-axis fleet widen — 2 more families unlocked (cookbook §85); 140/140
Continues the "see why and try again" chain. Diagnosed all 4 T0.2 failures to 4 DISTINCT causes:
  func_8013D53C  240x123  §84 derived-offset remap bug        -> BANKED (previous commit)
  func_8012CC88  105x137  §73/§30#2 RETURN-axis conflict      -> BANKED here
  func_8014D12C   93x137  §73/§30#2 RETURN-axis conflict      -> BANKED here
  func_80144090  154x136  LENGTH-DRIFT (+13 B, ~3 ins long)   -> genuine codegen, real work

THE FAILURE THAT TAUGHT THE FIX: widening only src/shared/engine_core.h banked the member in the
TARGET overlay and BROKE ov_SC01_077 (R22 139/140) — the source overlay carries its OWN local
`extern void func_X(...)` decls, so a shared-header-only widen puts them in direct conflict. The
per-binary gate passed while breaking a binary it never built (§63/§61: a T2 write set is only
provable by R22). A half-done axis is a guaranteed break, not a smaller win.

THE FIX: do the WHOLE axis — 3,668 `extern void` decl sites across 2,688 files widened to `s32`,
0 remaining (R32 completion assertion). Precondition verified first: 0 callers consume the return
value, so the widen is byte-neutral by construction. R22 clean-fleet 140/140 BYTE-IDENTICAL;
tools-health OK; dedup 1886/0; 0 NON_MATCHING (G4).

MY OWN ERROR, recorded (§85 trap): I first spot-checked ov_SC01_077 with
`make build | grep | head; echo rc=$?` and read rc=0 as success — that is the exit status of `head`,
not make, and the output had no BYTE-IDENTICAL line. I reported a false BYTE-IDENTICAL in the
interim. Assert on the SUCCESS STRING, never on $? after a pipe.

Fleet: instr 80.6% (10,589,503) · distinct-code 68.3% (3,846,656) · fn-count 89.19%.
2026-07-26 15:22:21 -06:00
Drew T b6bb04ecff feat(phase-29): CRACK THE PLUMBING FAILURE — the derived-offset remap bug (cookbook §84); func_8013D53C banked
Drew: "if it fails, see why and try again with new knowledge." It failed twice, then banked.

ROOT CAUSE, byte-proven: a family_remap member reached match_one MATCH (240 ins) and failed the
whole-binary gate by ONE BYTE. The exemplar carries a deliberate matching idiom — reach a symbol via
a DIFFERENT symbol plus a literal offset, so gcc cannot CSE the two %hi/%lo pairs:
    (*(S9*)&D_801DAA78) = *(S9*)(&D_801DA998 + 0x20);  /* same addr as &D_801DA9B8 */
family_remap substitutes the symbol NAMES correctly and leaves the literal 0x20 — but 0x20 is not a
constant of the algorithm, it is the DISTANCE BETWEEN TWO PER-OVERLAY SYMBOLS:
    exemplar 0x801DA998 + 0x20 = 0x801DA9B8  OK
    member   0x801A5778 + 0x20 = 0x801A5798  WRONG (real symbol 0x801A5790)
    member   0x801A5778 + 0x18 = 0x801A5790  correct
match_one MASKS HI16/LO16 so it is STRUCTURALLY BLIND to this — the §81 blindness in its DATA form.

TWO FIXES WERE EACH INDIVIDUALLY INSUFFICIENT: the byte fix alone re-failed as PLUMBING; the ladder
alone re-failed as DIFF. Together -> BANKED, R22 clean-fleet 140/140.

TWO LADDER CORRECTIONS (my own T0.2 error): bare harvest_verify is the LAST RUNG, not the ladder —
gate_stage runs canon_resident_calls -> cast_call_sites -> reconcile_tu -> ARITY -> sig_unify ->
harvest_verify, so T0.2's "8/8 PLUMBING" measured the UN-RECOVERED rate. And reconcile_decls.py is
RETIRED (R33, superseded by reconcile_tu): asking "what does the FLEET call this symbol?" is wrong by
construction in a loosely-typed engine (548 of its answers conflicted, rewriting 60 of 196 drafts) —
so Drew's suggested tool would have made it worse.

SCOPE, MEASURED (not over-generalised, §80): the idiom appears at only 5 sites corpus-wide — BUT one
gates a 123-member family (133 staged drafts all carry the un-recomputed +0x20 with different
per-overlay bases), so the mechanical fix is worth ~240 ins x 123 ~= 29,520 ins. It does NOT explain
the pool generally: func_80144090 / func_8012CC88 / func_8014D12C have ZERO derived-offset sites and
fail for a different, still-undiagnosed cause.

THE FIX IS MECHANICAL: correct_literal = mapped(aliased_sym) - mapped(base_sym). The remap already
holds both mappings, and the exemplar's own comment names the aliased symbol.

Also observed: the ARITY pre-pass left 40 TUs of caller-decl edits after a 0-bank run (same hygiene
bug as --normalize-self-decls, twice in one session) — reverted, both binaries byte-identical.
2026-07-26 13:47:06 -06:00
Drew T 1d44ce25f3 docs(phase-29): CORRECTION — the T0.1 "zero-crack pool" is NOT banked (4 of 1,073 members = 0.4%)
Drew asked whether the 347,892-ins pool was banked. It was not: 4 members (func_801463A0 x2,
func_8017B490 x2) = +396 instr-weighted / +194 distinct-code, ~1 part in 900 of the prediction.

FRAMING ERROR OWNED: I labelled the pool "FREE" and "the actionable shortcut". The h_seq
classification establishes "no DRAFTING needed" (exemplar matched, members structurally identical);
it does NOT establish "no WORK needed", which is how "FREE" reads. The probe located the work:
- 2 of the 8 top families (270 members) refused BEFORE any gate by the §42e pin guard => "FREE" did
  not even imply sweepable
- of the members that reached the gate, 67% hit declaration plumbing, 0% hit compiler walls
- both plumbing keys tried FAILED (--fix-def-sig regressed; --normalize-self-decls 0/123)

Pool status: real, structurally confirmed, not gcc-blocked, still LOCKED. To bank it: find the
working key (reconcile_decls.py — the DATA-symbol analog, and one failure text WAS a DATA symbol —
or canon_sig_reconcile v3.2, both untried), then re-sweep (mechanical, zero-token), and handle the
pin-refused families via --allow-pins.

Recorded rather than quietly superseded because this pool has been mis-called in BOTH directions
(P26 dead-off-a-broken-tool, P28 same family 89%). A prediction stated as a bank is how that happens.
2026-07-26 13:29:45 -06:00
Drew T adafeb13d6 feat(phase-29): T0.3b autopsy — 44% of the "near-miss backlog" are not near-misses; 315 are plumbing-blocked MATCHes
Recomputed every backlog residual from the bytes (1,699 rows, -j 12, zero agent tokens) through the
validated match_one path, deriving asm-subdir + -O0 from corpus.py. R34 cross-check PASSED (closeness
agreed with masked_diff.structured_diff on all 1,610 built rows, 0 classifier errors); 89 nobuild rows
REPORTED not dropped (R32).

BUCKETS: redraft 707 | structural 528 | integration 315 | permuter 57 | unknown 3.

1. HONESTY CORRECTION: 707 of 1,610 (44%) are class SIZE-MISMATCH — the stored best-draft is a
   PARTIAL, an incomplete attempt logged with a closeness score (the func_80183814 666-of-5,122
   shape). docs/backlog.md has been overstating readiness by ~44%. These route to a FRESH CRACK,
   not to a wall and not to the permuter.
2. ACTIONABLE: 315 entries are match_one MATCH *right now*, blocked only on the reconcile ladder —
   recomputing beat trusting the stored label because the tree moved since they were logged.
   ~108,959 gain-ins; top func_80174CB0 (16,482), func_801463A0 (13,534). §52b still applies: ~half
   of close=0 drafts fail the whole-binary gate, so these are CANDIDATES not banks.
3. The permuter bucket is 57/1,610 = 3.5% (Task-13B measured 7.7% and called targeting the problem).
   Extending the mutation set is CONFIRMED not the big lever — small, real, now bounded.
4. R34 again: 3 of 4 comparable labels DISAGREE with measurement — func_80140D68 / func_8012A328 /
   func_801549F8 recorded "schedule" but measure ADDRESSING -> cse. The grinder was aimed wrong.

CONVERGENCE: T0.2 (8/8 failures PLUMBING, 0 walls) and T0.3b (315 integration) independently point at
the SAME lever — the declaration/integration reconcile ladder, worth the 224,410-ins FREE pool AND
~108,959 backlog gain-ins. Two keys eliminated today; untried: canon_sig_reconcile v3.2 and
reconcile_decls.py (the DATA-symbol analog — one T0.2 failure text was a DATA symbol).
2026-07-26 13:25:29 -06:00
Drew T bce8cf3248 feat(phase-29): T0.2b + T0.3 — two levers eliminated for ~0 tokens; the backlog is current but 92% unclassified
T0.2b --normalize-self-decls: CLEAN NEGATIVE 0/123 on func_8013D53C (the family whose failure text
matches the flag's own documented fix). The FREE pool's blocker is a DIFFERENT declaration class than
either tested flag — --fix-def-sig REGRESSED it, --normalize-self-decls no-ops on it. Untested next
candidates: canon_sig_reconcile v3.2 and reconcile_decls (one failure text is a DATA symbol,
"conflicting types for D_800A651C", which neither tested flag targets).

TOOL HYGIENE DEFECT: the flag's transform is byte-neutral by construction, so the non-neutral
backstop never fires and it LEFT ALL 123 EDITS IN PLACE after banking nothing (123 files / 246+ /
246-). Byte-safe but a "git add -A" trap. Reverted; spot-rebuilt ov_SC01_000 + ov_SC07_010
BYTE-IDENTICAL. Candidate fix: on a 0-bank group restore the snapshot regardless of neutrality
(§61's law applied to the success path — "neutral" is not "wanted").

T0.3 triage: 1,622 live entries, P9 filter finds 0 stale (backlog.py's drop-now-matched works).
LEDGER DEFECT (R32): the addr field is null for 1,501/1,622 (93%) — the address survives only inside
the name field, so an addr-keyed consumer silently sees 7%. My own first pass fell into it (read 121,
reported off a 7% sample); name-derivation resolves 100%. Fix flagged, not applied mid-session.
Closeness: 333 at 0, 184 at 1-4, 589 at 5-20. 1,486/1,622 (92%) UNCLASSIFIED — matching
residual_class.py's own docstring. Highest-value next: run residual_class over the unlabelled set so
the close band routes to a lever instead of grinding undirected. Zero tokens.
2026-07-26 13:16:01 -06:00
Drew T 6676ed8406 chore(phase-29): preserve the func_80183814 round-1 deliverables (R20)
367k agent tokens of recon: the 99.3%-structural draft, the 15-row do-not-re-buy table WITH BASES
(§80), and the 6-file harness that regenerates the draft identically (edit the 72-ins template once
-> re-propagates to all 35 sites). .run/giants/*.{c,md,py} is the curated allowlist; round 2 starts
from these rather than re-deriving.
2026-07-26 13:12:28 -06:00
Drew T d3e6d6a702 feat(phase-29): T0.2 gate probe — the FREE pool is PLUMBING-blocked, not wall-blocked (4 banked)
MEASURED, not projected (R14): 12 gate attempts across ov_SC01_000 + ov_SC01_001, one draft per
build for clean attribution.

- 4 BANKED (func_8017B490 x2, func_801463A0 x2); 8 failed; **0 DIFF — zero compiler walls**
- all 8 failures are the §75a/def-side declaration class: `conflicting types for 'D_800A651C'`
  (DATA sym) and `conflicting types for 'func_8013D53C'` (the member's OWN def-side decl)
- => raw conversion 33%, but the ceiling is NOT 33%: the blocker is declaration plumbing, which
  this project has named tools for. Plumbing recovery has out-earned drafting in every phase that
  measured both (P19 fix_arity_callers, P28 dedup_extend 6,174 members / 95.6% from one new mode)

TWO CORRECTIONS TO MY OWN T0.1 POOL MATH, both downward:
- 2 of the 8 top "FREE" families were refused outright by the §42e pinned-exemplar guard (the 270
  skips) => "FREE" does NOT imply sweepable; pins are a third blocker the decomposition missed.
  Recoverable (--allow-pins; SESSION-19 banked pinned families x134), but I mis-labelled them
- n_templatable counts the matched exemplar, so every T0.1 family figure is ~1 member (~0.7%) high

NEGATIVE RESULT (§80, scoped to this base): --fix-def-sig REGRESSES this class — 0 banked and 2
PLUMBING became CC1-FAIL despite targeting the same error text. Do not re-buy without re-testing.

NAMED NEXT LEVER: family_sweep --normalize-self-decls, whose help text cites fixing "the conflicting
types for func_X that blocked 133/137 of func_801670E4" — exactly this failure. Gate-phase transform,
so it cannot run under --stage-only, and --limit caps FAMILIES not MEMBERS => needs a full ~123-member
family run. Highest-value outstanding probe, zero agent tokens.

R22 clean-fleet 140/140 BYTE-IDENTICAL; tools-health/dedup 1886/0; 0 NON_MATCHING (G4).
Fleet: instr 80.6% (10,589,065) · distinct-code 68.3% (3,846,416) · fn-count 89.19%.
2026-07-26 13:12:09 -06:00
Drew T 1fe4850136 feat(phase-29): T1.1 func_80183814 round 1 — 99.3% structural, named lever; cookbook §83
- VERIFIED INDEPENDENTLY (R14): match_one reproduces DIFF 5127 vs 5122, LENGTH-DRIFT/+5. Agent did
  not over-claim; tree untouched. Difflib-aligned truth: 36/5122 structural (99.3%), 17/21 cases
  EXACT, args+locals BYTE-EXACT at 216B
- §83a: on a LENGTH-DRIFT class match_one's mismatch count is NOT a progress signal — 4,622 and 36
  describe the same draft (index-wise comparison smears every index after the delta)
- §83b THE LEVER: the handoff's '35x repeated template' (which I passed on flagged UNVERIFIED) is
  TRUE and was the whole game — 2,625 of 5,122 ins (51%) from ONE parameterised 72-ins body. Three
  sub-levers: pointer walk (no strength-reduction under -G0), rand()%(u32) for divu, cast barrier vs combine
- §83c TRAP: the inherited 'dead local' pad[32] is gcc's OWN SPILL AREA — removing it made the locals
  area byte-exact. §83e: two 'pure allocation' residuals were a copy-pointer walk -> zero (§80 again)
- §83d THE STALL, cited: cse.c:8340 sizes the quantity table by WHOLE-FUNCTION pseudo count, so no
  per-case edit can move a function-global CSE fork. Next move = close the +5 (buys length parity AND
  perturbs max_reg), then re-run the do-not-re-buy table on the new base
- no pins in the deliverable (diagnostic-only, table row 15) — agent self-reported unprompted
- DECISION: round 2 QUEUED, not spent now — T0.2 (224,410-ins pool) outranks a ~0.04pp lever
2026-07-26 13:02:58 -06:00
Drew T 57ee715f3c feat(phase-29): T0.1 frontier survey re-run (138 ovs) — the family lever is ALIVE; a 224,410-ins zero-crack FREE pool
- verified the tool BEFORE trusting its scan (R35): load() correctly globs all 138 overlays, but the
  generated header hardcoded '134' -> fixed to derive from the same glob (a doc misreporting its own
  scope is the P28 img_path shape, one severity down)
- stale(07-23,134ov) -> fresh(07-26,138ov): fleet 88.5/79.0/68.4 -> 89.4/80.9/69.0%; families 2721 ->
  2688; substantial 558 -> 544; with-matched-sibling 74 -> 76. Structure STABLE => the P25 family
  reframe is NOT an artifact and P26's ~0% stays unsupported post-fix
- FINDING: 3,419 instances banked but only 85 distinct CLASSES fell -> recent yield was propagation,
  not new classes (SESSION-19's split, now fleet-wide)
- THE POOL: 76 zero-crack families (exemplar already matched) = 347,892 ins = 19.4% of remaining
  distinct code, decomposed by real blocker: FREE(PURE/non-jr/non-O0) 61 fams/224,410 ins = 12.5% of
  remaining; jr 13/57,311 (§81 chain); -O0 2/66,171 (known deferred build-infra, Arm A proved 9/9 bank)
- STILL A PREDICTION (R14/G3): T0.2 re-targeted from this data to measure the GATE conversion rate on
  8 members sampled across the FREE subset before any arithmetic scales
2026-07-26 12:46:34 -06:00
Drew T 4a8e8f7e36 docs(phase-29): the Drew-approved measured-order plan (T0 discovery-first) + the roadmap-to-100 gaps 2026-07-26 12:42:10 -06:00
Drew T 5873bd9cc8 docs(phase-29): record the absolute-include portability defect as a PhaseEnd carry item (Drew: handle later) 2026-07-26 12:19:08 -06:00
Drew T 661f5aa751 feat(phase-29): bank func_8017C730 x ov_SC03_013 (+1,061 ins) via the §81 carve chain
The SESSION-19 handoff's item 1, closed as specified — no drafting, no agent.

- §77 MINIMAL CLOSURE (519 lines, not the 2,993-line whole-file carry): 18 gte_* macros
  + 5 externs + the bandsetup static-inline helper -> match_one MATCH (1061 ins)
- §81 chain, each step byte-gated before the next: jr_isolate_all --only (2 fns/1 object)
  -> BYTE-IDENTICAL; jtbl_carve --func (single-table, 44-piece interleave) -> BYTE-IDENTICAL;
  harvest_verify --chunk 1 -> verified 1 / failed 0, 7042bc71 BYTE-IDENTICAL
- R22 clean-fleet 140/140 from a genuinely clean tree; tools-health OK; dedup 1886/0;
  0 NON_MATCHING (G4). FLEET distinct-code 3,845,161 -> 3,846,222 = 68.3% (+1,061, all
  distinct — a behemoth-class bank, not a propagation); instr-weighted 80.6%
- No §75a class spoke: the exemplar's ApplyMatrixSV(void*,void*,void*) canon fix was
  already carried, so the declarations were clean and it banked first try
- cookbook §77: the ladder CLOSED with all four rungs measured (-56 -> -34 ->
  MATCH-but-uncommittable -> MATCH+BANKED), plus a NEW subsection — the CANDIDATE gate
  and the REAL gate need DIFFERENT preambles (match_one compiles standalone, so a
  shared-type body's CC1-FAIL is a report about the PROBE, not the draft; the types
  header goes in a throwaway probe copy, never in the banked draft)
- FINDING, flagged not acted on (P5d): that shortcut already leaked an ABSOLUTE include
  path into 21 git-tracked files / 23 lines. All 21 verified semantically no-op (guarded
  engine_types.h via engine_core.h at line 2) => removal is byte-neutral, but cpp must
  still find the literal path, so those TUs cannot preprocess on any clone not at
  /home/musashi/bfm-decomp. Invisible to every byte-gate (R34's null-oracle shape, aimed
  at portability). Proposed as the next task.
2026-07-26 11:57:17 -06:00
Drew T 3b3728b19d docs(phase-29): checkpoint — an explicit START HERE NEXT SESSION block
Drew asked for the next-session recommendation to be logged. Added a ranked "START HERE" block
above the open-actions list:

1. func_8017C730 @ ov_SC03_013 FIRST (~30 min, +1,061 ins) -- the match ALREADY EXISTS; pure
   integration, no agent. Minimal preamble (bandsetup + 5 externs + 18 gte_* macros) then the §81
   carve chain. Explicitly warns NOT to re-carry the whole region file (standalone MATCH that
   fails the real gate -- the §77 corollary, measured).
2. THEN func_80183814 (5,122, the biggest left) with one Opus 5 agent @ xHigh, and an HONEST
   expectation reset: the family bonanza is over. All six behemoths banked this session were one
   renderer family with matched relatives bracketing them -- that is why 5 of 9 levers were
   readable rather than discoverable. func_80183814 has 0 fingerprint overlap and 37 callees; it
   is a different subsystem. Budget TWO passes (the func_8017BF14 shape, not the func_8017C954
   near-one-shot); a 99% round 1 is on-plan, not a stall.
3. Then func_8017D2DC (32 callees -- §71 IS usable) and func_8017DC1C (ZERO callees -- §71 CANNOT
   fire; use §79 DATA-symbol fingerprinting, shared syms only, and read a matched relative's
   fingerprint from its banked C since matched fns have no nonmatchings/*.s). A 0.00 from §71 on
   a leaf means "cannot answer", not "no relative" -- that error cost a whole agent brief today.

Also notes that behemoths were the ONLY thing that moved distinct-code this session
(+26,730 of +31,649), so they stay the lever if the queue holds.
2026-07-26 11:26:29 -06:00
Drew T cf570ec75c docs(phase-29): checkpoint — reflect §77's 5th variant now folded into the cookbook
The checkpoint said §77 'gains its 4th variant' and listed 4; the static-helper variant is the
5th and was only in the phase log until commit:1027 folded it into the cookbook proper. Both
mentions corrected so the checkpoint and the cookbook agree.
2026-07-26 11:19:29 -06:00
Drew T 2199c71192 docs(phase-29): §77 — add the 5th measured variant (static helper) + the minimal-closure corollary
Drew asked whether the cookbook was actually being updated per behemoth. It was (13 commits,
each paired with its bank), but the check found a REAL GAP: the last probe's two lessons went
into CURRENT_PHASE.md and a commit message and were never folded into §77 itself. So the
cookbook PREDICTED the static-helper variant (its closing line named it) without recording that
the prediction had since been CONFIRMED, and lacked the corollary entirely.

- VARIANT 5: a `static inline` helper, dropped by family_remap -> LENGTH-DRIFT/-56 with NO
  compile error at all. The nastiest variant precisely because it produces no diagnostic: the
  draft compiles clean and is simply ~56 instructions short, which reads as a codegen residual
  rather than a missing construct. Rule added: a NEGATIVE length drift with no compile error on a
  mechanically-remapped sibling means look for an uncarried static/inline helper BEFORE touching
  a lever.
- COROLLARY (measured, and it cost a bank): carry the MINIMAL TRANSITIVE CLOSURE of what the body
  references, not the whole file. Carrying the exemplar's entire 2,993-line region file produced
  a clean standalone match_one MATCH and then failed the whole-binary gate on PLUMBING --
  over-carrying trades a match_one failure for an in-TU collision. Measured ladder: -56 (nothing)
  -> -34 (helper + externs) -> MATCH-but-uncommittable (whole file); the minimal set is the only
  bankable point.
- Also recorded: the walk-back-to-previous-brace heuristic breaks on an ISOLATED REGION FILE
  (_jr_<addr>.c from jr_isolate_all), where the construct above the function IS the needed helper
  -- it returns a 1-line preamble. A preamble-carry tool needs a reference-closure rule, not a
  positional one.
2026-07-26 11:19:05 -06:00
Drew T 22798c2809 docs(phase-29): SESSION-19 FINAL CLOSING CHECKPOINT
Fresh session safe here. No background job running; tree clean; R22 clean-fleet 140/140 (12x);
tools-health OK; 0 NON_MATCHING; dedup 1886/0. HEAD at 38 commits this session.

FLEET 80.6% instr / distinct-code 3,845,161 = 68.2% / fn-count 89.18% (opened 80.0/67.7/89.02).
+31,649 distinct-code ins: 26,730 from SIX behemoths + 4,919 from the h_norm-remap pool. Every
propagation win contributed +0 to distinct-code -- the session's most actionable finding.

Records: the banked table (11 entries incl. six behemoths and the largest match in the project,
func_8017BF14 at 4,763 ins); ten cookbook entries §73-§82 all from measurement; the through-line
(almost every cap was our own tooling or my own use of it, including four of my own claims that
collapsed under checking); six ranked open actions with named causes; six method traps that
silently return 0.00 or a false MATCH; and the measured behemoth economics (two passes at
4,700+ ins, second cheaper; reading a matched relative beat the clever lever five times).
2026-07-25 23:28:37 -06:00
Drew T 9d04c0d6df docs(phase-29): func_8017C730 @ ov_SC03_013 — standalone MATCH, not banked (§77 4th variant)
- family_remap alone: -56 LENGTH-DRIFT. §77's own text predicted the cause verbatim (a "static"
  helper is a preamble construct the extractor does not carry): the exemplar uses
  "static inline void bandsetup(...)" -- the §82-oracle-1 inlined helper -- and none was carried.
  helper + its 5 externs: -56 -> -34. Whole 2,993-line region file as preamble: MATCH (1061 ins).
- BUT the full-file carry is wrong for BANKING (my error): right for a standalone match_one
  compile, collides wholesale in the real TU. Gate -> PLUMBING, reported as "conflicting types
  for memcpy" = the §58 red-herring; the real cause needs a hand-splice + real cc1 stderr.
- NAMED NEXT STEP: minimal preamble = bandsetup + its 5 externs + the 18 gte_* macros from that
  region file, then the §81 carve chain (this sibling is ALSO a jr function). Draft preserved at
  .run/giants/s19_func_8017C730_SC03_013_nearmiss.c
- §77 gains its 4th measured variant (static helper) + a NEW COROLLARY: the right carry is the
  MINIMAL CLOSURE of what the body references, not the whole file -- over-carrying trades a
  match_one failure for an in-TU collision. Also: s19_remap_tu.py's walk-back-to-previous-brace
  heuristic breaks on an isolated region file, where the preceding construct IS the needed helper.
2026-07-25 23:27:33 -06:00
Drew T 109ce6a2c3 feat(phase-29): BEHEMOTH #6 func_8017C730 BANKED (1,061 ins) + §82 two source-shape oracles
- CRACKED at xHigh and VERIFIED INDEPENDENTLY: match_one MATCH (1061 ins); agent re-matched 3x
  from clean runs (100% register-masked AND register-kept, all 10 regions, frame 0x270 exact).
  §81 carve chain clean first try: jr_isolate_all --only -> byte-identical cacaf7c2 -> jtbl_carve
  (43-piece set) -> byte-identical -> bank -> R22 clean-fleet 140/140, tools-health OK.
  instr 80.6%; distinct-code 3,844,100 -> 3,845,161.
- WHAT IT IS: the matched base func_8017CA80 + camera height-band cull + distance-driven CLUT
  fade. func_8004974C (TransposeMatrix) sits in a 36-ins prologue deriving a Y band; the
  part-level `lim >= g.otz` cull is GONE; flat arms gain an `sz < lim` near-plane cull. The
  base+one-extra-callee fingerprint predicted this exactly.
- §82 ORACLE 1 -- A DUPLICATED `addiu $aN,$sp,K` ACROSS A `jal` MEANS THE BLOCK WAS INLINED.
  `&X` on any non-first local always creates a pseudo and CSE always merges two of them
  (expr.c:6260 ADDR_EXPR -> force_operand(..., NULL); exception: virtual-stack-vars offset 0).
  So the same stack address re-materialised at two sites separated by a jal means CSE was
  PREVENTED from merging => not the same function body. 17 non-inline spellings failed; a
  `static inline` helper reproduced the prologue BYTE-FOR-BYTE first try. Reusable probe: scan
  the ~1,200 built objects for that signature in NON-INCLUDE_ASM functions.
- §82 ORACLE 2 -- SCALAR vs AGGREGATE DECIDES *WHEN* A STACK SLOT IS ALLOCATED: lazily at first
  `&` for a scalar, AT DECLARATION for an aggregate. Six GTE result words had to be six separate
  longs, not a struct, or they don't land after the inlined helper's temps and the frame isn't
  0x270. Second-order: it also flips MEM_IN_STRUCT_P (§30's /s) -- with one word a fixed-address
  scalar, ((PolyF3*)pkt)->rgbc stops aliasing it, so a store needed respelling to keep the
  target's nop. A scalar-vs-struct choice is simultaneously a frame-layout AND an aliasing
  decision.
- BANKING FOOTNOTE (§75a class A): first bank rejected `conflicting types for ApplyMatrixSV` --
  draft (MATRIX2*, SVECTOR2*, SVECTOR2*) vs the TU/fleet canon (void*, void*, void*), 2,286 of
  2,835 sites. Conforming the decl is byte-neutral and banked first try. On a jr function expect
  BOTH gates to speak: the carve chain answers the jump table, §75a answers the declarations.
- Also reproduced: §78 (reuse a busy variable), §80(i) (a lever went -8 -> exactly neutral as the
  base moved), §72 (a register pin made it worse).
- AGENT'S OWN CAVEAT, recorded not hidden: one zero-byte __asm__ keeps a vestigial `mnc = hmid`
  alive that flow.c would delete (costing 10 ins + the 0x130 spill slot). Emits nothing, compile
  is 1061 exact, but it is a documented stand-in -- 12 natural spellings measured, all DCE'd.
2026-07-25 23:24:11 -06:00