Commit Graph

2446 Commits

Author SHA1 Message Date
Drew T b1a91c6b8c feat(decomp): worker gate — +33 fns x0 propagated (fleet 95.6%) 2026-08-17 01:03:22 -06:00
Drew T 001d8e2127 docs(phase-31): cookbook §186 — cross-jumping runs after sched2 (no barrier reaches it); s16-locals frame signal; local_alloc-vs-global_alloc as a placement lever 2026-08-17 01:02:31 -06:00
Drew T 81a3b73f22 feat(decomp): propagate func_8018A1B4 (gate lane) 2026-08-17 00:58:37 -06:00
Drew T 1b3fae2c1b feat(decomp): propagate func_80184BAC (gate lane) 2026-08-17 00:55:13 -06:00
Drew T fe5208a839 feat(decomp): worker gate — +30 fns x0 propagated (fleet 95.6%) 2026-08-17 00:54:31 -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 6c51bb8999 feat(phase-31): MoveImage absorbs its splat-carved epilogue SYS_OBJ_8F4 (49 ins); 143dbb89 byte-identical 2026-08-17 00:04:08 -06:00
Drew T ef80215dcb feat(phase-31): SIX-WAY FRAGMENT MERGE — func_8005B7B0 (184 ins) absorbs 5 splat-carved fragments; 143dbb89 byte-identical 2026-08-17 00:03:33 -06:00
Drew T a416d3adc3 feat(phase-31): func_80030730 + func_80059BFC banked (TU prototype adopted, cast at use) 2026-08-17 00:01:47 -06:00
Drew T 419bfa6174 feat(phase-31): final-lane recoveries banked — incl. SYS_OBJ_1DC0 (the byte-wrong redraft) and func_80034C24 (symbol identity); 143dbb89 2026-08-17 00:00:57 -06:00
Drew T 35803ece9a chore(phase-31): S53 log — recovery backlog banked 15, four tool defects fixed, three TU retypes proven 2026-08-16 22:55:19 -06:00
Drew T a670f9d24d docs(phase-31): cookbook §184 (comment-blindness as a defect class) + §185 (verify-cheap-side; check a TU retype at its use sites) 2026-08-16 22:54:35 -06:00
Drew T 2dbf235efc feat(phase-31): forward-typedef fix unblocks func_8002C8F4 + func_80034314 (EXE 143dbb89 byte-identical) 2026-08-16 22:52:39 -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 8b567a21a8 fix(phase-31): retype D_8007622C to s32[] in the TU (byte-neutral, 143dbb89) — unblocks func_80037028 2026-08-16 22:47:31 -06:00
Drew T c7ef83066f fix(phase-31): two byte-neutral TU declaration corrections — func_8001ABBC returns s32; D_80076240 is a Slot16A[] (verified 143dbb89) 2026-08-16 22:45:39 -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 b15d195cc0 docs(phase-31): cookbook §183 — the declaration-reconciliation playbook, the name/shape trap, and why &D_x defeats the cast escape hatch 2026-08-16 22:39:34 -06:00
Drew T 61f3745193 feat(phase-31): S53 recovery — 10 more main functions banked (declaration reconciliation; EXE 143dbb89 byte-identical) 2026-08-16 22:38:47 -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 0ceba4f8ee chore(phase-31): fleet progress report after S53 banks 2026-08-16 21:08:05 -06:00
Drew T fdbc522cf2 chore(phase-31): regenerated family/backlog reports after the S53 atlas + banks 2026-08-16 21:08:00 -06:00
Drew T af7ae913d3 chore(phase-31): S53 checkpoint — 75 banked / 7,400 ins, fleet 213/213, wave S prepared (not started) 2026-08-16 21:07:53 -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 dc7e62c834 feat(decomp): worker gate — +21 fns x0 propagated (fleet 95.6%) 2026-08-16 20:16:34 -06:00
Drew T 486321996c feat(decomp): propagate func_801808C4 (gate lane) 2026-08-16 20:06:04 -06:00
Drew T d27ba537f8 feat(decomp): worker gate — +36 fns x0 propagated (fleet 95.6%) 2026-08-16 20:04:51 -06:00
Drew T e960382de7 feat(phase-31): wave R — 18 main functions banked (EXE 143dbb89 byte-identical) 2026-08-16 20:03: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 bc669d5407 chore(phase-31): S53 preflight — atlas regen, leftover-draft harvest (34 free MATCHes found), wave R launched 2026-08-16 18:08:46 -06:00
Drew T 9202c5f54e chore(phase-31): S52 final checkpoint — 131 banked, main 293 matched, 5 new tools, cookbook 543->564 2026-08-16 17:46:01 -06:00
Drew T f8bf50d8c0 docs(phase-31): cookbook §179 — eight idioms mined from the wave P/Q journals (S52 harvest)
12 reader agents over 172 substantial agent findings from waves P and Q; 16 survived their novelty
filter; merging duplicates and dropping what §174-§178 already own left EIGHT levers. The
synthesizer re-read every gcc/maspsx/masked_diff citation and corrected three wrong line numbers
inline; I independently re-verified two of them (loop.c:3500 `src = SET_SRC (bl->init_set)`,
valid_initial_value_p at :3509, bl->initial_value at :3511; and maspsx's tab-literal `.set\t` test
at __init__.py:844-848) -- both exact.

  A. A loop-walked pointer PARAMETER hands its argument register to the giv (9 byte-proofs).
     record_initial records assign_parms' own incoming-argument copy as the biv's init_set, so
     emit_iv_add_mult re-references the HARD register in the preheader, extending $aN's live range
     and costing the parameter its hard-reg tie. Fix: body-local copy, or an identity re-tie whose
     ASM_OPERANDS SET_SRC makes valid_initial_value_p refuse. Explicitly paired with §70, which
     documents the SAME chain used in the opposite direction.
  B. The hand-written-asm transcription checklist (10 byte-proofs): `.set` needs a literal TAB or
     maspsx never updates is_reorder and re-nops every branch; `.ent` does not update it either;
     displacements must be DECIMAL (bare int() before the range check); maspsx's LOAD-delay nop is
     UNCONDITIONAL, so you manage branch-delay nops and must NOT manage load-delay ones; internal
     labels need a `.L` prefix or masked_diff truncates the function at the stray symtab entry.
     Also flags a real CONFLICT with the banked trampoline rules and gives the A/B to resolve it.
  C-H. No-epilogue functions must be file-scope __asm__; gte_stflg must clobber $12; a
     >2*MAX_MOVE_BYTES copy is a struct assignment; pinning a walked pointer disables strength
     reduction entirely; a pin can CREATE a combine LOG_LINK (n=1, marked as such); a mid-body
     .global pair slices a byte-comparable fragment.

Ends with a "considered and NOT banked" list -- including a softened claim where the reader's
derivation was not traced line-by-line, and a corrected symbol typo. Nothing silently dropped.
Cookbook 554 -> 564 sections.
2026-08-16 17:40:06 -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 49fc465a51 docs(phase-31): cookbook §178 + §176j-2 + §176k — mine the wave-P journals, the repair-pass yield, two selector bugs
§178 — SIX LEVERS FROM THE WAVE-P JOURNALS, each byte-proven and source-cited. Four wave-P repair
agents REFUTED the first pass's own diagnosis by dumping cc1 -dS/-da and reading gcc-2.7.2. The
meta-finding leads the section: "REGALLOC-PERM" is this project's most over-diagnosed class -- in
four functions the symptom was a register swap and the cause was in cse.c or sched.c, decided
BEFORE allocation, which is exactly why pins and statement order all failed.
  A. The $0-add OPAQUE COPY defeats cse.c:826 make_regs_eqv (a PLUS is not a (set reg reg)), so the
     parm pseudo keeps its register. MATCH on first compile; 3 of 5 pins then became dead weight.
  B. A `return <const>` is a priority-1 hard-reg set that the BACKWARD list scheduler places FIRST
     in the block, making hard $v0 live across a temp's range. Lever: goto a shared return tail.
  C. birthing_insn_p (sched.c:2469) boosts only single-set destinations; splitting a 3-set temp
     boosts the insn and drags its feeder chain down.
  D. NEW IDIOM: a NARROW destination type blocks copy elision (SI->HI cannot be coalesced), so the
     copy survives at its source position -- one type change worth ~20 instructions.
  E. The ZERO-OFFSET ALIAS HOLE: memrefs_conflict_p's find_symbolic_term path is only reachable for
     offset-0 fields, so an offset-0 store silently loses its dependence and floats.
  F. MEM_IN_STRUCT_P asymmetry in true_dependence (sched.c:817): struct-varying vs scalar-fixed do
     not depend. Struct-vs-scalar externs are a scheduling decision, not cosmetics.
  G. Two modelling traps: `sw $a1,SYM($a0)` is ONE cc1 insn (the lui/addu/store triple is gas -G0
     macro expansion, not cc1 output); and __asm__ __volatile__ with a memory clobber is a FULL
     barrier that also sinks address chains.
Plus the exhaustion result: 2,240- and 5,040-variant statement-order sweeps moved nothing, because
the schedule was DAG-determined. When order does not matter, look for an alias or set-count
property, not a permutation.

§176j-2 — THE REPAIR PASS, MEASURED: 12 of 39 recovered / 579 ins, taking wave Q from 51 matches
(3,631 ins) to 64 (4,245). Closeness must be COUNTED, not read off the first differing index (my
first measurement reported six "closeness 0" drafts that were actually truncated).

§176k — two silent selector bugs: ranking gate groups by MEMBER COUNT collapses a wide band to the
smallest functions when the gate cost is per-slate (60 cards/2,604 ins chosen where 46/4,829 were
available); and a selector that globs its own output counts the previous attempt as spent (pool
106 -> 46). Any derive-from-disk rule must exclude the artifact it is about to produce.
2026-08-16 14:23:19 -06:00
Drew T 4adb559a29 docs(phase-31): cookbook §177 — the epilogue return-delay slot is decided by the SAVED-REGISTER SET
Eleven wave-Q functions in 800c/800c3 sat at closeness 1-3 with the same epilogue residual, and
every agent independently filed it as intrinsic ("epilogue-delay-slot-unfillable", "gcc/maspsx
structural wall"). It is neither intrinsic nor a scheduling problem.

Source-confirmed at gcc-2.7.2/config/mips/mips.c:5376 --

  int mips_epilogue_delay_slots () {
    if (current_frame_info.total_size == 0) return 1;                        /* no frame  */
    if (current_frame_info.mask == RA_MASK && current_frame_info.fmask == 0) return 1;  /* only $ra */
    return 0;
  }

gcc offers the epilogue a delay slot ONLY when the function allocates no stack, or saves nothing
but $ra. Otherwise the slot is never offered to the scheduler and the emitter puts the stack
restore there instead (mips.c:5276, the tsize > 0 path).

So the lever is the CALLEE-SAVED SET, steerable from C: the first value whose live range spans a
jal costs an $s register and flips the switch. To gain a filled slot, hold nothing across a call
(recompute or re-read after it); to lose one, hoist a load above the call. Register pins are the
WRONG tool here -- §176-C already established a pin cannot schedule across a call.

~600 instructions were three instructions from banked and about to be written off. The meta-lesson
(R17): when N independent agents call one residual "structural", read the compiler -- the answer
was forty lines of mips.c already sitting in tools/reference/gcc-2.7.2/.
2026-08-16 14:17:09 -06:00
Drew T c70f6d4e34 chore(phase-31): S52 closed — 91 banked, wave Q drafted-but-unbanked with a $0 resume path 2026-08-16 10:08:25 -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 8534c95321 chore(phase-31): S52 final checkpoint — 91 banked, R22 213/213, the UNKNOWN unlock + the 6k doctrine + reconcile-before-gating 2026-08-15 22:12:32 -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 071ce43e1c docs(phase-31): cookbook §176h — the batch-substitution hazard map (S52)
Wave P drafted at 97% and then cost a dozen clean rebuilds to bank, and not one of those rebuilds
failed on a matching problem. Banks the whole failure surface:

A. The SEVEN under-reporting holes in gate_main, all the same shape (R32): the checker never read
   the destination TU, shared headers, a draft's own definition, lines with trailing comments,
   typedef aliases, the build's own error text, or file order. Law: audit a batch-integration tool
   for what it DOESN'T look at -- its verdicts can be correct on the inputs it reads and still be
   worthless, because the compiler reads more.

B. Typedef handling, with the two wrong strategies that both look right: blanket STRIP (assumes
   the surviving definition sits above the insertion point -- src/800.c defines Rec14 at 7336
   while stubs wanting it sit at 7272), blanket RENAME (breaks drafts sharing an identical
   typedef, because their externs stop agreeing -- my regression, three drafts at once), and the
   rescan loop that deletes the definition it just renamed. The survivor is body-aware +
   position-aware in a single pass over a snapshot.

C. The remaining limit: conflict detection compares spelled type NAMES, so three drafts each
   defining their own Slot54 with different layouts all declare func_80032A74(Slot54*) and compare
   equal. Comparing struct LAYOUTS is the real fix.

D. The measured cost shape -- drafting cheap and solved, integration expensive -- and therefore the
   next lever: a STATIC pre-gate check over the substituted text, no make at all. Plus the R39
   lesson that negative controls apply to the tool you are FIXING, not just the one you ship.
2026-08-15 21:59:52 -06:00
Drew T ec42c2116c feat(phase-31): worker gate — +8 fns x0 propagated (fleet 95.5%) 2026-08-15 21:56:44 -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 34a4d63d4e feat(phase-31): worker gate — +4 fns x0 propagated (fleet 95.5%) 2026-08-15 14:02:43 -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 279037ece6 docs(phase-31): cookbook §176f — the declaration FORM is a matching lever; reconcile toward the form the match needs (S52)
Wave O: six drafts in one TU referenced D_80078D88, three declaring it scalar and three as an
array. One draft's own comment explained why the array form is load-bearing: with a scalar decl the
global load is a plain symbol_ref and sched1 HOISTS the lui/lw above a store; declaring it as an
array makes gcc-2.7.2 alias.c treat the access as possibly-aliasing and the hoist stops.

So §176b/§176d's 'pick one form and cast at the use site' is wrong for a scalar/array clash. Array
is the STRONGER form -- scalar users adopt it for free by indexing [0] (byte-verified on all three)
-- while forcing an array user to scalar can re-enable the hoist and break the match. Reconciling
toward the array form took the slate from 42 -> 37 compatible (5 dropped) to 42/42, every converted
draft re-verified MATCH. Waves J/K/L each lost 5-10 drafts to the greedy keep-first rule.
2026-08-15 11:16:43 -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