Commit Graph

493 Commits

Author SHA1 Message Date
Drew T 14b6ca1412 feat(waves): cc1 joins the default lever list — it was never a structural blocker
jtbl and o0 are held out of the default lane because a draft cannot BANK without
tooling first (a rodata carve; an -O0 object, and no Makefile glob matches md_* at
all). 'cc1' is not that: it records that some earlier draft failed to compile. A
compiling draft banks through the normal splice->build->SHA path regardless of its
history, and a non-compiling one fails fast at cc1 for the price of one build.

Holding 14 groups / 105 instances / 6,511 ins out of every wave bought nothing.
Effective on the next wave draw (build_wave_atlas is a fresh subprocess per draw —
no lane restart needed).
2026-08-24 11:16:09 -06:00
Drew T f574f3c80e feat(match_one): derive the opt level from the target — the -O0 oracle was never wired up
--o0 has existed since P29 and NOTHING ever passed it: api_draft.match_one(), the
oracle every wave agent iterates against, builds a fixed argv without it. Every agent
handed an -O0 target was shown an -O2 compile of its own C and a mismatch on every
instruction — feedback that cannot converge, for a reason invisible in the diff, and
it hit even the 51 functions already sitting in -O0 objects.

match_one now decides from the target (R33), with two oracles because neither alone
is sufficient (R34): the -O0 frame-pointer prologue in the target's own bytes, OR the
subseg being an object the Makefile compiles -O0. boot/start.s is built -O0 with no
ordinary prologue; an -O0 function stranded in an -O2 subseg has the prologue but
cannot bank — and match_one now says so instead of letting an agent chase it.

The prologue scan is anchored at glabel, not the top of the file: two md_MAIN_011
.s files open with a migrated jump table / .asciz blob and a naive scan read table
words as the prologue.

tools/test_o0_detect.py: 14,400 .s scanned, 167/167 signature files covered, 0 false
positives outside -O0 objects, both rodata-first files detected.

Census correction in the design doc: the handoff's refutation of md_MAIN_003/011 is
itself wrong (16 and 21 -O0 functions, byte-verified), and the 311 '$fp mentions'
figure over-counts because $fp is $s8, allocatable at -O2. True population: 167
files, of which 116 / 14,148 ins are -O0 functions the build compiles -O2 — double
the atlas estimate, with x2/x3/x4 sibling replication.
2026-08-24 11:15:33 -06:00
Drew T d980540095 fix(lanes): tells draws a SMALL band — the lane gap was a card-SIZE gap
Joined the campaign ledger to each wave's cards and to the functions its own commit
banked (removed INCLUDE_ASM lines), pooled over bb/bg (tells) vs bc/bf (default):

  nins      default        tells
  0-50      303/528  57%   27/ 67  40%
  50-80      43/145  30%   20/ 73  27%
  80-120      9/ 41  22%   10/100  10%
  120-200     1/ 30   3%    1/ 68   1%
  200+        2/ 35   6%    0/ 30   0%

At equal size the lanes are close below 80 instructions and both collapse above it.
What separated them is the card size MIX: default's cards are median 37-39 ins, the
tells pool median 89-95 (2.4x), so 'the tells lane is broken' measured the population,
not the lever. Tells now draws 5-80.

This also refutes the S58 handoff's one live hypothesis for tells (235, the phantom
symbol). Checked the recorded reloc_identity verdicts first (R38): among MISMATCH?
rows, the fraction whose instruction SHAPE already matched — the symbol-only class 235
describes — is 25/87, 16/79, 17/66, 13/57 on default waves but 6/66, 4/81, 5/47 on
tells. Tells drafts fail because the BODY is wrong, not the symbols, which is what a
2.4x larger median predicts.

Also adds tools/lanes/relaunch_drafter_shell.sh. bash parses a while...done body in
full before running it, so a lane-ARG change is invisible to the running shell and a
python bounce re-runs the OLD command line — measured at 11:02, when the bounced
python came back on the pre-S59 lane list 18 minutes after the file changed.
2026-08-24 11:09:38 -06:00
Drew T 63fcbeda98 fix(jr_isolate_all): the md_* leading island is not a jtbl carve
jr_inventory asserts every committed .rodata piece resolves to exactly one banked
owner (R32) — true for ov_*, where every .rodata piece was created by jtbl_carve.
On md_* the 154-A leading island - [0x0, .rodata, <alias>] is the module's own
rodata blob (INCLUDE_RODATA data + every stub's still-migrated table), has no single
owner, and made the check abort: md_SC03_076 reported UNOWNED 0x801ef468 and could
not be isolated at all.

rodata_carves() now takes the alias and skips that one piece. The discriminator was
verified across all 213 splat configs: a .rodata piece at offset 0 whose subseg is
the binary's own alias exists in exactly the 42 md_* configs and in none of the
others, so this is a provable no-op for every ov_*/main config.

md_SC03_076 --only func_801F218C now plans cleanly: 1 jr, 2 region .c files, no
carve repoints.
2026-08-24 10:57:19 -06:00
Drew T 6752653e6f feat(lanes): one-shot watcher that bounces the drafter python once the in-flight wave queues
Ships a lane-arg change without losing drafts: the running python still carries the
pre-S59 args, and killing it mid-wave discards everything the fleet has drafted for
that wave. Waits for .run/ready/<wave>.json, then pkills only the python — the
drafter SHELL, the lane that must never stop, relaunches it from .run/drafter.sh.
90-minute deadline so it never lingers.
2026-08-24 10:45:46 -06:00
Drew T 7aada7c4aa fix(jtbl_carve): route asm lookups through asm_dir() — main's tree is the repo root
main is the one binary splat writes to asm/nonmatchings/800/ and asm/data/ rather
than asm/<alias>/...; the hardcoded asm/<ov>/ in nine places made every main lookup
miss a directory that does not exist and report it as 'already spliced AND no stale
copy' — a true-sounding message about the wrong tree (R43).

With the path, the base and parse_config all fixed, main now reaches the real
analysis: func_8001A114 refuses because subseg 800 would host non-contiguous
.rodata carves (0x63238 = the existing LZSS table, 0x6327c = the new one), i.e.
isolate first — the same architectural refusal an overlay gets.

The island guard also moved AHEAD of jtbl_range, so an island table no longer
reports 'not found in the raw data asm — already carved / stale asm?' (true and
useless: an island table was never in the data asm). It now prints the exact line
the review prescribes: - [0x268, .rodata, md_SC03_076_jr_801F218C].

Review doc gets an S59 addendum with the measured class facts and where the two
examples now stop.
2026-08-24 10:45:01 -06:00
Drew T 7d10fd7e20 fix(jtbl_carve): parse_config now implements its documented contract (md_* c-line corruption)
parse_config's docstring said 'the trailing run of {data,.rodata} pieces after
the last c piece'; its code took data_pieces[0], the first such piece anywhere in
the file. Those agree on 171 configs and disagree on 42: the md_* modules open
with the 154-A leading island - [0x0, .rodata, md_XXX] BEFORE their c piece, so
apply()'s splice lines[:lo] + region + lines[hi:] deleted the c line and wrote the
yaml to disk before the tool errored out for unrelated reasons.

Fixed by deriving the region from the last c piece, plus an R43 guard that refuses
outright if any c piece lands inside the window apply() rewrites wholesale.

Also, correcting what S58's blanket refusal had lumped together:
  * md_* (42 configs) was the whole corruption class.
  * main was never in it - its pieces are already [all c ..., data, .rodata, data].
    main's real defects were the config PATH (there is no config/splat.main.yaml,
    it is splat.us.exe.yaml) and the FILE BASE: the EXE has a 0x800 header, so the
    delta is 0x8000F800, not the yaml's first vram: 0x80010000, and with the naive
    value payload_word silently read 0x800 early. Both fixed; the base comes from
    the single derivation in family_remap.vram_of (R33).

The cfg_path class refusal is lifted and replaced by an operation-level one: a carve
whose table lies below the data region is in the leading island, which a tail carve
cannot reach, so build_carve refuses and names the island-split lane (R43).

tools/test_jtbl_parse_config.py proves all three, read-only:
  NC-1 regression 171/171 configs byte-unchanged by the fix
  NC-2 defect     42/42 md_* lose a c line under the historical derivation; 42/42
                  keep every c line under the fix
  NC-3 main       cfg=splat.us.exe.yaml base=0x8000F800, region starts after the last c
2026-08-24 10:42:11 -06:00
Drew T 2257be920c fix(lanes): tells was removed for the wrong reason — pin it to the full band
The S58 removal cited four waves (as/aw/az/bd) and blamed the lane. All four ran
at band 120-2000. The campaign ledger splits the population by band instead:

  tells @ 120-2000   228 drafts ->   18 banked =  7.9%
  tells @ full band  655 drafts ->  161 banked = 24.6%
  default @ full     2,996 drafts -> 1,335 banked = 44.6%

Of drafts that actually reach a gate the lanes are indistinguishable (tells 54.4%,
default 56.4%) — the whole loss is reloc_identity discarding phantom-symbol drafts,
which is cookbook 235 and a BRIEF fix, not a lane deletion (R40).

Rotation now pins lane index%4 against band index%4 so slot 1 (tells) always draws
the full band and slot 3 (the large band) is always default; asserted over 200 waves.
Takes effect on the next drafter-python bounce; the running process still carries the
old args and the drafter SHELL is never stopped to ship a change.
2026-08-24 10:37:27 -06:00
Drew T d53c4bf796 fix(R42): gate_main self-reverts on abort; campaign refuses to adopt main sources
Closes the gap that made main red for nine hours. R42 ('commit a dirty tree rather than
revert it') is correct for a per-binary gate that leaves PROVEN banks uncommitted, and WRONG
for gate_main, whose substitution is unverified by construction until the SHA matches.

Two guards, defense in depth:
1. gate_main installs atexit + SIGTERM/SIGINT/SIGHUP handlers that revert its own substitution
   unless a bank actually succeeded. Killed mid-run, it now cleans up after itself.
2. ox_campaign's dirty-tree commit REFUSES top-level src/*.c (main's sources), reverting those
   and committing the rest. Verified: src/800c.c and src/800.c refused, src/ov_*/... and
   src/shared/engine_core.h still commit.

Also versions the autonomous lane scripts under tools/lanes/ — they lived only in gitignored
.run/, so a fresh clone had no drafter, gater, maintenance or stallguard at all.
2026-08-24 10:27:34 -06:00
Drew T fe66f181e1 fix(gate_main): terminating bounded bisect + idempotent typedef hoist
The P31 S58 main probe ran 38 minutes on 8 drafts and never produced a verdict. Two
independent non-termination bugs, both fixed and negative-controlled:

1. THE BISECT COULD NOT TERMINATE. On a failing multi-element chunk it did 'lo = head + lo',
   restoring lo to exactly its prior value, so the next iteration recomputed the same head and
   failed identically — forever. Replaced with an explicit-stack bisect that SPLITS a failing
   chunk and pushes both halves, so work strictly decreases and termination is structural.
   GATE_MAIN_MAX_STEPS (24) is a loud backstop, not the mechanism.
   Verified: 8 drafts with one poisoned -> 7 banked, 1 rejected, 7 rebuilds.

2. THE TYPEDEF HOIST WAS NOT IDEMPOTENT. The block is inserted AT the anchor, so anything
   hoisted previously still started after it and was re-hoisted every call, stacking a fresh
   marker comment each time ('hoisted 2 typedef(s)' x150; the tree held a duplicated marker).
   Now tracks the already-hoisted region and reuses the existing marker.
   Verified: 3 consecutive passes hoist [Foo,Bar], [], [] with exactly 1 marker.

Together these unblock main: 170 parked drafts and ~1,041 open stubs.
2026-08-24 09:40:24 -06:00
Drew T 840f280020 fix(gate_main): read implicit-declaration errors; document the non-idempotent hoist loop
The main probe (8 drafts) ran 38 minutes without a verdict. Two defects, neither about the
drafts:

1. FIXED — the compile-error shortcut matched only 'previous declaration of', but gcc printed
   'previous implicit declaration of func_80017930'. So a batch whose culprit gcc had already
   named fell through to bisection, which costs a full clean EXE rebuild per step. The matcher
   now accepts the implicit and conflicting-types forms too. (resolve_conflicts is separately
   blind to this class: an implicit decl comes from a call site with no prototype.)

2. NOT FIXED, documented — the typedef-hoist repair is not idempotent. It emitted 'hoisted 2
   typedef(s)' 150 times and left a duplicated marker comment; it re-hoists, rebuilds, fails
   identically and repeats, so it cannot converge. Make it idempotent and bound the bisect
   before gating main again.

Also: ox_campaign pre-draws the next wave AFTER launching shards (doing it before left the
fleet at 8 agents while a card job ran), collect_drafts grants stragglers a grace period
instead of letting 2 of 220 shards idle the fleet for 34 minutes, and drafter bands are now
mostly full-range (the 400-2000 band drew 9 cards for a 2,000-worker fleet).
2026-08-24 02:05:16 -06:00
Drew T de9a550edb fix(jtbl): refuse md_*/main in jtbl_carve — it corrupts those configs before erroring
Two independent reviews (ox design study + Fable validation) found parse_config does not
implement its own documented contract on md_*/main: it can DELETE the c config line and
corrupt the yaml on disk before failing. main also has no config/splat.main.yaml (it is
splat.us.exe.yaml), which is the FileNotFoundError seen on every main jtbl target.

Refuse loudly rather than corrupt quietly (R43). Lift only after parse_config is hardened
and proven on two examples.

Also: gate() stages into a per-run dir (a killed gate's 3,186 stale drafts survived into the
next gate of the same tag and poisoned every group); reloc_filter no longer treats
NOT-A-STUB as a pass (it means ALREADY BANKED — wave an staged 480 of them over source that
already byte-matches).

Designs recorded: docs/tool-designs/jtbl-island-split.md (ox) and -review.md (Fable):
14 CONFIRMED / 8 WRONG / 1 UNVERIFIABLE, verdict GO-WITH-CHANGES, and the correct fix is
SMALLER than proposed — one inserted .rodata carve line + jr_isolate_all.py --only, no _pre
piece and no ld_interleave leading mode.
2026-08-24 01:00:59 -06:00
Drew T e05dc116be chore(phase-31): S58 crash-recovery checkpoint + autonomous lane architecture
CURRENT_PHASE.md gains a CRASH-RECOVERY checkpoint (not a fresh-session handoff): what is
running, restart order, the measured fleet/scaling facts, the fixes that must not regress,
and the ordered work queue.

Lanes: drafter (never stop it), gater (restartable), maintenance (free A-prop sibling lane),
stallguard (60s auto-repair). Drafting holds no lock; one narrow draw-vs-gate lock exists
because build_wave_atlas reads corpus.stubs and misreads substituted drafts mid-gate.

main is off the wave critical path — 157 drafts parked to .run/main_queue/ rather than
stalling the gater for another hour on a bisecting whole-EXE rebuild.

api_agent: 5xx retried like 429 (a 502 was abandoning functions at near-19), HTTP_TIMEOUT
420s not 1800 (a hung request parked an agent 30 min), EXTRA_READABLE for tooling briefs,
and bare-directory paths no longer refused against their own granted root.
2026-08-24 00:24:11 -06:00
Drew T e7ebb1cd45 docs+rules(S58): R42 commit-banked-work-immediately, R43 refuse-unsupported-input
R42: gate_main reverted 61 byte-proven overlay banks it could not distinguish from its own
substitution (sweep_parallel gates commit=False by design). Fixed by committing overlay banks
before the main batch, chunking main at 8 to bound bisect cost, and replacing every blind
'git checkout -- src/ config/' with commit-or-refuse in ox_campaign and idiom_serial.

R43: sweep_parallel had an explicit branch admitting main, which cannot be gated incrementally
— wave ab banked 0/105 main cards while its non-main cards banked 94/115 (82%), and the wave
read as a drafting failure. sweep_parallel now refuses main and names gate_main.py.

Also: validate_targets now prefers the card's own addr field (named symbols like SYS_OBJ_F00
were MALFORMED and discarded whole 220-card waves); ox_campaign deals model lanes by
smallest-ratio scheduling (a 73-card wave had put 73 shards on ox and 0 on deepseek);
docs/accelerators.md gains the four vacuous-check defects.
2026-08-23 12:59:59 -06:00
Drew T cb65a62cad feat(phase-31): S57 external-model bake-off — 4 tool fixes, §205/§206, api_agent harness
An external-model bake-off, not a banking session. Nothing was banked; that is
the next session's first task.

FINDINGS
  - CARD FUEL is the biggest lever, bigger than model choice: the same 10 cards
    went 4/10 -> 9/10 when seed_ref/tu_ref/decl_prior were injected. The
    "60-instruction ceiling" was an artifact of withholding fuel.
  - Sub-50 is near-free: 19/19 verified MATCH at $0.007/function, blind.
    7,724 sub-50 open functions = 73.5% of the remaining set.
  - A free model (stealth/ox-alpha) cracked a 611-ins function and a jtbl
    exemplar, and distilled §206 — whose two negative results were
    independently byte-confirmed before banking.
  - §206 transfers WITHIN a family (40 turns -> 11) but NOT across (56 turns,
    0 compiles). jtbl costs ~40 turns of learning per family, not per class.

TOOL FIXES (all negative-controlled)
  - family_remap.gather_externs: took the first ALPHABETICAL extern across the
    overlay's TUs, carrying two types swapped (sh/lh vs lbu/sb). Now prefers
    the extracted unit's own file. Blocked a 4-member/2,444-ins family.
  - atlas.member_lever: aprop_card was loaded and never read while a bare
    ledger DIFF forced needs-autopsy. PURE now outranks it — rescues 32
    members / 11 families / 3,810 ins.
  - decl_prior._ASM_SYM: the %hi/%lo arm had never fired (0 of 1,210 over four
    waves). jal 306->306 zero regressions, data 0->299.
  - api_agent.py (new): --cards, --brief, --max-cost, nudge loop, 429
    attribution + backoff, transport retry, non-fatal tool faults, and a
    repeated-call guard.

RULES PROPOSED: R40 (exonerate the instrument before attributing a failure to
its subject — seven instances this session) and R41 (a cost/rate/yield number
ships with its denominator — I quoted $0.30 against a $6.31 bill).
2026-08-23 00:12:07 -06:00
Drew T 2c4637450b chore(phase-31): S56 checkpoint — 127 banked, wave AA built and staged (not launched)
Wave AA: 69 cards / 6,539 ins / 8 gate groups, 33 siblings, snapshotted 69/69.
All 69 passed the S46 validity gate pre-draft — the first wave drawn through it
since it was wired into build_wave_atlas, and the honest population negative
control for that gate (zero false refusals; a poisoned card is refused
OUT-OF-RANGE).

decl_prior now contributes 165 DATA rows to the cards (0 on every prior wave,
the §204-E fix). The 49 uncopyable pointer-to-function spellings are marked
`fleet_note` rather than dropped: the consensus is real information, the
rendering is not valid C. NOT fixed at the source — gate_main.typesig is the
banking driver's conflict oracle, so the safe fix is a display-only
pretty-printer in decl_prior, and no index rebuild is needed since the tuple is
what the index stores.
2026-08-18 15:44:55 -06:00
Drew T e9ac4db9ba fix(phase-31): wire the S56 findings into the tools, SETUP.md and memory
Audit after "did you update tooling for those findings" found three gaps
beyond the four committed fixes:

  - R21: docs/SETUP.md had no record of any of it. Adds the "four flow traps"
    table — each trap, its measured cost, and where it is now caught.
  - family_sweep --only had no coverage assertion, so the 3-vs-50 mis-scope
    could recur silently. It now resolves member addrs to their family, always
    prints the coverage line, and refuses when it resolves to zero families.
  - pregate_check modelled the banking driver's typedef strip but never checked
    the consequence. Adds [DROP-RISK] §203 USE-BEFORE-TYPEDEF. R39 NC: flags
    the known-bad splice, 0 false positives on the post-hoist file and all 7
    other wave-Z TUs (first draft read a typedef named in its own comment).

Also corrects §203 and the phase log: the text that banked was the RENAMED
variant, because gate_stage's backlog.save_draft() overwrote the original on
the failed attempt. Bytes are correct (R22 213/213); the claim "draft
byte-unchanged" was not.
2026-08-18 15:32:32 -06:00
Drew T 34b8f772b4 feat(phase-31): wave Z closed — 127 banked, R22 213/213, fleet 96.1/91.8/97.11
Wave Z: 74/75 MATCH, 72 banked first gate, 2 recovered for zero agent tokens
(§203 address-order typedef hoist; §202 alias on a DEFINITION for the §183.3
DEF-side return wall). Sibling sweep re-scoped after --only was keyed on the
atlas gid rather than family_hseq's exemplar: 3 -> 50 banked.

Harvest: 82 gap reports -> 5 laws, 16 rejected, 30 already-covered; the
adversarial verifier killed 7 of 12 claims. §204 written (510 lines).

Tooling, all negative-controlled:
  - the S46 validity gate was off the path for waves T-Z; wired into
    build_wave_atlas where cards are born, with the PRE-DRAFT-ONLY constraint
    documented (it condemns banked work if run later)
  - wave_snapshot honors the card's `sub` (9/9 legacy byte-identical, 66/66
    split-TU recovered)
  - §204-E: decl_prior's %hi/%lo arm had never fired (jal 306->306 zero
    regressions, data 0->299)
2026-08-18 15:25:49 -06:00
Drew T 68481253de fix(phase-31): §201-A — decl_prior's DEF row was address-keyed and wrong in the overlay window
Wave Y's harvest found a defect in the card field shipped EARLIER THE SAME SESSION (§196). Overlay
functions are named by VRAM address and 134 overlays load at the same window, so build()'s bare
defs[name] counter mixed N unrelated functions -- and §196 ranked that row ABOVE the destination TU.

Measured over the tree: 3,911 of 9,861 symbols with a definition are defined in >1 binary; 1,219
disagree on ARITY, 1,204 of them in the overlay window; 818 of the disagreements were a top-two TIE
that Counter.most_common broke by sorted-file order, so the lowest-numbered defining overlay
silently won. On wave Y's five binaries, 26 of 65 overlay-window DEF rows (40%) named another
overlay's function. Byte-proven cost: applying one row's arity to func_8017E83C took it from
MATCH (114 ins) to 113 ins / 83 mismatched.

Fix: defs are keyed by BINARY; a DEF row is emitted only from the target's own binary for
overlay-window symbols, and otherwise withheld with a stated reason ('N other binaries define this
address; none is yours') rather than silently omitted. Resident/shared/main symbols are fleet-unique
and were always correct (0 of 43 wrong). build_wave_atlas passes the target binary through.

Wave Z was ALREADY STAGED with 24 poisoned rows across its 292; redrawn with the fixed tool -> 0
poisoned, 24 withheld with a reason. This is the fourth same-session self-correction (§194-E ->
§193-A, §199-A -> §189-A, §197-A -> §136-9, §201-A -> §196), and the first one caught before the
bad data reached agents.
2026-08-18 12:29:34 -06:00
Drew T 1a603e0e22 fix(phase-31): gap_triage — threshold at 0.15, because a weak suggestion costs a reader's attention
Measured the top-candidate score distribution on wave Y's 67 gaps: min 0.009 / p50 0.08 / p90 0.26 /
max 0.53. Hand-checking the low half showed generic co-occurrence -- 'delay slot' matching every
section that mentions a delay slot -- not real answers. Emitting those is worse than emitting
nothing: an empty list costs a reader nothing, a plausible-but-wrong section costs them a read. The
tool now reports 14 confident candidates instead of 51 mostly-noise ones.
2026-08-18 11:08:29 -06:00
Drew T cabcba6262 feat(phase-31): gap_triage — deterministic pre-filter for the harvest's biggest cost
Every harvest so far spent most of its agents rediscovering that a gap was already answered: 61/71
(T), 44/64 (U), 76/67 (V), 41/68 (W), 56/63 (X). That majority verdict is the flywheel working, but
paying an adversarial verifier to reach it is waste -- the check is a text search, and a text search
does not need a model. gap_triage scores each gap against all 441 cookbook sections by IDF-weighted
distinctive-term overlap (file:line cites, section refs, register names, MIPS mnemonics, pass names)
and hands the reader its top candidates to open FIRST.

Deliberately dumb and deliberately non-authoritative: no embeddings, no model, and a candidate is a
starting point rather than a verdict -- its failure mode is a suggestion the reader ignores, not a
refusal that discards work (R39). R32: every gap appears in the output exactly once, asserted.

On wave Y's 67 gaps: 51 get a candidate section, 16 get none. Built during a four-round API outage
that blocked the harvest itself -- the retrieval half of the harvest needed no agents all along.
2026-08-18 11:07:54 -06:00
Drew T 894f3d5ce1 fix(phase-31): sym_of reads the asm-label alias — 1,210 verdicts corrected, 0 regressions
Second instance of the §192 defect class, found by wave Y's ov_SC02_017 slate. The project's own
§37/§124 idiom spells a renamed symbol as `extern s32 gVecX __asm__("D_80126B5C");`, and sym_of's
generic branch matched `__asm__` -- an identifier followed by '(' -- before reaching the real one.
Every aliased declaration therefore collided with every other one under the name `__asm__`.
Measured cost on one slate: 1 byte-verified draft DROPPED and 2 phantom CONFLICTING-EXTERN
failures, on an idiom this same session used to RECOVER work.

NC over src/ plus wave Y's drafts: 1,210 changed verdicts, every one `__asm__` -> the real alias
identifier (899 of them one symbol, aD800B9A02 -- the idiom is fleet-wide), 0 regressions.
2026-08-18 10:16:36 -06:00
Drew T 5f174cce53 feat(phase-31): §197 wave-W harvest + §198 UNKNOWN promoted to a default lever lane
HARVEST (10 agents, 68 gap reports): 4 CONFIRMED, 3 REJECTED, 41 already-covered. Yield fell from
14 because the readers were seeded with §193 AND §194 AND §195 -- three sessions of laws off the
table before they started, which is the flywheel working.

§197-A IS BANKED AS ONE ENTRY WITH ITS ATTRIBUTION FLAGGED CONTESTED. Two verifiers independently
confirmed the same phenomenon (lhu;sll 16;sra 16+K where the target has lh;sra K) and attributed it
to DIFFERENT passes -- one to cse's fold_rtx associative block with -da dumps showing the middle insn
already gone in x.i.cse, one to combine preferring the count-merge over forming lh. R34 forbids
treating agreement between disagreeing oracles as corroboration, so the observable, the tell and the
cure (a zero-byte asm re-tie; a fresh temp works with no second SET at all) are banked, and the pass
question is recorded as open. Both verifiers independently byte-refuted §136 type-form rule 9's cure:
u16 v[4] and SVECTOR v compile BYTE-IDENTICALLY in rule 9's own context. Banner added at rule 9.

§198 (in the tool's own help text): UNKNOWN was excluded from the default --levers while the wave
PROMPT told every agent "UNKNOWN does NOT mean hard -- it means the atlas could not name a lever."
The tool was silently overriding the doctrine. Measured: UNKNOWN held 607 of 1,179 in-band members,
more than every other lane combined; wave W drew 73 cards from it into 3 gate groups (24.3 drafts per
rebuild vs wave V's 7.8) for 71/71 drafted, 68 banked. Recorded cost: UNKNOWN groups are mostly
singletons, so the free sibling remap yielded ZERO where waves T/U/V got 49/34/25.

Also releases func_80185480 and func_8017EC98 from wave W's spent-card file -- their agents died on
server rate-limiting and no draft exists, so they must not be marked already-waved.
2026-08-18 02:17:23 -06:00
Drew T e24220aabe feat(phase-31): §196 decl_prior — the fleet's declaration consensus as a wave-card field
Wave-V token accounting picked this lever: drafting is 88.3% of a wave's output tokens (repair 8.4%,
reconcile 3.2%) at 9.3 match_one compiles per agent, so the leverage is in what a drafter must GUESS
before its first compile. Two such guesses are answered in the tree and were on nobody's card:
callee arity/return type (§195-A: NO positive tell exists in the asm, the procedure is a two-arity
A/B) and global type (every CONFLICTING-EXTERN drop). tools/decl_prior.py indexes 4,162 files ->
67,094 symbols (9,739 with a banked DEFINITION); cards now carry the rows for their own target's
symbols, ranked DEF > TU (authoritative, wave law 2) > FLEET modal + rivals with counts.

Control draw: 63 rows over 10 cards, 83% already settled by the destination TU (still a saving --
the agent was grepping for them by hand), 17% with no local answer at all.

Third instance in one session of the same law (§193-A seed_ref, §194-E tu_ref, §196): the answer was
already computed and the card did not carry it.
2026-08-17 23:49:11 -06:00
Drew T 0d39cd97e1 feat(phase-31): S54 wave-V harvest — cookbook §195 (14 laws) + close the internal-j blind spot in masked_diff
32 agents over wave V's 67 index_gap reports: 14 CONFIRMED, 9 REJECTED, 76 already-covered (the
third harvest this session; readers seeded with §193 AND §194 so neither could be re-derived).

THE HEADLINE IS A DEFECT IN OUR OWN VERIFIER (§195-D). masked_diff.mask_for short-circuited on the
OPCODE -- `if (word >> 26) in (2, 3): return 0` -- ahead of the reloc dispatch, so every `j` to a
LOCAL label was dropped from the comparison. The assembler resolves those itself and emits no
relocation, so there was nothing link-time about them. For a loop or switch arm, which label a `j`
targets is the difference between `break` (fall into the shared tail and execute its calls) and
`return` (skip them): byte-proven on ov_SC03_118:func_801825EC, where the banked `break;` and a
call-skipping `return;` variant differ in exactly one word (0800003e vs 08000041) and BOTH reported
MATCH. The blindness reached match_one, the permuter's MaskedScorer, family_cousins.tok and the
atlas similarity tiers at once -- nothing between a draft and the whole-binary gate could see it.
Now the 26-bit field is masked only when reloc_kind == "26" (the linker really does fill it).
R39 control: 35/35 already-banked wave-V drafts still MATCH against their snapshot .s.

Other confirmations of note: §195-A bounds §167-08 with a byte-proven false-negative class (an
argument that DIES at the call is allocated straight into $aN, so its only def is a plain load and
every use reads $aN -- there is no positive tell in either direction, only the two-arity A/B);
§195-B a CALL_INSN does not start a basic block in gcc-2.7.2, so a call-crossing temp can be a
LOCAL-alloc quantity; §195-M frame `vars` is a sequential bump-allocation, unifying §193-I's
CEIL(aggregate,8) term with §165-03/§167-06's 8x-orphan term as one frame_offset walk.
2026-08-17 21:37:23 -06:00
Drew T c5d7653801 feat(phase-31): S54 wave-U harvest — cookbook §194 (14 laws) + destination-TU locality on the card
26 agents over wave U's 64 index_gap reports (7 cluster readers, one adversarial verifier per
candidate defaulting to REJECT, seeded with §193 so it could not be re-derived): 14 CONFIRMED,
5 REJECTED, 44 already answered by an existing section (wave T: 9/5/61).

TWO OF THE 14 CORRECT WORK BANKED THE SAME DAY, and both are now cross-banner'd:
* §194-E — `exemplar` is not merely un-banked (§193-A): it names the card's OWN target on 42/73
  wave-U and 36/71 wave-T cards, and the `seed_ref` §193-A shipped is same-binary 0/51, so the card
  still carried ZERO destination-TU locality. Fixed both ways: a self-pointing exemplar is now
  emitted as null, and cards carry `tu_ref` — banked functions in the card's OWN .c ranked by
  symbols shared with the TARGET's .s relocations (62% of wave-T targets had such a neighbour vs
  19% for the cross-overlay literal grep). Operand-only extraction: a naive uppercase-word regex
  read the .s comment column's hex words as symbol names (34 "symbols", 31 of them hex).
* §194-N — §193-D's C dial is misstated: the lever is a SURVIVING CODE_LABEL, not "a label between
  the block and the call". jump_optimize deletes any label with LABEL_NUSES == 0 long before sched1
  and rewrites a C user label into NOTE_INSN_DELETED_LABEL, which is not a basic-block boundary.

Highlights of the rest: §194-A a zero-byte fence is a one-way wall RELATIVE to the statement being
steered (after = emit-first), and the barrier predicate is volatile-or-colon-less, not the "memory"
clobber; §194-J back-to-back identical stores are deleted by flow.c's last_mem_set unless volatile;
§194-K blinding sched1's alias oracle with a second SET is the first zero-byte dependence-CREATING
lever; §194-M a store in a conditional branch's delay slot proves its C statement DOMINATES the
branch.
2026-08-17 15:14:00 -06:00
Drew T 2ae87fc838 feat(phase-31): S54 wave-T harvest — cookbook §193 (9 laws) + the card's banked-twin pointer
19 agents over wave T's 71 index_gap reports: 5 cluster readers, then one adversarial verifier per
candidate defaulting to REJECT. Result: 9 CONFIRMED, 5 REJECTED, and 61 of 71 gaps answered by a
section that ALREADY EXISTS — a retrieval problem, not a knowledge problem.

§193-A is the fix for that retrieval problem, and it is a construction invariant, not drainage: a
wave card's "exemplar"/"sibs" come from the atlas's OPEN set (atlas.py:96 load_open -> corpus.stubs,
:657 max over open members), so they are stubs 0/34 and 0/146 measured on wave T — and 0% at any
project maturity. Meanwhile atlas.py:505-536 already computes a MATCHED-pool twin (pool = sig -
stubs) and build_wave_atlas kept only its similarity score while discarding the identity. Cards now
carry seed_ref + the "M:" matched kNN neighbours; verified on a fresh draw, seed_ref resolves BANKED
4/4 where exemplar resolves banked 0/12.

Two of the nine refute existing sections in place: §193-B byte-refutes §43's "the (s16)param_of_s32
cast form CANNOT reproduce this" (the decider is combine.c:929's cross-call guard, not regalloc),
and §193-G shows §164-54's "scope to >=4 arms" bound is byte-wrong (balance_case_nodes splits at
i > 2). §193-C bounds §8/§48-A1's cross-jump refund to TAILS only — gcc-2.7.2 has no prefix merge.
2026-08-17 12:30:38 -06:00
Drew T bb34eac42f fix(phase-31): S54 — four measured over-refusals in the overlay pre-gate path
Each one refused byte-verified work; each fix is probed, not reasoned:
* built-in redeclaration: a cc1 probe shows two conflicting "memcpy" declarations give
  "warning: conflicting types for built-in function" + exit 0, while the same pair on a
  non-builtin name errors. Every overlay TU in the fleet declares memcpy twice and compiles
  today -> CONFLICTING-EXTERN on a builtin is now WARN.
* driver mismatch: overlays bank via gate_lane -> gate_stage -> harvest_verify, which strips
  every typedef the target TU provides; pregate_check modelled gate_main's hoist/strip instead
  and reported DUPLICATE-TYPEDEF for exactly the duplicates the real gate removes.
  substitute() now takes an optional per-draft transform; pregate passes the overlay one.
* block-scope typedefs: two functions may each declare their own typedef inside their bodies
  (that is how a draft stays self-contained for match_one). _typedefs now honours the brace
  depth map the caller already computed.
* project scalar aliases: include/common.h's "typedef s32 M2C_UNK;" makes "extern s32 D_x" and
  "extern M2C_UNK D_x" the same declaration; _ALIASES now DERIVES those from common.h (R33).

Measured on the 5 leftover slates: 28 drafts, all re-verified MATCH by match_one, went from
"0 kept / phantom FAILs" to main 2 clean, ov_SC04_011 15 clean, ov_SC03_028 1 clean,
ov_SC06_029 4 + 1 named TU edit, ov_SC02_005 2 real TYPEDEF-USED-ABOVE-DEFINITION.
2026-08-17 11:20:32 -06:00
Drew T 85671bc1f7 feat(phase-31): S54 — wave T selector (--one-per-gid, --rank total) + the pre-gate ladder learns to see overlays (§192)
build_wave_atlas: --one-per-gid collapses same-skeleton siblings to one card and defers
them to <out>.siblings.json for the post-bank family_sweep remap (R32 accounting asserted);
--rank total ranks gate groups by DELIVERED mass (card + deferred siblings). Measured on the
wave-T draw: 6,557 drafted ins carrying 12,709 sibling ins behind 69 of 71 gids = 19,266
instructions of potential for 71 agents, vs 9,985 behind 57 under --rank mass. R39 NC: the
flag is byte-inert on a pool whose gids are unique.

gate_main/pregate_check (§192): three defects that made the pre-gate ladder main-only while
reporting "clean" on overlay slates — (1) resolve_conflicts/substitute hardcoded
corpus.stubs('main') -> per-binary _stubs_for(); (2) sym_of returned the keyword `void` for
every `extern void (*D_x[])(...)`, manufacturing 192 phantom CONFLICTING-EXTERNs (NC over
5,526,100 declarations: 189,301 changed verdicts, 0 regressions); (3) `void f()` and
`void f(void)` were normalized together, costing 40 more phantoms — C89's unspecified-
parameter rule is now gate_main.sig_conflict. §192b: the tool refuses when it substituted 0
files, and prints the per-draft [DROP] reasons it used to compute and discard.

Same overlay slate now reports 2 failures, both real (duplicate typedef; memcpy declared two
ways). Cookbook §192/§192b + index regenerated (585 sections).
2026-08-17 11:00:14 -06:00
Drew T d484d8f619 docs(phase-31): S53 journal harvest — §188 the epilogue tail is an ASSEMBLER artifact (corrects §177, answers §182), §189 five source-cited laws, §190 three prescriptions, §191 the rejected set; + tools/oracle_reorder.py 2026-08-17 09:55:46 -06:00
Drew T 6744fac650 feat(phase-31): gate_main strips a draft's FORWARD typedef instead of renaming it (§184b) 2026-08-17 00:07:11 -06:00
Drew T a6e522a96a feat(phase-31): typedef comparison ignores comments (gate_main + reconcile_slate); func_8001ABBC + func_80037028 banked via byte-neutral TU edits 2026-08-16 22:49:05 -06:00
Drew T bbd8a6c8e4 feat(phase-31): pregate_check sees brace-bodied externs (the D_80072780 clash that cost a rebuild); §183 addendum 2026-08-16 22:41:01 -06:00
Drew T 192759f732 fix(phase-31): pregate_check FAILs any return-type disagreement (G4P* under G3P* was rejected by gcc, cost a rebuild) 2026-08-16 22:23:10 -06:00
Drew T 1ebca23c87 feat(phase-31): gate_main hoists TU typedefs above the include block (§181 class 2); +3 recovered drafts banked 2026-08-16 22:20:56 -06:00
Drew T adfa7185c9 docs+tools(phase-31): cookbook §180d/§181/§182; fragment_check gains the mirror test (5.7% of main stubs); pregate_check scans masked text 2026-08-16 21:07:03 -06:00
Drew T 472ff5806a feat(phase-31): tools/scan_leftovers.py — re-verify unbanked drafts (found 37 free MATCHes / 3,240 ins); cookbook §180 2026-08-16 18:11:32 -06:00
Drew T 6bc8bb6894 feat(phase-31): wave Q — 40 main functions banked; + fragment_check.py and bisect_slate.py
Wave Q (90 cards / 6,249 ins) was stopped mid-flight, then a repair-only pass recovered 12 of its
39 unfinished drafts (+579 ins, 51 -> 64 verified MATCH). Banking took two new tools, because the
gate failed three times on causes no C-level check can see.

tools/fragment_check.py (NEW) — the enclosing-function trap, both halves, in milliseconds:
  * a draft whose [addr, addr+4*nins) SUBSUMES another symbol. splat labels addresses that are not
    functions (a bare epilogue tail, a jump-only branch target, an interior label); convert the
    enclosing function and a neighbour's `jal .L80050F24` loses its target.
  * a draft that DEFINES another stub's symbol in inline asm. MoveImage's repair agent correctly
    worked out that a trailing epilogue belongs to MoveImage (splat attributes those bytes to the
    NEXT symbol) and emitted `.globl SYS_OBJ_8F4` itself -- right bytes, match_one passes, and the
    assembler then sees the symbol defined twice because the fragment still has its own .s.
  My first hypothesis was the subsumption case; the tool REFUTED it (clean) and the redefinition
  case was the real one. Recording that order, because I built the tool before verifying the
  premise -- R35 inverted.

tools/bisect_slate.py (NEW) — gate_main's built-in bisect ran THREE HOURS on a 45-draft slate and
printed nothing. Three defects: it never checked whether the BASELINE builds (if no subset can
pass, the search is doomed from step 1), it logs only when it isolates a single draft, and its
re-split rule degrades toward one rebuild per draft. The replacement runs the NULL CONTROL first
(the same control that refuted this phase's "main is link-blocked" misdiagnosis), logs every step,
and does a true binary search: it found the culprit (SYS_OBJ_1790) in 7 steps and 176 SECONDS.

Result: 40 banked, main stubs 1,803 -> 1,763.
2026-08-16 17:21:10 -06:00
Drew T b021812c17 docs(phase-31): cookbook §176i/§176j — the pre-gate check's real boundary, and the cost of stopping a wave
§176i — WHAT A STATIC PRE-GATE CHECK CAN AND CANNOT PROVE. pregate_check validated wave Q's slate
as clean in 0.7s (the first slate all session to reach the gate pre-validated) and the build then
failed twice, both times outside what any text-only check can see:
  1. LINK-time undefined reference: `.L80050F24` lives INSIDE gfx2D_BG0_OBJ_698 and another
     function's .s branches to it -- converting a function to C deletes the local labels its
     neighbours jump to. Statically checkable, but from the .s files, not the .c: scan every other
     .s for label references landing inside a candidate's address range. Worth building.
  2. BYTE mismatch: with the link fixed the binary BUILT and the SHA differed -- the §174 law 1c
     class, which reloc_identity had already named six suspects for.
The division of labour to rely on: text checker for SHAPE, reloc oracle for IDENTITY, gate for
TRUTH. A clean pre-gate is a licence to build, not a prediction of success -- and when the binary
builds but the hash differs, BISECT, because it costs wall-clock and zero tokens.

§176j — STOPPING A WAVE MID-FLIGHT COSTS THE IN-FLIGHT TAIL. Wave Q stopped early: 51/90 verified
(3,631 of 6,249 ins) versus the 96-97% the same pipeline yields when allowed to finish. The loss is
SUSPENDED, not destroyed -- every draft persists on disk, 15 of the 39 unfinished sit at closeness
<=10. Do NOT resume the workflow to recover it (resume re-runs unfinished agents from scratch at
full cost); use a REPAIR-ONLY pass over the <=30 band instead. Decision rule: before killing a long
agent run, price the tail -- stopping converts near-matches into "needs a cheaper second pass",
which is a deferral, not a saving.
2026-08-16 10:02:22 -06:00
Drew T df7bca2e65 feat(phase-31): tools/reconcile_slate.py — drive a slate to 0-dropped BEFORE the first rebuild
The 68%->95% lever from §176h.C2, mechanized. Reconciliation belongs INSIDE the wave: a banked
draft's declarations become the TU's, so a sibling clash hardens into a file clash and post-bank
recovery is measurably worse (18 parked drafts still MATCH, only 1 survived after their wave banked
vs 5 before).

AUTO-FIXES, each re-verified with match_one and REVERTED if a byte moves (a declaration change is
a codegen change, §176f):
  * COSMETIC-TYPEDEF  two names for a structurally identical struct -> adopt the other. Compared by
    BODY, never by name (OtBlk_80015498 == OtBlk_80016450; Elem12 != B12). This body comparison is
    also the answer to §176h.C's spelled-name limit.
  * SIGNEDNESS / ALIAS / ARRAY-VS-SCALAR -> adopt the TU's spelling, fixing the use site.
  * DEFPARAMS (NEW LEVER) -> adopt the TU's parameter types on the DEFINITION and re-narrow with a
    shadowing local: `void f(s32 a0_p) { s16 a0 = (s16)a0_p; <body unchanged> }`. One textual
    insertion instead of rewriting every use site, and the cast emits the same sll/sra pair.
    Byte-identical on both cases tried.

REFUSES, with named reasons, because these are decisions and not edits: DIFFERENT-STRUCT (two real
layouts for one symbol), IMMOVABLE-TU-DECL (gate_main reverts src/, so it needs its own commit +
rebuild + R22), DEF-SIDE-RETURN (adopting the TU's return type usually costs the match -- measured
on func_8001ABBC), and BROKE-MATCH for anything its own verification rejects.

Measured on wave P's leftover slate: 6 -> 9 compatible, 3 auto-reconciled, 2 repairs reverted by
the tool's own byte check, 7 named for a human.

build_wave_atlas: --rank mass (main's gate cost is per SLATE, so ranking groups by member count
silently collapses a wide band to the smallest functions -- measured 60 cards/2,604 ins where 46
cards/4,829 ins were available), and the selector no longer counts ITS OWN OUTPUT as already-waved
(re-running with identical filters had been shrinking the pool 60 -> 46).
2026-08-16 08:36:36 -06:00
Drew T 74a95af4c1 docs+fix(phase-31): reconcile BEFORE the first gate (§176h.C2); pregate duplicate-typedef fix
MEASURED, and it inverts the obvious plan. "Bank the clean drafts now, recover the conflicted ones
later" is backwards: of 18 wave-O/P drafts parked and re-verified still MATCH, only 1 survived
resolve_conflicts once their wave had banked -- versus 5 before it.

The mechanism: a banked draft's declarations BECOME the TU's, so every parked draft that merely
disagreed with a SIBLING now disagrees with the FILE, which is the stricter arbiter (a sibling
clash can be settled by editing either side; a file clash only by editing the draft, and some
cannot be settled at all because gate_main reverts src/ before every build). Worse, the auto-rename
that reconciles a cosmetic clash pre-bank becomes a DUPLICATE TYPEDEF post-bank, because the name
it renames to is now defined in the file.

So iterate the dry run to `N -> N compatible, 0 dropped` BEFORE spending the first rebuild, and
budget reconciliation into the wave rather than after it.

pregate_check: DUPLICATE-TYPEDEF now fires on ANY redefinition, not only differing bodies. C89 has
no compatible-redefinition allowance for typedefs, and my first version missed exactly the case the
tool exists for -- two identical `OtBlk_80016450` definitions, which the compiler rejected on the
next rebuild. Measured, not reasoned.

Also banks the auto-reconciler's rule: tell a COSMETIC clash from a REAL one by comparing struct
BODIES, not names. OtBlk_80015498 vs OtBlk_80016450 are the same {s32 a; s32 b[4];} and rename
byte-identically (both re-verified MATCH); Elem12 vs B12 genuinely differ and were refused.
2026-08-15 22:11:06 -06:00
Drew T 207559daad feat(phase-31): tools/pregate_check.py — validate a main slate in 0.7s instead of a 5-min rebuild
Wave P drafted at 97% and cost A DOZEN clean rebuilds to bank, and not one of those rebuilds
failed on a matching problem -- every one failed on a TEXTUAL property of the substituted file
that a grep could have reported instantly. This is that grep.

gate_main's resolve_conflicts cannot answer it, and not from carelessness: it inspects the DRAFTS
while the compiler sees the FILE THEY LAND IN -- after typedef stripping and renaming, at each
draft's own insertion offset, interleaved with declarations the file already had. Those
transformations run AFTER the conflict check passes. So substitute() gained write=False and this
tool checks the artifact itself.

Five checks, each earned by a rebuild lost this session (§176h): typedef used above its
definition; type never defined anywhere; duplicate typedef with different bodies; one symbol
declared two incompatible ways; definition contradicting a visible prototype.

CALIBRATED AGAINST THE COMPILER, NOT AGAINST C89 PEDANTRY -- and this mattered. The first version
reported 4 hard FAILUREs on the slate that had just built BYTE-IDENTICAL:
- it ignored SCOPE, but the project deliberately uses block-scope extern blocks, and a declaration
  inside one function cannot conflict with a definition elsewhere. Now brace-depth aware.
- it split `void f()` from `void f(void)`, which gcc-2.7.2 accepts. Normalized.
- it called every def-vs-decl mismatch fatal, but gcc-2.7.2 accepted `void f(void*,s32)` against a
  `void f(s8*,s32)` definition and even `G3P *f(...)` against `G4P *f(...)`. What it REJECTED was
  a void/non-void RETURN split (func_8001ABBC). That split alone is FAIL; the rest are WARN.
Comments are masked via cdecl before any use-site scan (an unmasked scan reported 7 phantom hits).

R39 controls: the slate that banked is FAIL-free (exit 0, 3 informative warnings); four synthetic
defects each reported at FAIL; a clean text reports nothing; a block-scope extern does not
conflict; `short` vs `s16` does not conflict; array-vs-scalar does.
2026-08-15 22:03:42 -06:00
Drew T ad2aa6173f feat(phase-31): wave P — 32 main functions banked, main byte-identical (S52)
Wave P was the first full run of the 6k-ins doctrine: 60 cards / 6,589 ins in 2 gate groups,
59/60 claimed and 58/60 independently re-verified MATCH (6,372 ins), reloc_identity 58/58 AGREE
with ZERO symbol errors -- the second consecutive clean wave on symbol identity.

Banking cost a dozen rebuilds and exposed four more gate_main defects plus three regressions of
my own. The tool fixes, all NC'd:
- resolve_conflicts never read a draft's OWN DEFINITION, so the DEF-side wall (a draft defining
  s32 func_X against a TU prototyping void func_X) reached the compiler. Now definition-aware:
  it caught 13 conflicts up front where the build had been finding them one rebuild at a time.
- DECL and both typedef patterns anchored on end-of-line, so a TRAILING COMMENT hid a declaration
  or typedef entirely -- and agents comment nearly everything they declare. Seventh instance of
  one root cause: a scanner that looks green while reading less than it claims (R32).
- typedef handling is now BODY-AWARE and POSITION-AWARE, in a single pass:
    * identical definition visible ABOVE the insertion point -> strip and reuse;
    * same name, different shape -> rename (private to the draft);
    * definition below the insertion point -> never reuse (it is not in scope there).
  Three wrong strategies preceded this, each costing a rebuild: blanket strip (the file's copy can
  sit BELOW the draft -> implicit-int, then a collision), blanket rename (breaks drafts that share
  an IDENTICAL typedef, because their externs stop agreeing -- my regression, three drafts at
  once), and a rescan loop that found the definition it had just renamed and stripped it as a
  self-duplicate -> 'parse error before *'.

KNOWN LIMIT, recorded not fixed: the conflict check compares spelled type NAMES, so three drafts
each defining their own Slot54 with different layouts all declared func_80032A74(Slot54*) and
looked compatible. Comparing struct LAYOUTS for locally-defined types is the real fix.

13 + 4 verified-correct drafts are parked in two named buckets (competing local type models;
immovable TU declarations that gate_main reverts before every build).
2026-08-15 21:55:38 -06:00
Drew T 0753641d4b feat(phase-31): adopt the 6k-instruction wave doctrine + --target-ins (Drew, 2026-08-15)
A wave is now sized by INSTRUCTION MASS, not card count. The metric is instruction-weighted, so a
wave is worth what its instructions are worth: the 12-42-ins card lanes carried ~1,400 ins/wave
(~0.011pp, ~440 waves to finish) while wave O carried 6,266 ins at the same gate cost and the same
draft rate.

build_wave_atlas --target-ins draws cards until the instruction budget is met (still capped by n)
and refuses to under-fill silently. Standard recipe: --target-ins 6500 --min-ins 60 --max-ins 200
--max-bins 4, levers now including UNKNOWN.

THE MEASUREMENT BEHIND IT: draft rate barely decays with size -- wave M 98% at avg 51 ins, wave N
92% at avg 65, wave O 96% at avg 128. Mass is nearly free.

THE UNKNOWN UNLOCK: UNKNOWN is not a difficulty label, it means the atlas could not name a lever,
and it had been routed as needing its own bespoke lane. Wave O's 22-card R37 probe drafted it like
any other lane -- reclassifying ~138k ins (a quarter of everything open) as ordinary wave fuel.
With UNKNOWN in, 9,224 fns / 417,325 ins = 70% of all open instructions are agent-draftable; the
60-200-ins mass band alone is 164,357 ins ~= 27 waves, and is the band to work first.

Also banks the five-step PRE-GATE PROTOCOL (independent re-verify -> reloc_identity -> dry-run to
0-dropped -> reconcile declarations toward the form the match needs -> gate), cookbook §176g.
2026-08-15 14:05:15 -06:00
Drew T 110570e3fc feat(phase-31): wave O — 46 main functions banked, main EXE byte-identical (S52)
36 fresh wave-O cracks + 10 recovered wave-J/K/L drafts, verified in ONE clean rebuild:
143dbb89f34491258bbc27810d0a12ec8b43a8dd BYTE-IDENTICAL. main stubs 1881 -> 1835.

Wave O was a 3-arm 49-card wave (6,266 ins): main head-crack, main UNKNOWN, overlay UNKNOWN.
47/49 standalone MATCH, independently re-verified by me (R14) at 47/49 -- exact agreement --
and reloc_identity reported 46 AGREE / 0 MISMATCH, the first wave of the campaign with zero
symbol errors. THE UNKNOWN LEVER DRAFTS LIKE ANY OTHER LANE, which matters strategically: it is
~138k ins fleet-wide (a quarter of everything open) and was routed as "needs its own lane".

FOUR gate_main defects fixed here, each of which had been silently costing prior waves drafts:
- typedef stripping walked drafts in SLATE order while substitution happens at ADDRESS order, so
  the surviving typedef could land BELOW a draft using it -> "syntax error before D_800A651C".
  Verified the two orders genuinely diverge for both destination files in this slate.
- a BUILD failure (sha None) fell through to a silent bisect -- a full clean rebuild per step to
  rediscover what the compiler had already printed and discarded. Now the error lines are shown
  and the offending drafts named for undefined-reference/redefinition/conflicting-types.
  (My first version of that printer TAILED a stderr+stdout concatenation and faithfully showed 25
  lines of make progress chatter instead of the error -- selecting by position, not by content.)
- typesig treated "short" and "s16" as different types (R39 over-refusal). Aliases now normalize;
  11/11 NC cases pass, with signedness, volatile and array-vs-scalar still conflicting correctly.
- conflict detection ignored shared headers: engine_core.h's DEFINE_ macros declare symbols in
  their own bodies, so a draft's file-scope array decl of D_800A651C was illegal. Block-scoping
  the draft's extern fixes it byte-identically.

DECLARATION RECONCILIATION took the slate from 5 dropped to 0, and three of the four conflicts
were load-bearing CODEGEN, not style: the array form of D_80078D88 blocks a sched1 hoist (scalar
users adopt [0] for free); "volatile" on D_800B9A02 is required by one draft and fatal to two
others (plain u16 loses 1 bank, volatile loses 2); D_800A651C needs block scope. Cookbook §176f.
2026-08-15 14:01:19 -06:00
Drew T 965ae3902e measure(phase-31): the AGREE re-gate lane is a 5% null — symbol verification does not rescue stored drafts (S52-12)
R37 probe: 20 shape-verified AND symbol-verified stored drafts, 5 gate groups -> 1 banked (5%),
statistically the same as the project's A10 stored-verdict law (~0-8%; T1 measured 0/23 on the same
kind of pile earlier this phase). The null is the finding: a stored draft's rejection is almost
never symbol identity, it is TU plumbing (§176d) or staleness. reloc_identity's real home is a
PRE-GATE check on FRESH drafts, not a backlog resurrection tool. The remaining 30 groups are not
worth 30 rebuilds -- lane closed rather than pursued because the tooling was new and interesting.

Also names the SYMBOL-COLLAPSE class in the fixer's refusal (one draft extern standing in for N
distinct globals; a textual rename moves every occurrence together, so it needs one extern per
site) and records the R38 self-note: the 0/23 prior was already in the phase log.
2026-08-15 09:50:48 -06:00
Drew T 0487c0c627 docs(phase-31): cookbook §176e — symbol identity is COMPUTABLE offline, plus reloc_identity --fix (S52)
§174 law 1c recorded match_one's relocation blindness as a caution to the reader ('check every
symbol by hand after MATCH'). It is a computation, not a human's job. Banks the arithmetic, the two
failure shapes it separates (uniform-delta stale seed symbols vs wrong field offset), the four
traps that bit me building it (splat-derived names absent from the symbol files; MIPS o32 REL keeps
the addend in the instruction; index alignment is a precondition; a nearest-symbol label needs a
tight window), and the honest limit measured the same session: symbol-verified + shape-verified is
still NOT sufficient for a bank -- the first re-gate group of five such drafts banked 0/5, because
what remains is TU plumbing (§176d), not identity.

--fix rewrites only unambiguously-wrong symbols (every mismatch naming a symbol must imply the SAME
corrected base) and refuses otherwise: 10 of 12 repaired, 2 correctly refused.
2026-08-15 09:46:10 -06:00
Drew T 594a87e173 feat(phase-31): reloc_identity.py — the oracle that disagrees with match_one about SYMBOL IDENTITY (S52)
match_one masks relocations (26-bit jal field, HI16/LO16 immediates), so it verifies instruction
SHAPE and is structurally blind to WHICH symbol each relocation names: a draft calling the wrong
function or touching the wrong global reports a clean MATCH (§174 law 1c; wave K burned 5 gate
attempts on two swapped globals). Until now only the whole-binary gate caught it, and it reports a
hash, not a cause.

But the target .s comment column carries the FINAL LINKED WORD, so the true address behind every
masked field is recoverable arithmetically, and config/symbols*.txt maps it back to a name. This
tool resolves what the DRAFT names, computes what the TARGET references, and compares -- /bin/bash, no
rebuild, and it names the fix instead of reporting a mismatch.

match_one --emit-streams additionally carries mine_relocs (kind/operand per index); existing
consumers read mine/tgt only.

THREE INSTRUMENT BUGS FOUND BY ITS OWN NEGATIVE CONTROLS, each fixed before any verdict was
believed (R35/R39):
- splat-derived func_/D_/jtbl_ names are not in the symbol FILES (their address IS their name), so
  the first run checked ZERO relocations on a draft whose every callee was correct -- a checker
  that looks clean while checking nothing (R32).
- the nearest-symbol fallback used a 0x4000 window and labelled func_8001C9D0 as 'SsGetMute+0xC50';
  a wrong label is worse than none. Tightened to 0x200 with an explicit splat-derived fallback.
- MIPS o32 uses REL relocations: THE ADDEND IS IN THE INSTRUCTION, not the reloc entry. Reading it
  off the operand string reported 0 for every struct-field/array access and fabricated mismatches
  (the +1/+2/+3 signature on func_801F0734 was a byte-array walk, not three symbol errors).
Also refuses to answer confidently when the streams are not index-aligned (shape differs), since a
single inserted instruction shifts every later index -- my own NC produced exactly that phantom.

NC: known-good drafts AGREE (3 and 5 relocs actually checked); a synthetic callee 4 bytes off is
caught while match_one still says MATCH; a misaligned mutant is downgraded to advisory.
2026-08-15 09:42:58 -06:00
Drew T 02edb37265 fix(phase-31): gate_main conflict check reads the destination TU + is per-file (S52-7)
resolve_conflicts() had two defects, both found by the wave-J/K/L draft recovery:

(a) THE SYMBOL TABLE STARTED EMPTY -- only draft-vs-draft was compared, so a draft contradicting
    a declaration ALREADY IN the .c reached the rebuild and surfaced only as a compile error and
    a bisect. src/800.c carries 'extern void func_8001C9D0(void);' (from banked func_8001C2C4)
    while three wave-J drafts declared it (s32)/(void *). The TU now seeds the table, and the
    drop report names whether the clash is with the TU itself or an earlier draft.
(b) ONE NAMESPACE FOR ALL FILES -- 'seen' was global across the slate, so two drafts landing in
    DIFFERENT .c files could not legally disagree about a symbol. Separate TUs are separate
    namespaces; the table is now keyed per destination file (R39: over-refusal discards good work).

On the 11 recovered drafts the new check named 7 real TU conflicts that the old one missed
entirely. All 7 were repaired by adopting the TU's declaration verbatim and casting at the use
site -- including a NEW variant: when the TU's prototype takes no argument and the call must pass
one, cast through a function pointer, ((void (*)(s32))func_8001C9D0)(a0). All 11 re-verified
MATCH afterwards, so the cast is byte-identical in every case.

R39 NC: a synthetic draft re-contradicting the TU is still dropped; the repaired slate is 11/11.
2026-08-15 09:35:26 -06:00
Drew T fb4c1f03a1 fix(phase-31): wave-selector coverage + gate_lane crash-vs-empty (S52-1, S52-8)
build_wave_atlas.py:
- taken-set was a hardcoded 'abcdefghijkl' wave-letter literal -> glob('.run/wave_*_cards.json')
  (R33 derive, don't re-derive). NC: 634 -> 726 taken, strict superset, +92 cards from waves m/n
  that the literal would have silently re-issued.
- --exclude-bins defaulted to 'main' on the REFUTED link-defect rationale; default now empty and
  the help states the real reason (main cannot be gated incrementally; use tools/gate_main.py).
- new --only-bins allow-list: main waves need it, since gate_main rebuilds once per SLATE.

gate_lane.py: an unhandled gate_stage exception produced no JSON line -> v={} -> '0 banked',
indistinguishable from an honest empty gate (cost 2 cycles in P31). Now a non-zero rc or a missing
JSON line is labelled CRASH with the stderr tail, recorded in the results JSON, listed in the
summary as NEVER GATED, and exits non-zero. R39 NC both directions.
2026-08-15 09:31:10 -06:00