diff --git a/docs/cookbook-index.md b/docs/cookbook-index.md
index 9a3dfe2a7..849ef506c 100644
--- a/docs/cookbook-index.md
+++ b/docs/cookbook-index.md
@@ -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 / 505 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 / 506 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.
@@ -393,7 +393,7 @@
- **§163z** — THE UNVETTED REMAINDER (do not cite as law; each needs a dedupe pass) L11825
- **§3-The** — ADDRESS-CLASS TABLE: which load/store pairs even REACH the `/s` clause (P30 S48 wave 4, `func_80185B44`, ov_SC03_014) L14339
-### integration / TU plumbing (38)
+### integration / TU plumbing (39)
- **§8c** — Splitting a TU means rebuilding its DECLARATION ENVIRONMENT, not moving text (Phase 26 session 6) L437
- **§8d** — Templating a body INTO a TU must not CHANGE its declaration environment — demote the carried data externs (Phase 26 session 8, byte-proven on `func_8015AE2C` ×133) L483
@@ -433,8 +433,9 @@
- **§3-The** — same swallow, twice more, in the integration spine L9706
- **§3-A.** — Payload word0 is a global MODULE ID; code starts after the header L10524
- **§162** — THE LICM PAIR: what makes an address a movable AT ALL, and why the preheader order is the body order (P30 S47, `ov_MAIN_012`) L11140
+- **§166** — THE DESTINATION-TU ORACLE (P30 S48): the seven-attempt bug that was never codegen L14899
-### build graph, splat & the harness (107)
+### build graph, splat & the harness (108)
- **§4** — Flag/toolchain gotchas L190
- **Build** — mechanism — per-file opt override (splat resegmentation) L288
@@ -543,6 +544,7 @@
- **§162** — S48 WAVE-1 HARVEST (P30, 2026-08-11): the reach-ordered sibling campaign's first 12 targets L11011
- **§163** — S48 WAVES 2-3 HARVEST (P30, 2026-08-11/12): the five that were byte-probed and are actionable L11757
- **§165** — S48 WAVE-4 HARVEST (P30, 2026-08-12): banked the same day the wave landed L13686
+- **§166** — THE DESTINATION-TU ORACLE (P30 S48): the seven-attempt bug that was never codegen L14899
### process, measurement & doctrine (71)
@@ -1295,3 +1297,4 @@
- **§16Z** — SHARPENS *(sharpens §37 "the /s-DEP LATTICE", §136-13, §136-14, §16Xy, §162q, `gcc-2.7.2-map/sched.md` §64)* L14337
- **§3-The** — ADDRESS-CLASS TABLE: which load/store pairs even REACH the `/s` clause (P30 S48 wave 4, `func_80185B44`, ov_SC03_014) L14339
- **§165z** — REFUTED THIS WAVE: do NOT re-derive L14855
+- **§166** — THE DESTINATION-TU ORACLE (P30 S48): the seven-attempt bug that was never codegen L14899
diff --git a/docs/matching-cookbook.md b/docs/matching-cookbook.md
index 7d668e2c2..4ef186064 100644
--- a/docs/matching-cookbook.md
+++ b/docs/matching-cookbook.md
@@ -14893,3 +14893,46 @@ Target shape — a subtract whose three registers are all specific:
*Why:* Both halves fail. (1) NECESSITY, byte-refuted: replacing `if (t == 3) { goto sel_C94; }` with the duplicated body `if (t == 3) { sel = (u32)&D_801C3C94; goto set_sel; }` → MATCH 279. The 2-instruction `lui/addiu` + `j` tail clears §50-B's floor and jump.c merges it for you — this is precisely §164-69's 'a tail the compiler WILL merge must be duplic
* **`func_8017F2D4`** — `s32 lt = mode < 0xA;` must be an explicit local because gcc-2.7.2 has no gcse and the single `slti` must precede the branch both arms need it after.
*Why:* Byte-refuted by me: deleting the local and inlining the compare at both use sites — `if ((mode < 0xA) || (D_8011515A == 0x100))` and `if (!(mode < 0xA))` — gives MATCH 279. The C form is byte-INERT here, so it is not a lever and must not be taught as one. The mechanism phrasing is also imprecise: §164-52 byte-establishes that cse spans basic blocks
+
+---
+
+## §166 — THE DESTINATION-TU ORACLE (P30 S48): the seven-attempt bug that was never codegen
+
+**§166a — THE SPLAT ASM SUBDIR *NAMES* THE DESTINATION TU. A PROSE CITATION THAT DISAGREES WITH IT IS
+WRONG.** *(NEW. Nothing in §52b/§161c/§163a/§165-01 — the whole decl-conflict family — covers "you
+are editing the wrong file". They all assume the TU is known.)*
+
+ asm//nonmatchings//.s ⇒ the INCLUDE_ASM is in src//.c
+
+The third path component **is** the TU stem, derived from the split config. It is authoritative.
+
+**WHY THIS COSTS WAVES.** A `grep` for the function name also hits **callers** and **prototypes** in
+OTHER TUs of the same overlay, and those hits read exactly like a destination hit. `func_8017F2D4`
+was cited in five waves of notes as living in `ov_SC01_005_jr_8017C340.c` — that file holds only
+`ret = func_8017F2D4(c, ret);` and a prototype. The real `INCLUDE_ASM` is in
+`ov_SC01_005_jr_8017ED5C.c:3115`. Splicing into the wrong file is a **no-op**: the binary keeps its
+`INCLUDE_ASM` bytes, the SHA differs, and the run is recorded as a gate refusal.
+
+**THE COMPOUNDING FAILURE — a guess printed as a finding.** `gate_stage` labelled every such refusal
+`match_one MATCH but gate rejected (declaration/TU plumbing)`. That string is not a measurement; the
+tool never checked for a declaration conflict. Seven attempts across five waves hunted codegen and
+decl conflicts on a body that was **byte-correct from the first attempt**. Fixed: the message now
+states only what is true (the two oracles disagree) and hands over this check first.
+
+**THE RULE.** For ANY "standalone MATCH / whole-binary DIFF" entry: **re-derive the TU path from the
+asm subdir before hunting anything.** Then prove it in situ — splice into a private copy of the real
+TU (one directory deep, with a `shared` symlink so `../shared/engine_core.h` resolves), run the
+pinned triple end-to-end, and masked-diff your function out of the WHOLE-TU object.
+
+**TWO PROBE GOTCHAS** (both paid for in wave-5/6 agent time):
+* the wrong `--aspsx-version` yields ~32 spurious mismatches **all** of the `ori`-vs-`addiu` li-form
+ shape — that uniformity is the fingerprint of a version mismatch, never a codegen residual. Use
+ `--aspsx-version=2.56 --expand-div`.
+* a collateral-drift check must filter to symbols with a **real size** (`nm -S`): the
+ `*.NON_MATCHING` aliases are zero-size markers, so a masked diff falls back to the whole `.text`
+ and reports every one of them as drift purely because your function's bytes changed.
+
+*Byte evidence:* `func_8017F2D4` (ov_SC01_005 + ov_SC01_006, 279 ins). In-situ splice into BOTH real
+TUs: 279/279, 0 masked diffs, cpp/cc1 clean; collateral check 71/71 other sized symbols identical.
+The same agent corrected the family reach to **×2** (only two `.s` exist, byte-identical modulo the
+overlay name) against a map that claimed ×5.
diff --git a/tools/gate_stage.py b/tools/gate_stage.py
index 0dc901e2b..0673f07ac 100644
--- a/tools/gate_stage.py
+++ b/tools/gate_stage.py
@@ -450,8 +450,19 @@ def _run_gate_locked(drafts, binary, src, asm, out, good_sha, propagate, source_
sm = re.search(r"//\s*@stuck:\s*(.+)", body)
rclass = cm.group(1).strip() if cm else None # the worker's self-reported residual class
note = sm.group(1).strip() if sm else None
- if kind == "match": # match_one says MATCH but the whole-binary gate rejected -> plumbing/TU conflict
- status, where = "near", note or "match_one MATCH but gate rejected (declaration/TU plumbing)"
+ if kind == "match": # match_one says MATCH but the whole-binary gate rejected
+ # ⚠ DO NOT NAME A CAUSE HERE. This used to read "(declaration/TU plumbing)" — a GUESS
+ # printed as a finding. `func_8017F2D4` carried that label through SEVEN attempts across
+ # five waves while every agent hunted codegen; the body was byte-correct the whole time
+ # and the real fault was that the notes named the WRONG DESTINATION TU (a file holding
+ # only a caller + prototype). No declaration conflict ever existed. A diagnosis the tool
+ # did not measure must not be stated as one — say what is TRUE (the two oracles disagree)
+ # and hand over the check that resolves it (P30 S48).
+ status, where = "near", note or (
+ "match_one MATCH but the whole-binary gate rejected — CAUSE NOT DETERMINED. "
+ "FIRST re-derive the destination TU from the asm subdir: "
+ "asm//nonmatchings//.s => src//.c (the third path "
+ "component IS the TU). Only then look for a decl conflict or a codegen residual.")
near += 1 # …and COUNT it. It was logged as `near` and counted as NOTHING, so a
# run of 63 such drafts printed "banked 0, near 0, failed 0" — three
# zeros that do not sum to 63, and nobody ever added them up (R32).