Commit Graph

143 Commits

Author SHA1 Message Date
Christopher Williams 9729dd0ed7 phase12: match func_80102F58 (686 bodies / 695 regions) 2026-09-24 19:53:00 -04:00
Christopher Williams ae49fc4528 phase12: final tree cleanup -- worklist regeneration and a worker WIP file removal 2026-09-24 19:37:55 -04:00
Christopher Williams 603e221156 phase12: ledger verified clean at the stop (0 held-and-unregistered) + C's casing trap, and an unverified WIP file committed only for a clean tree 2026-09-24 19:37:04 -04:00
Christopher Williams 00f951d502 phase12: SESSION STOP at 684 bodies / 693 regions (+82) -- clean tree, handover in the ledger
Gate exit 0 (693 regions, MATCH), 344 tests OK, make check exit 0, tracked patch regenerated so
pristine + patch reproduces the working tree.

+4 this commit (worker D): 0x80100334, 0x800FBDC0, 0x800F88F0 (all maspsx=epilogue), 0x80042DD4.
Phase: 602 -> 684 = +82. Milestone 750 was not reached; +66 remains.

TWO TOOL INCIDENTS OF MINE, both reported by workers before I saw them, both repaired:
 1. I edited tools/sf3_match in place while four workers verified against it. The new add_argument
    block landed between `--fill-epilogue` and its help=, raising IndentationError at line 1129 --
    EVERY worker's verification failed for the duration. A verified with a probe copy and re-verified
    with the official tool once it parsed; D reported it independently.
 2. Reverting an untested transform, my scripted edit DELETED `_LOAD_RA`, the load-delay regex
    fill_epilogue needs, breaking maspsx=epilogue for all 21 regions carrying the token. The
    whole-binary gate caught it immediately (exit 2, no result line) and it is repaired at the point
    of damage.
Guard adopted for every future tool edit: ast.parse immediately after saving, and one atomic write
rather than a scripted multi-step edit. A worker cannot tell a transient tool break from a broken row,
and the cost lands in their context.

ONE DEFERRED ITEM COSTS A BODY: `gp=-` is SINGLE-VALUED per region. D's 0x80102F58 (60 B) needs
D_801221A8/AA/AC registered with gp markers (proven: 60/0 with, 72 without), but A's merged 0x800FFF60
needs those same three ABSENT from the registry because it uses them as SYMBOLS with per-site macro
expansion. Measured: adding the rows breaks A's 140 -> 124; adding them AND giving A three gp=- overrides
restores A to 140/0 -- but the parser refuses with `duplicate override key 'gp'`. The resolution is
proven; only the option key's multiplicity blocks it. Cheapest remaining body in the phase.

The la SYM+N harness transform the developer directed was implemented and REVERTED, because it did not
close its target and an untested transform in the shipped tool is worse than a documented design. Its
mechanism, its guard, where it belongs in the pipeline, and the open question about WHICH STAGE's text
it must rewrite are all in the ledger so it can be resumed without re-deriving.

Also recorded: the gp-rewrite bug worker A found and I fixed (a stale %hi from an unrecorded register
redefinition); the compiler-boundary class (3 rows no vendored cc1 can build); the per-region codegen
flag policy; D's measured tier finding (4/4 with a call or a frame, 0/4 on leaves); and the reading
rules discovered this session, each with at least two instances.
2026-09-24 19:34:42 -04:00
Christopher Williams 12ac628ea4 phase12: merge 28 (679 bodies) + the per-region codegen-flag policy, and A's semantics-not-codegen correction 2026-09-24 19:20:33 -04:00
Christopher Williams 656e78d9f1 phase12: merge 27 (677 bodies) + D's exit-shape rule, the verified D_801219D4 symbol, and the nested-comment trap 2026-09-24 19:17:23 -04:00
Christopher Williams 4588107cd7 phase12: merge 26 (674 bodies) + the epilogue token is a per-row property invisible in a length check 2026-09-24 19:14:00 -04:00
Christopher Williams 354e90d5e5 phase12: merge 24-25 (670 bodies) + worker A's sf3_match gp-rewrite bug, found and fixed
**670 bodies / 680 regions. Phase: 602 -> 670 = +68.**

**+3 bodies, all verified by me from fresh --work dirs at their REAL extents:**
  0x80107CCC (76 B, cc1bin=gcc-2.8.1-psx) -- A's row, unlocked by the tool fix below
  0x8002D060 (72 B, D)   0x8006FAEC (88 B, D)
And +2 earlier this round: 0x8004D7C8 (220 B, B), 0x8003A9C8 (240 B, B).

**WORKER A FOUND A REAL BUG IN tools/sf3_match AND I FIXED IT.** `rewrite_gp_accesses`'s `_GP_LO`
branch rewrote `lw $2,%lo(SYM)($R)` to `lw $2,%gp_rel(SYM)($gp)` and then `continue`d WITHOUT recording
that the rewrite redefines its destination register. `carrying` kept the stale `%hi`, the register's
next use was scored as an unrewritten use, and the `%hi` was deliberately kept -- so the region came
out exactly 4 bytes long, one instruction.

I reproduced the evidence independently before touching the code, in 0x80107CCC's pre-fix output:
    lui   $2,%hi(D_801221CC) # high
    lw    $2,%gp_rel(D_801221CC)($gp)
The same failure the docstring says was fixed for the adjacent-lines case, still live for the
load-whose-result-is-used-later case. One-line fix, A's reasoning and name in the comment, plus why it
stayed invisible: nothing breaks until a gp-marked LOAD feeds something.

Measured: 80/LENGTH-MISMATCH -> 76/0/MATCH. **The tool change was GATED, not trusted**: the pass
touches every gp region, so the full gate was re-run -- c_regions=678, differing_bytes=0, MATCH. That
also answers A, which asked whether the four merged cc1bin rows needed re-verification; the gate is
what proves it and it was green.

**AND WORKER D FOUND THE SAME PASS FROM THE OPPOSITE DIRECTION** on 0x800FFBBC: cc1 2.8.1 puts a safe
`lui $2,%hi(...)` in a branch delay slot, the rewrite folds the hi/lo pair to gp-relative, the lui
DISAPPEARS and the store slides into the delay slot (48 -> 44). So A's case is the rewrite failing to
fire and D's is the rewrite firing and changing a schedule -- two faces of one pass, on two rows, found
by two workers who could not see each other's row. D's framing is the open item: a maspsx mode that
re-derives the delay slot after the gp-relative rewrite would likely close a class.

**cc1bin's measured boundary, and a correction to my own guidance.** B swept the compilers: 2.5.7 /
2.6.0 / 2.6.3 / 2.7.2 / 2.7.2-cdk / 2.7.2-psx give a MERGED epilogue; 2.8.0-psx / 2.8.1-psx give
SEPARATE jr $31 per return. So return merging is a 2.7.2-family behaviour. BUT 2.8.x also changes the
GLOBAL ACCESS FORM (explicit lui %hi / lw %lo pair instead of the symbolic load maspsx turns into
lw v0,2080(gp)). **The lever moves two axes at once and is not free** -- which corrects what I told A,
B and D when routing the multi-exit rows.

**0x80107D7C sits BETWEEN the compilers and is a genuine open question.** B: default 112, 2.8.1 104,
target 108. I swept all TEN vendored builds because B's table listed eight: only 2.7.2-cdk produces 108
bytes, and it is 30 bytes off in content. 2.91.66 and 2.95.2 give 100. So the length is reachable and
the content is not, on any vendored compiler.

**Two corrections of record, both mine:** I guessed extents instead of reading
config/function_extents.tsv (testing A's 76-byte row against an 80-byte extent and reporting
LENGTH-MISMATCH from a correct candidate); and I told three workers cc1bin was a clean lever for the
multi-exit rows when B's sweep shows it moves two axes.

**Ruling on A's per-region codegen flag request (0x800AB504): (b) class-bound, with the diagnosis
kept** -- because `-fno-strength-reduce` gives 152 against a 148 target. A flag that closes a row is a
lever; one that moves a row 16 bytes closer to a different number is a diagnosis. But the PRINCIPLE is
recorded for the next such row: a codegen flag is admissible on the same terms as cc1bin, i.e. a named
mechanism in the bytes, and A has one (address normalisation creating a second loop-carried pointer).

**D: the indexed and pointer loop forms COEXIST and neither carries across rows.** 0x8002D060 matches
only as an INDEXED loop; 0x80107B40 wants the pointer form. A direct refutation of pattern-carrying
from the worker with the most reason to trust a pattern. Also: the both-sides-volatile rule TRANSFERS
(0x8006FAEC), with the narrowing measured (only interleaved accesses need it); and READ the displacement
and compute the symbol rather than inferring it from an adjacent symbol's name (gp+0xBA4 -> 0x801224DC).

**Charter note: `--cc1-flag=X` REPLACES the default `-quiet -O2 -G0` set**, so a diagnosis run must
pass all four explicitly. A's first -fthread-jumps probe read 200 B purely because -O2 was dropped.
2026-09-24 19:11:38 -04:00
Christopher Williams 58ff408f9f phase12: merge 23 (667 bodies) + five multi-exit rows ARE gate-permitted for cc1bin
MERGE 23: +2 bodies from worker D's un-attempted band, both re-verified from fresh --work dirs:
0x800582AC (64 B) and 0x800A82D0 (64 B). Gate c_regions=676, differing_bytes=0, MATCH.

DISPATCH CORRECTION, and it came from checking a claim rather than forwarding it. D reported three
multi-exit rows as unclosable because 'my multi-return probes share ONE exit under all ten cc1 builds'.
Measured from the binary: ALL SEVEN unregistered multi-exit rows are >=2 jr $31, which is the gate's
precondition for naming an alternative cc1 -- so the restriction PERMITS the lever on every one, with
four merged rows as precedent.

And C's measured row refutes the probe conclusion directly: 0x801008DC is 3 exits and the default gives
the correct 88 bytes with 58 DIFFERING bytes because it merges all three returns, while
cc1bin=gcc-2.8.1-psx gives 88/0/MATCH. The merge is SHAPE-dependent, and a synthetic probe speaks only
for the shape it probes. I made exactly this error earlier in the phase with a probe-dependent
mechanism written into tools/sf3_match.

Rows routed: A gets 0x80107CCC (3 exits, A has the diagnosis), B gets 0x800FF6DC / 0x80100998 /
0x80107D7C, D gets 0x800FFBBC.

Also recorded: D's self-caught harness hazard -- its run helper leaves the LAST variant on disk, so its
first verification of 0x800A82D0 ran against the wrong variant and report.tsv's md5 described it. D
re-verified from a fresh directory and made the re-write explicit in its procedure. This is the failure
mode that would produce a FALSE MATCH CLAIM rather than a missed one, and it is invisible from outside
because the report's own md5 agrees with the wrong file.
2026-09-24 19:01:41 -04:00
Christopher Williams 6109c3b0a9 phase12: record my git-add-src defect (benign this time, measured) + worker A's loop-invariant-motion class
My per-merge command used `git add -A $(git status --porcelain src/)`, which adds EVERY modified or
untracked file under src/ -- and four workers write there concurrently. One merge commit therefore
carried three files I had not verified: an edit to src/func_80013114.c, a new src/func_80085B44.c, and
a 20-line DELETION from src/func_800FBF5C.c.

Measured, not assumed: all three are UNREGISTERED, so no region references them and the full gate
re-run on the committed tree is c_regions=672 / differing_bytes=0 / MATCH. Also committed here: the
deletion of src/func_80013114.c, which a worker made after I had committed the file, so that HEAD and
the working tree agree again.

The dangerous variant is one edit away and did not happen: if a worker edits a REGISTERED region's
source mid-merge, that edit is committed and the gate verdict from moments earlier no longer describes
the committed tree. Rule from here: a merge adds the named claim sources plus config/docs/tools, never
src/ wholesale.
2026-09-24 18:55:23 -04:00
Christopher Williams 50270a95f6 phase12: merge 20-21 (663 bodies) -- worker B's maspsx predicate patch closes a released row
**+4 bodies: 0x80102A80 (132, B, maspsx=epilogue), 0x800FFF60 (140, A, maspsx=epilogue),
0x80102A00 (128, B's source + the NEW maspsx=regread predicate), and the positioning is now
663 bodies / 672 regions. Phase: 602 -> 663 = +61.** Gate byte-exact at every merge, SHA-1 unchanged,
make check exit 0, 344 tests green.

**THE HARNESS PATCH IS THE HEADLINE, because it turns a class into a predicate.** Worker B reported
`0x80102A00` as an UNCLOSABLE row with the mechanism pinned to the harness's own `--work`
intermediates, and refused to touch `tools/` -- correct, since it could not know the default path's
invariants. Its diagnosis: finding 27's gap was fixed by `line_jumps_via_reg` (a load feeding a
REGISTER JUMP), and **this row is the complementary case in the opposite direction -- the jump
ignores the register, but the instruction in its DELAY SLOT reads it.**

    lw  $2, D_8011FD2C      <- loads $2
    jal func_80103FCC
    sb  $0, 0($2)           <- the SLOT FILLER reads $2

cc1 emits that with no `#nop` and no marker, correctly by the documented load-to-use rule (gap of 1).
**ASPSX was conservative ACROSS a jump** -- the slot filler is part of the jump -- and maspsx's own
trace says so: `#nop # DEBUG: 'jal func_80103FCC' does not load from $2`. It declined the row for
exactly the reason the original accepted it.

Implementation: `_jump_slot_filler_reads_reg`, on the **existing opt-in** `--nop-on-reg-read` /
`maspsx=regread` token, so the default path is byte-identical and the green gate cannot move. Confirmed
safe rather than assumed: **no registered region named that token before this commit.** Restricted to
`j`/`jal` -- `jr`/`jalr` slot fillers are the same shape but UNMEASURED, and an opt-in predicate whose
whole purpose is default-identity has no place for an unmeasured widening.

Measured, on the source unchanged: **default 124 LENGTH-MISMATCH -> `maspsx=regread` 128 / 0 differing
/ MATCH.** So B's "unclosable" row is a body, and B's candidate source is now `src/func_80102A00.c`
with a header recording that the token is REQUIRED and must not be "cleaned up".

**THE PATCH WAS REGENERATED IN THE SAME COMMIT, which is the whole point of the discipline.**
`tools/patches/maspsx-phase10-r1r2.patch` went 229 -> 289 lines, and all 7 `test_maspsx_patch` tests
pass -- so pristine + patch reconstructs the working tree byte-for-byte and a fresh clone can still
rebuild the gate. Phase 11's `ea51ac9` did exactly this correction WITHOUT regenerating the patch and
left a fresh clone broken; that is why the test exists and it is why this was done in one commit.

**Cookbook, two new entries:**
* **61c -- THE REGISTRY DECIDES THE SPELLING.** A closed two rows today in OPPOSITE directions and the
  deciding fact is the registry entry, not the code's shape: `0x800FB758`'s address IS `gp`-marked and
  the original writes it absolutely, so the source must use the LITERAL (a symbol lets the harness
  rewrite to `%gp_rel`); `0x800FFF60`'s three addresses are NOT registered, so the source must use
  SYMBOLS (as literals the address becomes a value and cc1 CSEs it -- the index's `cc1=-G4` attempt was
  chasing the wrong thing). Rule: check the registry first. This is the source-side companion to
  finding 46.
* **61d -- the `nop_on_reg_read` second gap**, above, with the trace line as the receipt.

Also recorded: worker A's `0x80011484` is no longer the index's "UNRESOLVED" row -- A derived it from
the bytes as an octagonal distance approximation (`s=|dx|+|dz|`, `d=||dx|-|dz||`, then `d<h -> s-q`,
`h+q<d -> s`, else `s-(s>>3)`) with both absolutes as SWAPPED SUBTRACTIONS, and the build is 120 with
a scheduler residual. And A's `0x8002FB54` gave the phase a new lever type: **a DUPLICATED MASK is
evidence of the variable's WIDTH** (`unsigned char r` vs `int r &= 0xFF`), because a byte-typed object
masks at every read and no placement can fake that.
2026-09-24 18:53:38 -04:00
Christopher Williams dd412fe0b1 phase12: merge 19 (660 bodies), the twin TYPES refinement, and the 24-byte dual-cause trap 2026-09-24 18:48:48 -04:00
Christopher Williams 98a15efca5 phase12: merge 18 (659 bodies) + cookbook 61b: a duplicated mask is a WIDTH fact, not a schedule 2026-09-24 18:47:53 -04:00
Christopher Williams f1b840a177 phase12: merge 17 (658 bodies) -- maspsx=moves closes the 'mutual exclusion' class, first proven instance of finding 61 2026-09-24 18:46:18 -04:00
Christopher Williams 3cd0f91515 phase12: merge 16 (657 bodies), three-worker per-site gp evidence, C's decline endorsed, A's cross-jump test 2026-09-24 18:45:28 -04:00
Christopher Williams 401c92558e phase12: merge 15 (654 bodies), C's finding-44 direction, and the 3-of-6 reachability expectation 2026-09-24 18:42:41 -04:00
Christopher Williams ca5eb6e613 phase12: merge 12-14 (652 bodies) + the phase's first inline-asm row + charter s7's not-counted row
MERGES. +13 bodies across rounds 12-14, each re-verified by me from a fresh --work dir first:
  0x80042CE0 (80, D)  0x80018284 (80, A)  0x80094370 (84, A)  0x8002DF1C (104, B)
  0x80065494 (80, C)  0x800C3514 (88, C)  0x801097A0 (128, D)
Gate every time: differing_bytes=0 result=MATCH, SHA-1 unchanged. make check exit 0.
**652 bodies / 662 regions. Phase: 602 -> 652 = +50.**

**A DEPENDENT ROW WAS RE-VERIFIED WHEN ITS DEPENDENCY LANDED, and this is a new rule.** Worker C's
`0x80091490` was merged as a DEPENDENT claim whose callee `0x80018284` was unreconstructed, with a
3-argument prototype INFERRED from the call site. Worker A then reconstructed `0x80018284`. Merging it
changes `config/symbols.tsv`, which is the environment that row was verified IN -- so I re-ran
`0x80091490` after the merge: still `differing_bytes=0 result=MATCH`. The arity check I promised:
A's `int func_80018284(int *a0, int a1, struct V8 *a2)` vs C's inferred `(int, int, int *)` --
**same arity (3)**, differing only in pointee types on args 1 and 3, which are **byte-invisible** at a
pass-through and a literal-0 site. So the inference held. Rule recorded: **a dependent claim must be
re-verified whenever its dependency is merged**, because the symbol set is part of its input.

**`0x801097A0` -- THE PHASE'S FIRST GENUINE INLINE-ASM ROW IS IN.** Worker D needed seven spellings and
each of the four asm properties is a *measurement with its counter-shape*, which is what makes this an
authorised use rather than a shortcut:
 1. `$31` cannot be WRITTEN from C. `register int ra __asm__("$31"); ra = ...;` makes cc1 keep a frame
    (132 B) and DELETES the assignment -- reading `$31` is a documented binding, writing it is not
    expressible. That is the "provably cannot express" clause demonstrated rather than asserted.
 2. The call must NOT be a C call: as a C call cc1 preserves `$31` itself and adds a prologue and an
    epilogue (140 B), while **the original has NO FRAME AT ALL**. Written as an asm `jal`, cc1 never
    sees a call. This single fact is what makes the row expressible.
 3. Zero operands must be `$0` LITERALLY: `gte_ldOFX(0)` passes `"r"(0)`, cc1 materialises a register,
    and the original's `ctc2 zero,$24` becomes `move` + `ctc2`.
 4. The constant register must be pinned to `$8` (cookbook 160); otherwise the seven constants land in
    `a0` (10 differing bytes = five `li` plus five `ctc2` register fields).
Also measured: maspsx supplies the jump-slot `nop` after the asm `jal` (writing one explicitly gives
132 B -- two nops), and the CP0 constant is `0x40000000`, not `0x4000`: bit 30 is the COP2-enable bit,
and `0x4000` compiles to `ori` and does not match.

**I wrote `include/gtemac.h` myself, because worker D refused to and was RIGHT.** D's charter section 4
forbids it from writing under `include/`, and my authorisation conflicted with the charter. D held its
write scope and handed me the macro text with the note that "I followed the charter because it is a
file and it is unambiguous". **That is the correct resolution of a coordinator error and it is the
behaviour I want**: the charter is a file, it was unambiguous, and D did not quietly exceed it. The two
macros (`gte_ldZSF3`/`gte_ldZSF4`, $29/$30) are written in exactly the form of the existing entries,
with the evidence in the comment -- the NUMBERS are the evidence, not the register names. The open
question D raises is recorded for the next charter: whether `include/` should be a worker write scope
or whether worker-authored macro text passed to the coordinator is the right division of labour.

**CHARTER SECTION 7'S NOT-COUNTED ROW IS NOW EXCLUDED BY NAME, and this one is my miss.** Worker C found
`0x80107C5C` (112 B) in its pool and skipped it because section 7 says it "matches but has no
documented source -- it is NOT counted; do not claim it". **I had READ that sentence earlier in this
session and did not act on it.** A worker who did not know the sentence would have produced a body the
phase does not count. It is now in `NAMED_EXCLUSIONS` -- the one place an exclusion lives -- with a
comment explaining that its reason differs from the rest of the list.

**`sf3_free` CRASHED, worker B caught it, and the fix is in.** My `--claim` addition used `free` and
`failed` without initialising them, so **every FREE address raised NameError and exited 1**. Worker B
reported the reproduction and, critically, the asymmetry that makes it matter: **the crash happened
AFTER the `FREE` line was printed, so a worker reading stdout saw the right answer while `$?` said 1**
-- a false BLOCKED for anyone checking the exit code or running under `set -e`. The harmless direction
of the two, but it costs a row per occurrence and it would have looked like a ledger problem rather
than a tool problem. Fixed (`free: list[int] = []`, `failed = False`), B's exact reproduction now
exits 0, and the 25-test suite passes. **The test suite caught this on the very change that introduced
it** -- recorded because that is the tests earning their keep, and because the tool whose whole purpose
is to be trusted unread is the one that must never be wrong about its exit code.

**`--claim` added so check-and-claim is ONE step.** The race is real and worker B hit it: B appended its
`wip` row in the same shell line as the check, *before* reading the output, so the ledger's last row for
`0x80042CE0` said B while D held it. B caught it, did not touch the row, and restored D's hold by
appending the row back (last-row-wins). **In the safe direction** -- B's own row masked the true holder
as TAKEN -- but the inverse ordering, appending on a stale read, is how two workers end up on one
address. `--claim WORKER` now appends only if every address is free, and a refusal writes nothing.

**Worker C found the FOURTH row where the class name pointed at the right place and the mechanism was
not what the note said.** `0x800C3514`'s note described "all stack loads hoisted, beqz+nop+li groups";
the lever was **ARITY** -- the function takes SIX `unsigned char` parameters, the fifth and sixth
arriving on the stack (`lbu 16(sp)`/`lbu 20(sp)`), which the note read as hoisted loads. Measured: the
four-argument spelling gives 56 bytes, twelve short, with neither stack load present. The tally is now
`alloc-tiebreak` -> a pass-through argument (finding 164) | `constant-materialisation-order` -> source
statement order | `return-merge` -> a literal twin and a compiler revision | `alloc+layout` -> arity.
2026-09-24 18:39:16 -04:00
Christopher Williams 9053a4dc51 phase12: merge 11 (646 bodies / 655 regions) + sf3_free: claimed is HELD, not FREE
MERGE 11: +7 bodies. All seven re-verified by me from fresh --work dirs BEFORE merging:
  0x800A6B38 (104 B, D)        0x8010036C (56 B, A, maspsx=epilogue)
  0x800690E4 (68 B, A)         0x800FE970 (84 B, C, maspsx=epilogue)
  0x80022E44 (52 B, B)         0x80047468 (72 B, B, gp=-D_80121BFC)
  0x80042D88 (76 B, B)
Gate: c_regions=655 differing_bytes=0 result=MATCH, SHA-1 unchanged. make check exit 0.
Registry: 646 distinct bodies, 0 missing sources, 4 carry cc1bin. Phase: 602 -> 646 = +44.

**sf3_free: a `claimed` ledger row is HELD, not FREE.** Worker D found this and reported it rather
than changing a tracked tool on its own judgement, which is the right instinct. The old rule was
`wip` -> TAKEN, else FREE, so a row whose last ledger row was `claimed` -- a worker holding a
STAGED, UNMERGED claim -- read as free. That is a false FREE in the dangerous direction: the row is
neither registered nor abandoned, so the next worker re-derives it and the merge can then receive two
claims for one address.

**This was not hypothetical. It is exactly what the credit outage produced**: all four workers
stopped with claims in flight, and I re-dispatched the pools underneath them. I recovered those rows
by auditing the staging files, but that was luck of ordering, not a mechanism. D reported the two
live instances it could see (`0x800460AC` held by B, `0x800FE970` held by C). `0x800460AC` I had
already merged, which masked the first one; `0x800FE970` was live when D wrote.

A registered row never reaches the changed branch -- `region_owner()` returns TAKEN first -- so the
new rule only ever fires for a claim whose merge has NOT landed, which is precisely the window at
issue. Measured after the fix:
    0x800FE970  TAKEN by inflight (claim staged, merge pending)
    0x80012A98  FREE (last ledger row 'released')      <- a genuine release still reads FREE
    0x800FECF8  FREE (last ledger row 'released')
    0x800ACA10  FREE (last ledger row 'released')

The residual risk is the opposite direction and is now an orchestrator DUTY, recorded here: if a
claim is ever REJECTED rather than merged, the merge path must append a `released` row, or that
address stays held forever. Nothing has been rejected so far this phase.

Also recorded from this batch:
* **B's `0x80047468` is the second worked case of cookbook 46's per-site override**: the registry
  marks `D_80121BFC` gp, but this row reads it absolutely (`lui v1,0x8012 / lw v1,7164(v1)`), so
  without `gp=-D_80121BFC` the candidate is one instruction SHORT (68 vs 72). C independently hit the
  same class on `0x800A6658` with `D_801226E0`. **The registry's gp marker is per-SYMBOL and the
  access form is per-SITE**, and two workers in one hour each lost time to that.
* **B and C independently converged on the same conclusion about what these negatives are worth:**
  B -- "the recorded class named the SYMPTOM (a merged store, a folded shift, a strength-reduce)
  while the fix was an ORDER or a claim-row option"; C -- "in every one the recorded class NAME was
  right and the recorded MECHANISM was half wrong". Two workers, different partitions, same finding.
  That is the strongest argument yet for dispatching onto named negatives, and simultaneously a
  warning: **the class is a signpost, not an instruction.**
2026-09-24 18:30:21 -04:00
Christopher Williams 7090663275 phase12: sf3_negpool -- kill the five defects that reached the workers, and merge 10 (639 bodies)
MERGE 10: +4 bodies -> 639 bodies / 648 regions. All four re-verified by me from fresh --work dirs
with --symbols first: 0x80099E34 (40 B), 0x800AC9D8 (56 B), 0x800460AC (40 B), 0x80045540 (56 B).
Gate: c_regions=648 differing_bytes=0 result=MATCH, SHA-1 unchanged. make check exit 0.

The tool, and why it exists. The Phase 12 re-dispatch put all four workers on the ~193-row
classified-negatives index. I generated those pools with a one-off script, and that script was
wrong in FIVE ways. Every one of them shipped, and **every one was found by a worker, not by me**:

 1. The index documents its own schema on line 2 -- `# Columns: address<TAB>size<TAB>status<TAB>class`
    -- and my script **never read `status`**. 5 rows `blocked` + 1 `blocked,deferred` are rows
    charter section 7 forbids attempting (trapping arithmetic, the 0x80012xxx primitive-init family,
    the maspsx rare-epilogue mutual exclusion). **Six forbidden rows went out at prio 1, the top of
    the list.** Worker C found three in its own pool and refused them.
 2. `status` is not enough either: `0x80012A48` is status `near-match` with class `primitive-init
    scheduler-bound family`, so it reached prio 1 as well. Blocked families are now matched on the
    CHARTER's own wording, in both the status and the class column -- 7 rows dropped.
    Deliberately NOT blocked: `rare-epilogue-ORDER`. Worker A drew that line precisely (the order is
    work; only the maspsx mutual EXCLUSION is blocked), and cookbook 140/147/165 shipped
    `maspsx=epilogue` for it.
 3. The Makefile's `NAMED_EXCLUSIONS` (12 rows) was invisible to my script: **8 of the 12 leaked back**
    as fresh work -- $gp-switch thunk halves, a fragment, false extent starts. Worker A found two and
    asked whether they should be filtered. Eight were leaking, not two. The list now lives in ONE
    place (`NAMED_EXCLUSIONS`), and both `worklist` and the new `negpool` target are given it.
 4. The workers' own `negatives.tsv` staging was invisible: **94 of the 115 rows they had already
    classified were served as fresh work, at the TOP of the prio-1 order.** Worker C: "they are the
    first four rows in the file's own prio-1 order, so a worker starting at the top spends its first
    hours re-deriving my floor." 58 rows dropped as classified-with-no-mechanism (a second worker
    re-deriving a row that already yielded nothing is pure duplication), and a worker's OWN
    classification is dropped from that worker's own pool (12 rows) while still being dispatched to
    the others, who get the recorded mechanism as a lead.
 5. `named = class not in ('-', '')` is a test for a NON-EMPTY STRING, not for a mechanism. Worker D's
    staging labels 56 of its 92 rows `no-mechanism-yet` and 6 more `no-extent`, so **62 rows were
    ranked prio 1, "cheapest, mechanism already named", when their label says the exact opposite.**
    That is why worker A's pool led with rows it could not close. `names_a_mechanism()` now rejects
    absences, numbers and bare status words, and accepts prose (workers A and C write their class as
    a sentence, so a token-only rule would have missed their mechanisms).

This is a SCHEMA error, not a logic error, and it is worth naming as such: five separate bugs all
trace to generating a dispatch file from a table whose header I had not read.

One coordinator aside that belongs in the record: I had already "fixed" defect 3 by hand, adding
`--exclude 0x8001DC20` to the `worklist` target -- and **the counter caught me**:
`excluded_named_exclusion` did not move, because that row was already excluded from the worklist
under a different rule and had reached the worker through the negatives pool instead. Fixing one row
by hand while the generator leaked was treating a symptom, and the tool said so.

Pools: 176 unregistered -> **93 rows** (a 24 / b 23 / c 28 / d 18), every exclusion and blocked row
gone. C's pool now leads with `index-only` rows, i.e. genuinely fresh work.

Tests: 17 new, `tools/tests/test_sf3_negpool.py`. Fail-first is demonstrated rather than asserted --
the two INTEGRATION tests (which read `NAMED_EXCLUSIONS` out of the Makefile rather than hard-coding
addresses, because the whole defect was two lists that must agree living in different files) were run
against the pools the workers were holding at that moment and **failed, naming all 8 leaked rows**.
Two further bugs found in the process: my own `names_a_mechanism` accepted the numeric dashboard
columns (`'1'` is not a mechanism), which made the rule vacuous and dropped 0 rows instead of ~62; and
one of my tests asserted output that its own `--quiet` flag suppressed.
2026-09-24 18:27:39 -04:00
Christopher Williams 6da840f3ab phase12: recovery merge 9 — 635 bodies / 644 regions (+33, past the cycle-1 checkpoint)
All four workers stopped when the account ran out of credit. This commit recovers every row they
had produced and verified but not yet reported, and it is the first thing done on resumption.

RECOVERY AUDIT, because work that was produced but never claimed is INVISIBLE to the merge flow
(charter rule 13):
  untracked src/ files produced this phase          11
  distinct addresses claimed in ANY staging file    29
  src/ files claimed by nothing (orphans)            0
  claims whose source file is missing                0

One false alarm worth recording: my first pass globbed `.run/p12/w-*/claims.tsv` and reported
`src/func_801008DC.c` as an orphan. It is claimed -- in `claims-altcc1.tsv`, which is a DELIBERATE
separate pool because those rows need the `cc1bin` override. The audit tool was wrong, not the
worker. A `src/`-vs-claims audit must glob EVERY claims file a charter defines, not just the default
one, or it manufactures orphans.

I re-verified ALL 29 claimed rows from fresh --work directories with `--symbols` (and
`--cc1 gcc-2.8.1-psx/cc1` for the four `cc1bin` rows) BEFORE merging: **29 of 29
`differing_bytes=0 result=MATCH`, exit 0.** Then one `apply --skip-registered` over the combined
input: 19 skipped as already merged, 10 added, 0 rejected.

  sf3_match gate   c_regions=644  differing_bytes=0  result=MATCH
                   sha1 e173426c157384ebf1b6caf8c6fea18a85a14af9  (unchanged)
  make check       exit 0        extents-verify  regions=644 disagreements=0 AGREE
  make worklist    listed=984, excluded_already_registered=642
  registry audit   644 rows, 635 distinct bodies, 0 missing, 4 carry cc1bin

The 10 recovered rows, and they are the phase's best evidence that the dispatch re-cut worked:

  0x8006D3B0 (192 B) and 0x8006AA88 (244 B)  -- worker A, adjacency
  0x8006D250 (172 B) and 0x8006D2FC (180 B)  -- worker B, family/adjacency, sibling with a 5th arg
  0x8002515C (60 B) and 0x800254B0 (60 B)    -- worker D: BOTH twins of the registered 0x80025070,
                                                a THREE-member family, and twins of each other
  0x80101C5C (32 B) and 0x80102FE4 (48 B)    -- worker D, GTE class
  0x80091490 (84 B)                          -- worker C, DEPENDENT, merged on my ruling (below)
  0x801008DC (88 B)                          -- worker C, `cc1bin=gcc-2.8.1-psx`, 3 `jr $31`

**RULING ON THE DEPENDENT CLAIM `0x80091490`.** Worker C flagged it rather than quietly merging it,
correctly applying charter rule 2: its callee `0x80018284` is NOT reconstructed, and the 3-argument
prototype is INFERRED from the call site (the tell is `move a1,zero` -- an integer 0, not a null
pointer in v0). Ruling: MERGE, with the dependency recorded here and in the ledger. Reasoning:
(i) rule 2 forbids verifying against a SUPERSET, and no superset was used; (ii) the whole-binary
gate proves the bytes exact, so the call site's register setup is right; (iii) the residual risk is
that a future `0x80018284` reconstruction disagrees about arity, and **that risk is caught by the
gate the moment it happens** -- the row would stop matching and the cause would be visible. The
alternative, holding a verified body, costs a body for a risk the gate already covers.
**The worker who takes `0x80018284` must treat `0x80091490`'s prototype as a hypothesis to check.**

Also recorded: `excluded_already_registered` still lags the registry by exactly 2 (642 vs 644). The
lag did NOT grow when this merge added four negatives-derived rows, so it is not "negatives rows are
counted elsewhere" -- it is a fixed pair of registry rows the counter never counts. Still open,
still a diagnostic rather than a gate, still to be reconciled at the close with the full census.

Phase position: 602 -> 635 bodies, +33. The cycle-1 checkpoint was +30.
2026-09-24 18:15:57 -04:00
Christopher Williams b8502c5693 phase12: merges 7-8 — 625 bodies / 634 regions (from 621 / 630)
Merge 7: worker D's 0x8006AD5C (84 B, a GOAL B row, first spelling) and 0x8006D1C4 (140 B).
Merge 8: worker C's GTE pair 0x80010810 / 0x8009C69C (60 B each, on the DEFAULT toolchain, via
the inline-asm hatch). Every row verified from a fresh --work dir against the exact md5, merged to
a candidate, gated whole-binary, promoted only on result=MATCH.

  sf3_match gate     c_regions=634  differing_bytes=0  result=MATCH
                     sha1 e173426c157384ebf1b6caf8c6fea18a85a14af9  (unchanged)
  make check         exit 0     extents-verify  regions=634 disagreements=0 AGREE
  registry audit     634 rows ordered, non-overlapping, 625 distinct sources, 0 missing

INTEGRITY CHECK THAT PASSED, ON A REAL HAZARD. Worker C edited the headers of the three
ALREADY-MERGED cc1bin sources after I merged them, changing their md5s (6afb89a7 / 51ab9083 /
c30c5bfa). The registry points at those paths, so a content change would have made it stale. I
re-ran the full gate on the current registry: MATCH. Comments only. This is exactly what the
md5-in-claim-row guard exists for, and here the whole-binary gate is what settled it.

C'S INLINE-ASM HATCH WAS JUSTIFIED TWICE, AND THE SECOND REASON IS A NEW FINDING.
The GTE pair is the d=0/15 identical pair, so one solve meant two bodies. The hatch was needed
because (1) ~20 spellings and ALL TEN vendored cc1 builds fold the dead `move t0,a1` into the
negation, and `((y ^ -1) + 1)` is the only spelling reaching the right LENGTH while lowering to
nor+addiu -- so length alone was never evidence; and (2) THE ORIGINAL'S NEGATION IS THE TRAPPING
`sub` (funct 0x22), not `subu` (0x23). objdump prints `neg` for the original and `negu` for the
candidate, so a mnemonic comparison cannot see it: a one-byte funct-field difference, cookbook 6's
class. With the copy fixed but the C negation kept, the row sits at differing_bytes=1.

AND C CLOSED THE ALT-CC1 QUESTION AGAINST ITS OWN INTEREST. On the 0x80050674 pair the residual is
differing_bytes=1 on the default cc1 and differing_bytes=22 on BOTH 2.8.1 and 2.91.66-psx, so it is
not a compiler-revision artifact. C then declined to spend the inline-asm hatch on it, on the
grounds that the residual is a mundane operand order inside an otherwise all-C body and the hatch
would be doing COSMETIC work. Ruling: endorsed. The hatch is for shapes plain C provably cannot
express; pinning three registers to win one operand order is not that. The row is classified with
its mechanism and an exhausted dimension instead.

OPEN, RECORDED WITH THE EXPERIMENTS RATHER THAN A GUESS: the workflow's checklist item
`excluded_already_registered == registry size` held exactly through merges 5, 6 and 7 and diverges
at merge 8 (634 registry rows, counter 632). Two hypotheses were tested and BOTH REFUTED: the two
new rows are not extent-graded non-exact (both are `exact`/`term=jr_ra`), and it is not "negatives
rows are counted under the negatives filter instead" (11 registry rows have negatives addresses,
but removing only the 2 newest from the registry reproduces the count exactly). Two failed
root-cause attempts, so per AGENTS.md rule 10 it is recorded with the evidence instead of a third
guess. No correctness risk, and that is measured: the whole-binary gate proves all 634 rows
byte-exact simultaneously, the registry audit is clean, and extents-verify agrees. It is a
diagnostic, not a gate. To be reconciled at the close, where the negatives-index reconciliation
happens anyway and is probably the same question.
2026-09-24 17:30:17 -04:00
Christopher Williams 677d982398 phase12: merge 6 — worker A's 572 B body -> 621 bodies / 630 regions; exclude the 0x800C3490 fragment
Worker A's third claim, verified from a fresh --work dir against the exact md5 in the claim,
merged to a candidate, gated whole-binary, promoted only on result=MATCH. It is the largest body
any worker has taken this phase (572 B).

  sf3_match gate    c_regions=630 differing_bytes=0 result=MATCH
                    sha1 e173426c157384ebf1b6caf8c6fea18a85a14af9  (unchanged)
  make check        exit 0        make extents-verify   regions=630 disagreements=0 AGREE
  make worklist     listed=989, excluded_already_registered=630
  registry audit    630 rows, 621 distinct sources, 0 missing, 3 carry cc1bin

**ADJACENCY IS NOW 3-FOR-3 FOR WORKER A, ACROSS THREE BANDS.** 0x800297F4 (204 B, band 0) ->
0x800298C0 (392 B, band 1) -> 0x80029A48 (572 B, band 2): three CONSECUTIVE bodies, 1168 bytes,
four spellings total, with the density ranker not involved in any of the three picks. The chain
ends where no row starts at the next boundary. That is the strongest single piece of dispatch
evidence in the phase, and it is why the fresh-band files were re-cut to put adjacency above
density inside a band.

Worker A's lever, recorded because it is an operator RECOVERY rule rather than a spelling tip: the
original is `if ((x1 < 0 && x2 > 0) || (x1 > 0 && x2 < 0))`, and the truth-table-equivalent
if/else ladder is EXACTLY ONE INSTRUCTION SHORT. The emitted stream is `bgez x1` / `bgtz x2` /
`blez x1` / `bgez x2` -- each term's first test branches over its own second test -- and the ladder
has no `bgez x1` to emit. So the four BRANCH SENSES let you write the operator down without
guessing; cookbook 83's "branch direction distinguishes && from ||" turned into a recovery rule.
And `(x1 ^ x2) < 0` is the same predicate with the wrong codegen: the original compares.

**0x800C3490 IS EXCLUDED FROM THE WORKLIST.** Cookbook 114 (worker A, Phase 10) records it as a
FRAGMENT: it starts mid-expression, its body is a SHARED TAIL (`addiu sp,sp,48; jr ra`) that also
appears at 0x800C3470-0x800C348C, and it cannot be matched standalone. The extents table still
grades it `exact` with `term=jr_ra` because the boundary walk sees a well-formed terminal, so the
tool cannot catch it -- which is exactly why it needs a recorded exclusion rather than a tool rule.
Worker A recognised it for the SECOND time in Phase 12 and skipped it instead of spending reading
budget, which is the signal that the exclusion belongs in the Makefile: a row that has to be
re-identified by hand every phase is a row the dispatch should not be offering.

Added with its provenance in the Makefile comment, the same mechanism Phase 10 used for
0x8010080C's false extent start. excluded_named_exclusion 8 -> 9.

Ledger updated with merges 2-5, the cc1bin lever and its gate, the mechanism correction, the gp
pair rewrite and both of my failures in it, the dispatch measurement (369 of 993 rows abut SOME
region = noise; only 5 abut a PHASE-12 match = the signal), and the partition-membership fix.
2026-09-24 17:25:51 -04:00
Christopher Williams 63008d5a49 phase12: merges 3-5 — 620 bodies / 629 regions (from 604 / 613), incl. the first cc1bin rows
FIVE claim batches, every row re-verified by the coordinator from a fresh `--work` directory
against the exact md5 in the claim, merged to a candidate, gated whole-binary, promoted only on
`result=MATCH`. Merges 3 and 4 are separated so the every-3rd-merge full audit falls on merge 3.

  sf3_match gate      c_regions=629  differing_bytes=0  result=MATCH
                      sha1 e173426c157384ebf1b6caf8c6fea18a85a14af9  (unchanged)
  make clean && make all   exit 0      (full audit at merge 3)
  cmp                      exit 0
  make check               exit 0
  make extents-verify      regions=629 disagreements=0 result=AGREE
  make worklist            listed=991, excluded_already_registered=629
  registry audit           629 rows, ordered, non-overlapping, 620 distinct sources, 0 missing
  make test                318 tests, OK

**THE FIRST THREE `cc1bin` REGIONS ARE IN THE REGISTRY**, and the gate is byte-exact for the
whole binary with them -- the alternative-cc1 lever works end-to-end, not just in a scratch
harness. All three came from worker C, all three match with gcc-2.8.1-psx/cc1 and NONE matches
with the default (56/60/52 B LENGTH-MISMATCH). They carry `cc1bin=gcc-2.8.1-psx` in the region
row and the gate re-checks the >=2-exit-jump restriction on every build.

  0x800FF43C (64 B) and 0x800FF47C (64 B) — a TWIN PAIR, raw words d=2/16. They are also two of
    the rows worker F left open at the Phase 11 close with a named mechanism and a named untried
    lever. C closed them with the cc1 lever, not a re-spelling.
  0x80108578 (56 B) — needed TWO levers: the cc1 AND a source shape that re-tests the guard each
    iteration (`for(;;){ if (a0==0) return 0; ... }`), because the original's loop back edge
    targets the function's FIRST instruction. A `while` spelling rotates the loop, skips the
    guard, and leaves the 1-byte residual an earlier attempt had recorded.

**AND `0x800298C0` IS AN OVERTURN — the recorded negative, merged as worker A's claim.** Worker A
took it on ADJACENCY (the row immediately after its own previous match) and matched it first try,
overturning cookbook 128's named mechanism. It is also the row Phase 11's close had to release as
STALE-TAKEN because both holders had gone out of context: the row the close flagged as invisible
became a body in the next phase. Cookbook 182 demonstrated end-to-end.

**Worker D's GOAL B produced bodies, not just a classification:** 92/92 of its unclassified rows
are now classified, and TWO of them were raw-word twins of registered bodies and MATCHED
(0x8006AE04 = 1/20 words from 0x8006B66C; 0x80017A38 = 1/18 from 0x80017B50). A Goal B result that
adds zero bodies would still have been met; this one added two.

Worker B's 0x8006B964 is the fourth match in ONE translation unit (0x8006B0A8/B328/B5F0/B964),
which is what prompted the dispatch re-rank below.

MEASURED, AND IT IS THE PHASE'S DISPATCH RESULT SO FAR: every body Phase 12 has produced came from
ADJACENCY, a family/twin finder, or a named structural class -- none from the top of a
density-ranked file, and B hit the leading indicator (four consecutive small-residual near-misses,
no new mechanism) doing exactly that. So the fresh-band files are now ordered band-first, then by
ADJACENCY TO A PHASE-12 MATCH, then density. Plain adjacency is NOT discriminating -- 369 of 993
rows (37%) abut SOME registered region, in every band -- but only FIVE rows abut a region matched
this phase, and the mechanical key independently flagged 0x8006B398, which worker D had already
chosen from B's handoff. The key agrees with the human pick.

PARTITION MEMBERSHIP IS NOW A HASH OF (band, address), not a position in a list. I had this wrong
twice and told the workers membership was stable when it was not: first partitioned by
density-sorted position (correcting the payload bug moved rows; band-0 top-20 overlap 1/20), then
by (band, address) POSITION, which is stable only while the row set is unchanged -- and the set
shrinks by design as rows register (1001 -> 993). Verified: removing 10 rows moves 0 assignments.
2026-09-24 17:23:50 -04:00
Christopher Williams f5c3426e85 phase12: merge 2 — worker B's 3 rows -> 607 bodies / 616 regions
All three re-verified from fresh --work dirs against the EXACT md5 in the claim rows
(4ad25781c69f611450544ae097936d1a / 7188c87c37bfc87b8e0ebfddd47ded97 /
542de2b2242dd8e6149440e4bb034f77), merged to a candidate, gated whole-binary, promoted only
on result=MATCH.

  sf3_match range (x3, --symbols)    exit 0, differing_bytes=0, result=MATCH
  sf3_merge check-claims             exit 0, problems=0
  sf3_match gate --expect-sha1       exit 0, c_regions=616, differing_bytes=0, result=MATCH
                                     sha1 e173426c157384ebf1b6caf8c6fea18a85a14af9
  make check                         exit 0
  make extents-verify                regions=616 disagreements=0 result=AGREE
  make worklist                      listed=998, excluded_already_registered=616

WORKER B'S CHARTER CORRECTION, CONFIRMED INDEPENDENTLY. The charter's §3d verify command
omitted `--symbols config/symbols.tsv`. Without it this entire family is +4 LENGTH-MISMATCH,
because the gp-marked global D_80121E88 auto-resolves to an absolute address and becomes
lui+lw. Measured on 0x8006B5F0:

  without --symbols:  candidate_bytes=128  result=LENGTH-MISMATCH   exit 1
  with    --symbols:  differing_bytes=0    result=MATCH             exit 0

That is the THIRD defect in the charter I wrote for this phase (after the claims-file format
and the evidence-file name). A verify command that does not match the configuration the gate
uses is a command that reports a false negative, and it would have had workers discarding
correct spellings as LENGTH-MISMATCH.

TWO REUSABLE MECHANISMS FROM B, both named rather than ground out:

(a) A REGISTER RESIDUAL CAN BE ARGUMENT ARITY. 0x8006B328 came out at correct length with
    exactly 4 differing bytes, all the a1-vs-v1 register field of the record pointer. Cause:
    the callee was called with TWO arguments. With one argument cc1 allocates the record
    pointer to v1; with two, both values are already in a0/a1, the call emits NO setup at all,
    and the emitted code differs only in that field. Diagnostic: correct length + the only
    residual is one value in an argument register => try declaring an extra parameter.

(b) A LOOP-INVARIANT CONSTANT MUST BE A NAMED LOCAL DECLARED INSIDE THE GUARDED SCOPE, and
    the `s4` save is the tell. On 0x8006B0A8 (frame 40, saves ra+s4+s3+s2+s1+s0): a literal
    `&= -2049` rematerialises the `li` inside the loop and the whole s4 save/restore pair
    vanishes; a named local before the `while` hoists the `li` above the guard test and
    reschedules the entry block (20 differing bytes); declaring it inside
    `if (p != 0) { int hmask = ...; while (...) }` MATCHES. The guard wrapper is what matters,
    not the loop form (do/while is byte-identical to while). A SECOND mask in the same function
    must stay a literal -- the hoist is per-statement, not per-function.

  make test   306 tests, OK   (from 301)
2026-09-24 17:08:21 -04:00
Christopher Williams b3e3ec3ea7 phase12: merge 1 — worker C's two ADJACENT-TWIN negatives -> 604 bodies / 613 regions
The phase's first two bodies, and both are recorded negatives rather than worklist rows.
Worker C found them with the NAMED UNTRIED LEVER I handed it: tools/sf3_family driven over the
101 negative rows against the 611 registered regions. I measured candidates=0 for the fresh
band and told C that said nothing about its own pool. It didn't:

  score >= 1.000 :  3 rows   (NOT 0)
  score >= 0.99  :  4 rows
  score >= 0.95  : 15 rows

  -> 0x800518BC (88 B) -> 0x80051864   MATCHED (this merge)
  -> 0x800B34A4 (88 B) -> 0x800B34FC   MATCHED (this merge)
  -> 0x8010804C (16 B) -> 0x80085B80   blocked class (gp-thunk), correctly not attempted

Transfer rate 2/2 on the unblocked score-1.000 rows, against finding 166's 7/7.

I re-verified BOTH claims from fresh --work directories before merging (differing_bytes=0,
result=MATCH, exit 0 each), merged to a candidate, gated the whole binary, and promoted only on
result=MATCH.

  ./tools/sf3_merge apply ...                      exit 0, added_regions=2
  ./tools/sf3_match gate --expect-sha1 e173...     exit 0, c_regions=613
                                                   differing_bytes=0, result=MATCH
                                                   sha1 e173426c157384ebf1b6caf8c6fea18a85a14af9
  make check                                       exit 0
  make extents-verify                              regions=613 disagreements=0 result=AGREE
  registry audit                                  613 rows, ordered, non-overlapping,
                                                   604 distinct sources, 0 missing files

Worklist regenerated. It still lists 1001 rows, and the reason is worth recording because it
looks like nothing happened:

  excluded_already_registered   611 -> 613   (+2)
  excluded_recorded_negative    169 -> 167   (-2)

The two rows were NEVER in the worklist -- they were excluded as recorded negatives. Registering
them MOVES each row from the negatives-exclusion bucket to the registered bucket, so `listed`
is unchanged while both counts shift by exactly 2. excluded_already_registered == the registry
size, as required. No partition refilter is needed: both rows were in C's negatives partition,
not in any fresh partition.

WORKER C'S FINDING, which is worth more than the two bodies and is a Goal-B-grade result: BOTH
rows' recorded class strings were WRONG ABOUT THEIR OWN MECHANISM.

  0x800518BC was classed "return-merge/sltiu", with a note that three spellings all came out
    80 B and sltiu fixed the compare byte but not the 8-byte layout. RAW WORDS: 21 of 22 words
    are IDENTICAL to matched 0x80051864. The sole difference is the forward `j` to the shared
    return, and its target differs only because `j` encodes an ABSOLUTE address and the two
    bases are 0x58 apart. The body is a literal twin; no re-spelling was ever needed.

  0x800B34A4 was classed "alloc-tiebreak" (bit-index/base register pair a0/a1 vs cc1 a0/v1).
    RAW WORDS: 20 of 22 identical to matched 0x800B34FC; both differences are the OFFSET
    CONSTANT (`li a0,3` vs `li a0,20`). The register pair the note asked for was already there.
    The lever is the constant, not the allocator.

Both rows abut their matched twin, so the negatives index contains whole RUNS of repeated
bodies -- and C's operational conclusion is that the cheapest finder is a raw-word HAMMING scan
against the 611 registered regions, which the histogram tool only half-finds. C is running that
scan across all 101 rows now. That map, not the bodies, is likely the largest thing this phase
produces.

Note the discipline this vindicates: an unclassified negative is not overturnable (finding 182),
but these two WERE classified -- wrongly, in the same dimension, by earlier sessions that then
wrote off the row. Cookbook 182's rule needs its other half: a recorded mechanism is a HYPOTHESIS,
and a WRONG named mechanism is as unattemptable as no mechanism at all until someone reads the
raw words.
2026-09-24 16:58:39 -04:00
Christopher Williams 6141079f9b phase11: merge 61 — worker E's 0x8007F9B0 -> 602 bodies / 611 regions 2026-09-24 11:43:43 -04:00
Christopher Williams b0ed17d494 phase11: merge 60 + cookbook 181-182 — 601 bodies / 610 regions
Worker F's 0x8002622C (44 B) — A RECORDED NEGATIVE OVERTURNED, with a new class.

The old record said 'cc1 folds it, unreachable' and tried FOUR ALGEBRAIC re-spellings. All four
were doomed: the fold is at RTL combine, not in the front end, so no re-spelling can avoid it.
Only LIVENESS can. Same body with 'return 0' is 32 B LENGTH-MISMATCH; with 'return n' (the
difference live past the addition) it is 44/0/MATCH. Diagnostic that proves the pass: cc1 -da
shows the minus present in the .flow dump and gone in the .combine dump, while cse/cse2/jump/
loop/sched/sched2 all still contain it.

181: when an original keeps an arithmetically-cancelling pair (subu+addu, x-c+c), the intermediate
is LIVE PAST the second operation — find the later reader.

182: a negative with a NAMED mechanism is overturnable; one without is not. 'cc1 folds it' is not
a classification; 'RTL combine cancels it, and algebraic re-spelling cannot reach combine' is,
and it immediately implies the liveness lever.
2026-09-24 11:42:17 -04:00
Christopher Williams 5c3e5d0e07 phase11: *** 600 DISTINCT MATCHED BODIES — TARGET REACHED ***
609 regions / 600 distinct bodies, from the 484 / 493 baseline at phase start (+116 regions,
+116 bodies). Worker E's 0x80077D04 (120 B) is the 600th.

VERIFIED, not asserted:
  full-binary gate   : 609 regions, differing_bytes=0, result=MATCH, exit 0
  make check         : 253 tests, OK, exit 0
  every source file  : present on disk and tracked by git
  working tree       : clean

Corpus maximum 1232 B; 28 regions exceed the 244 B ceiling the phase was planned around.

Delivered by six workers over the phase: A 46, D 21, B 17, C 13, E 14, F 3 = 114 claims,
every one gated before merge.

The phase's premise was falsified early and replaced with working tooling:
 - the 244 B ceiling was a DISPATCH ARTEFACT (5 of 427 rows above it had ever been attempted);
 - ASPSX does not fill delay slots at all, so the authorised post-pass shrank from a modelling
   project to one mnemonic (maspsx=moves) plus an epilogue swap (maspsx=epilogue);
 - cost is TIE-BREAK DENSITY, not size, measured independently by two workers from opposite
   directions;
 - the GTE class moved from BLOCKED to OPEN (worker D's 0x800F3E18 is the first GTE row matched
   in this project).
2026-09-24 11:37:10 -04:00
Christopher Williams efbcc2bd01 phase11: merge 59 — worker F's 0x800261C0 -> 599 bodies / 608 regions, ONE to the milestone 2026-09-24 11:33:44 -04:00
Christopher Williams eeaaf5b059 phase11: merge 58 — worker E's 0x80027D88 -> 598 bodies / 607 regions, TWO to the milestone 2026-09-24 11:30:55 -04:00
Christopher Williams 7a93946795 phase11: merge 57 + cookbook 170-171 — 597 bodies / 606 regions, THREE from the milestone
Worker E's 0x8005E17C and 0x8002FAB8; worker F's first two claims 0x800FBE84 (216 B, FIRST
SPELLING with worker A's derivation) and 0x80026274 (108 B).

170 generalises the argument-evidence levers (157/164) into a mechanism: a redundant ENTRY-BLOCK
copy of an argument means that value is still live at a call whose argument setup CLOBBERS that
same register. The copy is materialised in the entry block because the tie to a0's home is
illegal. The test that nailed it: the same body with a 2-arg call is 104 B LENGTH-MISMATCH; with
the 3-arg call it is 108/0. Two prior corpus instances had the copy AT the call; this is the
hoisted-to-entry variant.

171: worker F confirmed EXHAUSTIVELY that the constant-division divisor is unique per magic --
(n*M)>>(32+s) == n/D has exactly one D. So finding 67's identity is not an approximation.
2026-09-24 11:27:41 -04:00
Christopher Williams c091483083 phase11: merge 55 + cookbook 123 SOLVED + 167-168 — 592 bodies / 601 regions
Worker E's 0x8002311C (160 B) CLOSES COOKBOOK 123'S OPEN QUESTION. Finding 123 recorded the
branchless MAX0 (x & -(x > 0)) as unreached -- 'no ternary and no bitwise spelling reached it'.
Worker E solved it: the lever is NAMING THE BOOLEAN.

  return s & -(s > 0);        -> BRANCHES
  return s > 0 ? s : 0;       -> branches
  flag = s > 0; return s & -flag;  -> EXACT (slt / negu / and)

Mechanism: naming the comparison forces cc1 to materialise it as a VALUE (slt) rather than a
test feeding a branch. That is finding 44's 'name the boolean' lever applied to the MAX half --
finding 44 previously had only the cond-into-&& direction for this family.

167: a 4-byte store cc1 DELETES means the object's address is never taken -- fold the word into
the array whose address IS taken by a call.

168: s = f(); s += f(); s += f(); loses one instruction vs three named results summed.
2026-09-24 11:22:01 -04:00
Christopher Williams db6022c9f7 phase11: merge 54 + cookbook 166 — 590 bodies / 599 regions
0x800F3DC0 (88 B) — a ONE-WORD sibling of the matched 0x800F3E18, found by worker E via
sf3_family at ratio 1.000 and confirmed by raw-word diff: identical in all 22 words except the
COP2 command field (0x4B70000C vs 0x4B78000C). The route was one copy, two renames and one field
change; every __asm__ and register binding carried over untouched.

166 records it, and notes it is the MIRROR of finding 161: on 0x800F3E18 the field 0x178000c was
the WRONG answer (one byte off, 0x170000c correct); on 0x800F3DC0 0x178000c IS correct. A count
tells you a field is COMMON, not that it is right -- and a ratio-1.000 sibling is the cheapest
place to learn which one a row wants. When the family tool reports one, diff the raw words FIRST.
2026-09-24 11:18:58 -04:00
Christopher Williams f4e14569bd phase11: merge 53 — worker E's 0x800B0E64 -> 589 bodies / 598 regions 2026-09-24 11:13:40 -04:00
Christopher Williams 22cfdc874d phase11: merge 50 — worker E's 0x80107DE8 -> 587 bodies / 596 regions 2026-09-24 11:06:56 -04:00
Christopher Williams a74bc32306 phase11: merge 49 + cookbook 162 — 586 bodies / 595 regions
Worker A's final row 0x8010A6C4 (132 B, first attempt, maspsx=epilogue) -- its ninth epilogue
row and its 46th claim.

162: a callee called with DIFFERENT argument counts needs a NON-PROTOTYPE declaration --
func_8010A444(1) / (2, x) / (3, s1, s0) is only expressible as 'void func_8010A444();', the C89
empty-parameter form, not '(void)'. Same constraint that cost worker A a compile on 0x8002DD14.

Worker A's final totals: 46 claims (33 first-attempt), 95 evidence rows, 39 levers, 3 deferred
rows with derivations, 1 blocked row, 9 rows carrying maspsx=epilogue.
2026-09-24 11:05:36 -04:00
Christopher Williams f27691f6c3 phase11: merge 47 + cookbook 158 — 584 bodies / 593 regions
Worker A's 0x800F4B88 (128 B, first attempt) -- its eighth epilogue-class row and its last.

158: two type views over the same halfwords are DELIBERATE. The first helper call loads with lh
(signed) and the second with lhu (unsigned) over the SAME pointer, so the source declared a
short* view for one expression and an unsigned short* view for the other. Writing the whole row
as short* gives lh for the second call too and changes the bytes. When one function reads the
same field both ways, the mixed lh/lhu pair over one pointer is the evidence.
2026-09-24 11:01:49 -04:00
Christopher Williams eb4b23b219 phase11: merge 46 + cookbook 155-157 — 584 bodies / 593 regions
Worker A's three epilogue rows (0x800F452C, 0x800F6DD0, 0x800F6E50).

155 is a DISPATCH finding: the epilogue list is ALSO a family list. 0x800F6DD0 and 0x800F6E50 are
siblings differing in exactly two ways, and worker A read one and got the second for free, both
first try. Adjacent pairs already identified: 0x800F6DD0/0x800F6E50, 0x800F42AC/0x800F452C,
0x800FFFEC/0x80100038. A worker taking an epilogue row should read its NEIGHBOURS first -- the
class was selected on a TAIL SHAPE, and tail shape correlates with the translation-unit layout
that makes neighbours siblings. Generalised: any class selected by a structural feature clusters
its results by address.

156: the three writes are ASSIGNMENTS not accumulations -- the original never loads the old
destination value, so writing += adds three loads.

157: fewer argument registers set than parameters means the source passes its OWN LIVE parameters
directly. Now confirmed on three rows.
2026-09-24 11:00:24 -04:00
Christopher Williams 57c1cd22f2 phase11: merge 44 + cookbook 151-153 — 580 bodies / 589 regions
Worker A's 0x800F4098 and worker D's 0x800FB54C (104 B, first attempt, maspsx=epilogue).

151: the 2^k-1 add-back rule is CONFIRMED on two independent divisors -- worker C derived it
from 63 (0x800FEE3C) and worker D found it again on 127 (0x800FB54C, magic 0x81024409). Same
structure, two divisors, so finding 67's decision table is complete and not hypothesised.

152: FIVE finders each produced bodies over worker D's 20 -- redundancy rank 6, size rank 5,
adjacency 4, epilogue class 2, constant-division census 1, family 1. No single finder dominates.
This broadens finding 109: 'five different finders each produced bodies, and the price was set
by the LEVER, not the finder.' The tools cover different parts of the population, so keep every
finder running rather than consolidating onto the current best.

153: a saved register can force a local to be SMALLER than the data written through it, and
enlarging it to fix that breaks the frame.
2026-09-24 10:55:29 -04:00
Christopher Williams 150e672b5a phase11: merge 42 + cookbook 144-146 — 574 bodies / 583 regions
Worker D's 0x800FAF84 (104 B), its first maspsx=epilogue match.

144: THE EPILOGUE CLASS NEEDS ONLY ONE TOKEN. Worker A asked for a second one; it does not need
it. The 120 rows split into two shapes -- A) lw $31 immediately before the release, which needs
the release moved AND a nop inserted after lw $31; B) other loads in between, where the release
moves and the trailing nop is DROPPED. My first implementation did A only and left every B row
4 bytes long. Verified on both: 0x800FFBEC (80/0/MATCH) and 0x800F44D0 (92/0/MATCH, a row worker
A had released as unfixable).

145: read the frame arithmetic and the saved-register offsets TOGETHER -- worker D's local had to
be 8 bytes not 12 because the saved s0 sits at sp+24 and the callee writes through sp+16. Third
instance of the size family, first where the constraint came from a saved register.

146: the SAME expression at two divisors produces two unrelated code shapes (/64 branchy bias vs
/63 add-back magic), which is why worker D's divisor sweep missed it.
2026-09-24 10:52:37 -04:00
Christopher Williams dce9c40896 phase11: merge 41 — 0x800F44D0 closes on the CORRECTED transform (shape B) — 573 bodies / 582 regions
Worker A released this row as 'the three-load-with-nop shape that the swap cannot fix' and
requested a --no-load-delay-nop token. It does not need one: the corrected transform already
handles it, by DROPPING the trailing nop for shape B rather than moving it. Verified: 92 bytes,
differing_bytes=0, MATCH, with maspsx=epilogue. It was 96 bytes before the fix.

So the epilogue class does NOT need a second token -- it needed the transform to distinguish the
two shapes, which worker A's report 24 is what revealed. The 120 rows should now be attemptable
with maspsx=epilogue alone.
2026-09-24 10:51:28 -04:00
Christopher Williams ea51ac9629 phase11: merge 40 + the epilogue transform now handles BOTH shapes — 571 bodies / 580 regions
Worker A's two epilogue-class rows (0x800F42AC 96 B, 0x80100038 104 B), both carrying the
maspsx=epilogue token -- the first rows closed through the new mode.

AND THE TRANSFORM IS NOW CORRECT FOR BOTH SHAPES, which worker A's report 24 showed was
necessary. The 120 rows split:
  A) lw $31 IMMEDIATELY before the release -> the release moves into the slot AND a nop must be
     inserted after lw $31, or j $31 lands in its load-delay slot.  0x800FFBEC.
  B) other loads between lw $31 and the release -> the release moves into the slot and the
     trailing nop is DROPPED; no load-delay nop is needed.  Worker A's 0x800F44D0.
My first implementation did A only and left every B row 4 bytes long. Both are handled now, and
the discriminator is whether the jump's own register was loaded immediately before the release.

A BUG WORTH RECORDING: reading out[-1] to find that preceding instruction saw maspsx's own
'#nop # DEBUG: ...' comment instead of the lw, silently producing the shape-B answer for a
shape-A row and turning a MATCH back into a LENGTH-MISMATCH. The scan now skips comments.
2026-09-24 10:50:09 -04:00
Christopher Williams a72a8d4127 phase11: merge 39 — 4 rows (A's 0x80038D48, 0x800F6D60; D's 0x8009B56C, 0x80018458)
Worker A's two: the addition operand-order row (a1[i]+a0[i] vs a0[i]+a1[i] -- same length,
16 bytes apart, because cc1 evaluates the right-hand operand first) and the unconditional
p[0]=0 that lands in a branch delay slot.

Worker D's two: 0x8009B56C closed on cookbook 43 trigger 1 after D had nearly written the row
off, and 0x80018458.
2026-09-24 10:44:06 -04:00
Christopher Williams 05be974ce2 phase11: THE EPILOGUE POST-PASS SHIPS (maspsx=epilogue) — 565 bodies / 574 regions
Finding 84 named the transform; it is now implemented and 0x800FFBEC matches (80 B, 0 differing)
where it was 6 differing bytes without it.

IT IS A SWAP, NOT A MOVE, and getting that wrong cost one implementation: the candidate is
lw $31,16(sp) / addiu sp,sp,24 / jr $31 / nop and the original is lw $31 / nop / jr $31 /
addiu sp,sp,24 -- SAME instruction count, two words swapped. My first version moved the release
after the jump and dropped the nop, producing 3 instructions instead of 4 and turning an 80-byte
row into a 76-byte LENGTH-MISMATCH. A 'small mechanical transform' still has to be checked
against the bytes.

SCALE: 120 unclaimed rows have the filled epilogue in the ORIGINAL (scanned every worklist row's
tail for jr $31 followed by a positive addiu sp,sp,N). They are mostly SMALL -- 76, 76, 80, 92,
92, 96, 104 B -- so this is a large class of cheap rows that were blocked on a HARNESS GAP rather
than on source shape. 770 other rows have the unfilled shape and need nothing.

The tracked patch is regenerated and verified to reproduce both modified maspsx files from the
pristine checkout.
2026-09-24 10:42:29 -04:00
Christopher Williams 686e906b97 phase11: merge 37 + calibrate sf3_family + cookbook 136-137 — 563 bodies / 572 regions
Worker A's 0x80036F70 (460 B, first attempt, family score 1.000 AND adjacent to its own
0x80036DA4). Its family run finished 7 for 7 with five first-spelling matches.

136: worker A CALIBRATED the family tool. It checked the two 0.97-scoring entries and NEITHER
shares its sibling's body at all -- one is a table-allocation routine, the other a summing
loop. '1.000 is the useful band; below ~0.99 the histogram is matching common idioms, not
bodies.' That is the same false-positive mode as the redundancy ranker (finding 110). The
default threshold is now 0.99.

137: a family's signature can be a CONSTANT TRIPLE -- worker A's 0x80036F70 differs from its
sibling only in six constants, whose signature is (A, A+12, A-58). Searchable in a way no
similarity metric can be, because the shapes are identical and only the immediates differ.
2026-09-24 10:37:35 -04:00
Christopher Williams bd3619d41e phase11: merge 36 — FIVE family-list rows in one pass -> 561 bodies / 570 regions
Worker A closed 0x800259A0, 0x80012918, 0x8006B2D4, 0x8003022C and 0x800506E4 -- every one a
sibling found by tools/sf3_family, which was built an hour ago from worker D's insight that
'the finder varies, the price does not' and therefore families should be SEARCHED for rather
than waited for.

That is the tool's first harvest and it is 5 bodies from one list. The family scores were
1.000/1.000/1.000/1.000/0.998 -- exact opcode-histogram and size matches against rows worker A
had already matched, so the levers transferred unchanged.
2026-09-24 10:35:06 -04:00
Christopher Williams 887155a733 phase11: merge 35 + 5-way re-partition — 556 bodies / 565 regions
Worker D's 0x80025ADC (136 B). Partitions re-interleaved 5 ways because workers B and C
are both at ~94% context and effectively exhausted, leaving 2 active workers against 45
remaining bodies. A fifth worker restores capacity.
2026-09-24 10:32:46 -04:00
Christopher Williams bc4c046625 phase11: merge 34 + cookbook 127-129 — 556 bodies / 565 regions
Worker C's 0x8009F4B4 (248 B) and 0x80068874 (156 B), both first attempt.

127 CLOSES WORKER D'S OPEN QUESTION. D left 0x800FEE3C's magic 0x82082083 unexplained; worker C
solved it and the answer is a general rule: the divisor 63 is of the form 2^k-1, which is why
cc1 uses that magic with an ADD-BACK (mfhi; addu; sra 5) instead of a plain shift. An add-back
magic is the tell for a 2^k-1 divisor, NOT for a large one. Finding 67's decision procedure is
now complete: no mflo -> constant division D = 2^(32+s)/M; mfhi+addu+sra -> a 2^k-1 divisor;
mfhi AND mflo -> a genuine 64-bit multiply.

128: worker C classified a division-by-constant row on decode WITHOUT attempting it, because
'every division expression has several equally-plausible spellings, so it is idiom-redundant by
construction'. That characterises the ranker's false-positive class from the SOURCE side for the
first time -- exactly the class finding 110 showed cannot be separated by operand comparison.

129: adjacency is now 9-for-9 across three workers (A 3/3, C 5/5, D 1/1).
2026-09-24 10:30:10 -04:00
Christopher Williams a3b5db4f61 phase11: merge 32 — worker A's three roving-list rows -> 555 bodies / 564 regions 2026-09-24 10:27:40 -04:00
Christopher Williams bbe342d5dd phase11: merge 31 — worker D's 0x80018210 -> 552 bodies / 561 regions 2026-09-24 10:26:31 -04:00