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.
Worker A's 0x800F452C (112 B, maspsx=epilogue, second attempt) -- a sibling of its own
0x800F4098, matched with the mid-function-branch-target lever applied first try.
Worker A asked FIVE TIMES for the lui-page rule to be prominent, having made the mistake five
times. It is now cookbook 154 and the FIRST of the five things the workflow doc tells a new
worker, with all five instances tabulated. The fifth instance added the missing half: the
immediate is a SIGNED DISPLACEMENT, and the signature of the error is 'a residual of ONE
differing byte whose only difference is a lui or addiu immediate'.
Also confirmed from worker A's side: the shape A/B correction works -- 0x800F452C is a shape-B
row and closed with the single token, so the tail filter is obsolete and A has dropped it.
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.
Worker E's first four claims (0x80028CE0, 0x80028C34, 0x8009D798, 0x80091674).
147 IS A CORRECTION TO THE COORDINATOR'S OWN BROADCAST. I sent workers a 120-row epilogue list
selected by scanning the ORIGINAL's tail. Worker E showed that is not sufficient: 0x80091674 has
that exact tail shape but cc1 FILLS the slot itself, so the default toolchain matches (104 B) and
--fill-epilogue BREAKS it (100 B LENGTH-MISMATCH). The mode must be decided per row by compiling
and reading the CANDIDATE's tail. Diagnostic: 4 bytes SHORT with the mode on means the candidate
was already correct.
148: order and allocation are SEPARABLE -- the la position and the addu destination are two
independent facts, and the fix is to split the base into its own named local. Three spellings
with exact results.
149: a body 4-8 bytes SHORT with MISSING load-delay nops means a later independent block must
move earlier in the source -- the opposite tell from finding 100.
150: worker E hit a RACE DUPLICATE on 0x80100038 (free.sh read a registry 6 rows stale),
restored HEAD's file verbatim, re-verified it, dropped the row, and reported the incident.
Nothing corrupted because both spellings match -- but it proves the protocol's window is real.
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.
Finding 122 declared GTE rows a blocked class needing a harness token. The gap was smaller than
it looked: the header already covered $0-$7 and $13-$31 but NOT $8-$12, which is where
IR1/IR2/IR3 ($9/$10/$11) live, and it had no macro for the GTE command instruction itself.
Added gte_ldIR1/2/3, gte_stIR1/2/3, gte_ldMAC1/2/3, gte_stMAC1/2/3, and a GENERIC gte_cmd(field)
so any command whose 25-bit field has been read off the original can be emitted without first
earning a semantic name.
VERIFIED, NOT ASSUMED: the field values are confirmed by counting them over the whole binary
(0x486012 x51, 0x49E012 x11, 0x41E012 x4) and a test compilation emits gte_cmd(0x486012) ->
cop2 0x486012 -> 0x4A486012, exactly the original's word.
Five rows were blocked on this across three workers, all with complete or partial derivations.
The disposition matters as much as the token: three workers independently reached 'no amount of
spelling will find this' and declared it BLOCKED rather than grinding -- which is what made the
gap visible as a TOOLING problem instead of three separate source puzzles.
141: worker D's own reads_unsaved_saved scanner flagged 0x80103434, D read the row, and the
verdict is NOT a fragment -- the detector treats op 0x12 (COP2) as a GPR use, which for
ctc2/mtc2/mfc2 is wrong. Every COP2 row in that flag list is suspect. That is the THIRD
independent instance of the same lesson: every shape heuristic in this project has needed a
worker to read a flagged row and say 'this is legal, and here is why'.
142: worker C's 2^k-1 add-back rule CLOSES worker D's open question on 0x82082083. D recorded it
as a bounded negative with a named direction; C recognised the answer from an unrelated row.
The third time a worker's finding closed another worker's negative -- the strongest argument for
recording a named open question rather than a vague failure.
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.
Worker D's 0x80025A2C (176 B, 2 spellings) -- the EXACT MIRROR of its own 0x80025ADC.
131 is now THREE instances with an identical mechanism (88->96, 48->56, 56->64, always a clean
8-byte frame deficit with everything else identical, always fixed by an unreferenced array
local). It is a rule with a one-line fix, not a trick.
138: a MIRROR PAIR -- the two rows are inverse conversions, adjacent in the image, both callees
of 0x80031BBC. Finding one made the other a two-spelling row. sf3_family cannot see this relation
because inverses are not similar: their opcode histograms DIFFER (one stores where the other
loads). A pair of inverse functions has a STRUCTURAL signature, not a similarity one.
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.
132: worker B found the SECOND independent instance of 'when the original's short path is the
fall-through, INVERT the condition' (30 bytes of layout on 0x800A8984; the same shape as its
own 0x800FCA90). That promotes it from a heuristic to a rule with a stated direction -- and it
is the opposite of the usual instinct to write the guard as an early-exit.
133: worker A's one-byte family residual was a DECLARATION -- 'int i' emits slt where 'unsigned
int i' emits the original's sltiu. The family transfers the SHAPE and the LEVERS; the
declarations must still be re-derived.
134: a family hit is also a hint about the CALLEE -- the relation crosses the call graph, and
sf3_family does not model it. Two of worker A's family rows call rows that are themselves
unclaimed with the same object layout.
135: worker A predicted a fourth family member by pattern; I scanned all 1046 unclaimed rows for
the predicted bases and ZERO reference them. The family has exactly three members. A predicted
member that does not exist is worth recording so nobody re-derives the search.
130: worker D found the ranker's top is SIZE-BIASED -- redundancy and size are correlated because
a bigger body has more chances to repeat a 3-gram. Its top was 3288 B at 0.91 while the rows it
was matching sat at 0.64-0.74 in the small tail. The metric is not wrong about rows of equal
size; the raw score just cannot be compared across bands. Since the milestone counts BODIES, a
3288 B row and a 248 B row are worth one body each and the large one costs many times the
context. Worker D's effective filter was redundancy PER UNIT SIZE, now approximated by
--max-size, which is documented in the tool.
131: an unreferenced ARRAY local homes but an unreferenced SCALAR does not -- second independent
instance, identical mechanism, so it is a rule: when the frame is a clean multiple of 8 bytes
short and everything else is identical, add an unreferenced array local of that size.
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).
Worker A found that last-row-wins makes an unconditional 'released' from a worker who never
held the row WRONGLY FREE IT from whoever does. A found two addresses held as wip by worker C
and correctly re-appended C's wip row rather than its own released -- had it not, C's rows
would have been silently released and both workers could have started them.
New rule: only append 'released' for an address you yourself appended 'wip' for. Order is
check (range-aware) -> if free append wip -> work -> append your own released.
Second finding from the same episode: the roving list I gave worker A deliberately overlapped
the other partitions and its first two picks were both already held. A shared queue is only
worth it when the work is NOT already partitioned. Once the partitions were re-ranked on
global redundancy, the roving list was strictly worse than a worker's own slice, so it is
retired. That is the fifth defect found in a coordinator-written rule this phase.
Worker D's 0x8009F890 (248 B, first attempt) -- the THIRD member of a family it had already
matched twice, and the three were found by three different means: the size ranker, adjacency,
and the rebuilt global-redundancy rank. 'The finder varies, the price does not.' The
operational conclusion: families should be SEARCHED for explicitly rather than waited for.
126: worker D withdrew a named direction after checking its own notes and finding it had
already been tested and made the row worse. It declined a fifth spelling on a falsified lever
and corrected the row's record to the honest state. A direction that has been tested and
failed must be struck, or the next worker inherits a false lead.
123: a 'clamp to zero' written as a BITWISE MASK (x & (x >> 31)) compiles branchlessly while
the same thing as a TERNARY branches -- the opposite of the intuition. Worker B's MIN half now
matches exactly with the bitwise form. The MAX half (branchless slt/negu/and, i.e. x & -(x>0))
remains UNREACHED by any ternary or bitwise spelling, with a plausible SDK min/max macro as the
source. Recorded as a named open question.
124: worker B found that the partition design CONFOUNDED the size/redundancy measurement. The
partitions were rank-interleaved by the original worklist order (tier, size, address), not by
redundancy, so 'my <=200 B hit rate was high because my slice happened to be redundancy-rich,
not because small rows are inherently easy'. The within-worker evidence for 'cost is redundancy'
stands (A: 548 B first-attempt vs 176 B nine failures), but any CROSS-worker band comparison was
measuring the partition, not the rows. Fixed by re-partitioning on global redundancy. General
lesson: when work is divided among workers, any per-worker statistic is contaminated by the
division.
Worker A's 0x800689DC and worker C's 0x8009F4B4.
120: worker B's justification for why the ranker works -- 'the allocator makes copies
non-identical, so OPCODE repetition survives while WORD repetition does not'. That is exactly
why finding 110's full-word metric failed and why the opcode metric works. A repeated source
block produces the same opcodes with different registers; requiring operands to match destroys
the signal rather than sharpening it.
121: the filled-delay-slot class has TWO sub-cases with DIFFERENT fixes -- reorg fills the slot
(source-shape hunt) versus maspsx mode changing WHICH instruction lands in the slot (a harness
token choice). Same diagnostic, different remedy. Check whether toggling maspsx changes the
fill before hunting a source shape.
122: NEW BLOCKED CLASS -- a GTE coprocessor body needs a harness token, not more spellings.
Worker B's 0x8001FAFC reads mfc2 $12/$13/$14 and branches on t7/s6 which are NOT the o32
argument registers, so the inputs arrive through a non-standard convention. Team rule: if a
body contains mfc2/mtc2, do not spend spellings on it -- these are tooling-blocked rows to be
worked as a batch once a token exists.
Worker D's 0x80106AA8 (136 B, first attempt) -- found by the REDUNDANCY filter, not
adjacency, which is the first row where the ranker did the finding alone. Eight stores
through four global pointers, each re-materialised per store.
Cookbook 105's dial now has THREE measured settings: per statement (0x8006BC74 46x and
0x80106AA8 8x, both matched), once per block (matched), once per function (does not match).
So per-statement re-reads are the NORMAL shape, not an extreme.
119: worker D ran the fragment check, called 0x80058BA0 a confirmed fragment, then
SELF-CORRECTED -- it is legal, because in o32 a frameless leaf may both read and write the
caller's outgoing argument area (sp+0..sp+31). All three of D's suspects are legal. Worker B
found the read side, worker D the write side, and both had to read the row to do it: a
heuristic keyed on shape must state its exclusions, and only the worker reading the row can
find them.
116: worker A's hypothesis that the ranker cannot see LIVE RANGES (its conversion rate went to
zero over three consecutive rows, all failing on register allocation with the structure fully
confirmed). I tried to make it computable -- callee-saved registers saved and s-registers per
call -- and the proxy FAILED: matches span 0.00-2.00 s/call and failures span 0.17-1.33, with
a matched row having the HIGHEST s-register count and another matched row having 16 calls and
0 saved registers. The hypothesis is not refuted; the proxy is just not a good
operationalisation of 'live range'. Recorded as an open axis.
117: worker C's new signature -- correct control flow, +3 instructions, all three being j/jr
delay slots. DISTINCT from a nesting error (both give a small residual, but nesting moves
branch displacements while this moves instructions ACROSS a jump). Belongs with the post-pass
family.
118: call COUNT does not separate a real block from a call chain -- worker C's matched
0x80058CE8 is four loops that each call, and its false positive has zero calls. Fails in both
directions; recorded so nobody re-derives it.
Worker B's 0x80050CA8 (120 B, first attempt).
Lever: the status word is masked by TWO separate statements (&= -3; &= -5;), and the original
emits one load, two ands against two different constants, one store. Combining the masks
folds to a single and and LOSES an instruction -- the same principle as finding 81 (a slot
stored twice is two statements) applied to read-modify-write. Companion: the status load is
hoisted above nine halfword clears, so the clears' source order is only observable through
the store order.
The first version of the fragment check fired on any nonzero sp offset and flagged 2 of the
555 registered regions. Worker B read its own hit (0x800B704C) before committing and showed
it is a LEGAL FRAMELESS LEAF WITH EIGHT ARGUMENTS: in o32 the callee's sp is unchanged at
entry, so sp+16..sp+28 IS the caller's outgoing area -- arguments 4-7 -- and reading it
before any addiu sp,sp,-N is exactly what a frameless >4-argument leaf looks like. Its
evidence: exactly one jr ra, zero jal, zero addiu sp,sp,-N, zero sw ra/lw ra across all 324
bytes, and no callee-saved register touched.
Excluding the incoming argument area (sp+0..sp+31) and flagging only a negative offset or an
offset beyond the 8-argument area makes the check DISJOINT:
registered regions flagged: 2 of 555 -> 0 of 555
suspects across 4 partitions: 5 -> 1
The one remaining suspect is worker A's 0x800C3490.
This is the cleanest example in the phase of a worker ADJUDICATING a tool's output rather
than obeying it -- B was explicitly told 'advisory, do not skip', read the row anyway, and
its adjudication turned a noisy heuristic into a precise one.
Worker A found 0x800C3490 is not a matchable body: it starts mid-expression with sw v0,32(sp)
before any frame setup, and its identical tail also appears at 0x800C3470, so it is a
shared/jump-target block Ghidra promoted to a function -- inside no region, with the code
before it in no worklist, so nobody can match it standalone.
Its generalised rule is narrower than 'first instruction is not prologue-like', because a
function may legally start with beq/sh/move: a row is a FRAGMENT if its first instruction
touches the stack before any addiu sp,sp,-N, reads a stack slot, or uses a callee-saved
register that is never saved.
Implemented as a --fragments scan. Measured: 5 suspects across all four partitions, but 2
false positives across the 555 REGISTERED regions, so it is ADVISORY not an exclusion --
sufficient-but-not-complete like the trapping check. A hit means read before spending a
spelling, never skip.
111: $sp cannot be moved from C, so a stack switch is INLINE ASM and the statement SPLIT is
byte-load-bearing -- seven separate __asm__ volatile statements with the call between them,
and the nop in each jal's delay slot falls out of the following statement being a compiler
barrier. One asm block, or a register int sp, does not reproduce it.
112: an intermediate that must live in MEMORY (the original reloads it before each of three
calls; a scalar local gets register-allocated and loses 8 bytes of frame); the chained
assignment's store order; and finding 100's then/else diagnostic confirmed on a new row.
113: a region's END address is EXCLUSIVE, so the registry free-check must be a containment
test, not a string match. Worker A was wrongly blocked on 0x8006B7C0 because it is the
exclusive end of 0x8006B778's range. This is the third defect found in a coordinator-written
rule this phase; the workflow doc now requires a range test.
Worker D's 0x800307FC (92 B) and 0x80031EBC (112 B), both first spelling by adjacency.
109 records worker D's calibration claim -- 'adjacency finds the ROW, redundancy predicts the
PRICE' -- and the harder discipline behind it: D read 0x8006C044, identified it as a
tie-break-dense 3D-maths routine, and RELEASED it in favour of two small adjacent rows that
together cost less context than a first draft and returned two bodies instead of zero-to-one.
110 records a NEGATIVE RESULT from the coordinator. Worker C found a real false positive (the
ranker's top row is tie-break-dense, its score inflated by a repeated multu/mflo/sra idiom)
and proposed comparing full instruction words instead of opcodes. I implemented that and
measured it: it scores two KNOWN matches at ZERO and the known false positive HIGHEST. The
reason is fatal -- a genuine repeated source block does not produce identical instruction
words across copies because the allocator assigns different registers, so 'same opcodes,
different operands' describes a repeated block and a repeated idiom equally well. They are
indistinguishable at the instruction level. The opcode metric stays.
Worker B's comparability caution: the redundancy score is the MAXIMUM of the 2/3/4-gram repeat
ratios, so a high score means 'some length scale is very repetitive', not 'repetitive at every
scale'. Any implementation using a mean or a different normalisation gives non-comparable
numbers, which matters because workers were sharing rankings across partitions. Pinned in the
tool's docstring with this tool named as the reference implementation.
107: finding 43's goto lever is NECESSARY BUT NOT SUFFICIENT -- worker B found it overshoots
(152 vs 140) on 0x800FCA90, and this is the second independent instance after worker A's
0x800256F0. Treat it as one attempt, not as the fix.
Worker A appended three rows optimistically, classified them as near-matches, and then had
no way to clear them -- because the ledger was append-only with no terminal state. Every
other worker would have skipped three free rows.
Fixed: a status column (wip / released) where the effective state of an address is its LAST
row, and a rule that a worker must append 'released' when it stops. Worker A's three rows
are released.
The general lesson is recorded in the workflow doc: a write-ahead log needs a TERMINAL
state, not just an opening one, or the log itself becomes the stale data it was meant to
prevent. Any append-only coordination file needs a way to say done.
105: the pointer re-read granularity is a DIAL, measured in both directions by worker D --
re-read per store (700 B row, 46 times, matched), once per block (276 B row, matched),
per statement (80 bytes too long), and never (does not match). Cause is aliasing. It is the
same property as finding 45's named-locals family but as a COUNT rather than a yes/no.
Plus: a genuinely uninitialised read in the original must be preserved, not corrected.
106: which failure class a row lands in depends on the FILTER, not the band. Worker D's
cheap rows fail on frame/combiner/allocation/batch-shape and never on the branch
diagnostics, so those belong on tie-break-dense rows. Route a diagnostic to the row shape
it matches rather than broadcasting it.
Worker A's 0x80033DC8 (360 B) and 0x8006A98C (132 B), plus two gp symbol rows
(D_80122724, D_80122728) that unblock 0x800A4CA8.
PROCESS DEFECT FOUND AND FIXED. Worker C and worker D both matched 0x800320D8
independently and D overwrote C's source file. Nothing corrupted -- both spellings match
and the region still reports 276/0/MATCH -- but one worker's effort was duplicated. The
partitions are genuinely disjoint (273/279/278/271, union 1101 = sum), so there was NO
assignment error: the gap was that no worker could know another had started a row, since
the registry only knows about MERGED claims and both started before either merged. The
root cause is the adjacency rule (cookbook 99, 4-for-4) crossing partition boundaries --
the best dispatch heuristic found so far invalidated the assumption the assignment rested on.
Fix: .run/p11/inflight.tsv (write-ahead log alongside the merge registry's commit log),
with the protocol written up in docs/ORCHESTRATOR_WORKFLOW.md so the next orchestrator
inherits it.
Worker A's 0x800556E8 and 0x80055654 (both first/second attempt).
Cookbook 99 is a DISPATCH rule, not a codegen one: take the row ADJACENT to one you just
matched. The binary is laid out by translation unit, so neighbours share the author's habits.
3 for 3, all first or second attempt, and it beat both the size ranker and the LRS ranker.
Cookbook 100 puts the three branch-shaped diagnostics side by side -- each maps a residual
shape to exactly one cause and each is a glance rather than a spelling:
branch displacement words only -> block NESTING (95)
first few instructions, right length -> then/else ORDER of a single-statement arm
whole prologue, same multiset -> declaration vs assignment order
Vector copies are now confirmed on FIVE independent rows.
Worker A's two adjacent claims (0x80036B14, 0x80036DA4).
Cookbook 95 is the cleanest diagnostic of the phase: a correct-length candidate whose residual
is a handful of BRANCH WORDS means the block NESTING is wrong, not the code inside the blocks.
Worker A got exactly 656 bytes (correct length) with exactly 2 differing bytes, both branch
displacements, by writing two guards as siblings instead of nested. Residual = 2 bytes at a
branch displacement => go look at your braces.
Also: the project's 4-int vector type is identifiable from the frame (multiple of 16 with
offsets stepping by 16); vector copies are struct assignments (third independent confirmation);
and an OPEN question is recorded -- 'the original spills everything, cc1 promotes' -- with a
request for a recipe from any worker who has solved it.
Worker B's four first-attempt claims (0x800B255C, 0x8004857C, 0x80030858, 0x800909D8).
Cookbook 94 is the strategic one: worker B's failures cluster into exactly TWO mechanical
classes -- reorg slot-fill choice and rare-epilogue fill -- and neither is a shape problem.
Both are the post-pass family, which two workers have now independently arrived at and
stopped on. That is the strongest argument yet for writing the post-pass rather than
grinding these rows with source spellings.
Worker D's most transferable finding yet: the ADDRESS SYMBOL is what selects the addiu form.
A literal (int)0x8013F9B8 gives lui+ori; &D_8013F9B8 gives lui+addiu. So 'ori where the
original has addiu' means the source used a literal where it should reference the address
symbol -- a 40-byte error that presents as a LENGTH mismatch, which is why it reads like a
codegen problem. Extends findings 4/46.
90: a (gp)-relative access to an unregistered address is a SYMBOL REQUEST, not a source
problem -- the harness derives gp-ness only from config/symbols.tsv and no CLI option adds
it. Worker D verified its row by pointing --symbols at a scratch copy of the registry.
91: a real source-order quirk (two successive call addresses swapped) is preserved in a
match and is not a transcription error.
Worker A's 0x8009C904: 548 bytes, matched on the FIRST spelling. Cumulative evidence for the
redundancy ranker is now 548B/1st, 248B/3 spellings, 1232B/2, 700B/3 -- against 176B/9
failures for a tie-break-dense row.
Cookbook 87 is the important negative: cc1 does NOT unroll a constant-trip-count loop (the
for-loop form is 212 B against the original's 780 B), so whenever an original is unrolled the
SOURCE is unrolled too. That explains why the >800 B band is full of cheap rows -- those
bodies are unrolled in the source, and an unrolled repetitive body is exactly what the
ranker scores highest.
Worker B's 0x80069580 (88 B) and 0x8007E7FC (96 B), plus two gp symbol rows
(D_80122168, D_801221D0).
Cookbook 84 is the harness row for the post-pass: worker B isolated the rare-epilogue
transform exactly (move the frame release into the jump slot AND insert the load-delay nop
after lw ra), and established the load-bearing detail that as will NOT perform this fill
because doing so would put jr ra in the lw ra load-delay slot. So a post-pass that merely
moves the release into the slot produces wrong code. Also measured: maspsx=off is WORSE on
this row (72 bytes) because it strips nops from the beqz/jalr slots the original keeps, so
the two mechanisms are not substitutes.
85: cc1 folds SYM+N into a single la and SIX spellings do not defeat it.
86: cc1 cross-jumps identical guards; goto to a shared return label is the named lever.
Worker C's 0x800320D8 (276 B), matched on the FIRST spelling where its sibling 0x80031FC4
took 5 -- the family lever measured, on one family, both ways. Finding 55's limit confirmed
on the same family: a third row calling the same callee is NOT the same body and sits at
+16 instructions. The family transfers the derivation method and the stable positions,
never the body.
Also recorded: an OR nested inside an && chain is observable from the branch DIRECTIONS --
bne to the call block on one test and bnez to the manual-copy block on the other is
if (x == 0 && (a != 6 || b == 0)) call; else manual;
Worker D's 0x800910BC (280 B), its 8th match.
TWO CORRECTIONS TO THE COORDINATOR'S OWN COOKBOOK ENTRY, both from measurement:
- 67 was INCOMPLETE and cost worker D a spelling. The magic alone is AMBIGUOUS: D = 2^(32+s)/M
where s is the shift of the sra after the mfhi. 0x2AAAAAAB is /6 at s=0, /12 at s=1, /24 at
s=2, and worker D read it as /6 when the shift was 1. The corollary is worth having too: the
same magic twice in one function is not a contradiction (0x66666667 serves both /10 at s=2
and /5 at s=1, materialised once into a callee-saved register).
- The named-local rule is PER-SITE within one function. Naming a result the original consumes
immediately costs 2 words; naming one the original reuses is free. Apply the decision once
per VALUE, not once per function.
That is now the fourth correction to coordinator work this phase, and every one came from a
worker measuring something the coordinator had asserted.
Worker A built a repetitiveness score (repeated 2/3/4-instruction opcode subsequences,
normalised by body length) and produced the cleanest controlled comparison in the phase:
3 spellings on a 248 B repetitive row vs 9 failures on a 176 B tie-break-dense one. That
converts 'prefer a repetitive body' from a hunch into a sortable number, so size is
deprioritised as the ranking signal.
Also recorded: a transposed temp array is byte-required (int m[3][4] used as m[c][r]) with an
exact diagnostic -- right length + right instruction multiset + residual only on sp-relative
offsets means the frame LAYOUT is wrong, not the code; and when the original stores the same
slot twice, suspect two source statements rather than a scheduler quirk (GCC 2.7.2 has no DSE).
Worker C's 0x80031FC4 (276 B). Cookbook gains six entries, the most important of which is
worker C's correction of the COORDINATOR: a DEPENDENT row is one you cannot VERIFY, not one
you have MATCHED. C's 0x800A613C and 0x800FD120 had symbol rows outstanding, but
re-verifying against the tracked registry gave byte-identical results to the overlay runs --
both are still near-matches blocked on an ALLOCATION lever. Adding a symbol row unblocks the
verification, not the match; conflating the two would have had a worker stop working a row it
had not solved.
Also recorded: the address-taken value may be a PARAMETER not a local (frame 8 too big with
all offsets shifted by 8 is the tell); address-taken form forces a register; the struct
assignment is what BATCHES the loads where element stores serialise behind maspsx nops; and
the cop2 operand is the 25-bit field (0x486012 -> 0x4A486012).
Worker C found the defect while characterising an above-ceiling row: the Phase 10 fix
that made maspsx honour cc1's explicit `#nop` marker unconditionally is wrong for a
bare-symbol store consumer, because the store's own `lui $at` expansion fills the
delay slot and the marker is SPURIOUS. On 0x800AFDBC the original is
`lhu` / `lui at` / `sh` with NO nop, and honouring the marker costs 2 instructions --
turning a row that was otherwise byte-identical into a LENGTH-MISMATCH.
But the fix is genuinely needed for 0x80107C5C (112 vs 108). So the two rows want
opposite behaviour from the same instruction shape, and the honest resolution is to
make it a per-region mode rather than a global default.
`maspsx=nopmarker` -> --honour-nop-marker (default OFF)
VERIFIED BOTH WAYS:
make check with the mode OFF -> regions=510 AGREE, differing_bytes=0 MATCH, 253 tests OK
0x80107C5C WITH the mode -> 112 B, differing_bytes=0 MATCH
0x80107C5C WITHOUT it -> 108 B LENGTH-MISMATCH
So the default is the long-standing behaviour, and rows that need the marker opt in.
The tracked patch is regenerated and verified to reproduce both modified files exactly
from the pristine pinned checkout (tools/maspsx is git-ignored, so the patch is the only
reproducible carrier). docs/SETUP.md records the correction.
This is the third time this phase a worker found a defect in work the coordinator had
already shipped as verified -- the pattern is worth noting: a fix proven regression-free
against the CORPUS can still be wrong for an UNMATCHED row, because the corpus only
exercises the paths that already work.
Worker D's 0x8005A33C (312 B) and 0x8009F5AC (244 B) MATCH, both verified independently
and gated on the whole binary before promotion. Corpus max is now 312 B (244 -> 248 ->
264 -> 312 across two workers).
COOKBOOK 63 — NEW LEVER, the combiner constant-fold class. A residual of exactly one
addiu plus a matching shift in every displacement of one base register means cc1's
combiner folded a constant offset the original kept in a register. Writing the base
inline triggers it (308 B, one instruction short, 6 differing words all the same
thing); materialising the offset as a NAMED LOCAL blocks the fold and matches. It is a
4-byte class that is neither source structure nor scheduling, and it is INVISIBLE to a
mnemonic-level diff because every mnemonic is right.
COOKBOOK 64 — finding 59's family now has three load-bearing properties: row stride
(0x8009F6A0), total size (0x800308C4), and MEMORY RESIDENCE (0x8005A33C, where a union
lets cc1 scalarise the packed scratch into and/sra/sll).
COOKBOOK 65 — THE AMENDMENT TO FINDING 41 IS NOW MEASURED. Worker D ran the paired
control deliberately: the body immediately PRECEDING its first ceiling match, the
inverse transform with an identical frame shape. <=244 B: 1 attempted, 1 matched (100%),
first spelling. >244 B: 4 attempted, 3 matched (75%), 2-7 spellings. THE BAND WAS NOT
THE VARIABLE -- THE LEVER SET WAS. And the two LARGEST bodies were the EASIEST (312 B
and 264 B at 7 and 2 spellings), because a larger body has more redundant structure to
key off.
COOKBOOK 66 — a bounded negative with a named direction beats a blocked class: the
allocation failure on 0x800FF5A8 is the named-locals family in the OPPOSITE direction
(the original keeps a0 in TWO callee-saved registers, so what is missing is a named
local the allocator cannot coalesce).
Worker D's 0x800308C4 (264 B) MATCHES, verified independently and gated on the whole
binary before promotion. Three bodies now sit above the old 244 B ceiling
(248/248/264 B) from two independent workers, so the dispatch-artefact verdict is
settled by result.
COOKBOOK 59 EXTENDED WITH A NEW MECHANISM. D's first attempt was 8 words from a match
and all eight were the FRAME; 56 of 64 words were already byte-identical including
every call site, delay slot and register. The missing 8 bytes are an UNREFERENCED
ARRAY LOCAL, and the mechanism is measured across five spellings: cc1 allocates stack
space for an unreferenced ARRAY local but NOT for an unreferenced SCALAR local
(`int pad0, pad1;` gives no home; `int pad[2]` and `short pad[4]` both match). The 8
bytes are size-load-bearing and content-free -- a limit of the reconstruction, not a
recovered fact, recorded as such in the file header.
AMENDED RULE: when the residual IS the frame, vary the local aggregate's declaration
in THREE directions -- element type, row stride, and total size. Instance 1
(0x8009F6A0) needed the row stride; instance 2 (0x800308C4) needed the total size.
Finding 58 AMENDS finding 41, which was Phase 10's headline result and drove every
dispatch decision. The "1-in-12 for 200-800 B" comparison was taken on a queue that
had never been attempted (5 of 427 rows above 244 B ever tried, 1.2%; three in an
excluded class; both non-excluded attempts near-matched). The first row attempted
above the ceiling matched. The band's measured yield is a function of the lever set at
the time of measurement, and the lever set grows -- RE-MEASURE a band before
concluding it is exhausted, and never treat a band as closed at ~1% attempt coverage.
Findings 59-62: the local-aggregate row-stride/element-size lever that broke the
ceiling (int t[3][4] not int t[9]; frame 48 vs 40; residual concentrated on the frame
adjustment); the division_check trapped class; the localisation of the maspsx/GNU-as
mutual exclusion with the developer's ASPSX-as-oracle-only rule; and the fail-fast
validation of region override keys in sf3_merge.
Written at the developer's request after the control model changed from "the developer
spawns sessions" to "the orchestrator spawns, monitors and retires its own workers",
with pi-context-tools installed globally.
Covers: authority and what the orchestrator decides versus escalates; the herdr control
model with exact commands, the verified 2x2 topology, and the known failure modes
(including the transient agent_pane_not_found on a just-split pane, and the
mutual-ask intercom deadlock); the context model and the compaction-first policy with
the requirement it creates (state must live in files); roster sizing and the Goal A /
Goal B assignment split; the session lifecycle including the updated capability probe
with context_info; the hardened merge flow with the md5 drift guard; the verification
ladder; the full charter template; all nineteen process rules WITH THEIR PROVENANCE
(each earned by a real incident); and the phase open and close checklists.
Supersedes the session-management sections of PHASE8_PROTOCOL.md and PHASE9_PROTOCOL.md;
the merge/verification contract in those documents still stands.
MILESTONE MET AND EXCEEDED: 484 distinct matched bodies / 493 registered regions
(target 475, from the 400 baseline) — +84 bodies. Developer confirmation of the
milestone was requested and given before any close record was written.
Closing checklist all green from clean:
make clean && make all exit 0
cmp exit 0
SHA-1 both files e173426c157384ebf1b6caf8c6fea18a85a14af9
make test 237 tests, OK
make extents-verify regions=493 disagreements=0 AGREE
make gate c_regions=493 differing_bytes=0 MATCH
registry audit 493 rows, 0 overlaps, 0 unsorted, 0 bad extents,
0 missing sources, 484 distinct sources
worklist listed=1193, excluded_already_registered=493
negatives index 194 rows, address-ordered, 0 registered
git status --short src/ empty (0 untracked files)
firewall 0 prohibited-root paths (591 tracked files)
New records:
phase-ends/PhaseEnd_Phase10.md the phase record
docs/PHASE10_VERIFICATION.md the verification record
docs/MATCHING_COOKBOOK.md findings 41-57 (57 total)
phase-ends/CURRENT_PHASE.md CLOSED, with the checklist itemised
phase-ends/DIGEST.md the Phase 10 digest entry
The headline finding is methodological (finding 41, THE SIZE-BAND LAW): the matched
corpus median is 48 bytes with 454/459 at <=200 B while the remaining levered rows
had a median of 456 B, and two independent measurements — one controlled — put the
small band at 1-2 attempts per row against 1-in-12 for 200-800 B.
The phase's character: five of the coordinator's own generalisations were bounded by
workers (rare-epilogue class, register-field diagnostic, polarity lever, goto
trigger, load-delay consumer form). The rules that survived are the ones that were
bounded.
Five incidents recorded rather than smoothed over; the candidate gate rejected three
batches and the tracked registry was never corrupted. Scope held: the blocked classes
stay excluded, no scheduler-changing flag was granted, and inline asm was extended
only to shapes C provably cannot express.
STOPPING HERE. Phase 11 does not begin in this session.
cc1 emits an explicit `#nop` marker when it wants a load-delay nop. maspsx used to
RE-DERIVE the need and could overrule cc1 for a BARE-SYMBOL STORE consumer:
uses_at('sw\t$2,D_801221C4') -> True (macro store, expands via $at)
uses_at('sw\t$2,0($4)') -> False (register+offset, no macro)
nop_at_expansion is False for ASPSX >= 2.30
so neither test in _handle_nop_before_next_instruction fired, nop_required stayed
False, and an instruction cc1 had explicitly asked for was dropped (0x80107C5C at
108 vs 112; worker B's 0x8003A9C8). The fix honours the marker instead of
overruling it; only that path changes.
Worker B2 found the gap but mis-diagnosed it: its proposed fix was to extend the
`line_loads_from_reg` predicate, which ALREADY returns True for a store source, so
that patch would have been a no-op. The coordinator traced the actual call and found
the real mechanism in the uses_at / nop_at_expansion interaction. B2 then appended a
CORRECTION row to its own staged report superseding its paragraph — the right
response, and it records the general lesson: a named mechanism is a hypothesis until
it is traced, even when the observation is solid and reproducible.
REGRESSION VERIFICATION (the whole point of gating this):
make check exit 0
regions=489 disagreements=0 AGREE
c_regions=489 differing_bytes=0 MATCH
237 tests OK
All 489 previously-matched regions are byte-identical with the fix in place.
Carried as a tracked patch (tools/maspsx/ is git-ignored, so an in-place edit would
not survive a fresh clone); patch verified to reproduce both modified files exactly
from the pristine pinned checkout. docs/SETUP.md records the fix and its provenance.
0x80107C5C NOW MATCHES (112 B, 0 differing, verified against worker B2's variant
X3.c) — but that is a BARE variant with no header, and the project convention requires
a documented source stating the observed instructions and limits. So the row is
UNBLOCKED and one documented source away, not claimed. Recorded as a carry-forward.
Developer decision (2026-09-24), extending Phase 8's coprocessor/kernel allowance.
Trigger conditions recorded: plain C attempted and mechanism identified; the
inexpressibility is structural rather than a scheduling/allocation tie-break;
integer logic stays in C and scheduling still comes from cc1/maspsx; per-file
header documents the sequence, the reason and the limit.
First accepted instance: the PSX scratchpad stack switch (lui at,0x1f80 /
sw sp,1020(at) / lui sp,0x1f80 / ori sp,sp,0x3dc ... / lw sp,1020(sp)), found
independently by workers A (0x800BC658) and B (0x800BC6EC). A stack-pointer READ
was already covered by the register-name binding and needs no exemption; only the
switch does.
Explicitly does NOT reopen trapping-arithmetic, rare-epilogue or primitive-init.
32 new bodies from 400, all verified on the candidate whole-binary gate before
promotion. SHA-1 e173426c157384ebf1b6caf8c6fea18a85a14af9 stable.
Registry requests granted (each byte-verified with a failing control):
cc1=-G8 on 0x800A6BEC; gp=-D_80121B88 on 0x80015D50
symbols D_80122700, D_80122704, D_80121AD4 (gp)
Harness: per-region maspsx modes wired through sf3_match (maspsx=noreordernop,
maspsx=regread) plus --no-jump-slot-nop/--nop-on-reg-read for range. Both are
opt-in and default-off; make check green at 441 with them off, suite 229 -> 232
tests. Carried as a TRACKED patch (tools/patches/maspsx-phase10-r1r2.patch)
because tools/maspsx/ is git-ignored, so an in-place edit would not survive a
fresh clone; patch verified to reproduce the working tree byte-identically.
R1/R2 are recorded as a MEASURED NEGATIVE: neither closes a region (cookbook
finding 40 has the mechanism and the remaining developer-owned route).
Docs: cookbook finding 40 (rare-epilogue mechanism + why the obvious maspsx fix
fails); SETUP.md maspsx patch provenance and apply step.
Negatives: 0x8010AA28 imported; index sorted by address (140 rows, 0 registered).
Full clean audit green: make clean && make all exit 0, cmp exit 0, both SHA-1
match, registry 441/0 overlaps/0 bad extents/0 missing sources, 0 firewall.
Phase 9 closed at 400 distinct bodies / 409 regions (from 149/158), all
gates green from clean: CMP_OK, SHA-1
e173426c157384ebf1b6caf8c6fea18a85a14af9, 229 tests, make gate
c_regions=409 MATCH, extents-verify AGREE, worklist listed=1343 with
excluded_already_registered=409, firewall 0, src/ 0 untracked.
Milestone partially met (400 of 500): the blocker is recorded with per-class
evidence (tier-1 tie-break domination, class disjointness, measured rate
decay) per the plan's Or branch. PhaseEnd_Phase9.md, digest entry, and the
ledger archive (logs/Phase9.md) written. docs/PHASE9_PROTOCOL.md (scale-run
retrospective: queue fix, tier-2 pivot, family-set lever, rotation
discipline, leading-indicator rule, measured budget), docs/PHASE9_VERIFICATION.md
(all gates, incidents, class exclusions), cookbook findings 29-39,
conventions additions, README updated to 400/409.
Phase10_PLAN.md drafted (Goals A/B/C: tail squeeze vs library-boundary
investigation, developer decisions enumerated) — requires explicit approval
before any Phase 10 task; no Phase 10 work begins in this session.
Developer-confirmed milestone: 149 distinct byte-identical bodies across 158
registered regions, up from 34 / 39 at Phase 7 close - 115 new bodies against a
target of 70, produced by three sessions working one worktree under a written
protocol.
Adds PhaseEnd_Phase8.md and the digest entry, archives CURRENT_PHASE.md as
logs/Phase8.md, and completes docs/PHASE8_PROTOCOL.md with the retrospective the
developer asked for: which rules were load-bearing (worker/coordinator file
ownership, candidate-first merging, worklist regeneration, self-contained
charters), where the protocol needed a decision, the four failure modes seen with
their mitigations, and reusable CHARTER and worker-report templates so the next
coordinated phase does not pay the cost of writing them again.
Final gates: 203 tests; make clean, make all, cmp and SHA-1 e173426c...; make
check green at c_regions=158 with 0 differing bytes and regions=158
disagreements=0; registry audit 0 violations; no untracked file in src/; 244
tracked files, none under a prohibited root.
All gates exit 0 from a clean state: make clean, make all, cmp, both files SHA-1
e173426c..., and make check (203 tests, regions=158 disagreements=0, c_regions=158,
0 differing bytes). The registry audit reports 0 violations across 158 ordered,
non-overlapping regions whose every extent is graded exact and whose every source
exists. The worklist regenerates with excluded_already_registered=158, equal to
the registry. 244 tracked files, none under a prohibited root, and no unmatched
draft survives in src/.
Phase 8 went from 34 distinct bodies / 39 regions to 149 / 158 against a target of
70, with three pi sessions working one worktree under a written protocol. Adds the
verification record, the open-negatives index (addresses, sizes, statuses and class
labels only - no bytes, no instruction text), and the README status.
Worker B's last act is recorded as a result rather than a footnote: it tested
worker A's mirrored-layout lever against its own two bounded negatives and
produced a decision rule instead of a match - the lever applies when the
instruction count is already correct and the block order is wrong, so where the
count is wrong and the order right it only trades one defect for another.