cookbook: findings 58-62 — the size-band amendment, the ceiling break, division_check, the ASPSX oracle rule

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.
This commit is contained in:
Christopher Williams
2026-09-24 08:56:45 -04:00
parent 5d41f97421
commit 7b501ef8b0
+100
View File
@@ -919,3 +919,103 @@ that merely read one, so the count is a lower bound.
- **One attempt on a named lever, then classify.** A harness classification backed by a proof is worth
more than an assumption; the model case is `0x80023D40`, where cc1's duplicated `slti` in the `bgez`
slot is **correct code**, so no source shape can prevent it.
## Phase 11 — amendments and new findings (2026-09-24)
### 58. AMENDMENT TO FINDING 41 (the size-band law) — the "1-in-12" figure was measured against a queue that had never been attempted
Finding 41's headline measurement was "≤200 B yields at 1–2 attempts per row against 1-in-12 for
200–800 B", and it drove every dispatch decision in Phase 10. **Phase 11's Goal B census showed the
comparison was not what it appeared to be.**
| | rows | ever attempted |
|---|---|---|
| >244 B | 427 | **5 (1.2%)** |
Three of those five are the already-excluded trapping class. **Both** attempted rows that are not in an
excluded class reached **near-match** (4 and 5 differing bytes). The corpus's size profile — 456 of 493
registered regions at ≤120 B, 0 above 244 B — is simply **a queue consumed from the small end**.
**The first row worker D attempted above the ceiling matched** (`0x8009F6A0`, 248 B), setting a new
corpus maximum. So:
- **The 244-byte "ceiling" was a dispatch artefact, not a measured wall.** Nothing about 248 B was
special.
- **The 1-in-12 figure was taken on rows attempted mostly *before* the length-class levers existed** —
cookbook 54's row-stride lever, the named-locals family, the two-arm branch family and the rest. A
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.**
- The "soft" verdict is a claim about **dispatch**, not a promise of yield: the two near-misses above the
ceiling still failed at 4–5 bytes, so per-row cost there is real.
*What survives from finding 41:* small bodies are still cheaper per attempt, and a worker should still
start there. *What does not:* treating a size band as *closed* because nobody has matched in it. **A
band with ~1% attempt coverage has not been measured at all.**
### 59. A local aggregate's row stride and element size are byte-load-bearing (extends finding 54)
`0x8009F6A0` (248 B) needed **`int t[3][4]`, only columns 0..2 used** — not `int t[9]`. The 4-word row
stride moves the 2nd and 3rd triples to `0x10` and `0x20`, makes the frame **48 B instead of 40 B**, and
leaves the unused `0x0C`/`0x1C` slots the original shows. Four spellings:
| spelling | result |
|---|---|
| nine `short` locals | 100 B LENGTH-MISMATCH — cc1 drops the sign extension (`lhu`+`subu`), no frame |
| nine `int` locals | 100 B LENGTH-MISMATCH — right signedness (`lh`), still no frame |
| `int t[9]` | 248 B, **19 differing bytes**, first difference **at the frame adjustment itself** |
| `int t[3][4]` | **MATCH** |
**Diagnostic: correct length + right instruction multiset and order + residual concentrated on the FRAME
ADJUSTMENT and every sp-relative offset ⇒ suspect a local aggregate's row stride / element size, not the
control flow.** The element type is the other half of the lever — `short` locals let cc1 drop the sign
extension, `int` locals keep it.
### 60. A new trapped class: compiler-generated division checks (`div` + `break`)
`break` **never** appears without `div` and `div` **never** appears without `break` — 75 worklist rows,
0 exceptions. The shape is GCC's divmodsi4 with `MASK_CHECK_ZERO_DIV|MASK_CHECK_RANGE_DIV`: `div` /
`bnez`+`break 7` (zero check) / `li at,-1` + `bne` + `lui at,0x8000` + `bne quotient,at` + `break 6`
(range check) / `mflo`. **`break` cannot be produced from C.**
**Measured disjoint from the matched corpus: 0 of 493 registered regions contains a div, a rem or a
break.** Same signature as finding 26's trapping class.
Compiler matrix: every available cc1 either emits a **bare** div with no check (open 2.5.7–2.91.66-psx,
and Sony CC1PSX 4.0–4.5, which reject `-mcheck-zero-division`), or emits a check with a **different
shape** (open 2.95.2-psx and CC1PSX 4.6: `mflo` *before* the check, scratch `$3`/`$4`, comparing the
**divisor** against `0x80000000`; the original checks before `mflo`, uses `$at`, and compares the
**quotient**). Not reproducible with this toolchain — the route is a cc1 build with those masks in
`TARGET_DEFAULT`, a developer-owned toolchain decision. Implemented as the counted exclusion
`division_check` (81 rows).
### 61. The maspsx / GNU-as mutual exclusion, localised — and the ASPSX oracle rule
**ASPSX 2.56 does BOTH the `move`→`addu` conversion and the macro-expansion delay-slot fill.** maspsx
does the first only; GNU `as` reorder mode does the second only; and **the two cannot be combined** —
maspsx must be in noreorder to supply its own nops, while `as` needs reorder *from the function start*
and then orphans cc1's own slots. Measured on `0x800FA5D8` across five spellings: `maspsx=off` gives the
correct length with all four fills and breaks all four `move` copies (`or` instead of `addu`, one byte
each); `maspsx=noreordernop` gives the right length with 30 differing bytes; `.set reorder` from the
function start over-fills to 144 B (reproducing finding 40); a per-site `.set reorder` window stays at
148 B (confirming that a mid-function `.set reorder` does not re-enable the fill).
Worker B measured **42 rows (14%) in one partition** with this signature, so roughly **~170 across the
worklist**. It is the *same* mutual exclusion that blocks the rare-epilogue class — **two phases of
evidence converging on one root cause**, and the strongest argument that one post-pass closes both.
**THE ORACLE RULE (developer decision).** `tools/psyq/psyq4.0/psyq4.0/ASPSX.EXE` under `wibo` is the
*exact* assembler that built the original, and it is tempting to use it as the build stage. **Do not.**
It is a proprietary, git-ignored binary, so a build depending on it could not be reproduced by anyone
else — and reproducibility is the point of a matching decompilation. Phase 10 hit the same trap with
maspsx, and that fix worked *only* because maspsx is open source and patchable; ASPSX cannot be
redistributed. **Use ASPSX as a read-only diagnostic ORACLE to characterise the behaviour, then carry
that behaviour as tracked code.**
### 62. Fail fast on an invalid region row
A worker placed the source md5 in a claim row's 4th column. `sf3_merge` passed it through as a region
override, so the row **merged** and only `sf3_match gate` failed later with `unknown override key
'md5'`. **`sf3_merge` now validates override keys at merge time** and rejects the row with a message
naming the valid keys and pointing at `report.tsv` for per-claim metadata. The lesson generalises:
**validate at the earliest stage that can see the error**, because a late failure is diagnosed as a
gate problem rather than a claim problem.