Commit Graph

3478 Commits

Author SHA1 Message Date
Drew T d349bc053a feat(decomp): parallel gate — 9 fns across 8 binaries (12 workers)
ov_SC03_089    func_80181F9C
  ov_SC05_001    func_801809B8
  ov_SC05_008    func_80180E84
  ov_SC03_094    func_801814F4
  ov_SC03_118    func_801832E0
  ov_SC06_025    func_80182170
  ov_SC03_105    func_8017FAE8 func_80185680
  ov_SC07_002    func_80185FB0
2026-09-01 10:47:13 -06:00
Drew T f95b895909 docs(phase-31): S69 FINAL checkpoint — 44 MATCH of 84 agents, the §378 lever, four self-inflicted defects measured 2026-09-01 10:45:07 -06:00
Drew T c076f9016d feat(decomp): parallel gate — 10 fns across 8 binaries (12 workers)
md_MAIN_045    func_800CCB28
  md_MAIN_033    func_800CB4EC
  ov_SC05_000    func_8017E1E8
  ov_SC03_099    func_8017D2AC
  ov_SC02_005    func_8017ECAC
  ov_SC02_011    func_80182564 func_80182FF0
  md_MAIN_003    func_800D0100 func_800D0A7C
  ov_SC06_025    func_8017E48C
2026-09-01 10:39:02 -06:00
Drew T 4949acdf31 revert(integration): drop the §378 casts for the 25 chain candidates that did not bank
cast_self_callers casts a function's call sites in PREPARATION for banking it.
When the draft then fails, the cast must come back out — the tool journals every
edit for exactly that, and I did not run the undo.

The cost was concrete: the leftover cast on func_8017F8B8 made ov_SC07_000 fail
to COMPILE at HEAD, so every subsequent gate verdict on that binary was measuring
a broken baseline rather than the draft. Two drafting agents reported it as
BASELINE-RED before I noticed.

24 casts reverted across 11 files in 7 binaries; all 7 rebuild green. This is the
discipline recover_integration already documents ('REVERTS the caller edits for
anything that doesn't bank') applied to the new tool.
2026-09-01 10:35:29 -06:00
Drew T d8d4f8ac6f revert(decomp): ov_SC04_011/func_80182DCC — a FALSE BANK committed under --no-r22
Reverts commit:3472. The binary was RED at HEAD: sha1 9c94d36a vs expected
8bc09c42. The gate that banked it ran with --r22 disabled because 24 drafting
agents were live (R22 does make clean, which deletes asm/ under them), so the
one check that would have caught it was the one I had turned off.

The revert must carry the CARVE STATE, not just the C: the bank moved
JTBL_PADS 0,0,4,4 -> 0,0,4,4,4 plus the splat yaml, and a src-only revert left
4 tables against 5 pad specs ('table-count drift vs the carve'). Reverting the
whole commit restores BYTE-IDENTICAL.

Found only because two drafting agents independently reported their target's
binary as BASELINE-RED and I checked their claim against the bytes.
2026-09-01 10:35:11 -06:00
Drew T 6a96a62f27 feat(decomp): main in-tree gate — 2 fn(s)
main  func_8001DA34
  main  func_80021D38
2026-09-01 04:53:15 -06:00
Drew T c794091725 feat(decomp): main in-tree gate — 8 fn(s)
main  func_80020A28
  main  func_80021284
  main  func_800221A8
  main  func_8002374C
  main  func_80026514
  main  func_8002D904
  main  func_800377D8
  main  func_8003DC90
2026-09-01 04:51:47 -06:00
Drew T 02d0461d46 feat(decomp): parallel gate — 1 fns across 1 binaries (12 workers)
ov_SC04_011    func_80182DCC
2026-09-01 04:02:41 -06:00
Drew T cb7a28551c chore(integration): §378 chain applied to 25 fresh closeness-0 candidates (pre-gate) 2026-09-01 03:57:19 -06:00
Drew T 05195783b9 docs: mark the triage-ladder spec BUILT with its three corrections; accelerator #16
next-session-triage-ladder.md was still written as a to-build spec. It now leads
with the shipped status, the acceptance numbers, and the three things the spec got
wrong (the '32 free banks' were 0/28; the autodecl arm is worse in-tree than the
raw draft; PRE and POST cannot be the same pass because residual_rules_b needs a
draft), plus the one found by building it — never classify on a moving tree.

accelerators #16: a 'verified, just bank it' claim must name the compilation it
survived. Day-one kit material for a new decomp: any per-function oracle compiles
in isolation, every real bank compiles in context.
2026-09-01 03:49:21 -06:00
Drew T 6bc674ae45 docs(phase-31): S69 checkpoint — 8 banked from the '32 free banks' class, the §378 lever, the triage ladder acceptance-green 2026-09-01 00:11:20 -06:00
Drew T ae7c68a89a fix(gate): carry the per-function verdicts out of the worktree, and gate the in-tree retry on them
The first version of this parsed 'failed by class:' from the worker's stdout and
was INERT: the worker is gate_stage, which never prints that line (harvest_verify
does, one level down). classes came back empty for all 17 binaries of a batch and
the retry gate that consumed it fired ZERO times — a field that is always empty
makes its consumer a silent no-op (R54). Verified the claim only after re-reading
the log; correcting it here.

Now parallel_gate copies harvest_verify's <stem>.classified.txt out of the
worktree before teardown (it lives in the worktree's own .run/, which is not
symlinked and dies with it) and derives the class summary from those rows. That
also PRESERVES the verdict layer, which until now survived only as a side effect
of gater_lane re-running the whole binary in-tree afterwards (R47).

gater_lane judges the retry on the rows: a class with no per-function diagnostic
is the blind-worktree signature; anything cc1 named is a real compile error and
the serial rebuild would only reproduce it.

Verified live on ov_SC07_000: 'NOT retrying in-tree' fired, and the verdict row
landed at .run/gate_lane/ov_SC07_000.pgate.classified.txt.
2026-09-01 00:05:25 -06:00
Drew T b2d73108e2 feat(decomp): parallel gate — 1 fns across 1 binaries (16 workers)
ov_SC03_029    func_801847FC
2026-09-01 00:01:54 -06:00
Drew T ef504ffc32 chore(integration): decl-sync for the 21 remaining §376 candidates (pre-gate) 2026-08-31 23:54:17 -06:00
Drew T 1dd15eda32 feat(triage): the triage ladder — built, wired, and acceptance-green
tools/triage_ladder.py — the zero-token pre-agent pass, split PRE (target-side:
BANKED/WALL-332/PARKED, no build) from POST (residual_rules_b, needs a draft).
--escalate refuses a walled or banked target; --acceptance is the R39/R32 harness.
Refuses on a non-quiescent tree: a merging gate makes the stub oracle wrong in
both directions (measured, ov_SC01_004:func_8017EB30).

Acceptance, on the whole corpus: false-skip 0/1367 open stubs, recall 426/426
matched, wall tier fires on exactly the 10 enumerated walls (0 extra, 0 missing).
The first wall control asked for evidence that CANNOT exist — it scanned banked
functions' .s, which splat never writes — and printed '0 scanned / 0 tripped',
indistinguishable from a pass. The R32 empty-denominator assertion caught it on
its first run; replaced with a two-sided sweep over all open stubs.

tools/cast_self_callers.py — the §378 lever + --sync-decls for the narrow-param
case C89 forbids no-proto from reaching (§378a).

Wiring: wave_args drops walled/parked targets at draw time via pre_classify (one
implementation, R33); escalate_fable.js refuses any target without triage:'DRAFT'.

Tool fixes found by measurement:
* fix_arity_callers was blind to main entirely (globbed src/main/main*.c; main is
  src/*.c) — reported success over an empty file set through three gates. Now
  refuses when --binary selects no files.
* parallel_gate records each worker's 'failed by class' line (was truncated out of
  the 200-char tail); gater_lane retries in-tree ONLY on the diagnostic-free
  blind-worktree signature — S69 ran 22 serial retries against real cc1 errors.

docs: cookbook §376/§377/§378 (index 1033), SETUP.md, wave-playbook §4b.
2026-08-31 23:53:50 -06:00
Drew T c6f6f668ba feat(decomp): main/func_80036D58 — the narrow-param decl-sync completes the §378 chain
A no-proto decl is ILLEGAL against a definition whose parameter is affected by
the default argument promotions (s16 here): C89 requires the parameter types be
promotion-stable when one declaration has no prototype. So fix_arity_callers'
--any-proto cannot reach this case (it skips it as 'narrow-param').

With the call sites already cast (§378) the decls emit no code, so syncing them
to the draft's exact signature is byte-neutral: 3 decls in src/800.c rewritten to
extern void func_80036D58(s16). Byte-identical, main.
2026-08-31 23:51:19 -06:00
Drew T 779f3e2a19 feat(decomp): parallel gate — 5 fns across 5 binaries (16 workers)
ov_SC04_019    func_8017E7CC
  ov_SC01_080    func_8017D72C
  ov_SC01_077    func_80185E68
  ov_SC05_011    func_8017D818
  ov_SC07_000    func_8017F098
2026-08-31 23:49:37 -06:00
Drew T 72d3f61dcd chore(integration): self-caller casts for the 27 remaining §376 candidates (pre-gate) 2026-08-31 23:36:59 -06:00
Drew T 53fad0d031 feat(decomp): ov_SC04_010/func_8017D6CC — the self-caller cast lever, byte-verified
The §376 class's real blocker: after fix_arity_callers no-protos the conflicting
forward decl, the draft's definition becomes the prototype in scope and the TU's
own call site fails with 'too few arguments'. Casting THAT call site to a 0-arg
function pointer is byte-neutral (gcc-2.7.2 folds a cast of a known symbol back
to a direct jal, §20) and banks the function.
2026-08-31 23:18:43 -06:00
Drew T 6cca1e3ea0 feat(decomp): main in-tree gate — 0 fn(s) 2026-08-31 23:00:25 -06:00
Drew T a47981a494 docs(phase-31): S68 FINAL-2 checkpoint — 39 closed (453 -> 414), fleet 213/213, harvest drained 2026-08-31 22:33:02 -06:00
Drew T a22d5646f6 docs(cookbook): §374/§375 — two more bounds on the register-pin lever
§374 a register __asm__($30) reservation is NOT honoured by move_movables under
pressure, and the corruption is SILENT (the build succeeds) -- audit the raw
objdump register uses before trusting a pinned build that compiles.
§375 an $a0-$a3 pin used LATE relocates an EARLIER outgoing-call use of that same
register ~26 slots early, identically across three structural variants; argument
pins are not local the way $s pins are.

With §368 and §373 these now form a usable four-way rule for when a pin helps,
when it fights the allocator, when it is ignored entirely, and when it acts at a
distance.
2026-08-31 22:26:02 -06:00
Drew T 5c317dbe5f docs: wire the S68 tools into SETUP.md (R21) and the playbook (when to use them)
A tool nobody knows about is invisible work. Audit found neighbor_ref (built an
hour ago), residual_rules, lane_inflight and r22_verify in NEITHER doc, and
wall_sweep in the playbook but not the inventory.

SETUP.md gains a tooling-inventory row for all five with what each is FOR.

wave-playbook gains §2b: run neighbor_ref for EVERY card, placed right after the
seed_ref step because it answers the weaker and far more common question ('which
matched function should this agent READ?') that seed_ref structurally cannot. It
carries the measurement that justifies it -- a ~20x token swing on that single
variable -- and the failure it prevents: func_8017BEBC's card said 'no banked twin'
while a matched 755-instruction near-twin sat 3,700 lines up IN ITS OWN FILE.

Also states the two honest limits: an opt-level mismatch is PENALISED not merely
ranked low (§116 -- an -O2 example misleads an -O0 target), and a neighbour is a
worked example to READ, never a body to copy (§168 law 1, cousin-remap 0/26).
2026-08-31 22:25:43 -06:00
Drew T 02592300df feat(triage): the residual-classifier head-to-head — both implementations, kept
residual_rules.py (mine) and residual_rules_b.py (an independent Fable build,
forbidden from reading mine). Committed because the EXPERIMENT is the artifact:

                          mine    b
  classified            85/113  113/113
  errored                   28        0
  any rule fired           18%      88%
  certain/high              1%      63%
  pure residual-SHAPE      ~1%     1.8%

The last row is the finding. Two independent implementations CONVERGED at ~1-2% on
pure cookbook-shape rules, so that tier's ceiling is the POPULATION, not the code:
surgical single-mechanism residuals live at the END of escalations, not in
first-pass wave output. The shape tier belongs in escalation loops; the ladder's
value is everything above it (banked / wall / compile / autodecl / integration).

b also diagnosed my 28 errors exactly: they are functions banked DURING S68 after
the eval set was drawn, so corpus.stubs() no longer contains them and my resolver
raised IndexError on every one. It detects the same condition via corpus.matched()
and calls it ALREADY-BANKED — stale card, spend zero tokens.

Two things b did better that are worth copying: it never parsed disassembly TEXT
(every decision decodes the raw 32-bit word, so the two-disassembler formatting
disagreement that cost me two bugs never touched it), and it REMOVED three of its
own false-positive mechanisms found on held-out cases, all score-reducing, and
disclosed them.

Spec for finishing the ladder: docs/next-session-triage-ladder.md
2026-08-31 22:24:56 -06:00
Drew T 2daacec346 feat(decomp): ov_SC03_121 in-tree retry after a blind worktree gate 2026-08-31 22:22:45 -06:00
Drew T e32fdd47dd feat(decomp): parallel gate — 2 fns across 2 binaries (4 workers)
md_MAIN_011    func_800D1254
  ov_SC05_017    func_80189240
2026-08-31 22:22:43 -06:00
Drew T da32350873 feat(decomp): main in-tree gate — 1 fn(s)
main  func_80013B64
2026-08-31 22:18:35 -06:00
Drew T ba394da75a docs: spec the TRIAGE LADDER for next session, with its 32 free banks listed
The companion to neighbor_ref.py (built). Written so a session with none of S68's
context can finish it: what it is, where it sits in the pipeline, that
tools/residual_rules_b.py is already ~70% of it with its held-out scores, the
asymmetric failure mode that makes the R39 acceptance test mandatory (a false
'skip - already banked' silently drops a bankable function), and the falsifiable
predictions.

Includes the immediate payoff, verified by me rather than taken on trust: 10 drafts
are byte-MATCHES once an extern derived from the target's own .s is added (10 of 10
confirmed with match_one, closeness 0, patched drafts at .run/rules_b/*/autodecl.c),
plus 22 more that already match standalone and were misfiled as failures.

Also records the experiment's most important number: two INDEPENDENT
implementations converged at ~1-2% on pure cookbook-shape rules, so that tier's
ceiling is the POPULATION (surgical residuals live at the end of escalations, not
in first-pass wave output) and it belongs in escalation loops, not wave triage.
2026-08-31 22:09:11 -06:00
Drew T e936556ff0 feat(cards): neighbor_ref.py — retrieve MATCHED functions as worked examples, ranked
seed_ref answers 'is there a byte-identical twin?'. This answers the weaker but far
more common question: 'which matched function should I READ before drafting this?'

S68 measured a ~20x swing on that variable. Every cheapest large match came from an
agent finding a matched neighbour (func_800D1254 555 ins/72k; func_800D12D0 657
ins/122k FIRST COMPILE; func_8018AD9C 397 ins/87k; func_8017BEBC 753 ins/177k),
while main functions with no neighbour ran 200-350k for ~80 instructions.

THE FAILURE THAT MOTIVATED IT: func_8017BEBC's card asserted 'no banked twin' while
a MATCHED 755-instruction near-twin sat 3,700 lines up IN ITS OWN FILE, its header
comment documenting the four levers the target needed. seed_ref joins on signature
hashes and the two bodies are not hash-identical, so it was structurally invisible.
Three other S68 agents found their unlock the same way, unprompted.

Ranks on what actually worked, not intuition: SAME TU first (solved against the same
decl environment, and its header records the levers), then same binary, then shape
(li-normalised skeleton / call-sequence hash / reloc-kind sequence / CFG counts /
opcode-histogram cosine, all precomputed in .run/feat.*.jsonl), then instruction-
count proximity, with a HARD PENALTY for opt-level mismatch (§116 — an -O2 example
actively misleads an -O0 target). It surfaces the neighbour's HEADER COMMENT, which
is the payload agents actually consumed.

Explicitly NOT a remap claim: §168 law 1 measured cousins at 0/26. A neighbour is a
worked example to READ; seed_ref remains the tool for the byte-identical case.

Validated against ground truth: for main/func_80024054 (265k tokens, ended NEAR 32)
the top three neighbours are func_8003A0E4, func_800242D0 and func_800241C0 -- all
three MATCHED THIS SESSION, same TU, same call sequence, same reloc-kind sequence.
src/800.c holds 657 matched functions and the card offered none of them.

Bug fixed en route, and it is a repeat: the atlas writes addresses as hex STRINGS
while corpus.Stub.addr is an int. T4's verifier already lost rows to exactly this
string-vs-int mismatch (the R32 silent-no-op class). Normalised in _addr().
2026-08-31 22:08:34 -06:00
Drew T 7392fcf7c3 docs: accelerators #15 (the differential-oracle harness) + the generic decomp package thesis
#15 — the tool worth building FIRST in any decomp, because it works at 0% and
compounds: run every question down TWO independent paths on a schedule and fail on
disagreement. Ten-plus S68 blockers had one shape — a tool computing a TRUE number
about a NARROWER world than we believed it covered — and EVERY one was caught by
two measurements disagreeing, never by review. R32/R34/R40 already say this and
were not enough: they are rules applied by whoever writes the tool, and in S68 I
wrote R34's warning into one docstring and rebuilt the exact defect it warns about
an hour later in another file.

Includes Drew's scheduling half, which this project only ever did by accident: the
widening is PERIODIC. Tooling is not wrong when written, it goes STALE as new
idioms reveal populations it cannot see. At every phase close ask 'which scanner's
denominator just got wider?' — that question converts new knowledge into free
banks. The §332 sweep is the worked example: one review, 10 fns / 1,027 ins
reclassified, one in-flight escalation stopped mid-spend.

generic-decomp-package.md — what a NEW decomp inherits on day one and does BEFORE
cracking: mine the COMPILER SOURCE and sibling projects for idioms (this project's
best late idioms came from reading gcc-2.7.2's own passes and needed no matched
function at all — week-1 work done in month N), port the families/twins/dedup/carve
layer first, then the oracle harness, and only then crack. With the honest caveat
that tooling-first makes the cheap half free and does NOT shrink the hard tail.
2026-08-31 22:06:20 -06:00
Drew T d8b7fb4e49 docs(playbook): §1b — the §332 walls are now ENUMERATED, wire the sweep into the draw
tools/wall_sweep.py --emit-exclude feeds draw_waves --exclude directly. 10
functions / 1,027 instructions over 1,378 open-stub .s files, against §332's
'6 fleet-wide' with two named.

Recorded what it caught immediately: main/func_8005D734 was already escalated to
Fable at closeness 8 when the sweep listed it, and its site is exactly the residual
that agent described -- stopped. Filtering the live queue dropped two more before
they were drafted (func_8005D9C4 133 ins, func_8005F450 159 ins).

The §188 epilogue half is still NOT built and is now named as such rather than left
implied: its detector exists inside oracle_reorder.py and has never been run as a
sweep.
2026-08-31 20:02:56 -06:00
Drew T cd03c67652 feat(walls): wall_sweep.py — ENUMERATE the §332 delay-slot macro walls, 10 fns / 1,027 ins
§332 states the class is "6 FUNCTIONS FLEET-WIDE, NONE BANKABLE FROM C" and names
TWO of them. §332a then says, correctly, "Filter before drafting" -- but a filter
needs the LIST, and the rest were never written down, so the draw kept handing them
to agents. A COUNT WITHOUT AN ENUMERATION CANNOT DRIVE A FILTER.

Measured cost of that gap today: main/func_80061FA8 -- a fable agent produced C
that oracle_reorder proves BYTE-CORRECT (0 diffs / 103 ins) and that the pinned
triple still cannot emit. 92,684 tokens to rediscover a documented class. Plus
main/func_8005F0C8 at 289k tokens, the same story via §188.

The sweep is now the list: 10 functions, 1,027 instructions, derived from 1,378
open-stub .s files with 0 unreadable.

TWO DEFECTS IN MY OWN DETECTOR, both caught by demanding it reproduce members I
already knew -- the same rule I have been applying to every other tool today:
  * It returned a confident 0 across all 1,378 files. The .s lines carry a
    slash-star offset/addr/bytes star-slash comment prefix, and my regex anchored
    the mnemonic at start-of-line, so it matched NOTHING. A sweep returning 0 must
    prove it CAN return non-zero before the 0 means anything.
  * Widened, it found 6 but MISSED func_8005DBD8, which §332a names. Its delay slot
    holds a store through %lo -- the tail of a lui-%hi / store-%lo MACRO, not a la.
    Same mechanism, different mnemonic: ANY %lo in a delay slot is the second half
    of an assembler macro that gcc emits as one atomic insn, so C can never put it
    there.

IT PAID FOR ITSELF WITHIN MINUTES: main/func_8005D734 is in the list, and I had
escalated it to Fable at closeness 8 twenty minutes earlier. The sweep's site for
it is EXACTLY the residual that agent described. That escalation could never
succeed and has been stopped.

Ledger: .run/S68_walls_332.txt (--emit-exclude form, ready for draw_waves).
2026-08-31 19:59:27 -06:00
Drew T 0e50fbc84f docs(playbook): §1b — the walls ledger is always incomplete, and each gap costs an agent run
A wall nobody has met yet is invisible to the draw filter, so new ones are found by
PAYING an agent to hit one. Twice in S68 on main: func_8005E228 (a full run, then
banked the §265 verbatim-asm way) and func_8005F0C8 (289k tokens to reach closeness
36 with the residual confirmed as §188's epilogue by oracle_reorder.py).

Neither is a model failure. An agent handed a wall returns a NEAR with an
unexplainable tail, which looks exactly like a hard function -- and an escalation
cannot beat the toolchain, so escalating one is guaranteed waste.

The fix is named rather than left as folklore: run the §188 epilogue-shape detector
over every open stub AT DRAW TIME. It already exists inside oracle_reorder.py and
has never been run as a sweep. Until then, treat 'NEAR with an epilogue-shaped
tail' as a walls candidate and check it with the oracle BEFORE escalating.
2026-08-31 19:54:36 -06:00
Drew T e2f64a7c62 feat(o0): md_MAIN_003 second carve — func_800D12D0 (657 ins) banked as real -O0 C
MY HYPOTHESIS WAS WRONG AND THE AGENT SAID SO. I predicted the ownership oracle
was blind to verbatim-asm owners. It is not. 0x800cedf8 is the §154-A LEADING
RODATA ISLAND (the module-id header + jtbl/ptr table at segment offset 0), which
rodata_carves already exempts via 'off == 0 and sub == ov'. The S68 first carve
legitimately renamed that subseg to md_MAIN_003_jr_800D12D0 (§371: spimdisasm
rodata migration is same-subseg-only), so the 'sub == ov' conjunct stopped firing
and offset 0 leaked in as a 'carve'. The island has NO single owner BY DESIGN --
which is why the exemption exists -- so widening owner kinds could never have
restored 1:1.

The fix drops one conjunct: offset 0 alone is the honest structural key, because a
carve is a table LIFTED OUT OF THE DATA TAIL and can never sit at the segment's own
offset 0. Verified across all 213 configs: every offset-0 .rodata piece is an md_*
leading island; ov_*/main have none. The R32 hard abort is UNTOUCHED -- this widens
the recognised-island set, it does not soften the refusal.

NEGATIVE CONTROL (R39) over all 184 binaries with .rodata pieces: OK 182 -> 183,
ABORT 2 -> 1, and exactly ONE verdict moved (md_MAIN_003). The remaining us.exe
abort (UNOWNED 0x80073238, the LZSS jtbl carve whose owner LzssDecodeSector does
not live under src/us.exe/*.c) is byte-identical before and after -- PRE-EXISTING,
not newly hidden, and logged rather than silently absorbed.

Carve byte-neutral and bank byte-identical, both re-verified by my own rebuild:
sha1 dd1b32ecf1103c6f7cf1943d25546a3046e17b14 == config/check.md_MAIN_003.sha.
md_MAIN_003 12 -> 11 stubs.

THREE o0_subsplit GAPS surfaced and hand-finished, and they must be fixed before
the remaining 7 -O0 stubs here are carved: build_new_config drops a cut at the
object start so region 0 kept the -O2 name while the tool PRINTED the _o0 name;
parse_overlay_c folds pre-anchor text into the FOLLOWING anchor, so a verbatim body
inside region 0 attached to region 1; and the island .rodata piece needs repointing
to whichever TU ends up holding its emitters.
2026-08-31 19:46:10 -06:00
Drew T 3f9430e569 feat(gater): retry IN-TREE when the worktree gate FAILS every draft and banks none
The worktree gate is silently unable to build some binaries and reports it as
'failed', which is indistinguishable from bad drafts. Measured twice this session:
main (its psyq_integrate link inputs are not staged) and ov_SC06_010 (root cause
still unknown) each reported 'banked 0' while the SAME drafts banked byte-identical
through harvest_verify in the main tree. In the ov_SC06_010 case that was 1,191
instructions I re-gated twice and nearly wrote off as bad drafts.

Now any binary whose worker failed EVERY draft and banked none gets one in-tree
retry. A genuinely bad draft fails there too and costs one build; a harness-blind
binary banks. A real NEAR is left alone -- only all-FAILED is treated as suspicious.
The whole-binary SHA remains the sole arbiter (G3/P9), so this cannot launder a
wrong draft into the tree; it only stops the harness misattributing its own
blindness to the model.
2026-08-31 19:28:36 -06:00
Drew T 3897886acb feat(decomp): ov_SC06_010 func_8017E764 (438) + func_8017BEBC (753) — 1,191 ins
Both banked in the MAIN TREE after parallel_gate's worktree reported 'banked 0'
TWICE. The drafts were never the problem:
  * baseline ov_SC06_010 builds byte-identical (05c2d8c4 == check.sha) -- so the
    binary was not red the way main was;
  * both drafts probe MATCH in their REAL TU via rtu_match/blocker_probe;
  * harvest_verify in the main tree: verified 2 / failed 0, final SHA
    05c2d8c46363483a1dce434ee745957b76d0530e BYTE-IDENTICAL.

So the worktree gate has a second binary it cannot handle, and it reports that as
'banked 0' -- indistinguishable from a wave of bad drafts, and the reason I re-ran
this gate twice before doubting the harness instead of the model. Same shape as the
main worktree defect: the failure is SILENT and its symptom points at the wrong
suspect. Root cause not yet identified for ov_SC06_010 specifically; recorded here
rather than left as folklore.

func_8017BEBC's unlock is worth keeping: its card said 'no banked twin', but a
MATCHED 755-instruction near-twin sat in the DESTINATION FILE ITSELF 3,700 lines up
(func_8017CAD4), and its header comment documented the four levers the target
needed. seed_ref joins on signature hashes, so a structurally-similar
non-hash-identical neighbour is invisible to it -- and a same-TU neighbour is
exactly where the richest context lives.
2026-08-31 19:27:37 -06:00
Drew T c42b3dbc35 docs(cookbook): §373 — the dead-reset cse-breaker, the anti-dep pin, and pri(asm)=1
From the fable escalation that closed ov_SC06_010/func_8017E764 (8 -> 0, BOTH
clusters), and it is three findings not one:

1. DEAD-RESET CSE-BREAKER. To stop cse merging two computations of the same
   expression WITHOUT an asm's scheduling footprint: name it, use it, then
   'p = 0;' immediately after. cse invalidates at the second set and flow deletes
   the dead set BEFORE sched1 -- zero bytes, zero LUID disturbance. An empty-asm
   re-tie by contrast is a REAL pre-call insn whose def->asm->arg chain fronts that
   argument's addiu, and on this function that WAS the second residual cluster
   (§361 confirmed: the lever caused the bug it was later blamed on). Removing the
   dead-reset costs +2 ins / +8 frame bytes, so it is load-bearing.

2. A REGISTER PIN THAT DELETES A sched2 ANTI-DEP. sched1's birthing boost sinks a
   single-set 'la' to its consumer, local-alloc reuses the freed scratch, and
   sched2 is then walled by store-reads-$v0 -> la-writes-$v0. A pin on the address
   pointer deletes the anti-dep. Note this is where a pin is RIGHT, against §368
   where pins measured worse -- the discriminator is breaking a false
   anti-dependence (works) vs out-arguing local-alloc about an allocation (fails).

3. HARD FACT: gcc-2.7.2 insn_cost (sched.c:1363) sets LINK_COST_FREE on any dep
   whose consumer is unrecognizable (INSN_CODE<0 = every inline asm), so
   pri(asm)=1 ALWAYS. An asm can never inherit a load's latency into its priority.
   That closes off a whole family of plausible levers.

Also cross-referenced §370: this run was briefed to test that bound FIRST and
reported it did NOT explain the residual. §370's claim is unchanged and still
narrow; the transferable habit is checking whether a recorded bound covers your
case before declaring a residual unreachable.
2026-08-31 19:20:46 -06:00
Drew T 30caa67127 fix(r22 guard): record liveness, do not infer it — my mtime heuristic failed BOTH ways
I shipped a guard that used drafting-scratch mtimes as a liveness proxy. It failed
in both possible directions within minutes:

* FALSE PASS: the find included '.run/*wave*', which expanded past ARG_MAX
  ('Argument list too long'). find then matched nothing, the guard PASSED, and I
  ran clean: removed build/, expected/, and the regenerated splat tree (asm/, assets/, include macros, undefined_*_auto.txt). on a live lane — deleting asm/ under five drafting agents. I
  restored it immediately (extract-all 212/212) but that is damage control, not a
  design.
* FALSE PASS, structurally: even with the glob fixed, an agent that THINKS longer
  than the window is indistinguishable from a finished one — the exact flaw I had
  already written into gater_lane's docstring for the verdicts file ('a quiet file
  mtime is deliberately NOT accepted as one') and then rebuilt here anyway.

tools/lane_inflight.py is the fix: liveness is RECORDED, not inferred. The
orchestrator adds a target when it launches the workflow and removes it when the
verdict returns — both actions it already performs, so the ledger cannot drift
without skipping a step that is taken anyway.  exits non-zero when any agent
is live, which IS the guard, and both r22_verify.sh and parallel_gate --r22 now use
it instead of touching the filesystem.

Negative-controlled both directions: refuses with 5 live agents named and their
start times; passes when the ledger is drained.

The lesson worth more than the fix: I had already identified 'a quiet mtime is not
a completion signal' as a defect class, documented it, and then re-implemented it
in a different file. Writing a rule down does not stop you applying its opposite
somewhere else.
2026-08-31 18:59:50 -06:00
Drew T 8a347cdb93 fix(r22_verify): rewrite — my own edit had left a SECOND make check-all in it
A scripted patch I applied inserted three lines that (a) re-ran the whole
check-all inside a process substitution and (b) grepped /dev/null. Caught by
reading the file back instead of trusting the edit reported success.

The rewrite does what was intended: capture check-all's output ONCE, clear
.run/R22_DEBT only when the summary line says '0 failed' AND the exit code is 0
(R53 -- a failed build leaves the previous binary on disk and sha1sum reads green,
so the exit code alone is not enough), and leave the debt standing otherwise.
2026-08-31 18:57:14 -06:00
Drew T 601a34f728 fix(pgate): guard --r22's own make clean, and make the deferred check COUNTABLE
The exclusivity guard I added to tools/r22_verify.sh left the path actually used
most -- parallel_gate --r22 -- unguarded, because the destructive 'make clean'
lives in BOTH. Four times this session a drafting agent reported 'asm/<binary> is
MISSING from the tree' mid-draft; one survived only by finding an old snapshot and
still returned MATCH, which is luck, not safety. Drafters never write src/, which
is precisely why a dirty-tree check cannot see them: they DEPEND on state the
operation destroys. A guard belongs where the operation is (R54).

Skipping R22 DEFERS a fleet check rather than performing one, and a deferred check
nobody tracks reads as 'verified' at session close -- the same failure mode as a
loud error nobody counts (R32). So the skip now appends to .run/R22_DEBT with the
commit it deferred after, and a green clean-fleet run DELETES that file. The
session checkpoint must quote it.
2026-08-31 18:56:43 -06:00
Drew T 2a8120f539 feat(decomp): parallel gate — 1 fns across 1 binaries (2 workers)
ov_SC01_000    func_8017D490
2026-08-31 18:55:28 -06:00
Drew T 672431e325 fix(r22+gater): R22 now REFUSES while drafters are live; §372 the copy-capture pair
tools/r22_verify.sh (NEW, promoted from .run so it survives the session):
'make clean' deletes asm/ AND build/, and THREE times this session that raced a
live lane -- a subagent authorised to splice src/800.c produced a FALSE
'212 passed, 1 failed' red, and two drafting agents reported their target's asm/
tree MISSING mid-draft (one survived only by finding an old snapshot). Drafting
agents never WRITE src/, which is exactly why 'check for a dirty tree' does not
catch them: they DEPEND on state this operation destroys. The guard refuses when
any wave scratch dir was touched in the last 6 minutes, names the live agents, and
offers R22_FORCE for a drained lane. R54 -- a guard that is not running is not a
guard, so this refuses instead of relying on me remembering.
Negative-controlled BOTH directions: refuses with 5 live agents named; passes on an
idle lane AND on a lane whose scratch is 30 minutes stale (no false positives).

fix(gater): the in-tree main commit message said '0 fn(s)' for a commit that
contained a real bank. corpus memoizes, so querying corpus.stubs immediately after
the bank returns the STALE pre-bank set. Derive the list from harvest_verify's own
verified-out file instead (R33: derive from the invariant the tool already wrote).

§372 ★★★ THE COPY-CAPTURE PAIR. Tell: a REGALLOC-PERM residual whose wrong-register
rows READ the destination of a nearby MATCHING copy insn. Two passes re-base uses
onto a copy's destination -- cse.c make_regs_eqv (canonical-reg rewrite of later
same-EBB uses) and local-alloc.c optimize_reg_copy_1 (forward-substitution when the
copy's src does not die in it) -- and BOTH die to one zero-byte edit: spell the copy
'P = X + zr' so SET_SRC is a PLUS, which is not a reg-reg copy and records no reg
equivalence, while emitting the byte-identical 'addu $rd,$rs,$zero'.
Notably the escalation was told to CHECK whether §368's tell applied rather than
assume it; it reported that it did NOT (pure shift/slti rows, no commutative
operands) and found the real cause from RTL dumps. That is §361's procedure working.
2026-08-31 18:51:21 -06:00
Drew T 0dacc7b3f9 feat(decomp): main in-tree gate — 0 fn(s) 2026-08-31 18:50:44 -06:00
Drew T 86918184da feat(decomp): main in-tree gate — 0 fn(s) 2026-08-31 18:47:29 -06:00
Drew T f1e963f3e8 fix(gater): commit what the in-tree main gate banks
The worktree path commits via parallel_gate; the main path runs harvest_verify
directly in the main tree and did not. A banked function therefore sat UNCOMMITTED
until I noticed, and the next tool to see a dirty src/ either refuses (parallel_gate
does, correctly) or sweeps it into an unrelated commit. Caught on the func_8005E228
bank. R42: commit banked work the moment it exists.
2026-08-31 18:44:00 -06:00
Drew T 70843992a7 feat(decomp): main func_8005E228 (83 ins) — a §265 VERBATIM-ASM bank, not a C crack
Recorded honestly for the accounting: this function is banked as a raw __asm__
transcription of the target disassembly, NOT as decompiled C. Its epilogue
(jr $ra with addiu $sp in the delay slot, two restores above) is unreachable from
C at this project's pinned triple -- the §177/§188 toolchain-wall class.

It is a legitimate route by the project's own established convention, verified
before banking rather than assumed: src/800c3.c already banks InitHeap,
FlushCache and func_8005CE38 exactly this way, and the agent followed the
convention of its immediate neighbour func_8005E3AC in the same TU.

Note it was NOT on .run/S67_walls.txt or the exclude list, so it was fairly drawn
and the wall was DISCOVERED by drafting it. That is a gap in the walls ledger
worth closing: an epilogue-shaped wall that no one has met yet is invisible to the
draw filter, so the next wave can spend an agent rediscovering it.

harvest_verify in the main tree: verified 1 / failed 0, final SHA
143dbb89f34491258bbc27810d0a12ec8b43a8dd BYTE-IDENTICAL. main 1045 -> 1044.

Also fixed en route: a maspsx sltu-operand parse quirk needs no spaces after
commas in the transcription (the submodule itself is UNCHANGED -- verified).
2026-08-31 18:43:37 -06:00
Drew T 54c0624e49 docs: §371 the module-binary -O0 carve route + the spimdisasm rodata trap; SETUP.md S68 tooling rows (R21)
§371 ★★ carving a SINGLE-OBJECT module binary. One 'unaddressable content'
message was THREE stacked causes (interior-YAML-comment symbol-list truncation, a
trailing verbatim-asm chunk with no region, bare tag forward decls) -- fix one and
the message does not change, which is why it read as an impassable wall.

Then the reusable part: spimdisasm migrates single-referenced rodata into a
function's .s ONLY within the same subseg, so a carve that moves the function
silently DROPS it, and INCLUDE_RODATA cannot bring it back (splat marks it migrated
segment-wide and emits nothing). Rename the .rodata subseg to the object its
emitters moved to; the regenerated .s coming back byte-identical is the proof.

Also recorded: the Makefile -O0 glob hunk is PART of the carve, not a follow-up;
interleave_check's DRIFT on md_MAIN_003 is PRE-EXISTING and must not be 'fixed';
the still-open second-carve refusal (UNOWNED rodata 0x800cedf8); and the §126 plan
for the remaining 8 -O0 stubs (three are ADJACENT so one region covers them).

SETUP.md (R21): three tooling-inventory rows covering gater_lane/escalate_fable/
o0_boundary, the six overlay-layout fixes, and the module-binary carve route.
2026-08-31 18:34:38 -06:00
Drew T 650bb7285d feat(decomp): parallel gate — 1 fns across 1 binaries (1 workers)
md_MAIN_011    func_800CFDB4
2026-08-31 18:22:18 -06:00
Drew T a731022d96 docs(phase-31): S68 post-reset — module-binary -O0 route opened, twin flywheel closed 2026-08-31 18:18:01 -06:00
Drew T 7a969d1c61 feat(o0): md_MAIN_003 carve — the module-binary -O0 route opens, func_800D0D6C banked (345 ins)
The single-object module binaries could not be carved at all: o0_subsplit planned
correctly and then jr_isolate_all refused with 'unaddressable content'. That
blocked 9 of the 12 remaining -O0-in-an--O2-TU functions fleet-wide, including a
byte-correct 345-instruction draft with nowhere to go.

THREE ROOT CAUSES behind the refusal, all fixed here:
* overlay_src_split.load_ov_syms: an interior YAML comment terminated the
  symbol-file list. md_MAIN_003's yaml annotates the list body, so only
  symbols.us.txt loaded and D_800D3200 resolved to None -> refusal.
* jr_isolate_all._partition: a trailing content chunk (the verbatim-asm pair after
  the last addressable anchor) now attaches to the LAST region when every symbol it
  defines resolves at/after the last cut, instead of hard-refusing.
* _file_scope_decls: bare tag forward decls (struct S_D2394;) exempted from the
  dedupe refusal; plus addr_of's D_<hex8> fallback.

THEN A LINK FAILURE THE CARVE CAUSED, worth knowing: spimdisasm migrates rodata
referenced by exactly one function into that function's .s ONLY within the same
subseg. The carve moved func_800D30D0 into the jr subseg while the .rodata island
stayed on md_MAIN_003, so three dlabel string blocks were SILENTLY DROPPED ->
undefined reference to D_800CEE58/D_800CEE80. Adding INCLUDE_RODATA does not
resurrect them (splat marks them migrated segment-wide and emits nothing). The fix
is to rename the .rodata subseg to the jr object, where every island emitter lives.
The regenerated func_800D30D0.s came back byte-identical to the pre-carve .s.

Makefile: the -O0 glob widened to src/md_*/md_*_o0?.c. Without it the region file
compiles -O2 -- byte-neutral while stub-only, but every -O0 draft banked into it
would mystery-fail the gate (§362's trap class). This is why the Makefile and tool
hunks MUST land with the carve: a fresh clone would otherwise lose the -O0 flag.

VERIFIED INDEPENDENTLY of the agent that did it: sha1
dd1b32ecf1103c6f7cf1943d25546a3046e17b14 == config/check.md_MAIN_003.sha, from a
rebuild I ran myself; md_MAIN_003 13 -> 12 stubs; func_800D0D6C absent from
corpus.stubs. interleave_check's DRIFT on this binary is PRE-EXISTING (identical on
a clean tree, verified before any change) -- md_MAIN_003 has no _JTBL_INTERLEAVE
block and must not get one; forcing ALIGNED moves the leading rodata island after
.text and shifts every address by 0xD8. config/overlays.mk untouched (R59/R60).

8 of the 9 md_MAIN_003 -O0 stubs remain: they need drafts and follow-on carves.
2026-08-31 18:13:42 -06:00