docs(phase-30): SS130 — an INCREMENTAL build reported BYTE-IDENTICAL for a change the CLEAN build cannot LINK

I reported func_8013B83C + func_8013BD74 as "BOTH BANKED - BYTE-IDENTICAL". That was
WRONG. My in-loop gate ran `make extract && make build` without `make clean`, and it gave
a FALSE PASS. The clean rebuild does not link at all:

    ov_SC01_077_o0.c:(.text+0x10f8): undefined reference to `$L105'
    ov_SC01_077_jr_801588CC.o: undefined reference to `func_8013C938'

- the first is a local label from the C-emitted jump table;
- the second is a PREVIOUSLY-MATCHED cluster fn going undefined (the incremental build
  reused objects that still satisfied it).

Reverted; R22 140/140; nothing lost, nothing was committed.

SS42b named the stale-object trap for a FALSE FAIL. This is its MIRROR: a FALSE PASS, on a
change that is not even linkable — false in the most convincing direction possible, a green
SHA. Anything touching config/ (carve, resegment, split) must be gated by a full
clean-fleet R22 before it is BELIEVED, let alone reported.

THREE WRONG CLASSIFICATIONS ON THIS PAIR, all corrected in the ledger:
  CC1-FAIL-UNREAD      -> it has no compile error at all
  JTBL-PAD-SPEC-DRIFT  -> I had carved ONE fn of a TWO-table span; carving both gives
                          pad spec [0,4,4] and the filter is satisfied
  "both banked"        -> the false pass above
Real class: JR-PAIR-IN-ONE-O0-OBJECT. Both bodies ARE byte-correct (match_one --o0 and
rtu_match --o0 both MATCH, 272 / 198 ins); the blocker is integrating TWO jr fns into ONE
-O0 object. Untested escape: SS81 step 1, isolate one into its own code subseg so each
object owns exactly one table.

Also records the diagnostic ladder that found it: diff the two BINARIES and bucket each
differing byte against the function's own vram range. 3,749 of 3,791 diffs were OUTSIDE the
function, first diff near the overlay START, image 57 bytes LONGER - the SS8 signature of
.rodata floating to the front. That fingerprint separates a codegen residual from a layout
effect in one build, and it is what finally redirected three wrong guesses.
This commit is contained in:
Drew T
2026-07-31 18:47:00 -06:00
parent 7281e0af57
commit 8d40d55c7a
3 changed files with 58 additions and 4 deletions
+2
View File
@@ -1286,3 +1286,5 @@
{"ts": "2026-07-26 21:04:59", "addr": null, "name": "func_8013DD68", "reach": null, "klass": null, "nins": null, "status": "failed", "closeness": null, "where_stuck": "won't compile standalone (loose-typing / missing decl)", "best_draft": ".run/backlog_drafts/func_8013DD68.c", "binary": "ov_SC07_006", "source": "worker", "residual": null, "passes_tried": null}
{"ts": "2026-07-27 22:33:49", "addr": "0x80186b08", "name": "func_80186B08", "reach": null, "klass": null, "nins": null, "status": "near", "closeness": 43, "where_stuck": "residual: 43 mismatch", "best_draft": ".run/backlog_drafts/func_80186B08.c", "binary": "ov_SC02_005", "source": "grinder", "residual": [[0, "27bdffe8 addiu\tsp,sp,-24", "84830070 lh $v1, 0x70($a0)"], [1, "afbf0010 sw\tra,16(sp)", "00000000 nop"], [2, "00002821 move\ta1,zero", "2862000e slti $v0, $v1, 0xE"], [3, "3c070000 lui\ta3,0x0", "10400026 beqz $v0, .L80186BB0"], [4, "24e70000 addiu\ta3,a3,0", "00031040 sll $v0, $v1, 1"], [6, "24060014 li\ta2,20", "00220821 addu $at, $at, $v0"], [7, "8fbf0010 lw\tra,16(sp)", "94245b4c lhu $a0, %lo(D_80195B4C)($at)"], [8, "27bd0018 addiu\tsp,sp,24", "00000000 nop"], [9, "03e00008 jr\tra", "2c820016 sltiu $v0, $a0, 0x16"], [10, "00000000 nop", "14400005 bnez $v0, .L80186B48"], [11, "--", "00000000 nop"], [12, "--", "3c068019 lui $a2, %hi(D_801961E0)"], [13, "--", "24c661e0 addiu $a2, $a2, %lo(D_801961E0)"], [14, "--", "08061ad4 j .L80186B50"], [15, "--", "2484ffea addiu $a0, $a0, -0x16"], [16, "--", "3c068019 lui $a2, %hi(D_801961B8)"], [17, "--", "24c661b8 addiu $a2, $a2, %lo(D_801961B8)"], [18, "--", "00001821 addu $v1, $zero, $zero"], [19, "--", "3087ffff andi $a3, $a0, 0xFFFF"], [20, "--", "00c02021 addu $a0, $a2, $zero"], [21, "--", "84820006 lh $v0, 0x6($a0)"], [22, "--", "00000000 nop"], [23, "--", "1447000e bne $v0, $a3, .L80186BA0"], [24, "--", "000328c0 sll $a1, $v1, 3"]], "passes_tried": null}
{"ts": "2026-07-31 14:42:33", "addr": "0x8013B83C", "name": "func_8013B83C", "reach": 138, "klass": "CC1-FAIL-UNREAD", "nins": 272, "status": "blocked", "closeness": null, "where_stuck": "S28 wave: rtu_match --o0 MATCH (adversarially re-verified), whole-binary gate CC1-FAIL. The actual cc1 diagnostic has NOT been read yet - do that first (SS125 rule 1), do not infer. Worth 138.", "best_draft": ".run/s28w/func_8013B83C.c", "binary": null, "source": "ov_SC01_077", "residual": null, "passes_tried": null}
{"ts": "2026-07-31 18:46:12", "addr": "0x8013B83C", "name": "func_8013B83C", "reach": 138, "klass": "JR-PAIR-IN-ONE-O0-OBJECT", "nins": 272, "status": "blocked", "closeness": null, "where_stuck": "S28 CORRECTION x2 (supersedes CC1-FAIL-UNREAD, which was WRONG - it has no compile error). Body is byte-correct: match_one --o0 AND rtu_match --o0 both MATCH (272 ins). It is a jr/switch fn (jtbl_801D8254, 13 cases) sharing the -O0 object ov_SC01_077_o0 with func_8013BD74 (jtbl_801D828C). Carving BOTH tables together passes an INCREMENTAL build but the CLEAN build FAILS TO LINK: \"undefined reference to $L105\" (local label from the C-emitted jtbl) + \"undefined reference to func_8013C938\" (a PREVIOUSLY-MATCHED cluster fn going undefined). Blocker is INTEGRATION of two jr fns in one -O0 object, not codegen.", "best_draft": ".run/s28w/func_8013B83C.c", "binary": null, "source": "ov_SC01_077", "residual": null, "passes_tried": null}
{"ts": "2026-07-31 18:46:14", "addr": "0x8013BD74", "name": "func_8013BD74", "reach": 138, "klass": "JR-PAIR-IN-ONE-O0-OBJECT", "nins": 198, "status": "blocked", "closeness": null, "where_stuck": "S28 CORRECTION (supersedes JTBL-PAD-SPEC-DRIFT, which was a MIS-DIAGNOSIS: I had carved one fn of a TWO-table span; carving both gives pad spec [0,4,4] and the filter is satisfied). Real blocker = same as func_8013B83C: two jr fns in one -O0 object link-fail on a CLEAN build ($L105 + func_8013C938 undefined) while passing an INCREMENTAL one. Body byte-correct (rtu_match --o0 MATCH, 198 ins).", "best_draft": ".run/s28w/func_8013BD74.c", "binary": null, "source": "ov_SC01_077", "residual": null, "passes_tried": null}
+9 -4
View File
@@ -2,7 +2,7 @@
> **Generated by `tools/cookbook_index.py` — do not hand-edit** (R33). Regenerate after adding a cookbook section.
>
> `docs/matching-cookbook.md` is ~716 KB / 348 sections. Grepping it blind is how three P30 wave-1 agents each "discovered" an idiom that was already written down. **Start here, then read the section.** A section appears under every symptom it addresses.
> `docs/matching-cookbook.md` is ~716 KB / 350 sections. Grepping it blind is how three P30 wave-1 agents each "discovered" an idiom that was already written down. **Start here, then read the section.** A section appears under every symptom it addresses.
**How to use:** name what you SEE in the diff (a stolen delay slot, an extra `la`, a swapped register pair, a `conflicting types` error), find that symptom below, read those sections first. If nothing fits, THEN grind — and add a section when you win.
@@ -219,7 +219,7 @@
- **§3-Do** — NOT "strip the duplicate typedef" — it breaks the extern that uses it <sub>L8030</sub>
- **§121** — Synthesise externs for macro-DEFINED callees from the macro's own definition head (Phase 29 T95) <sub>L8054</sub>
### jump tables & switches (21)
### jump tables & switches (22)
- **§8** — rodata island (compiler jump tables) — the `.data→.rodata→.data` sandwich (Phase 7) <sub>L320</sub>
- **§8a** — rodata island in a flat OVERLAY — the tail sandwich, per matched jr-function (Phase 26 — PoC PROVEN) <sub>L342</sub>
@@ -242,6 +242,7 @@
- **§129** — Post-carve, `rtu_match`/`match_one` COUNT THE JUMP TABLE AS INSTRUCTIONS; and a carve must never be committed without its owner (P30 S28, `func_8013BD74`) <sub>L8430</sub>
- **§129a** — the target instruction count is INFLATED after a carve <sub>L8434</sub>
- **§129b** — never commit a carve whose owner is still a stub (it strands the carve) <sub>L8455</sub>
- **§130** — An INCREMENTAL build can report BYTE-IDENTICAL for a change the CLEAN build cannot even LINK (P30 S28, the jr pair) <sub>L8479</sub>
### optimisation level (-O0/-O2) (11)
@@ -360,7 +361,7 @@
- **§112** — A macro-scoped declaration only collides where the macro is INSTANTIATED (Phase 29 T67/T69, `audit_header_sigs.py`) <sub>L7723</sub>
- **§114** — The THIRD decl axis: a CALLEE the draft declares differently from the target TU (Phase 29 T76/T77) <sub>L7801</sub>
### build graph, splat & the harness (86)
### build graph, splat & the harness (87)
- **§4** — Flag/toolchain gotchas <sub>L190</sub>
- **Build** — mechanism — per-file opt override (splat resegmentation) <sub>L288</sub>
@@ -448,6 +449,7 @@
- **§122** — GATE RAW BEFORE TRANSFORMING; the undo belongs to the WRITER, as a per-edit journal (P30 T0a, 2026-07-30) <sub>L8077</sub>
- **§123** — PROPAGATE A FAMILY WITH THE TOOL ITS TIER NEEDS: `dedup_propagate` is h_exact-only; its refusals are statements about the TOOL (P30 wave 1, 2026-07-30) <sub>L8109</sub>
- **§126a** — a bare `except: continue` around a coverage-asserting oracle re-creates the silent skip (P30 S28) <sub>L8310</sub>
- **§130** — An INCREMENTAL build can report BYTE-IDENTICAL for a change the CLEAN build cannot even LINK (P30 S28, the jr pair) <sub>L8479</sub>
### process, measurement & doctrine (47)
@@ -499,7 +501,7 @@
- **§125** — Split the CARVE from the BODY before calling a jr residue a wall — and measure it by SHA from a CLEAN tree (P30 SESSION-28; **this section's first draft was WRONG and the method caught it**) <sub>L8199</sub>
- **§3-The** — meta-lesson <sub>L8250</sub>
### (unbucketed — title matched no symptom vocabulary) (93)
### (unbucketed — title matched no symptom vocabulary) (94)
- **§3-How** — to use this <sub>L30</sub>
- **§1** — Idiom catalog (asm pattern → C that produces it) <sub>L39</sub>
@@ -594,6 +596,7 @@
- **§128** — A raw NUL in C source makes grep SILENTLY SKIP the file (P30 S28, 137 files) <sub>L8386</sub>
- **§128a** — a negative control must corrupt a SCRATCH COPY, never the tracked file <sub>L8418</sub>
- **§3-The** — real blocker underneath, for the record <sub>L8470</sub>
- **§3-The** — diagnostic ladder that finally located it (reusable) <sub>L8516</sub>
## All sections, in order
@@ -946,3 +949,5 @@
- **§129a** — the target instruction count is INFLATED after a carve <sub>L8434</sub>
- **§129b** — never commit a carve whose owner is still a stub (it strands the carve) <sub>L8455</sub>
- **§3-The** — real blocker underneath, for the record <sub>L8470</sub>
- **§130** — An INCREMENTAL build can report BYTE-IDENTICAL for a change the CLEAN build cannot even LINK (P30 S28, the jr pair) <sub>L8479</sub>
- **§3-The** — diagnostic ladder that finally located it (reusable) <sub>L8516</sub>
+47
View File
@@ -8475,3 +8475,50 @@ With the carve applied, splicing the draft fails cc1 with
genuine §8e work (re-derive the multi-table pad spec including the new owner), not a wall — and it
was only reachable after §129a stopped the phantom 206-instruction "diff" from misdirecting the
diagnosis.
## §130 — An INCREMENTAL build can report BYTE-IDENTICAL for a change the CLEAN build cannot even LINK (P30 S28, the jr pair)
R22 has always said "verify from a clean rebuild." This is the sharpest instance yet of *why*, and it
cost two cycles because I used a fast in-loop gate that skipped `make clean`.
**The setup.** Two jr/switch functions, `func_8013B83C` (jtbl_801D8254) and `func_8013BD74`
(jtbl_801D828C), both in the SAME `-O0` object `ov_SC01_077_o0`. Both bodies are byte-correct —
`match_one --o0` and `rtu_match --o0` each report MATCH (272 and 198 ins).
**What the incremental gate said.** Carve both tables in one `jtbl_carve` call (correct — it is
additive and re-derives the full span; the object's pad spec becomes `[0,4,4]`), splice both drafts,
`make extract && make build` → **BYTE-IDENTICAL**. I reported both banked.
**What `make clean` said.**
```
ov_SC01_077_o0.c:(.text+0x10f8): undefined reference to `$L105'
ov_SC01_077_jr_801588CC.o: in function `func_801596F0':
undefined reference to `func_8013C938'
```
It does not link **at all** — and note the second error: a *previously matched* cluster function
becomes undefined. An incremental build reused objects that still satisfied those references; a clean
one has nothing to reuse and the real state surfaces.
**The rule, sharpened.** A byte-gate result from an incremental build is not weak evidence — it can
be **actively false**, and false in the most convincing direction (a green SHA). §42b named the
stale-object trap for a FALSE FAIL; this is its mirror, a **FALSE PASS on a change that is not even
linkable**. Anything that touches `config/` (a carve, a resegment, a split) MUST be gated by
`make clean && make extract-all && make check-all` before it is believed, let alone reported.
**The residual class this exposes: two jr functions matched in ONE object.** §8b already warns that a
single object contributes at most one contiguous `.rodata` run; the pad-spec machinery (§8e) handles
a multi-table span in principle, but matching *both* owners in the same `-O0` object produced
unresolved local labels (`$L105`) from the C-emitted tables plus a collateral undefined symbol. The
integrated per-sibling path (`jtbl_family_bank`) banks ONE jr function per object per transaction and
has never hit this. **Ledger class: `JR-PAIR-IN-ONE-O0-OBJECT`.** The escape, untested, is §81 step 1:
isolate one of the two into its own code subseg first so each object owns exactly one table.
### The diagnostic ladder that finally located it (reusable)
The function-level tools all said MATCH, so the signal had to come from the image:
1. Build with the splice, build without, **diff the two binaries**.
2. Compare each differing byte's vram against the function's own `[lo, lo+4*nins)` range.
Here: **3,749 of 3,791 diffs were OUTSIDE the function**, first diff near the overlay's START, and
the image was **57 bytes LONGER** — the §8 signature of `.rodata` floating to the front, i.e. "this
function emits a jump table", not "this function's code is wrong."
That size-and-location fingerprint distinguishes a codegen residual from an integration/layout effect
in one build, and it is what redirected the diagnosis away from three wrong guesses.