Files
BFM-decomp/phase-ends/logs/Phase35.md
T
Drew T e6d98dd1d4 chore(phase-35): CLOSE — Gen3's dedup phase: one source per unique function (3,175 shared headers, 3,173 registry groups incl. the h_text tier, the macro header gone, the five twins from one directory, S1 a tools-health gate with a second oracle, 14 macro-era tools frozen and 4 retired behind a guard, the published counts corrected) — v2.1.0
- PhaseEnd_Phase35.md written; CURRENT_PHASE.md archived to phase-ends/logs/Phase35.md (R19); DIGEST §0/§2 appended (step 3b)
- T0–T8 + T5b across S94–S96 (S94 died at 91 % without a checkpoint; S95 recovered it from the transcript; S96 finished)
- close: R22 clean fleet 218/218; tools-health OK (S1 10,180/10,180, 0 violations; dedup-check 3173/0; the guard 0)
- rules: R96–R99 ratified at gate 1; candidates (a)–(g) proposed for Phase 36 gate 1
- v2.0.0 -> v2.1.0
2026-09-08 23:19:40 -06:00

978 lines
112 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CURRENT_PHASE — Phase 35: Gen3 opens — the dedup phase, "one source per unique function" (v2.0.0 → v2.1.0)
> **Gate 1 approved 2026-09-08 (Drew, plan mode, Max, Fable 5.1; session S94).** Rules **R96–R99 ratified at gate 1** (the Phase-34
> candidates (a)–(d); binding; full text in `phase-ends/DIGEST.md` §3). The approved plan is reproduced VERBATIM at the end of this file
> (§"Approved plan") — its `~/.claude/plans/` copy is not part of the repo. **Gen3 order (Drew, S94): dedup → pins → structs → names,
> one phase each, planned one at a time; this phase is dedup only.** Gen3's charter: `docs/gen3-handoff.md` + `docs/gen3-standards.md`.
> **Baseline HEAD at open: `48170fd7f`** (Drew's Phase-34 CLOSE commit = v2.0.0; tree clean; `origin/main == main`).
> **Build inputs change in this phase (every shared body moves): R22 is owed after every batch that touches `src/`** — the clean fleet run
> `make clean && make extract-all JOBS=16 && make check-all JOBS=16` → `check-all: 218 passed, 0 failed of 218`, read by exit code (R97).
> **The 2026-09-02 "leave the dedup backlog" decision is REVERSED at this gate** on evidence read from sotn-decomp's tree (X2): sotn shares
> stage code once as plain C in `src/st/<name>.h`, instantiated per stage by a `.c` stub that `#include`s it; it does not write duplicates.
## Milestone (gate 2 — what Drew confirms, each with its literal output)
1. `git grep -c '^#define DEFINE_func_' -- src` empty; no `DEFINE_func_X()` site; `src/shared/engine_core.h` absent; every registered body a
plain-C header under `src/shared/<space>/`.
2. `tools/share_census.py --check` exit 0: same-vram duplicate copies 0 (or each ledgered with a reason in `config/dedup_exceptions.tsv`); the
five twin overlays built from one source directory each; cross-vram classes published as a deferred count.
3. `make tools-health` OK with the S1 invariant strict and the macro-form guard; every consumer updated, frozen or retired per its dictionary row.
4. `make clean && make extract-all && make check-all` → `218 passed, 0 failed of 218` (R22).
5. README/wiki/kit regenerated (R75), decision log (R31), SETUP rows (R21), `PhaseEnd_Phase35.md` + DIGEST written; v2.1.0.
## Effort / model (R7/R26/R27 — every transition is PROMPTED, never assumed)
- **Max** for T0's probe, T1, T2's design, T3's probe pair, T4's and T5's tool, T9 (Tier 1 — prompt Drew before starting the close);
**xHigh** for the mechanical runs (T3's other pairs, T4's batches, T5's batches, T6, T7, T8); **Low** for T0's bookkeeping.
Max is session-only — ask Drew to re-apply `/effort max` at each session start. **No Ultracode anywhere** (nothing is agent-breadth; the
parallelism is machine parallelism: L0 at JOBS=16 ≈ 13 min fleet-wide, the clean fleet run 3–5 min).
- **Drew-only (R6):** every `git push`; the gate-2 confirmation; the close commit + tag. Claude commits per task (R42 for banks: the moment a
batch is green, before the next command that can touch `src/`).
## Tasks (plan order; one commit each; ☐ → ☑ with the verify line quoted in the log)
- ☑ **T0** — Open + ground (S94): this file; DIGEST §0/§3 (R96–R99); `.gitignore` `.run/P35/`; the R22 baseline from clean
`check-all: 218 passed, 0 failed of 218` in **157 s wall**; `make tools-health` **OK** (exit 0, 474 s — after two red runs fixed at
their cause: the kit's record copies regenerated by `make kit-corpus`, and R96–R99 dispositioned in `config/kit_coverage_map.tsv`);
the probe on ov_SC06_033: **34/34 objects byte-identical** in the include form, the binary `BYTE-IDENTICAL` both ways, build 1.12 s →
0.52 s wall (CPU 12.7 s → 5.7 s), warnings 801 → 663 (all 137 macro-redefinition warnings gone), `.d` 11.6 KB → 182 KB
(`.run/P35/probe/probe_ledger.txt`).
- ☑ **T1** (S94) — `tools/share_census.py` + `config/dedup_exceptions.tsv`: `selftest: 7/7 verdicts correct`; `362,389 sig instances ·
362,389 classified · 0 UNACCOUNTED · 10,180 h_exact classes`; the four controls as measured (see the log); SETUP + dictionary rows.
- ☑ **T2** (S94) — the health chain learns the header + twin forms while the macro form still builds (16 tools; the source-dir
oracle; `share_census.header_defs` the one reader). Negative control on the unchanged tree: the chain's substantive rungs
green (audit-binaries OK · dedup-check 2220/0 · cdecl · report + `progress.py --check` fresh · doc_links · wiki_render · kit_lint ·
cookbook-index · gccmap_cites · ghidra_roster), one red rung — the kit's verbatim copies of the edited tools — fixed by
`make kit-corpus` (17 copies) with the rungs after it re-run individually by exit code. The +7 count correction published.
- ☑ **T3** (S94) — twin binaries → one source directory: the five pairs collapsed (probe SC01_005/006 with the race test; SC03_118/119 +
SC02_000/003 equal carves; SC04_018/019 + SC03_014/015 with the primary's carve, interleave + pads regenerated, R60 checks), each
twin and primary BYTE-IDENTICAL, 166 source files deleted; the census: same-vram backlog 4,667 → 1,099 classes; the clean fleet
run after the last pair (see the log).
- ☑ **T4** (S94) — `tools/macro_to_header.py`: 246,347 sites in 3,818 TUs → 2,215 per-function headers under `src/shared/<space>/`
(213/213 binaries and 4,121/4,121 objects byte-identical), the legacy headers converted (clearTbl40 = the parameterized control),
the whale moved, 7 alias bodies bound, the registry text-edited, `engine_core.h` deleted; `--verify OK`; R22 `check-all: 218
passed, 0 failed of 218` in 145 s.
- ☑ **T5** (S94–S96) — `tools/share_body.py` (+ `--repair-registry`, `--reexemplar`, `share_body_cycle.sh`): bucket 0 183 classes → 135 shared / 48 ledgered; bucket `new` 915 → 914 shared / 3 ledgered; S1 10,180/10,180 satisfied, 0 violations; registry 2,220 → 3,135 groups; same-vram backlog 1,099 → 51 classes (= the ledger); R22 218/218. Recovery history (S94 died mid-task): `tools/share_body.py`
built + probed (commit `0bccef901`); bucket 0 (`--bucket extend`, the registered-incomplete classes: 183 at the start) run twice —
the first pass committed (`571374b97`: 788 members added to the registry, 141/141 + 146/146 binaries green per binary), the second
run finished after the session died and its output (170 sites → include in 38 TUs of 8 overlays, 55 TU-CONFLICT ledger rows) is
UNCOMMITTED on disk. Residue: 55 classes / 325 (class, binary) pairs stay private, 317 of them still LISTED as registry members
(the first pass extended wholesale); three tool defects found by S95 (the cause extractor, the cross-batch edit loss, the ledger
reason). NEXT inside T5: commit the run-2 bank → fix the tool → repair the registry → replay the suspect rejections → decide the
E_func_80168B70 exemplar → bucket `new` (915 classes / 3,567 private sites) in bands, one batch per run, R22 after each.
Plan text (unchanged): bucket 0 `--extend`, then the same-vram buckets largest reach first (no trivial exception); cross-vram
classes untouched; `share_census --check` same-vram unregistered = 0 or ledgered.
- ☑ **T6** (S96) — consumers: **14** macro-era tools FROZEN (`tools/frozen.py`, main()-time refusal; the plan's 7 + 7 the guard census
found), **4** retired to `tools/sunset/` (dedup_propagate, dedup_extend → share_body.py; macro_draft; test_reconcile_ledger), 12 live
tools' dead macro branches dropped, the `ast` macro-form guard in `tool_census --check` (0 LIVE hits; the pre-T4 tree flags 15 —
the negative control), `progress.py`'s empty-shared fold corrected (+459 denominator, REAL −10,211 → EMPTY), kit corpus, SETUP;
`tools-health: OK`.
- ☑ **T7** (S96) — S1 strict (`--strict-macros --strict-text`) + `--selftest` in `make tools-health`; C2c/C2d in `dedup_integrate --check`;
`progress.py` fields (105,007 bodies written once; 160 / 51 duplicate copies, all ledgered) + the dated corrections (+7, +459, REAL
−10,211) + the README sentence; the gate negative-controlled in place (exit 1 naming the class; C2d naming the member); the second
oracle's finding published: 380 texts deferred inside cross-address classes, 38 texts / 2,030 sites byte-variant — PENDING Drew.
- ☑ **T5b** (added S96, Claude's delegated decision; done S96 — 38 groups / 2,030 sites, 138/138 green, R22 218/218) — the text tier: registry `tier: h_text` (text hash; per-member `h_exact` in the
verbose member form), `dedup_integrate` C1 per member; `share_census` text classes at one address (same text, ≥2 distinct TUs, outside
the deferred/ledgered sets) → the S1 text half a violation, not PENDING; `share_body --bucket text` (header named by the text hash) over
the 38 classes / 2,030 sites, gated per binary, one batch per run + R22; the two counts (deferred 380/1,668; text-tier shares) in the
digest + README sentence. Effort: high (Drew's default).
- ☑ **T8** (S96; doc_links strict 0 broken, wiki 32/32, cookbook §453 + index, decision log, accelerators, DIGEST §4, the memory) — the record: wiki (4 pages), how-to ch.10, README:117, the charter rows as dated snapshots, the cookbook section, decision log
(R31), accelerators, DIGEST §4, sunset rows, the memory rewritten; doc_links/wiki_render/cookbook_index/kit_coverage green.
- ☑ **T9** (S96, xHigh by Drew's choice) — close (Tier 1): R22 → 218/218; tools-health OK; the metrics table; the reviewer sequence; PhaseEnd + DIGEST + log archived; v2.1.0.
- Rules check (P6): after T3 and after T7.
## Decisions (owner's words, in order)
- S94 gate 1 (AskUserQuestion): twin binaries → "One source directory per payload"; cross-address classes → "Census + defer to the names
phase"; the 62 macro-era tools → "Freeze with a loud refusal"; the four rule candidates → "Ratify all four as R96–R99".
- S94 (plan): no "trivial" exception — a duplicated 3-instruction accessor is still one function (this project already shares 5-instruction
bodies).
- S94 (Drew, mid-planning): "we will need to update our tooling to be aware that we aren't duplicating funcs across overlays anymore and
that there is only one source for a unique func" → the S1 invariant + the consumer tasks (T2, T6, T7).
- S95 (Drew, 2026-09-08 evening — the recovery session): "this is a recovery session. last session context filled up and didn't write a
checkpoint … read last session since the last checkpoint and write an updated checkpoint"; "dont run anything like gates/builds, your
job is only to capture the info from the session and create an updated checkpoint." → S95 ran no gate, build, census or tool; it read
the S94 transcript (`~/.claude/projects/-home-musashi-bfm-decomp/e902c34e-e4b3-41a6-b1e4-7d2ef019a371.jsonl`), the two T5 commits,
`.run/P35/share/` and the dirty tree, and wrote the log entries + the 🛑 block below.
- S96 (Drew, 2026-09-08, after the E_func_80168B70 question): "I am not sure the best answer to this question. I have toggled Max effort.
now decide the best answer to the question. and then pause so I can switch back to high effort" → **Claude decided at Max: RE-EXEMPLAR
the header from the majority text** (the tool's own exemplar rule; T4 inherited the 7 late overlays' minority text because the registry
group had been created for them; the majority's cast-at-call form compiles under BOTH declaration environments, so 141/141 is expected
with no TU edit, and 7 private copies would beat 134 if not). The offered alternative "fix the 7 TUs' declarations" was mis-stated —
the header passes one argument, so the TU-side repair would be the 134 units + every other caller of func_80146C3C = Phase 37's
canonical-declaration work; rejected. **Policy for the other 48 rejected classes:** their headers already carry the majority text and
their causes are genuine declaration conflicts in the late overlays' units → they stay ledgered TU-CONFLICT with the real diagnostic
until the types phase (R95); ≈190 private copies of ≈3,900 collapsible, inside the plan's 2–5 % budget (exact count at T5's close).
- S96 (Drew, after the T7 report): "Max effort set for this decision call. you review the information and decide what is best, make the
decision, and then pause" → **Claude decided at Max: T5b NOW for the 38 byte-variant same-address texts (a text tier), DEFER the 380
inside the cross-address classes.** The normalizer masks only the function's own name, so the 38 are the same C; their bytes differ per
overlay because the ORIGINAL builds compiled that source under per-overlay declaration environments (our TUs carry that environment) —
the types phase cannot collapse them and the names phase cannot remove them; the include-at-site form already fits (body in the header,
declarations in the TU, bytes proven per binary) and only the registry's single-hash group blocks it. The 380 are identical bytes at
thousands of addresses (empty bodies, tiny accessors): one header per address would be undone by the names phase's parameterized form —
gate 1's deferral stands; both counts are published. A plan change (P5d) under Drew's delegation: **T5b inserted before T8.**
## Log (append-only; one entry per step, with the literal verify line)
- **S94 2026-09-08 — session start.** Load order read; Phase 34 closed at `48170fd7f`; no CURRENT_PHASE.md → new phase. Drew: order
dedup → pins → structs → names; plan mode at Max. Exploration (two Explore agents, one Plan agent, ~680k agent tokens) + this
session's own measurements; sotn's sharing model verified from its tree (`src/st/e_red_door.h` + the ~90-byte per-stage stubs).
- **S94 — T0 in progress.** DIGEST §0 (the opening paragraph) + §3 (R96–R99 in full) written; `.gitignore` `.run/P35/` allowlist added;
the R22 baseline from clean launched in the background (`.run/P35/baseline/r22_open.log`, `/usr/bin/time` wall clock recorded);
the harness task list built (10 tasks, R28).
- **S94 — T0 baseline.** `.run/P35/baseline/r22_open.log`: `extract-all: 217 extracted, 0 failed of 217 (+ main, serial)` ·
`check-all: 218 passed, 0 failed of 218` · `wall=157.09 s user=2231.42 s sys=542.99 s` · `exit=0`. The fleet's build-time baseline
for the phase (the 8.8 MB header is preprocessed by every overlay TU today).
- **S94 — T0 tools-health, run 1 RED (464 s):** `tool_census --check: FAIL` — 2 GAPs, both "corpus copy differs from its source"
(`decomp-architect/corpus/record/phase-ends/DIGEST.md`, `…/PhaseEnd_Phase34.md`). Cause: the gate-1 DIGEST edit (and a pre-existing
drift of the P34 record copy behind Drew's close commit); the instrument was right (R40). Fix: `make kit-corpus` (360 copies + 28
pointers materialised) → `tool_census --check: OK` (`.run/P35/baseline/kit_corpus_open.log`, `tool_census_open.log`).
- **S94 — T0 probe** (`.run/P35/probe/probe_convert.py`, in-tree then restored by `git checkout -- src/ov_SC06_033`): `engine_core.h: 5147
define lines, 3516 distinct, 1631 defined twice` · `ov_SC06_033: 34 TUs, 1813 sites, 1813 distinct shared bodies` · `applied: 1813
headers under src/shared/ov, prelude written, 34 TUs rewritten` · A rebuild (macro form) `wall=1.12 s user=12.72 s`, 801 warnings
(137 "redefined") · B build (include form) `wall=0.52 s user=5.70 s`, 663 warnings (5 "redefined" — none of them `DEFINE_func_`) ·
`sha1 1e91d46e… == config/check.ov_SC06_033.sha (BYTE-IDENTICAL)` both ways · **`obj A/B: 34/34 byte-identical, 0 differing`** ·
`.d` bytes 11,632 → 182,252 (1,813 header dependencies; harmless) · restore verified (`git status` shows only T0's files). The L0
object oracle is proven on a whole overlay; the plan's D6/L0 stands.
- **S94 — T0 tools-health, run 2 RED (460 s):** `kit_coverage: FAIL (4 gap(s))` — `rule R96…R99 is neither cited by a registry provenance
line nor dispositioned in config/kit_coverage_map.tsv` (R92, the same class P34 hit at its close). Fix: four map rows — R96/R98/R99 →
DK-81 (its facts 4/1/2 ARE those rules), R97 → G29 (provenance R49 + R53). `kit_coverage: OK`; the rungs after it (xsig 8 OK,
work_evidence selftest OK, split_indicator SELF-TEST PASS + 218 OK) individually green.
- **S94 — T0 tools-health, run 3 GREEN:** `tools-health: OK — sigs fresh; corpus(+resident) + cdecl + binaries + report(lint+dedup) +
cookbook-index all green.` `exit=0`, `wall=474.23 s` (`.run/P35/baseline/tools_health_open3.log`). `docs/story-timeline.md/.svg`
regenerated by the chain (74 rows; the v2.0.0 tag row). **T0 ☑.**
- **S94 — T1 `tools/share_census.py` + `config/dedup_exceptions.tsv`.** Built as designed (one pass per TU with comments, dead
`#if 0`/`NON_MATCHING` halves and macro-continuation text masked; forms macro / macro-param / include / param-include / def / stub /
asm-verbatim; addresses from each binary's symbol stack, never names; the fleet, the source dirs, the contracts (`<alias>_CHECK_SHA`,
main = `check.us.sha`), the address spaces (`_VRAM_BASE`) and the twin sets derived from the Makefile and the contracts; classes
through `dedup_integrate.group_members`). Five scanner defects found by its own self-test and coverage line, each fixed at the cause:
string contents were blanked before include paths and asm labels were read; K&R definitions (a blank tail after the last `;`,
and the one-line `void f(a, b) void *a; s16 b; {` form); a second definition head on the same line after an `extern …;`; the
alias regex spanning a previous macro site's parentheses; `asm(` as well as `__asm__(`; implicit-return-type heads with no type
line; `static inline` helpers (no address) excluded from the unresolved list. **Result: `--selftest` → `selftest: 7/7 verdicts
correct`; the census → `218 binaries · 362,389 sig instances · 362,389 classified · 0 UNACCOUNTED · 10,180 h_exact classes with >=2
instances`; verdicts A 1,712 · B 282 · C 6,107 · D 1,861 · M 218 (instances 214,478 / 45,226 / 14,839 / 5,796 / 462); flags ALIAS 44 ·
E 3,826 · F 54 · PINS 687 · TWIN-PENDING 3,748 · TYPEDEF 96; SAME-VRAM UNREGISTERED 4,667 classes · 35,976 instances · 11,767 private
copies · 31,309 collapsible · 1,710,469 ins (twin-pending 3,568 classes / 7,136 instances); CROSS-ADDRESS/SPACE deferred 3,801 classes ·
30,347 instances · 12,938 private copies; macro sites 255,947; duplicate-text classes 6,845 (23,269 sites); 43 s uncached, 41 s cached
(the cache is not the cost — profile at T7); exit 0.** Controls (R39): `func_80144B9C` = B/141/{'include': 141}/missing 7 (the registry
lists 134 — never extended to the 7 later overlays; T5 bucket 0), `clearTbl40` = A+E/2, the three setters = B/282(E), B/145(E), B/141
(the same 2-instruction bodies also sit at other vrams), twin symmetry SC01_005/006 = 653/653. **Instrument findings for the record:**
the B class is 282 registered-but-incomplete groups (204 never extended, 74 with a private-copy site — demacroized escapes —, 2 with a
member the sigs do not show); a 2-instruction empty-body class (`jr ra; nop`) has 11,852 instances across every space (E,F → deferred
with the cross-address set; the names phase decides how an empty function is written). Outputs `.run/P35/census/{share_census.json,
share_census.txt, coverage_notes.txt}` (tracked), `classes.jsonl` + `cache/` (ignored). SETUP + dictionary rows (R21/R87). **T1 ☑.**
- **S94 — T1 slip, named (R97/R66):** commit `1dbffee87` says "kit corpus regenerated" while `make kit-corpus` had exited 2 and
`tool_census --check` had refused the new row (`phase=P35` — the column is the KIT LADDER phase, P1–P10, not the project phase);
the chain had `&&` on an echo, not on the checks. Fixed in `72a77e7d3` (row → P10, the readability tools' rung; corpus 361 copies;
`tool_census --check: OK`; `kit_coverage: OK`), chained on the checks' own exit codes; `docs/tool-index.md` (generated) committed after.
- **S94 — T2 the health chain learns the header + twin forms (both forms accepted).** ONE source-dir oracle: `corpus.src_dirs()` /
`src_dir()` / `twin_of()` from `<alias>_SRC_DIR` / `<alias>_TWIN_OF` in the Makefile and `config/*.mk`; `corpus.src_files` and
`o0_subseg` go through it; `compile_only.src_dirs` delegates to it; `progress.set_binary` takes its dir from it (the table's `src`
is only a cross-check); `dedup_integrate._src_paths` uses `corpus.src_files`. The include form: `share_census.header_defs()` is the
one reader (`[(name, empty)]`, masked — a macro-only header defines nothing); `progress.classify` counts an include site as the
function's definition (REAL/EMPTY by the header body) with the `#define SHARED_FN` / `#undef` state for the parameterized form, and
the dedup fold keeps included members in the shared count (`(members − real − stubs) ∪ (included ∩ members)` → the README's
255,632 unchanged); `overlay_src_split` gains the `include` item kind (anchor by the header's defined name, `#undef SHARED_FN`
travels with the item; `split_header` no longer swallows a leading body include; `macro_externs`/`macro_proto` read the header's
parts; a macro invocation with no table entry is a loud refusal, R43) and `jr_isolate_all` admits the kind; `dedup_integrate`
C2a′ requires a plain-C source to DEFINE `func`; `audit_binaries` accepts `engine_prelude.h`, resolves the main TU through the
oracle, and gains CHECK 3b (twin citizenship: primary onboarded and not a twin, same src dir, no `src/<twin>/`, equal contracts,
equal carves); `cdecl.audit_differential` reports "not applicable" when the macro header is gone instead of crashing;
`lint_symbol_refs` / `family_remap` / `fix_arity_callers` glob `src/shared/**/*.h`; `blocker_probe.macro_scope` and
`demacroize.macro_bodies` tolerate the header's absence; `shared_lock` docstring; `harvest_verify` comment.
**Verify:** `py_compile` 16 tools OK; `share_census --selftest` 7/7; `audit-binaries: OK`; `dedup-check: 2220 validated, 0 failed`;
the split tool on `src/ov_SC06_033/ov_SC06_033_o0b.c` (a real include-form TU) → the whale's include is an item.
**A count correction surfaced (R14):** `FLEET matchable 363,214 → 363,221` — the 7 overlays whose `_o0b.c` includes the whale
header but were never registry members (`ov_MAIN_012`, `ov_SC02_037`, `ov_SC03_107`, `ov_SC07_006/007/010/011`) were counted by
NEITHER classify (include lines skipped) NOR the registry fold; the source now decides (ov_MAIN_012: matchable 2,401 → 2,402, the
known row). `docs/progress.json` + the README block regenerated by `make report BINARY=main` (`progress.py --check: … fresh`);
the generated timeline follows the COMMITTED digest, so it is regenerated after the commit that carries the new digest.
- **S94 — T2 close.** The chain's only red rung was the kit's verbatim copies of the 16 edited tools (`tool_census --check` 17 gaps) —
`make kit-corpus` belongs in EVERY commit that edits a tool; the rungs after it re-run individually by exit code; the chain also
regenerates the timeline's commits-per-day cell (it moves with every commit — committed with each task). Commits `6cb056c93`
(the tools + the +7 numbers), `4ec752e68` (the timeline), `24e8ff72d` (the close). **T2 ☑.**
- **S94 — T3 probe pair SC01_005/006 (Max).** The mechanism: `config/overlays.mk` `ov_SC01_006_TWIN_OF := ov_SC01_005` +
`ov_SC01_006_SRC_DIR := src/ov_SC01_005`; the Makefile's twin block (static pattern rules `build/src/<twin>/<twin>%.o ←
src/<primary>/<primary>%.c` + the bare-name rule, the same recipe; `TWIN_O0_OBJS` keep `-O0`; the twin's own `OBJS`/`C_DEPS`);
`config/splat.ov_SC01_006.yaml` `create_c_files: False` (src_path stays the twin's name → the .ld's 120 object paths under
`build/src/ov_SC01_006/`); `git rm -r src/ov_SC01_006` (30 files). **Verify:** `make extract BINARY=ov_SC01_006` exit 0, no
`src/ov_SC01_006/` recreated; `make check BINARY=ov_SC01_006 -j16` → `sha1 56760dbe… == config/check.ov_SC01_006.sha (BYTE-IDENTICAL)`
(the CC lines read `(twin of ov_SC01_005: src/ov_SC01_005/…c)`); the primary still BYTE-IDENTICAL; **the race test** — both objects
trees deleted, `make check` for 005 and 006 run CONCURRENTLY → both BYTE-IDENTICAL; consumers: `progress.py --binary ov_SC01_006`
2503/2503 (of which dedup-shared 1838, unchanged), `dedup-check --binary ov_SC01_006` 1838 validated / 0 failed, `audit-binaries: OK`
(CHECK 3b passes: primary onboarded, same dir, no src/<twin>, equal contracts, equal carves), `compile_only --list ov_SC01_006`
→ 30 TUs from `src/ov_SC01_005/` (2 at -O0). One instrument slip caught by the census's R32 gate: my Makefile variable
`TWIN_SRC_DIR` matched the `_SRC_DIR` oracle regex as a binary named `TWIN` → renamed `TWIN_SRCDIR`; after it the census shows
**TWIN-COVERED 575 classes** for the pair (twin-pending 3,748 → 3,173), twin symmetry 653/653 unchanged. The census's `copies` now
counts DISTINCT private sites (a twin's instance resolves to its primary's TU — one source), `collapsible` = copies − 1.
Commit `9b0816e74`.
- **S94 — T3 pairs 2–3 (equal carves, mechanical):** `ov_SC03_119` TWIN_OF `ov_SC03_118` (31 files removed; both `77ee78dd…`
BYTE-IDENTICAL), `ov_SC02_003` TWIN_OF `ov_SC02_000` (37 files; both `5ece4bca…`); `audit-binaries: OK`; dedup-check per twin
0 failed. Commit `e79cfcc06`.
- **S94 — T3 pairs 4–5 (carve alignment, R60):** the twin's yaml := the primary's with the alias and `FILE_nnn` substituted (+ the
twin note + `create_c_files: False`), its `_JTBL_INTERLEAVE` := the primary's substituted — and, found by the first build's
failure (`missing .end at end of assembly` on `ov_SC04_019_jr_8017AE2C.o`): the per-object **`JTBL_PADS` lines are keyed by
OBJECT PATH** (`build/src/<twin>/…`), which my alias-normalized block diff (lines starting with the alias) never compared, so the
twin still carried its retired carve's pads (4 differing lines for SC04, 2 for SC03; the three committed pairs re-checked: 0, 0, 0).
Regenerated from the primaries by substitution → 0 differing. `pads_audit.py` built `src/<b>/<tu>.c` by hand and raised
IndexError on a twin — now reads the source through the oracle (`corpus.src_dir` + `twin_of`). **Verify:** `ov_SC04_019` +
`ov_SC04_018` BYTE-IDENTICAL (`fe9b413f…`), `ov_SC03_015` + `ov_SC03_014` (`d84b01a2…`); `interleave_check` ALIGNED (n=51, n=46);
`pads_audit` ok on every carve object; dedup-check 0 failed; `audit-binaries: OK` (CHECK 3b incl. equal carves). Commits
`2648aa5a9`, `6ebb3dac3`.
- **S94 — T3 census after the five collapses:** TWIN-COVERED 3,748 classes (3,580 non-A, 7,186 instances); **same-vram unregistered
4,667 → 1,099 classes · 4,755 private sites · 3,658 collapsible · 28,840 instances** (twin-pending 0); `S1: … 10,180 classes, 9,081
satisfied (3,580 twin-covered, 0 excepted, 3,801 deferred cross-address), 1,099 VIOLATION(S)` — the backlog T5 shares. The controls
unchanged (twin symmetry 653/653). `make kit-corpus` for `pads_audit` + `share_census`; `tool_census --check: OK`.
- **S94 — Rules check (P6, after T3 = four tasks): re-read complete (CLAUDE.md's eight mandatory behaviours; PROJECT_CONTEXT's 23
P/G/H/X rules). Continuing with T4.**
- **S94 — T3 close, R22:** `make clean && make extract-all JOBS=16 && make check-all JOBS=16` → `extract-all: 217 extracted, 0 failed of
217 (+ main, serial)` · **`check-all: 218 passed, 0 failed of 218`** · `wall=308.78 s` (the census and the kit corpus ran beside it) ·
`exit=0` (`.run/P35/baseline/r22_t3.log`). Twins: 5 pairs / 10 aliases / 166 source files deleted; every twin builds from
its primary's directory. **T3 ☑.**
- **S94 — T4 `tools/macro_to_header.py` (Max) + the fleet conversion.** `--plan` on the post-twin tree: `engine_core.h: 5147 define lines,
3516 distinct, 1631 twice (4 divergent asserted); 8 directives — the prelude is complete` · `sites 246,347 in 148 binaries · TUs
including engine_core.h 3,818 · macros: shared 1,959 · single-site→header 257 · single-site→inline 0 · dead 1,300` · naming
`{'suffixed': 86, 'plain': 1873}`. **`--apply` over all 213 non-twin binaries** (`.run/P35/convert/batch2.sh`: snapshot objects → one
apply → per binary `make check` + every object compared): **213/213 BYTE-IDENTICAL, `objects byte-identical: 4121/4121`**, apply
ledger `{"sites":246347,"tus":3818,"headers_written":2215,"dead":1300}`, 224 s wall (commit `c53a9e1a9`, 6,036 files). **`--finalize`:**
the three legacy sites (`SETTER`/`RETCONST` ×3 in SC01_005 → `ov/func_8012AD64.h` etc.; `CLEAR_TBL40` ×2 in `src/800_c.c` → the
`#define SHARED_FN` / `#include "shared/main/func_80037004.h"` / `#undef` form — the cross-address control), the whale moved to
`ov/func_80144B9C.h` (guard removed) with every `-O0` includer rewritten, **7 alias-form bodies given their own binding** (`s32
aF80131CA8(int a0) __asm__("func_80131CA8");` derived from the head; the K&R head gets the unprototyped form), the registry's 2,224
`source:`/`func:` lines text-edited by id, the 3 non-shared headers that included a macro header rewritten (`src/800_shared.h`,
two per-overlay `_shared.h`), `engine_core.h` + `ov_setters.h` + `clearTbl40.h` deleted; `--verify: OK — 0 macro sites, no macro
header, the prelude present`. **Gates:** main `143dbb89…`, ov_SC01_005, ov_SC01_084, ov_SC02_005, ov_SC06_033, ov_SC02_027,
ov_SC02_011, ov_SC02_026 and the 7 late whale includers all BYTE-IDENTICAL; `dedup-check: 2220 validated, 0 failed`;
`audit-binaries: OK`; the census `362,389 classified · 0 UNACCOUNTED · macro sites 0`, verdicts A 1,712 · B 282 · C 6,640 · D 1,545 ·
M 1, same-vram backlog 1,099 classes (unchanged — T4 shares nothing new); **R22: `check-all: 218 passed, 0 failed of 218`, wall
145.10 s** (157 s at the open; user CPU 2,230 s vs 2,231 s — the per-overlay cpp saving the probe measured is small against extract,
assemble and link fleet-wide). **Instrument findings, each caught by a gate and fixed at the cause:** (1) the first apply of "main"
swapped the include line in all 3,818 TUs (main's dir is `src/`, a prefix test matched the fleet) — restored with `git checkout --
src`, the test made exact; (2) the per-binary driver paid the 218-file sig load per binary (36 binaries > 10 min) → the batch form
pays it once (224 s for 213); (3) five mid-file `match_one-only` includes with trailing comments and (4) seven whale includers in
`_o0c.c`/`_o0d.c` (a `*_o0b.c` glob) were missed — the census's coverage line (7 unaccounted) and `--verify` found them; (5) `verify`
listed 3 of N offenders (a `[:3]` slice) — it now lists every include LINE and ignores prose; (6) `.run/P35/convert/` was not
allowlisted — added. `docs/SETUP.md` P35 T3–T4 section + the dictionary row (P10, PROJECT-ONLY). **T4 ☑.**
- **S94 — T5 `tools/share_body.py` (Max) + the R37 probe (commit `0bccef901`, 18:12 local).** Built as designed (411 lines): `census(jobs)` =
`share_census.classify(…, keep_instances=True)` (the census now exports per-instance `line`/`end`/`nlines` and the class's `groups`; a
census with unaccounted or multi-form instances is a refusal, R32); `candidates()` → `extend` = verdict-B classes (registered, some
instance still a private `def`), `new` = unregistered same-vram classes with ≥1 private def — both skip A, E/F flags, TWIN-COVERED and
EXCEPTED classes, sorted by (−instances, −nins); `choose_exemplar` = majority normalized text → pin-free → fewest lines (printed);
`Batch.add_class` (B: the registry's header must define `func` or the class is refused; the extension list = instances not yet
listed; new: header `func_<VRAM>[__h8].h` via `macro_to_header.Oracles.header_rel`, banner + `bind_alias_header`; every private site →
`include_line(tu, hdr)` at `[line, end]`; every instance's binary is "touched", twins with their primary); `gate()` = `make check
BINARY=<b> -j16` by exit code + every object compared with the pre-batch snapshot (`.run/P35/share/check_<b>.log`); a red binary is
BISECTED (restore its TUs, re-apply classes one at a time, the culprit REJECTED and ledgered, the survivors kept); `registry_append`
(shorthand, by text — never `yaml.safe_dump`), `dedup_extend.add_members_surgical` for extensions; `batch_<label>.json` per batch
(`classes`, `refused`, `results` per binary, `registered`, `extended`, `rejected`, `exemplars`). `--plan` on the post-T4 tree: `census
28 s · extend (registered-incomplete) 183 classes · new (unregistered same-vram) 916 classes / 3,569 private sites · new by band {'32+':
446, '16-31': 255, '8-15': 204, '1-7': 11}; differing-text classes 27; with pins 139; alias-form 6` (183 + 916 = the census's 1,099
same-vram backlog; the plan's "206 never-extended" counted the B census before the twins). First probe run: `KeyError: 'line'` (the
census's per-instance records lacked the line range) → fixed in `share_census.classify`. **The probe (R37):** class
`c1c085b28d16c2417f2d9ce46553d16fe45b4508` = `func_801681FC` (2 instances, ov_SC04_008 + ov_SC05_009, 22 ins): `[new1] 1 classes ·
2 sites in 2 TUs · 1 new headers · gating 2 binaries` → `gated 2/2 binaries green · registered 1 groups · extended 0 members · rejected
classes 0`; header `src/shared/ov/func_801681FC__c1c085b2.h` (suffixed: the vram hosts >1 class fleet-wide), group `S_func_801681FC`,
ledger rows 0. `bind_alias_header` factored out of `macro_to_header` (shared by both tools). SETUP §"P35 T3–T5" + the dictionary row
(`tools/share_body.py P10 ADAPT LIVE`); `make kit-corpus` (363 copies) + `tool_census --check: OK`. 18 files, +1,198/−81.
- **S94 — T5 bucket 0, FIRST PASS (commit `571374b97`, 18:30 local; `.run/P35/share/run_extend.log`, 617 s, exit 0).** `share_body --apply
--bucket extend --batch 120`: `census 24 s · extend 183 classes · new 915 classes / 3,567 private sites` · `[extend1] 120 classes · 774
sites in 190 TUs · 0 new headers · gating 141 binaries` → `gated 141/141 binaries green · registered 0 groups · extended 754 members ·
rejected classes 88` · `[extend2] 63 classes · 486 sites in 406 TUs · gating 146 binaries` → `gated 146/146 binaries green · extended 34
members · rejected classes 37`; 482 REJECTED lines; the registry +788 members (13,062 → 13,850 member entries in 102 `binaries:` lines);
539 src files changed; 690 files in the commit. **Diagnosis before committing (the replay method: apply ONE class's edit from the census's
instance record, look at the text/compile):** (1) the failure "detail" was the FIRST line containing `error`/`conflicting` — the
fleet-wide benign `warning: conflicting types for built-in function 'memcpy'` labelled 233 of the rejections; (2) the bisect re-applied
the kept classes SEQUENTIALLY with the census's ORIGINAL line numbers — every earlier class's edit shifted the later sites, so the later
classes landed on the wrong lines (duplicate definitions → `{standard input}: Error: symbol 'func_80168B70' is already defined`, 123
lines; `parse error before 'extern'` at a header's line 3, 14 lines; `parse error before '}'`) — **125 of the 183 classes were rejected
on the tool's own artifacts**; (3) no ledger row was written for a rejected EXTEND class; (4) `add_members_surgical` extended every
group with ALL planned members, rejected or not. **Fixes in the same commit** (`tools/share_body.py`, 60 lines): the detail from
error lines only (`warning:`/`note:` lines excluded); the bisect's `apply_selected()` restores the TU and applies the selected classes
in ONE bottom-up pass from the original text; rejected extend classes ledgered `TU-CONFLICT` with the rejecting binaries; a group
extended only with members whose sharing survived the gate. **What the commit therefore holds:** the sites the (artifact-prone) first
bisect kept, every binary gated green in its final state (per-binary `make check` + object A/B — NO clean fleet run), and a registry
that lists 788 new members regardless of whether their site shares (the wholesale extension ran BEFORE fix 4 existed).
- **S94 — T5 bucket 0, SECOND RUN (`.run/P35/share/run_extend2.log`; started 18:30:35 local, 511 s, exit 0 — finished at ~18:39 AFTER the
session's context filled: the session hit 91% context at 18:32, Drew asked for a hook and then for a checkpoint, both answers were
"Prompt is too long", the run's completion notification arrived to a dead session; Drew let the script finish before exiting).**
`census 37 s · extend (registered-incomplete) 150 classes` (33 classes completed by the first pass) `· new 915 classes / 3,567 private
sites` · `[extend1] 120 classes · 503 sites in 182 TUs · 0 new headers · gating 141 binaries` → `gated 141/141 binaries green · registered
0 groups · extended 0 members · rejected classes 48` · `[extend2] 30 classes · 53 sites in 23 TUs · gating 141 binaries` → `141/141 green ·
extended 0 members · rejected classes 7` · `share_body: done — 150 classes in 2 batch(es); see the ledger`. 303 REJECTED lines, 129 of
them one class (`93d5fccdcc` = `E_func_80168B70`, rejected in 134 binaries — so 136 of 141 binaries went through the bisect in extend1;
9 in extend2). "extended 0 members" is CORRECT for this run: the first pass had already listed every planned member. **Left on disk,
uncommitted (`git status`): 38 `src/` files in 8 overlays (ov_MAIN_012 5 sites, ov_SC01_077 19, ov_SC02_037 6, ov_SC03_107 5, ov_SC07_006
49, ov_SC07_007 29, ov_SC07_010 28, ov_SC07_011 29 = 170 private definitions → `#include "../shared/ov/func_X[__h8].h"`; every added line
is such an include, verified by S95), `config/dedup_exceptions.tsv` +55 rows (all `TU-CONFLICT`, "share_body extend1/extend2: extend of
E_func_X rejected in [binaries]"), `.run/P35/share/batch_extend1.json` + `batch_extend2.json` (overwritten with this run's records), 139
of the 146 `check_*.log` (each ends `BYTE-IDENTICAL` — the final gate of each binary), untracked `run_extend2.log` and
`.run/P35/baseline/kit_corpus_t5a.log`.**
- **S95 — RECOVERY (2026-09-08 evening; Fable 5.1 at Max; no gate, build, census or tool run — Drew's instruction).** Reconstructed the
above from the S94 transcript (dumped to text with a 60-line script: user text / assistant text / tool_use / tool_result per record),
the two commits, the batch records and the tree. **Findings, each read from an artifact:**
1. **The dirty tree is run 2's net output and is bankable as-is (R42/R66):** 170 include lines, 0 other added lines; every one of the 8
overlays' final `check_<b>.log` reads `[ OK ] … (BYTE-IDENTICAL)`; the run's exit was 0. It has NOT had the clean fleet run — **R22 is
owed for all of T5 so far** (the first pass too: its gates were per-binary incremental builds).
2. **The registry runs ahead of the source (the first pass's wholesale extension):** the 55 ledger rows name 325 (class, binary)
rejections; **317 of those binaries are LISTED as members of the class's group** in `config/dedup.us.yaml` (computed by S95 from the
ledger notes × the registry's `binaries:` lines). `E_func_80168B70` alone: 7 members before the first pass (the 7 late overlays), 141
after, 134 of them private copies. `share_census --check` will count these classes as EXCEPTED (the ledger), so S1 stays green — but
T7's planned C2d ("every member's site includes the source") fails on all 317 until the members are removed or the classes crack.
3. **The cause extractor still misses gcc-2.7.2's error lines** (`tools/share_body.py:204-206` requires `\berror\b|Error\b|undefined
reference|already defined|multiple definition|parse error|\[FAIL\]`): cc1 prints errors as `file:line: message` with NO "error" token
(`too many arguments to function`, `conflicting types for`, `redeclaration of`, …), so 254 of the 303 rejection lines read only `make:
*** [Makefile:1033: build/src/<b>/<tu>.o] Error 33` (33 = cc1's fatal exit status). **The real cause of the dominant class**, read
from the dead session's own replay residue (`/tmp/claude-1000/-home-musashi-bfm-decomp/e902c34e-…/scratchpad/cc1.err`, the single edit
of `func_80168B70` in ov_SC01_074 through cpp → cc1): **`src/ov_SC01_074/../shared/ov/func_80168B70.h:13: too many arguments to
function 'func_80146C3C'`**. The header's text is the 7 late overlays' spelling (`func_80146C3C((u8 *)a0)` against THEIR `extern void
func_80146C3C(u8 *a0)`); the 134 main overlays declare `extern void func_80146C3C(void)` and their private copies call `((void
(*)(s32))func_80146C3C)(a0)` — same bytes (h_exact `93d5fccdcc…`), incompatible source environments. The header carries the MINORITY
spelling because the group was created for the 7. (The first pass's `symbol 'func_80168B70' is already defined` for this class WAS
the bisect artifact; the second run's rejection is real.) First-pass lines already showed the same family in ov_MAIN_012:
`func_80168BDC.h:5: conflicting types for 'ApplyMatrixSV'`, `func_80168070.h:7: conflicting types for 'ApplyMatrixSV'`.
4. **A cross-batch edit-loss defect (R42 class — a gate destroying banks):** `restore()` (`share_body.py:221-223`) is `git checkout --
<tus>`, and `Batch.apply_edits` uses the census's line numbers from the START of the run. Within one run, batch N+1's edits land on
TUs batch N already changed (uncommitted) → stale lines → the initial gate fails → the bisect `git checkout`s those TUs, WIPING batch
N's kept includes, then re-applies only batch N+1's classes (which pass) → the binary ends green with FEWER shares than reported.
Evidence: run 2's extend2 bisected ov_SC07_006/007/011 (007 and 011 "3 classes kept, 0 rejected" — a batch that fails whole yet
passes class by class is the stale-line signature) and their `_jr_801457A4.c` lost extend1's six kept sites `func_80149374,
func_801493D0, func_80149450, func_801494CC, func_80149544, func_8014964C` (present in no diff); ov_MAIN_012 / ov_SC02_037 /
ov_SC03_107 lost `func_80146128, func_80146FC4, func_80153800, func_801539F8` the same way; ov_SC01_077 ~11 sites. S95's estimate:
≈50–63 kept sites lost in run 2 (the same happened inside the first pass). They stay private and reappear as verdict-B classes in the
next census (NOT ledgered → retried automatically). Fix: snapshot each touched TU's text in memory before `apply_edits` and restore
from the snapshot; re-derive the edit positions per batch (re-census, ~30 s) or run ONE batch per invocation and commit between.
5. **The residue's causes, as recorded (to be confirmed by replay — the ledger says TU-CONFLICT for all 55):** (a) prototype/type conflicts
between a header's preamble or calls and the TU's declarations — the late overlays ov_MAIN_012, ov_SC02_037, ov_SC03_107, ov_SC07_006/
007/010/011, ov_SC01_077 (+ ov_SC01_000, ov_SC06_018 for 2 classes), and the 134-binary `E_func_80168B70` case; (b) **suspected census
definition-range defects** — `parse error before 'if'` at `<b>_jr_8015C32C.c:3388-3390` (`d743c21bd9` = `E_func_8016163C`) and
`before 'not'` at `:3438` (`3576d059c2` = `E_func_80161774`) in exactly the five twin-pair primaries (SC01_005, SC02_000, SC03_014,
SC03_118, SC04_018 — one shared source text), `<b>_jr_801380E0.c:1050 before 'if'` (`9bda7673e9` = `E_func_80138C30`, SC03_014/118);
(c) **symbol-name conflicts at link** — `undefined reference to func_80161A90` (`3576d059c2`, the five primaries' `_after.c`),
`func_8012A68C` (`6f62feba35` = `E_func_8012A328`, SC04_018), `func_80161B18` (`7604e5daf1` = `E_func_80161A90`, SC04_018),
`func_8012A598` (`bdb48359cd` = `E_func_8012A568`, SC03_014/118): the header names a callee those binaries' symbol stacks do not carry
under that name; (d) **GATE-REJECT (bytes differ)** — `[FAIL] build/ov_SC07_00x` for `7529ad8f17` = `E_func_801376E8` (an alias-form
header, `aF801376E8`) in SC07_006/007/010/011; (e) `c9866fcb86` = `E_func_8012F49C` in SC07_006, cc1 error, cause uncaptured. The
reason column should say which (TU-CONFLICT / RANGE-DEFECT / SYMBOL-NAME / GATE-REJECT), not one word for all.
6. **T6 recon the session had done (read-only, reproduced by S95):** 30 live-tree tools carry a non-docstring string constant naming
`DEFINE_func_` or `engine_core.h` (the plan's negative control said "exactly the 22 parsers" — the measured set is 30 and includes the
three Phase-35 tools, which are LEGITIMATE mentions the guard must whitelist): aprop_autodraft 2 · audit_binaries 1 · auto_driver 1 ·
blocker_probe 3 · build_engine_types 1 · bulk_harvest 1 (STILL-NEEDED) · canon_sig_reconcile 1 · cdecl 7 · conform_decls 1 ·
dedup_extend 1 · dedup_integrate 1 · dedup_propagate 3 · demacroize 2 · export_pairs 1 (STILL-NEEDED) · family_cousins 1 ·
family_remap 1 · family_sweep 6 · fix_arity_callers 2 · gccmap_cites 1 · gen_harvest_targets 3 · jr_isolate_all 1 · macro_draft 1
(STILL-NEEDED) · macro_to_header 19 · normalize_self_decls 2 · overlay_src_split 3 · p16_improve 4 · recover_giant 1 ·
recover_integration 2 · share_census 4 · sig_unify 1. The 7 freeze tools' entry points: `family_sweep` main:878/1060,
`gen_harvest_targets` 154/294, `p16_improve` 68/136, `recover_giant` 71/81, `restore_dropped_decls` 73/129, `normalize_self_decls`
193/210, `o0_subsplit` 64/197; dictionary status today: all LIVE except restore_dropped_decls + macro_draft STILL-NEEDED.
Nothing else changed on disk in S95 except this file.
- **S96 (2026-09-08 evening, same terminal as S95; Drew: `/effort high` — testing Fable 5.1's default; the plan's Max for T5's tool work is
waived by that choice) — T5 step 1: run 2 BANKED** as the checkpoint's §2 step 1 specifies: 37 `src/` files (170 shared includes in
8 overlays), `config/dedup_exceptions.tsv` (+55 rows), the batch records, 139 gate logs, `run_extend2.log`, `kit_corpus_t5a.log` —
181 files. No gate re-run (each binary's final `check_<b>.log` is the tool's success line, R66); R22 still owed for T5.
- **S96 — T5 step 2: `tools/share_body.py` fixed** (the three S95 defects): `failure_cause()` = the first `file:line: message` (or
`file:(.text+0x..): message`) diagnostic that is not a warning/note/banner, then the assembler/linker phrases, then the make line;
`reason_for()` → SYMBOL-NAME / GATE-REJECT / PARSE-ERROR / TU-CONFLICT (the two new codes documented in the ledger's header); the
bisect restores from `tu_snapshot()` (the pre-edit text of every touched TU, in memory) — `git checkout` is gone; `--batches` (default
1) stops after one batch with a "COMMIT, then re-run" line; the per-class cause is kept (`causes` in the batch JSON, in every ledger
note); the docstring carries the run-on-a-committed-tree rule. **Controls (R39):** on the dead session's real red `cc1.err` (64 warning
lines) the cause is `func_80168B70.h:13: too many arguments to function 'func_80146C3C'` → TU-CONFLICT; six synthetic logs (linker,
assembler, `[FAIL]`, parse error, conflicting types, a warning-only log) each give the expected code and never a warning line.
`make kit-corpus` + `tool_census --check: OK`.
- **S96 — T5 step 3: the registry repaired** — `share_body.py --repair-registry` (new: every listed member whose site is still a private
definition, derived from the census and matched on (binary, vram) — the first cut matched the binary NAME alone and removed 9,269
members from 214 groups because h_exact is position-independent and a listed binary can hold the same bytes privately at ANOTHER
address; caught by `dedup-check: 14 failed` + a known-true control (ov_SC01_004's include of func_80146E90), reverted, fixed):
`repair-registry: 378 listed-but-private members removed from 79 groups; groups left with <2 members: 0; verbose groups … 0` —
317 of them the ledger's rejected pairs (325 minus 8 never listed), 61 the sites the S94 bisects wiped plus older escapes;
`E_func_80168B70` back to its 7 late-overlay members. `dedup-check: 2221 validated, 0 failed | C1 coverage 256120/256120`;
census `362,389 classified · 0 UNACCOUNTED`, verdicts A 1,812 · B 183 · C 6,639 · D 1,545 · M 1 (A 1,712 → 1,812 = the 100 classes
bucket 0 completed), same-vram backlog **1,099 → 999 classes · 3,883 private copies · 2,887 collapsible**; the whale control
`A/141/missing 0`. `.run/P35/share/repair_registry.json` is the record. kit corpus + `tool_census --check: OK`.
- **S96 — T5 step 4: the suspect rejections replayed; a FOURTH tool defect found and fixed; the extend bucket re-run (`run_extend3.log`).**
The census's definition ranges for the three parse-error sites are CORRECT (e.g. `ov_SC01_005_jr_8015C32C.c:3389-3426` covers all of
`func_8016163C`) and each single edit compiles clean (cc1 rc 0) — so the failures were not the class's edit. Cause: **a twin's instance
resolves to its PRIMARY's TU, so a twin pair queued the SAME (tu, line) edit twice; the second replacement swallowed the NEXT function**
→ `parse error before 'if'` (the orphaned body) or `undefined reference to <the following function>` at link — in exactly the five twin
primaries; 7 classes (`d743c21bd9`, `3576d059c2`, `6f62feba35`, `7604e5daf1`, `9bda7673e9`, `bdb48359cd` + one) were the tool's artifact.
Fix: `Batch.add_class` dedupes edits by (tu, line). `E_func_801376E8` (`7529ad8f17`, SC07_006/007/010/011) IS a real GATE-REJECT:
the SC07 private copies read `((void (*)(s32))func_80139BE0)(obj)` where the header calls `func_80139BE0(obj)`, and those TUs declare
the callee `s32 (s32)` — the header text allocates registers differently there (replayed: `[FAIL]`, the one object differs inside
`func_801376E8`). The ledger's 54 S94 rows were dropped for re-judging (7 artifacts, the rest without a captured cause);
`E_func_80168B70`'s row kept with its real cause (step 5 pending). **Run 3** (`--bucket extend --batch 400 --batches 1`, 165 s):
`extend (registered-incomplete) 83 classes` · `[extend1] 83 classes · 235 sites in 55 TUs · gating 141 binaries` → `gated 141/141
binaries green · registered 0 groups · extended 87 members · rejected classes 48`; 157 REJECTED lines (303 before), every cause now a
real diagnostic (`conflicting types for 'ApplyMatrixSV'` ×6 classes, `too few arguments to function 'func_8014A51C'`, `conflicting
types for 'D_80126CC4'`, `… 'D_800A651C'`, `… 'func_801497A8'`, `too few arguments to function 'func_80146C3C'` — the late overlays'
declaration environments vs the headers' text); 78 more sites shared in 24 TUs; 49 ledger rows. A fifth extractor wart (a chained
`a.h:65: b.h:15: warning: …` line picked for 3 classes) fixed in this commit — those 3 rows carry a warning as cause and are
re-judged with the step-5 run. `dedup-check` 0 failed; kit corpus + `tool_census --check: OK`. Effort: high (Drew's choice).
- **S96 — T5 step 5 DECIDED (Max, delegated by Drew): re-exemplar `E_func_80168B70` from the majority text; the 48 late-overlay conflicts
stay ledgered for the types phase** (the decision and its reasoning are in §Decisions). NEXT: `share_body.py --reexemplar <h_exact>` —
header ← the majority private text (`choose_exemplar` over the private copies, the current header's text counted once per includer),
`bind_alias_header` if needed, gate every current includer's binary FIRST (roll the header back on any red), then the class as a normal
extend batch (`--only`), the registry extended with the members that passed; its ledger row removed just before the run. Effort: high.
- **S96 — T5 step 5 DONE: `share_body.py --reexemplar 93d5fccdcc`** (`.run/P35/share/run_reex_93d5fccd.log`, 84 s): `header
src/shared/ov/func_80168B70.h ← src/ov_SC01_000/ov_SC01_000_jr_8015C32C.c:5792 (majority text; texts 1); 134 private copies, 7 includers
to re-gate first` → `7/7 includer binaries green under the majority text` → `[reex_93d5fccd] 1 classes · 129 sites in 129 TUs · gating
141 binaries` → **`gated 141/141 binaries green · registered 0 groups · extended 134 members · rejected classes 0`**; the previous
ledger row dropped (48 rows remain, all late-overlay declaration conflicts for the types phase). The old header text is
`.run/P35/share/reexemplar_93d5fccd.old.h`. `dedup-check` 0 failed; SETUP paragraph extended (`--batches`, the snapshot restore, the
cause rule, `--repair-registry`, `--reexemplar`; R21); kit corpus + `tool_census --check: OK`. **Bucket 0 is CLOSED:** 183 classes at
the start → 135 fully shared, 48 ledgered TU-CONFLICT with real diagnostics.
- **S96 — R22 after bucket 0** (`.run/P35/baseline/r22_t5_bucket0.log`): `make clean && make extract-all JOBS=16 && make check-all JOBS=16`
→ `extract-all: 217 extracted, 0 failed of 217 (+ main, serial)` · **`check-all: 218 passed, 0 failed of 218`** · `wall=84.02 s
user=1334.73 s sys=185.72 s` · `exit=0` — every T5 bank so far (five commits) proven from clean; fleet CPU 2,230 s (T4) → 1,335 s.
- **S96 — T5 bucket `new`, batch 1** (`.run/P35/share/run_new1.log`, 99 s; `--apply --bucket new --batch 120 --batches 1`): `census 29 s ·
extend (registered-incomplete) 5 classes · new (unregistered same-vram) 915 classes / 3,567 private sites` · `[new1] 120 classes · 1482
sites in 535 TUs · 120 new headers · gating 165 binaries` → **`gated 165/165 binaries green · registered 120 groups · extended 0 members ·
rejected classes 1`** ([new1] ov_SC07_010: class 2e822a8f62 REJECTED — src/ov_SC07_010/../shared/ov/func_80140D68.h:41: 'D_800B9A02' undeclared (first use this function) — an exemplar body relying on a
file-scope extern outside its definition; ledgered TU-CONFLICT, 49 rows). 795 classes of the bucket remain; the 5 unexcepted B classes
get an `--bucket extend` run at the end.
- **S96 — T5 bucket `new`, batch 2** (`run_new2.log`; record copied to `batch_new2.json`): `120 classes · 360 sites in
69 TUs · 120 new headers · gating 9 binaries` → **`gated 9/9 binaries green · registered 120 groups ·
extended 0 members · rejected classes 0`**.
- **S96 — T5 bucket `new`, batch 3** (`run_new3.log`, 107.16 s; `batch_new3.json`): `120 classes · 358 sites in
59 TUs · 120 new headers · gating 12 binaries` → **`gated 12/12 binaries green · registered
120 groups · extended 0 members · rejected classes 0`**.
- **S96 — T5 bucket `new`, batch 4** (`run_new4.log`, 109.92 s; `batch_new4.json`): `120 classes · 360 sites in
69 TUs · 120 new headers · gating 15 binaries` → **`gated 15/15 binaries green · registered
120 groups · extended 0 members · rejected classes 0`**.
- **S96 — R22 after bucket `new` batch 4** (`.run/P35/baseline/r22_t5_new4.log`): `check-all: 218 passed, 0 failed of 218` · `wall=221.86 s` · `exit=0`.
- **S96 — T5 bucket `new`, batch 5** (`run_new5.log`, 79.54 s; `batch_new5.json`): `120 classes · 329 sites in
75 TUs · 120 new headers · gating 15 binaries` → **`gated 15/15 binaries green · registered
120 groups · extended 0 members · rejected classes 1`**; rejected: [new5] ov_SC03_126: class 60be554c70 REJECTED — src/ov_SC03_126/../shared/ov/func_8017EF6C__60be554c.h:4: 'D_80126942' undeclared (first use this func.
- **S96 — T5 bucket `new`, batch 6** (`run_new6.log`, 28.66 s; `batch_new6.json`): `120 classes · 240 sites in
40 TUs · 120 new headers · gating 14 binaries` → **`gated 14/14 binaries green · registered
119 groups · extended 0 members · rejected classes 1`**; rejected: [new6] ov_SC04_006: class 250070c4bf REJECTED — src/ov_SC04_006/../shared/ov/func_8017D0FC__250070c4.h:4: 'D_80126954' undeclared (first use this func.
- **S96 — R22 after bucket `new` batch 6** (`.run/P35/baseline/r22_t5_new6.log`): `check-all: 218 passed, 0 failed of 218` · `wall=84.30 s` · `exit=0`.
- **S96 — T5 bucket `new`, batch 7** (`run_new7.log`, 32.90 s; `batch_new7.json`): `120 classes · 240 sites in
63 TUs · 120 new headers · gating 34 binaries` → **`gated 34/34 binaries green · registered
120 groups · extended 0 members · rejected classes 0`**.
- **S96 — T5 bucket `new`, batch 8** (`run_new8.log`, 26.95 s; `batch_new8.json`): `75 classes · 150 sites in
30 TUs · 75 new headers · gating 4 binaries` → **`gated 4/4 binaries green · registered
75 groups · extended 0 members · rejected classes 0`**.
- **S96 — R22 after bucket `new` batch 8** (`.run/P35/baseline/r22_t5_new8.log`): `check-all: 218 passed, 0 failed of 218` · `wall=88.98 s` · `exit=0`.
- **S96 — T5 CLOSE.** Bucket `new` done in 8 batches (`share_body_cycle.sh` 3–5 and 6–8 unattended; batches 1–2 by hand): 3,519 sites in
980 TUs → 914 groups registered, 3 classes rejected (new1, new5, new6 — each a body relying on a declaration outside its definition,
ledgered TU-CONFLICT); R22 green after batches 4, 6 and 8 (`r22_t5_new{4,6,8}.log`, `check-all: 218 passed, 0 failed of 218`). The
tail run (`run_extend_tail.log`): `new (unregistered same-vram) 0 classes / 0 private sites`; the 5 leftover B classes had NO private
copy — every one of their 141 instances already includes the header while the registry listed 16 (or 2): the eight VERBOSE `members:`
groups, which `dedup_extend.add_members_surgical` skipped silently (no `binaries:` line) — the inverse defect, the registry BEHIND the
source. Fixes: `add_members_surgical` refuses a verbose group loudly (R43); `--repair-registry` gained the second half (an
including-but-unlisted member is added; a verbose group is converted to shorthand): `639 including-but-unlisted members added to 5
groups (5 converted to shorthand: D1_ov_dup_8012AD64, E_func_80128EA8, E_func_8012A568, E_func_80132EC4, E_func_80138C30)`; 3 verbose
groups remain (I0_clearTbl40 + the two SC01_005 setters, cross-address controls). **Verify (T5's milestone items, literal):**
`share_census --check` → **`S1: one source per unique function — 10,180 classes, 10,180 satisfied (3,580 twin-covered, 51 excepted,
3,801 deferred cross-address), 0 VIOLATION(S) — OK`**, exit 0 (`.run/P35/census/check_t5.txt`); verdicts A 2,760 · B 149 · C 5,751 ·
D 1,518 · M 2; **same-vram unregistered 51 classes · 160 copies · 109 collapsible — exactly the ledger (50 TU-CONFLICT + 1
GATE-REJECT, every row with its diagnostic)**; `dedup-check: 3135 validated, 0 failed | C1 coverage 260543/260543`;
`audit-binaries: OK`; kit corpus + `tool_census --check: OK`; the last R22 (`4ba4b2080`) stands — no build input changed after it
(the tail run shared 0 sites; the registry is not a build input). **Before → after (T4 close → T5 close):** registry groups 2,220 →
3,135; shared headers 2,215 → 3,137; same-vram backlog 1,099 classes / 4,755 private copies / 3,658 collapsible → 51 / 160 / 109
(all ledgered); verdict A 1,712 → 2,760; fleet clean-run wall 145 s → 84 s (CPU 2,230 s → 1,335 s); 23 commits in T5. **T5 ☑.**
- **S96 — T6 part 1: freeze + retire.** `tools/frozen.py` (the one refusal, printed from main() — never at import: cdecl imports
gen_harvest_targets, 18 tools import family_remap). **FROZEN (14, status FROZEN + successor in the dictionary):** the plan's 7
(family_sweep, gen_harvest_targets, p16_improve, recover_giant, restore_dropped_decls, normalize_self_decls, o0_subsplit) + 7 the
guard census found reading or writing the macro form (inject_capped_externs, aprop_autodraft, canon_sig_reconcile, conform_decls,
blocker_probe, demacroize, and family_remap's command line — it writes a `#define DEFINE_func_` head; its library stays). Deviation
from the plan's "7": the S95 ast census measured 30 hits, not 22; twin_sweep/recover_integration (which exec frozen CLIs) stay LIVE
and would print the refusal; family_cousins stays LIVE (`make atlas` runs it) and loses its dead branch in part 2. **RETIRED (4, git mv
to tools/sunset/, README rows):** dedup_propagate + dedup_extend → share_body.py (add_members_surgical and the library surface
onboarded_overlays / load_sig / registered_addrs / sym moved verbatim; validate_targets imports share_body; inject_capped_externs
frozen), macro_draft (product: the headers), test_reconcile_ledger (retired with its subject). Six lane callers repointed to
`share_body.py --apply --bucket new --batches 1` (gate_stage, gate_lane, grinder's message, lora_grind, auto_driver, bulk_harvest);
audit_binaries' hint reworded. `tool_census` accepts FROZEN. Verify: every touched file compiles; `family_sweep.py --source x` →
`FROZEN since Phase 35 … Successor: tools/share_body.py` rc 1; `import share_body, family_remap, gen_harvest_targets,
validate_targets` OK; no live tool names a retired file; `tool_census --check: OK` after `make kit-corpus` (the first chain checked
BEFORE regenerating and a `;` let a partial commit through — R97 again; amended into one commit).
- **S96 — T6 part 2: the dead macro branches dropped + the guard.** Twelve live tools lost their macro-form code: audit_binaries
(`SHARED_INCLUDES` = the prelude only), auto_driver + bulk_harvest (`git add src/shared`), build_engine_types (the generated comment),
cdecl (`MACRO_STMT` = INCLUDE_ASM only; the fifteen-incumbent differential reduced to its honest statement — the macro header is gone —
and its dead body deleted, the gen_harvest_targets import with it), dedup_integrate (`PARAM_FUNC_RE` = SHARED_FN), export_pairs (mines
the shared headers under src/shared/ instead of macro bodies), family_cousins (`seed_body_ref` finds the shared header), fix_arity_callers
(the shared-header glob only), jr_isolate_all (the prelude include maps to engine_types.h + common.h), recover_integration (message +
snapshot list), sig_unify (help text). **The guard** (`tool_census.py --check`, `macro_form_guard`): every LIVE `.py` parsed with `ast`;
a non-docstring string constant carrying `DEFINE_func_` or `engine_core.h` is a gap; FROZEN, retired and six whitelisted detectors
(share_census, macro_to_header, share_body, overlay_src_split, gccmap_cites, tool_census) excluded; `--guard-root DIR` runs it against
another tree. **Verify:** `macro-form guard: 0 LIVE tools reference the retired form (217 scanned, 14 frozen, 6 whitelisted detectors, 38
retired)` · `tool_census --check: OK`. **Negative control (R39):** a worktree of the pre-T4 commit `0e38b51a3` → `15 LIVE tools reference
the retired form (197 scanned, 14 frozen, 4 whitelisted)` — exactly the twelve fixed here + the three retired, none frozen or whitelisted.
**`make tools-health` went red twice, both instrument findings fixed at the cause:** (1) `progress.py`'s R32 bucket assertion —
`func_801EF790`/`func_801EF85C` (md_SC03_073/074/075/078, empty-bodied shared headers) in BOTH `real` and `empty`: the include rule
classifies an empty header body as EMPTY while `report()`'s fold `real ∪= shared` re-added every included member; under the macro
form an empty shared body had been invisible to the classifier and counted as REAL through that fold. Fix: the fold excludes `empty`.
**Two published-number corrections follow (R14/R75; stated here, carried to T7/T8):** the fleet denominator **363,221 → 363,680
(+459)** = ov_SC03_015 +219 (its single-site macros, the plan's expected correction) + ov_SC03_118 +3 + 2 in each of ~118 overlays
(the never-extended members of the five under-listed groups — macro sites invisible until T4 made them includes); and **FLEET REAL
substantive 360,744 → 350,533 (−10,211)** with EMPTY up by the same — empty-bodied shared functions were REAL under the macro form's
fold and are EMPTY now, honestly; the instruction-weighted metrics (13,492,113 / 5,820,205 / 45,150) are unchanged. (2) `timeline.py`'s
self-check: its last row follows the COMMITTED digest (T2's lesson) — this commit carries the corrected `docs/progress.json` + README
block + `progress.fleet.md`; the timeline is regenerated in the next commit and the chain re-run for T6's verify line.
- **S96 — T6 CLOSE.** `make tools-health` red three times, each an instrument finding fixed at its cause, green on the fourth: (1) the
`progress.py` fold (above); (2) `doc_links`: `docs/wiki/The-dedup-engine.md` linked the retired `tools/dedup_propagate.py` — repointed to
`share_body.py` with a note (T8 rewrites the page); (3) `kit_lint` LEAK: my dictionary row for `share_body_cycle.sh` carried rule ids
(`R42`, `R22`) — project-specific text the kit's manifest must not inherit — reworded. **Verify (T6's line):** `tools-health: OK — sigs
fresh; corpus(+resident) + cdecl + binaries + report(lint+dedup) + cookbook-index all green.` (`.run/P35/baseline/tools_health_t6d.log`,
484 s, exit 0) with `macro-form guard: 0 LIVE tools reference the retired form (217 scanned, 14 frozen, 6 whitelisted detectors, 38
retired)`; the timeline regenerated by the chain against the committed digest (74 rows, self-check OK). **T6 ☑.**
- **S96 — T7: the invariants wired + every published number regenerated.** Makefile `tools-health`: after `audit-text-sources`, before
`report BINARY=main`, two rungs — `share_census.py --selftest` (7/7) and `share_census.py --check --strict-macros --strict-text --quiet`
(exit code, R97) — so `progress.py` reads a fresh census json. `dedup_integrate --check` gained **C2c** (one source under `src/shared/`
defining exactly one function) and **C2d** (every member's site is an include of THIS group's source — from the census's per-instance
forms, one cached scan; a `def` site = the registry ahead of the source). `progress.py`: `counts.unique_function_bodies` (REAL + EMPTY −
shared instances + their one header each = **105,007** bodies written once in C), `duplicate_source_copies` / `_classes` (**160 / 51**,
from the census json, all ledgered), a dated `corrections` list (T2 +7; T6 +459; T6 REAL −10,211) and the README sentence "One source
per unique function (Phase 35): …". **The second oracle (R34) disagreed with the byte oracle and the disagreement is measured:** of
4,312 same-address definition texts duplicated across TUs, 3,495 were a twin and its primary (one TU — the oracle now counts distinct
TUs), 28 are ledgered, **380 texts (1,668 sites) sit inside the E/F-deferred cross-address classes** (an empty body's hash spans every
address, so gate 1's deferral took its same-address pairs along — published as the names phase's inheritance), and **38 texts (2,030
sites) are the same C at the same address compiling to DIFFERENT bytes per binary** (singleton h_exact classes — e.g. `func_8012AAAC`,
138 copies, 133 byte patterns, h_norm differs in 5 ways): one source the byte tier cannot register — published as PENDING, a decision
for Drew (an `h_text` tier now, or the names phase); 0 texts a shared byte class holds across two TUs — the first oracle has no hole.
**Negative control (R39), in place on the probe class `S_func_801681FC` (2 members):** ov_SC05_009's include reverted to the 19-line
private copy → `S1 … 10,179 satisfied … 1 VIOLATION(S) — FAIL` naming `B - h=c1c085b28d … ['ov_SC04_008', 'ov_SC05_009']`, exit 1;
`[FAIL] S_func_801681FC: ov_SC05_009:0x801681fc … its site is a def in … (C2d)`, `dedup-check: 3134 validated, 1 failed`; restored →
`0 VIOLATION(S) — OK`, exit 0. Instrument slips on the way, each caught by the run: a missing `os` import (C2d's first run), the
`--check` path not keeping per-instance records, twin pairs counted as two TUs, "classed" mis-defined as any classed site (fixed to
"one class across two TUs"). **Verify:** `make tools-health` → `tools-health: OK — …` (`.run/P35/baseline/tools_health_t7.log`, 551 s,
exit 0) with `S1: … 10,180 satisfied … 0 VIOLATION(S) — OK`, `dedup-check: 3135 validated, 0 failed | C1 coverage 260543/260543`,
`macro-form guard: 0 LIVE …`, `progress.py --check: … fresh`, `timeline --check: fresh (74 rows)`. **T7 ☑.**
- **S96 — the second oracle's finding DECIDED (Max, delegated by Drew): T5b (a text tier) for the 38 byte-variant same-address texts;
the 380 inside the deferred classes stay deferred** — the reasoning is in §Decisions; T5b's design in its task row. NEXT: T5b at high.
- **S96 — T5b: the text tier.** `config/dedup.us.yaml` gains `tier: h_text` (the group hash = the normalized text; verbose members each with
their own `h_exact`); `dedup_integrate` C1 checks an h_text member against ITS recorded byte hash (TIERS += h_text); `share_census`
indexes h_text groups by site (a byte class whose sites they list is registered by TEXT — no `extra`), exports `text_classes()` (same
normalized text, same address, ≥2 distinct TUs, outside the deferred/ledgered sets) and makes the S1 text half a VIOLATION (was PENDING);
`share_body --bucket text` shares them (header `func_<VRAM>__t<hash8>.h`, banner `h_text`, the body from any site — identical by
construction — declarations stay in each TU; registration in the verbose form with per-member h_exact). **Run (`run_text1.log`, 1,489 s):**
`text tier — 38 same-address text classes / 2,030 private sites` · `[text1] 38 classes · 1957 sites in 1540 TUs · 38 new headers · gating
138 binaries` → **`gated 138/138 binaries green · registered 38 groups · extended 0 members · rejected classes 0`** (e.g. `T_func_8012AAAC`:
138 members, 133 byte patterns, one header). **Verify:** `dedup-check: 3173 validated, 0 failed | C1 coverage 262573/262573`; the strict
census `S1 … 10,180 satisfied … 0 VIOLATION(S) — OK` (twin-covered 3,580 → 3,507: the twin pairs' sites are text-tier includes now) with
the second oracle `0 same-address definition texts duplicated` and `381 … inside the deferred cross-address classes (1,668 sites)`;
**R22 `check-all: 218 passed, 0 failed of 218`, 90 s** (`r22_t5b.log`); `progress.py --check … fresh` — the digest now says 103,015
bodies written once (3,173 headers instantiated 262,573 times), 160 duplicate copies in 51 ledgered classes, 38 text-tier functions
(2,030 sites), 1,668 same-address copies of 381 tiny bodies deferred to the names phase (the README sentence carries all of it). The
census json is rewritten after the strict-text keys are set (progress.py read empty counts before). SETUP §T7/T5b (R21). **Verify
(the chain):** `tools-health: OK — …` (`.run/P35/baseline/tools_health_t5b.log`, 439 s, exit 0) with `S1 … 10,180 satisfied … 0
VIOLATION(S) — OK`, `dedup-check: 3173 validated, 0 failed | C1 coverage 262573/262573`, `macro-form guard: 0 LIVE …`; the timeline
regenerated (74 rows). **T5b ☑.**
- **S96 — T8: the record.** `docs/wiki/The-dedup-engine.md` rewritten for the shared-source model (the shape, the three tiers incl.
`h_text`, the tool and its four first-day defects, the S1 gate with the second oracle, the numbers as a dated derivation, the honesty
rules); `Repository-layout.md` (`src/shared/`), `Where-the-project-goes-next.md` (item 3 done; the macro row 5,147 → 0),
`Verification-and-progress.md` (the S1 rungs in the reviewer table); how-to ch.10 (dedup in both eras; the old "leave the backlog" rule
scoped to matching); README prose ("shared engine code lives as one plain-C header per function"); `gen3-standards.md` (§4 row
struck through with the Phase-35 measurement, the DoD's "0 macro bodies" marked met); `gen3-handoff.md` (§2.2 and §3 rows as dated
snapshots); cookbook **§453** (the include-at-site share, the exemplar rule, the gate, the four defects, S1, the second oracle, the two
corrections) + a retirement banner on §14; `docs/decision-log.md` P35 (R31: the reversal with the sotn evidence, the session death and
recovery, the six decisions, the measurements, four hindsights); `docs/accelerators.md` P35 (five); DIGEST §4 (the Phase-35 doc map);
the memory `dedup-backlog-leave-it` rewritten as REVERSED with its index line. **Verify:** `doc_links: 56 documents, 455 relative links
checked, 0 pending, 0 broken` (strict) · `wiki_render --selftest: 12 cases, 0 failed; reachability: 32 pages, 0 unlisted` · the cookbook
index regenerated (`1170 sections, 14 symptom buckets`) and `cookbook-index OK` · `kit_coverage: OK` · kit corpus + `tool_census
--check: OK`. **T8 ☑.** (`tools/sunset/README.md` rows were written at T6.)
- **S96 — T9, the close (run in S96 at xHigh by Drew's choice; the plan said Max in a fresh session).** R22 from clean:
`extract-all: 217 extracted, 0 failed of 217 (+ main, serial)` · **`check-all: 218 passed, 0 failed of 218`** · `wall=87.62 s` · exit 0
(`.run/P35/baseline/r22_t9.log`). `make tools-health` run 1 (467 s) was green through S1 / dedup-check / the guard and red only at
`tool_census --check` — the kit's record copies of `DIGEST.md` and the new `PhaseEnd_Phase35.md` (both written while the chain ran);
`make kit-corpus` regenerated them and the chain was re-run (its line is appended below). The headless Ghidra MCP stopped via the
sentinel: `Save succeeded for processed file: /SLUS_007.26` (R23; unused all phase). `PhaseEnd_Phase35.md` written (the metrics table,
the milestone items with literal output, the reviewer sequence, seven rule candidates, the recap); DIGEST §0 refreshed + §2's P35
synopsis appended (step 3b). **The chain, run 2:** `tools-health: OK — sigs fresh; corpus(+resident) + cdecl + binaries +
report(lint+dedup) + cookbook-index all green.` (`.run/P35/baseline/tools_health_t9.log`, 431 s, exit 0, 0 gaps) with `S1 … 10,180
satisfied … 0 VIOLATION(S) — OK`, `dedup-check: 3173 validated, 0 failed`, `macro-form guard: 0 LIVE …`. The log archived to
`phase-ends/logs/Phase35.md` (R19); `make kit-corpus` + `tool_census --check` after the PhaseEnd's last edit; everything left
uncommitted for Drew's close commit (R6). **T9 ☑ — Phase 35 milestone met; v2.1.0.**
## Approved plan (verbatim, gate 1 — 2026-09-08)
# Phase 35 — Gen3 opens: the dedup phase, "one source per unique function" (v2.0.0 → v2.1.0)
> Plan mode, Max, S94, 2026-09-08. Gen3 order set by Drew this session: **dedup → pins → structs → names**, one phase each,
> planned one at a time; this plan is Phase 35 only. Gate 1 also ratifies **R96–R99** (Drew: "ratify all four").
## Context
Gen2 closed at v2.0.0 (218 binaries byte-identical, the repository public). Gen3's charter is readability on a byte-exact
floor. The first readability defect is that the same function exists many times in the tree, in two forms:
1. **Shared bodies as macros.** The dedup engine instantiates one body in every location overlay as a `DEFINE_func_XXXXXXXX()`
macro from `src/shared/engine_core.h` (8.8 MB, 227,730 lines, backslash-continued, `#include`d whole by 3,981 TUs; measured
cost 0.35 s + 94 MB RSS of cpp per TU ≈ 19 CPU-minutes per fleet build). `docs/gen3-standards.md` §2 rule 3 requires shared
engine functions to live as C, not macros.
2. **Literal duplicate copies.** The July-2026 family sweeps banked proven bodies as a private copy per overlay and registered no
group. On 2026-09-02 that backlog was deferred on the belief "sotn writes duplicate funcs explicitly" (memory
`dedup-backlog-leave-it`); the decision log caveated that the claim rested on one cookbook parenthetical — which is about a
cross-jump barrier idiom (`docs/matching-cookbook.md:253`), not about sharing.
**Verified this session, as data (X2), from sotn-decomp's tree:** sotn does NOT duplicate. Shared stage code lives once as plain C
in `src/st/<name>.h` (full definitions, `static` tables, `#ifdef STAGE_IS_…` where one stage differs), and each stage overlay
carries a ~90-byte `.c` stub (`#include "nz0.h"` + `#include "../e_red_door.h"`) that instantiates it at that stage's link
position; per-stage parameters are `static` data in the stub before the include. "Written once, instantiated per overlay by an
include at the site" is the community shape — structurally what our macros do, minus the macro form. The 2026-09-02 decision is
reversed on this evidence (recorded in the decision log at task 8).
**Drew's requirement (this session):** after the phase the tooling knows that functions are no longer duplicated across overlays
and that there is exactly one source for a unique function — an invariant asserted by a gate (R36), not remembered.
**Decisions at gate 1 (Drew, AskUserQuestion):** twin binaries share one source directory · cross-address classes are censused
and deferred to the names phase · macro-era tools are frozen with a loud refusal (the direct predecessors retire) · R96–R99 ratified.
Added by the plan: **no "trivial" exception** — a duplicated 3-instruction accessor is still one function (this project already
shares 5-instruction bodies; 341 tiny classes / 42,744 instances are in scope).
## Measured shape (this session; every number carries its rule; task 1 makes them an instrument)
- **Registry** (`config/dedup.us.yaml` via `dedup_integrate.group_members`): 2,220 groups / 255,708 members, all `h_exact`; 2,212
shorthand (one vram + name for every member), 8 verbose; median 141 members; spans 2,207 ov / 12 md / 1 main; one group with
differing member names (main's `clearTbl40` pair). Sources: `engine_core.h` 2,215 · `ov_setters.h` 3 (name-parameterized
SETTER/RETCONST, the SC01_005≡006 pair) · `clearTbl40.h` 1 · `func_80144B9C.h` 1 (a 319-line ordinary-C header included by
141 `<ov>_o0b.c` — already the target form; "a header-share is as valid as a macro-share", cookbook §38).
- **The header:** 5,147 `#define DEFINE_func_` lines = **3,516 distinct macros**; 1,631 defined twice (copy 1 inside the `#ifndef
ENGINE_SHB` block ending at line 99,164, copy 2 after; 1,627 identical, 4 differ in one extern's `(void)` vs `()`; cpp keeps the
LAST); **2,215 shared (all registered) · 218 single-site (217 of them in ov_SC03_015, 1 in ov_SC03_118) · 1,083 dead.** 4,600
carry `extern` preambles, 451 asm-label aliases, 357 register pins (Phase 36's; kept verbatim), 0 `#if`, 0 `__LINE__`/`__FILE__`;
exactly 8 non-`DEFINE_` directives (the prelude is provably complete). All macros zero-argument. Invocation sites: 255,947 lines
in 3,153 files across 153 binaries; the site is always one line `DEFINE_func_X() /* dedup: … */`; max 539 sites in one TU (p50 31).
The charter's "5,147 macro bodies" counted define lines (R14/R41 correction, published at task 8).
- **Why include-at-site is byte-neutral and a separate object is not:** a macro expands AT THE SITE to `[externs + definition]`,
so its `extern`s become file-scope declarations at that line and later functions rely on them (cookbook §112; decision-log
:441-457). An include reproduces the text at the same point. **Probe P1 (run): three real bodies compiled both ways through
cpp → cc1 → maspsx → as give byte-identical `.o` files (1,632 B, identical relocations); cc1 adds only a `.file` directive that
`as` consumes.** A separate `.c` object would delete ~4,000 declaration lines from ~3,153 TUs and cannot place interleaved
functions (shared and local bodies interleave in address order, `src/ov_SC06_033/ov_SC06_033.c:1-31`). `src/shared/` is not
pruned from main's source find (Makefile:841-847) → shared bodies are `.h`. `-MMD` tracking (:849-855) recompiles on header edits.
- **Backlog, from the 218 fleet sig files (362,389 instances; 255,937 macro sites · 105,262 inline definitions · 1,190 alias-form
unaccounted):** h_exact classes with ≥2 instances fleet-wide: **10,180 (280,801 instances)**; registered 1,994 hashes / 2,220
groups; **206 registered classes have 3,996 instances not listed as members** (never extended); unregistered classes with ≥2
inline copies: **7,956 classes / 20,038 copies / 12,082 collapsible copies / 478,773 ins** — 4,259 same-vram (9,698 copies),
**3,697 cross-vram (10,340 copies; the member's own name differs per site → deferred)**, 54 cross-space. Under the old propagate
rule alone: 11,289 copies / 1,932 addresses / 5,083 bodies. Source text across copies: **91% identical, 8.3% (403 bodies) differ**
(`uniquify_type` suffixes, extern scoping, 9 with pins) → one chosen text re-gated per member; type-carrying bodies via
`tools/lift_types.py` first.
- **Twin binaries:** five overlay pairs have IDENTICAL payloads (equal `config/check.*.sha`): SC04_018/019, SC03_118/119,
SC03_014/015, SC02_000/003, SC01_005/006 (10 of 141 aliases; 136 distinct payloads); their piles ≈ 7,900 of the 11,289
same-vram copies (~70%). Each twin's yaml differs from its primary's only in `target_path` (+ a carve delta: SC04_019 has one extra
`_jr_80181804` split, SC03_015 two extra); `overlays.mk` blocks differ only by alias; the linker script names objects by alias.
- **Header-name collisions:** 6,346 of 13,355 overlay-slot vrams host >1 h_exact class fleet-wide (max 134) → a shared header
cannot be keyed by address alone; of today's 2,220 groups, 1,961 get a clean name and 259 need a stable suffix.
- **Consumers:** 55 live tools reference the architecture; 22 text-parse the macro form; the rest ask cpp (form-independent) or only
read the registry. Two would CRASH `make tools-health` on the header's deletion: `cdecl.audit_differential` (`tools/cdecl.py:1292-1323`
opens `engine_core.h` unconditionally; `make audit-cdecl` is in the chain) and `overlay_src_split` (:667 degrades to a silently
EMPTY macro table). 51 tools build `src/<binary>` paths themselves; only `compile_only.py` reads the Makefile's `<alias>_SRC_DIR`;
`corpus.py:312` maps link objects to `src/{binary}/{subseg}.c`. `progress.py` undercounts ov_SC03_015 by 219 (single-site macro
sites invisible to `classify()`) — a real undercount the phase corrects and publishes.
## Target architecture (decided)
```
src/shared/
engine_prelude.h # engine_types.h + ENGINE_SHB — every overlay/module TU includes this at line 2 (was engine_core.h)
engine_types.h # unchanged (tools/build_engine_types.py owns it)
ov/ func_80128EA8.h · func_80150000__2905b55f.h # the overlay slot 0x80128158
slot_800CAE08/ … slot_801EF468/ # the 11 module slot bases, derived from <b>_VRAM_BASE (R33)
main/ func_80037004__a0744d60.h # the clearTbl40 pair (the one intra-binary, name-parameterized share)
```
- **One plain-C header per shared body**, keyed by the h_exact class: directory = address space, filename = `func_<CANON_VRAM>` +
`__<h8>` iff (space, vram) hosts >1 class fleet-wide or the class spans >1 vram/space — both predicates on immutable ROM data, so
names are stable under later additions (R48: never a bare name). Flat per space (address-named files list in assembly order).
- **Body text** = the live macro's LAST definition, continuations stripped, re-indented; every `extern`, alias, comment and pin
verbatim (asserted to round-trip to the same token stream). **Pure fragment** (no includes of its own; the prelude comes from
the TU, sotn's contract). **No include guard** (a second include of a fixed-name header is the loud duplicate-definition error
we want; the parameterized form legitimately includes twice). Banner: h_exact, canonical vram, "one source — instantiated by
#include at each member's site; members: config/dedup.us.yaml" (no member count stored — derived, R51).
- **Two site forms, only two:** `#include "../shared/ov/func_80128EA8.h"` for same-vram classes; for the cross-vram control
(clearTbl40 only in this phase) `#define SHARED_FN func_80037334` / `#include …` / `#undef SHARED_FN`, the header defining
`void SHARED_FN(void) {…}`. More than two parameters → refuse (R43). The parameterized form is what the names phase will
apply to the deferred cross-vram classes — clearTbl40 is its worked example and permanent negative control.
- **Single-site bodies** with no fleet-wide twin → inlined at their site as plain C; **dead macros** dropped (listed by name in the
run log — counted, never silent); `engine_core.h`, `ov_setters.h`, `clearTbl40.h` deleted; `func_80144B9C.h` moved to `ov/`,
its guard removed (one shape). End state: `src/shared/` = the prelude, `engine_types.h`, the per-function tree. Nothing else.
- **Twin binaries:** `config/overlays.mk` gets `ov_B_TWIN_OF := ov_A` and `ov_B_SRC_DIR := src/ov_A`; the Makefile maps a twin's
objects to its OWN `build/src/ov_B/` from the primary's sources (`-O0` object lists and `.d` paths twin-aware; `check-all`'s
per-binary parallelism never races); the twin's yaml = the primary's carve with its own `target_path`/`sha1`; its `check.sha`
still proves it; `src/ov_B/` deleted. **One oracle "binary → source dir"** (Makefile-derived, R33) replaces the hardcoded
`src/<binary>` shapes in the Gen3-live tools. The registry keeps twins as members (their site resolves through the oracle to
the primary's TU); no per-function groups for twins.
- **Registry v2:** same file, same `group_members` oracle, shorthand kept; `source:` = the header path; `func:` = the defined
token (`SHARED_FN` for the parameterized form, as `CLEAR_TBL40` is today); optional `form:`; **text-edited only, never
`yaml.safe_dump`** (the H5 precedent that decimalized every vram and deleted 47 comment lines while every gate stayed green,
`tools/dedup_extend.py:78-89`). `dedup_integrate --check` gains C2c (the source is under `src/shared/`, defines exactly one
function, and `func` is the token it uses — via `cdecl`) and C2d (every member's site file includes the source and no inline
definition of that member survives in the binary).
- **The S1 invariant** (permanent, in `make tools-health`): every same-(vram, h_exact) class with ≥2 instances is a registry group
with the include at every member, or twin-covered, or a ledgered exception (`config/dedup_exceptions.tsv`: class hash · nins ·
instances · reason code {JTBL-CARVE, O0-LOCAL, TU-CONFLICT, GATE-REJECT, PINNED, CROSS-VRAM-DEFERRED, CROSS-SPACE} · evidence);
no `DEFINE_func_` token in `src/`; and a second, sig-blind oracle: no name-blind normalized definition text appears in >1 file
under `src/` outside `src/shared/` (R34 — it cannot fail the way the sig join fails).
## The three tools
**`tools/share_census.py`** (permanent; T1) — inputs: the 218 sigs enumerated from `dup_report.BINARIES` (refuse on a missing
sig; never a glob — `.run/` holds 223 sig files incl. two non-fleet ones), the registry, `src/` through the source-dir oracle.
Source-form index: every sig instance resolves to exactly one of def / include / param-include / stub / asm-verbatim / linked /
blob — 0 or ≥2 forms is a coverage DEFECT (R32; strictly stronger than `progress.py`'s `unplaced`, which is vacuous where
`asm/<bin>/nonmatchings/` does not exist). Verdicts per class: A registered-complete · B registered-but-site-missing · C
unregistered-identical-text · D unregistered-differing-text · flags E cross-vram · F cross-space; `--json`, a human table,
`--check` (S1 + the text oracle, exit code), `--selftest` (an in-memory fixture of 2 binaries × 7 classes covering every
verdict, R39). Negative controls on the real tree: `func_80144B9C` = A/141; clearTbl40 = A+E; the 3 ov_setters = A;
`ov_SC01_005` = 657 unregistered items (S75 said 557 at 174 binaries; printed with its denominator, R41).
**`tools/macro_to_header.py`** (ONE-OFF; T3) — `--plan` / `--dry-run [--diff]` / `--apply --binaries …` / `--verify`
(idempotence: a second apply changes 0 files). `macro_index()` keeps the LAST definition (reusing `macro_draft.extract`'s
dedent/continuation strip, `tools/macro_draft.py:31-43`; the 4 divergent names cross-checked against `blocker_probe.py:83-90`'s
independent list); `directive_audit()` asserts the 5,147 + 8 directive inventory before touching anything; `site_index()` refuses
any site not of the one known shape; `header_path()` per the naming rule; `emit_header()` asserts token-stream round-trip;
`rewrite_tu()` (line 2 → the prelude; each site → its include, relative to the TU's own directory); `inline_single_site()`;
`drop_dead()`; registry `source:`/`func:` rewritten surgically. Asserts no emitted header carries a stray trailing `\`.
**`tools/share_body.py`** (permanent; T5 — the successor of `dedup_propagate` + `dedup_extend`) — `--plan [--bucket] [--limit]`,
`--apply --plan-file`, `--extend --binaries` (the never-extended 206 classes). Per class: exemplar = the registry's source if
registered; else the majority name-blind text; tie → pin-free; tie → shortest (the rule printed with every share). Guards reused
(R33): `family_hseq.has_mid_jr` → JTBL-CARVE; the `-O0` split guard (`dedup_propagate.py:676-687`, extended to `_o0b/_o0c`) →
O0-LOCAL; the inline-type guard (:750) → `lift_types` first. Sites replaced at the same position with the function's own
preamble (the `overlay_src_split` Item model). Then **L0 per TU** (build that one object before/after the batch's edits in that TU;
byte-equal, the Probe-P1 oracle; the exact per-TU recipe asked of the Makefile via `parallel_gate.generated_paths`' `--eval`
trick, never re-derived) → on failure bisect within the TU, drop the member, ledger the compiler's message class → **L1**
`make check BINARY=` per touched binary → register (shorthand) → **commit (R42)** → **L2** clean fleet per batch. "shared" is
printed only from the gate's success line (R66). Library surface: `onboarded_overlays`, `load_sig`, `sym`, `find_site`,
`registered_addrs` moved verbatim so the three importers of `dedup_propagate` change one line.
## Tasks (in order; one commit each; effort per `docs/effort-map.md`; every verify line read by exit code, R53/R97)
**T0 — Open + ground (Low, then Max for the probe).** `CURRENT_PHASE.md` with this plan verbatim; DIGEST §3 gets R96–R99 in full;
`.gitignore` allowlists `.run/P35/`; harness task list (R28). The R22 baseline from clean: `make clean && make extract-all JOBS=16 &&
make check-all JOBS=16` → `check-all: 218 passed, 0 failed of 218`, wall time recorded; `make tools-health` OK. **The probe (R37):**
in a scratch worktree, convert ov_SC06_033's 34 TUs by hand-driven script and run the L0 object A/B on all 34 (+ `make check
BINARY=ov_SC06_033`), measuring the `.d` growth and the per-TU cpp saving. Verify: `34/34 objects byte-identical`; `[ OK ] … (BYTE-IDENTICAL)`.
**T1 — `tools/share_census.py` + `config/dedup_exceptions.tsv` (Max; new instrument).** Built BEFORE anything moves so every claim has
a before/after. SETUP + dictionary rows (R21/R87). Verify: `--selftest` → `7/7 verdicts correct`; `--json` on the unchanged tree prints
the class counts above with their denominators, and the four negative controls; coverage line `classified == instances`.
**T2 — Teach the health chain the new forms while the old tree is still green (Max design, xHigh apply).** The source-dir oracle
(`corpus.src_dir(binary)` from the Makefile's `<alias>_SRC_DIR` — one function; `compile_only.src_dirs` folded in); `corpus.py` (definitions
may live under `src/shared/**.h`; :312 through the oracle); `cdecl.py` (`audit_differential` includes `src/shared/**/*.h` when
`engine_core.h` is absent; `MACRO_STMT` :109); `overlay_src_split.py` (+ `jr_isolate_all`: an include-site anchor kind; header-resident
bodies; :667 becomes a loud refusal); `progress.py` (`classify()` recognizes an include of a `src/shared/` path as the definition the
registry names for that site; the fold at :773 becomes `shared = real ∩ dedup_members` — derived from source, R33); `dedup_integrate.py`
(C2c/C2d; the oracle for `_src_paths`); `audit_binaries.py` (the prelude include; twin citizenship: equal `check.sha`, `SRC_DIR` = the
primary's, primary not a twin, carve equal — R36); `lint_symbol_refs.py` (:89 recursive glob); `shared_lock.py`; `fix_arity_callers.py`
(:41); `family_remap._unit_from_macro` → read the header; `blocker_probe.py`; `demacroize.py` (`macro_bodies()` reads headers);
`export_pairs.py`; `harvest_verify.py:167`. Both forms accepted during the transition. Verify: `make tools-health` OK on the UNCHANGED
tree (the negative control) and `share_census --selftest` still 7/7.
**T3 — Twin binaries → one source directory (Max for the mechanism on the probe pair; xHigh for the other four).** Probe SC01_005/006
(carves already equal): the `TWIN_OF` declaration, the Makefile mapping, the twin's yaml = the primary's carve with its own
`target_path`; delete `src/ov_SC01_006/`; `make extract BINARY=ov_SC01_006 && make check BINARY=ov_SC01_006` → `56760dbe…`;
`ov_setters.h`'s three groups become twin-covered (the header goes at T4). Then SC03_118/119 and SC02_000/003 (carves equal), then
SC04_018/019 and SC03_014/015 (adopt the primary's carve for the twin; R60: `interleave_check` + `pads_audit` on the twin). One commit
per pair; the clean fleet run after the last. Verify per pair: the twin's `make check` sha line; after all: `share_census` twin-covered
copies ≈ 7,900, same-vram backlog copies fall by that count, ov_SC03_015's 217 single-site macros become dead.
**T4 — The conversion: `tools/macro_to_header.py` applied fleet-wide; `engine_core.h` deleted (Max for the tool; xHigh to run).**
Order: `--plan`, `--dry-run` reviewed; `--apply --binaries ov_SC06_033` + L0 + `make check`, commit; the remaining binaries in ~8
batches of ~27, each L0-gated (`obj A/B: N/N byte-identical`); then delete `engine_core.h` / `ov_setters.h` / `clearTbl40.h`
(clearTbl40 rewritten as the parameterized control), move `func_80144B9C.h`, rewrite the 141 `<ov>_o0b.c` includes; the registry's
`source:`/`func:` rewritten surgically; L2. Verify, in order: `--verify` → `idempotent: 0 files would change`; L0 fleet `N/N`;
`check-all: 218 passed, 0 failed of 218`; `git grep -c '^#define DEFINE_func_' -- src` and `git grep -cP '^\s*DEFINE_func_' -- src`
empty; `tools/audit_text_sources.py` OK (every new include resolves in-repo); the fleet build wall time vs T0's baseline (the 8.8 MB
header no longer preprocessed per TU); `share_census`: 2,215 registered classes intact, the 1,083 dead listed, the fleet function
count +219 (ov_SC03_015's correction — published at T8 as a corrected undercount, never buried).
**T5 — `tools/share_body.py` + the same-vram backlog (Max for the tool; xHigh for the runs; batched largest reach first).** Bucket 0:
`--extend` the 206 never-extended classes (3,996 instances). Then the same-vram buckets: 32+ members same-text (1,899 classes / 4,234
copies / 249,554 ins) → 32+ differing-text (168 / 760 / 55,820) → 16–31 (1,033 / 2,231) → 8–15 (986 / 2,106) → the tiny classes
(no trivial exception). Per batch: `share_body --apply` → `banked N/M classes, K copies collapsed; J gate rejects` → `check-all: 218
passed` → commit. Differing-text bodies: one chosen text per the rule; type-carrying bodies: `lift_types` first, retry; what still
fails is ledgered with its message class (budget ~2–5% of sites). **Cross-vram classes are not touched** (listed by T1; published at
T8 as CROSS-VRAM-DEFERRED with their count and copies). Verify at task end: `share_census --check` → same-vram unregistered copies 0
(or = the ledger's count, each with a reason).
**T6 — Consumers, second half: freeze / retire / the guard (xHigh).** Drop macro-form support from the T2 set; FREEZE the 7
matching-era parsers (`family_sweep`, `gen_harvest_targets`, `p16_improve`, `recover_giant`, `restore_dropped_decls`,
`normalize_self_decls`, `o0_subsplit`) with one shared refusal in `main()` — never at import (`cdecl.audit_differential` imports
`gen_harvest_targets`; an import-time exit would take the health chain down) — dictionary status `FROZEN`, successor named; RETIRE
the 3 direct predecessors to `tools/sunset/` (`dedup_propagate`, `dedup_extend` → `share_body.py`; `macro_draft` → product: the
headers) after repointing their 9 callers (`gate_stage.py:522`, `bulk_harvest.py:267`, `auto_driver.py:140`, `lora_grind.py:230`,
`gate_lane.py:86`, `grinder.py:351`, the 3 importers); the guard in `tool_census.py --check`: every LIVE, non-FROZEN tool parsed
with `ast` — no non-docstring string constant contains `DEFINE_func_` or `engine_core.h` (prose mentions in comments/docstrings are
historical record and survive; 4,570 `DEFINE_func_` mentions in `src/` comments are deliberately not rewritten). Negative control
(R39): on the pre-T4 tree the guard flags exactly the 22 parsers and none of the comment-only tools; flipping one row to FROZEN
un-flags it. `make kit-corpus`, SETUP rows (R21). Verify: `tool_census --check` → `macro-form guard: 0 LIVE tools reference the
retired form (7 frozen, 3 retired)`; `make tools-health` OK.
**T7 — Wire the invariants + regenerate every published number (xHigh).** In `make tools-health` after `report BINARY=main`:
`share_census.py --check` (strict) + `--selftest`; `dedup_integrate --check` C2c/C2d. `progress.py`: two generated fields
(`unique_function_bodies`, `duplicate_source_copies`) in `docs/progress.json` and the README block (:1146-1166) sourced from the
census; `progress.py --readme --check`, `timeline.py`, `make audit-digest` (the +219 correction stated with its cause). Negative-
control the new gate: in a worktree revert one member's site to an inline copy → `share_census --check` exits 1 naming that class.
Verify: `make tools-health` OK; `progress.py --readme --check` fresh.
**T8 — The record (xHigh).** Wiki: `The-dedup-engine.md` rewritten as the shared-source model (headers, twins, the census, the
sotn fact as read), `Repository-layout.md`, `Where-the-project-goes-next.md`, `Verification-and-progress.md`; how-to ch.10 (the
Gen3 outcome; history kept); README:117 prose; `gen3-standards.md` §4 row + DoD line and `gen3-handoff.md` §2.2/§3 as dated
snapshots with the derivation; a cookbook section replacing §14/§38's macro narrative; `docs/decision-log.md` P35 (R31: the
2026-09-02 reversal with the sotn evidence, the 5,147-vs-3,516 miscount, the twin discovery, the +219 undercount);
`docs/accelerators.md`; DIGEST §4; `tools/sunset/README.md` rows; the memory `dedup-backlog-leave-it` rewritten; `doc_links --strict`,
`wiki_render --selftest`, `cookbook_index --check`, `kit_coverage`. Verify: doc_links 0 broken / 0 pending; the checks green.
**T9 — Close (Max, Tier 1).** R22 clean fleet run → 218/218; `make tools-health` OK (S1 strict + the guard); the metrics table
before/after (macro lines 5,147 → 0; `engine_core.h` 8.8 MB → deleted; same-vram duplicate copies → 0 + ledger N; twins 5 pairs /
10 aliases; cross-vram classes deferred N / copies M; registry groups 2,220 → ~6,500; fleet build wall time; the +219); the
reviewer sequence documented; `PhaseEnd_Phase35.md` + DIGEST §0/§2/§3 + the log archived (R19), left for Drew's close commit; v2.1.0.
Rules check (P6) after T3 and T7.
**Effort:** Max for T0's probe, T1, T2's design, T3's probe pair, T4's and T5's tool, T9; xHigh elsewhere; **no Ultracode** (the
parallelism is machine parallelism: L0 at JOBS=16 ≈ 13 min fleet-wide; the clean fleet run 3–5 min). **Sessions (R41, summed from the
task sizes above): T0–T1 ≈ 1 · T2 ≈ 1 · T3 ≈ 1 · T4 ≈ 1 · T5 ≈ 1–2 · T6–T7 ≈ 1 · T8–T9 ≈ 1 → ≈ 7–8 sessions;** the tail is the
differing-text and type-carrying bodies and the consumer edits, not the gates.
## Which gate proves which step
| step | gate | the line that banks it |
|---|---|---|
| one TU converted/shared | L0 object A/B (Probe P1) | `obj A/B: N/N byte-identical` |
| one binary | `make check BINARY=<b>` | `[ OK ] build/<b>/<b> … (BYTE-IDENTICAL)` |
| a batch / the phase | `make clean && make extract-all JOBS=16 && make check-all JOBS=16` (R22) | `check-all: 218 passed, 0 failed of 218` |
| the registry is honest | `tools/dedup_integrate.py --check` | `dedup-check: N validated, 0 failed` |
| the invariant holds | `tools/share_census.py --check` | `S1: one source per unique function — …, 0 unregistered, N excepted` |
| no live tool assumes the macro form | `tools/tool_census.py --check` | `macro-form guard: 0 LIVE tools reference the retired form` |
| everything | `make tools-health` | `tools-health: OK — …` |
## Milestone (gate 2 — what Drew confirms, each with its literal output)
1. `git grep -c '^#define DEFINE_func_' -- src` empty; no `DEFINE_func_X()` site; `src/shared/engine_core.h` absent; every registered
body a plain-C header under `src/shared/<space>/`.
2. `tools/share_census.py --check` exit 0: same-vram duplicate copies 0 (or each ledgered with a reason); the five twins built from one
source directory each; cross-vram classes published as a deferred count.
3. `make tools-health` OK with S1 strict and the macro-form guard; every consumer updated, frozen or retired per its dictionary row.
4. `make clean && make extract-all && make check-all` → `218 passed, 0 failed of 218` (R22).
5. README/wiki/kit regenerated (R75), decision log (R31), SETUP rows (R21), PhaseEnd + DIGEST written.
## Verification (the reviewer's sequence from a fresh clone, documented at T9)
```
make bootstrap
make clean && make extract-all JOBS=16 && make check-all JOBS=16 # 218 passed, 0 failed of 218
make tools-health # tools-health: OK — …
tools/share_census.py --selftest && tools/share_census.py --check # S1 … 0 unregistered, N excepted
tools/tool_census.py --check # macro-form guard: 0 LIVE …
tools/progress.py --readme --check # docs/progress.json + README block are fresh
git grep -c '^#define DEFINE_func_' -- src # (no output)
```
## Risks — settled by probe, or carried with its probe
Settled: include-vs-macro byte identity (P1, `.o`-level); the 4 divergent twins (last wins, uniform: copy 1 < 99,164 < copy 2);
`__LINE__`/`__FILE__` absent and no `-g`; the prelude's completeness (the 5,147 + 8 directive inventory); header-name collisions
(the stable suffix rule); `ld_interleave` never reads C. Carried: `.d`/file-count growth (measured at T0; fallback a `vram>>12` fan-out,
mechanical since paths are generated); a backlog site's TU rejecting the exemplar text (L0 localizes; ledgered); the registry text
edits (never `yaml.safe_dump`); the health-chain crash on deletion (T2 lands before T4); pins spread by a share (the exemplar rule
prefers pin-free; PINNED is a ledger class, never a spread).
## Rules at gate 1
**R96** (a) a scratch prune is an instrument change — re-run every tool that writes under it before calling the prune done.
**R97** (b) "green" is read from a check's EXIT CODE, never its last line; every chain sets `pipefail`.
**R98** (c) a step that hands a file to a third party is proven through that party's own toolchain on the file itself before the
owner's browser session, and the proving tool writes the paste.
**R99** (d) what a public tree carries is decided before the flip; a retired document gets its Archive-index row and is deleted in the
same commit — history keeps it.
Candidate for the PhaseEnd: "a shared body has exactly one source; a duplicate copy is a defect the health chain asserts, and a
count of them is published with its rule" (this phase's invariant).
**Candidates from S95 (the recovery):** (b) "every commit that advances a task — an intra-task bank included — carries its log line and
the 🛑 headline; a checkpoint older than the last commit is a dead session's checkpoint" (S94 refreshed the block at every TASK close but
made two T5 bank commits with no log line, then filled its context during a background wait; the successor rebuilt 35 minutes of state
from the transcript). (c) "a tool that restores files never uses `git checkout` on a tree it did not commit — it restores from its own
snapshot" (share_body's bisect wiped the previous batch's uncommitted shares; R42 for tools). (d) "a failure-cause extractor is
negative-controlled against the compiler's real message forms, not against the word error" (gcc 2.7.2 prints errors without it;
254 of 303 rejection lines read `Error 33`).
## 🛑 SESSION CHECKPOINT — S96 close (2026-09-08): Phase 35 COMPLETE — T0–T9 ☑ (+ T5b); PhaseEnd_Phase35.md written, this log archived; NEXT = Drew's close commit, then Phase 36 (pins) in a fresh session at Max, plan mode
### 0. How to use this block
A fresh session (S97) reads CLAUDE.md's load order, replays THIS block verbatim, asks Drew for `/effort max` (T9 is Tier 1: the PhaseEnd
synthesis), and executes §2. Every task's literal verify lines are in the log above (append-only, one entry per step); the numbers below
are copied from those entries — re-derive any you publish (R75). The tree is CLEAN at **HEAD `ce837b1e4`** ("T8 — the record"); Drew
pushes (R6). The context guard (`~/.claude/ctx_guard.sh`, PostToolUse, user settings) fired at 90 % and this block was written at once.
### 1. Where things stand
- **Done, committed, gated:** T0 open/probe · T1 `share_census` · T2 the health chain learns the include + twin forms · T3 five twin pairs
→ one source dir · T4 every macro body a header, `engine_core.h` deleted · T5 `share_body` (bucket 0: 183 classes → 135 shared / 48
ledgered; bucket new: 915 → 914 shared / 3 ledgered; the E_func_80168B70 re-exemplar) · T5b the `h_text` tier (38 classes / 2,030
sites) · T6 14 tools FROZEN, 4 retired, the macro-form guard · T7 S1 strict + C2c/C2d in tools-health, the digest fields and
corrections · T8 the record. Sessions: S94 (T0–T4 and the start of T5; died at 91 % without a checkpoint), S95 (the recovery, no runs),
S96 (T5 → T8). Commits since the open `48170fd7f`: `git log --oneline 48170fd7f..HEAD` (≈50).
- **The last gates on this tree:** R22 `check-all: 218 passed, 0 failed of 218` from clean at `ac8c29edb` (`.run/P35/baseline/r22_t5b.log`,
90 s; T6–T8 after it changed tools and docs only — T9 re-runs it anyway); `make tools-health: OK` at `ac8c29edb`
(`tools_health_t5b.log`, 439 s) with `S1: one source per unique function — 10,180 classes, 10,180 satisfied (3,507 twin-covered, 51
excepted, 3,801 deferred cross-address), 0 VIOLATION(S) — OK`, `dedup-check: 3173 validated, 0 failed | C1 coverage 262573/262573`,
`macro-form guard: 0 LIVE tools reference the retired form (217 scanned, 14 frozen, 6 whitelisted detectors, 38 retired)`; after T8,
`doc_links` strict 0 broken, `wiki_render` 32/32, cookbook index 1,170 sections OK, `kit_coverage: OK`, `tool_census --check: OK`.
- **Open by design (recorded, not owed):** 51 ledgered classes / 160 copies in `config/dedup_exceptions.tsv` (50 TU-CONFLICT — the late
overlays' declaration conflicts, the types phase's; 1 GATE-REJECT `E_func_801376E8`); 3,801 cross-address classes deferred to the names
phase (E flag 3,826), including 381 same-address tiny-body texts / 1,668 copies inside them (the parameterized `SHARED_FN` form is
their shape). The Ghidra MCP: launched by the hook, unused all phase — stop it via `tools/ghidra_mcp_stop.sh` before the close commit
(R23). Disk ≈ 30 GB free.
### 2. T9 — the close (Max; one session; leave the deliverables UNCOMMITTED for Drew's milestone-close commit, R6)
1. `/effort max` confirmed. `make clean && make extract-all JOBS=16 && make check-all JOBS=16` → `check-all: 218 passed, 0 failed of 218`
by exit code (`.run/P35/baseline/r22_t9.log`); `make tools-health` in the FOREGROUND (≤ 600 s; ~440–550 s) → `tools-health: OK` with the
S1 line, dedup-check 3173/0, the guard line (`tools_health_t9.log`).
2. **The metrics table (before → after)** for the PhaseEnd, each cell from its command: macro define lines 5,147 → 0 (`git grep -c
'^#define DEFINE_func_' -- src` empty; `git grep -cP '^\s*DEFINE_func_' -- src` empty); `src/shared/engine_core.h` 8.8 MB / 227,730 lines
→ deleted; shared headers 2,215 (T4) → 3,175 (`find src/shared -name 'func_*.h' | wc -l`); registry groups 2,220 → 3,173 (38 `h_text`),
instances 255,708 → 262,573; same-address backlog 1,099 classes / 4,755 private copies / 3,658 collapsible → 51 / 160 / 109, all
ledgered; S1 10,180 / 10,180, 0 violations; twins 5 pairs / 10 aliases / 166 files deleted; cross-address deferred 3,801 classes;
`unique_function_bodies` 103,015; the fleet denominator 363,221 → 363,680 (+459, cause in the T6 part-2 entry) and REAL 360,744 →
350,533 (empty-bodied shared functions now EMPTY); instruction-weighted metrics unchanged (13,492,113 / 5,820,205 / 45,150); fleet
clean-run wall 157 s (T0) → 84–90 s; 14 tools FROZEN, 4 retired; `docs/progress.json` `corrections` (3 entries).
3. **The milestone (gate 2), item by item with literal output:** (1) `git grep -c '^#define DEFINE_func_' -- src` empty, no
`DEFINE_func_X()` site, `engine_core.h` absent, every registered body a header under `src/shared/<space>/` (`dedup_integrate --check`
C2c); (2) `tools/share_census.py --check --strict-macros --strict-text` exit 0 — same-vram copies 0 or ledgered, the five twins from one
directory (`make audit-binaries` CHECK 3b), the cross-vram count published; (3) `make tools-health` OK with S1 strict and the guard;
every consumer updated/frozen/retired per its dictionary row (`config/tool_dictionary.tsv`); (4) R22 218/218; (5) README/wiki/kit
regenerated (T7/T8), decision log (T8), SETUP rows (T1–T7), PhaseEnd + DIGEST written (this task); v2.1.0.
4. **The reviewer sequence** (document it in the PhaseEnd's Build Log): `make bootstrap` · `make clean && make extract-all JOBS=16 && make
check-all JOBS=16` · `make tools-health` · `tools/share_census.py --selftest && tools/share_census.py --check --strict-macros
--strict-text` · `tools/tool_census.py --check` · `tools/progress.py --readme --check` · `git grep -c '^#define DEFINE_func_' -- src`.
5. **Write `phase-ends/PhaseEnd_Phase35.md`** (the format in PROJECT_CONTEXT.md; the P33/P34 files are the exemplars; R25 plain-English
recap; "what we believed / what failed / what we'd do sooner"; the sessions' cost in agents: none — no subagents this phase; the
S94 death and the S95 recovery as a Deviation; the plan's "7 frozen" → 14 and "3 retired" → 4; T5b added; the E_func_80168B70 and
the 38-text decisions delegated to Claude at Max). **Rule candidates for Phase 36 gate 1 (P10), from §"Rules at gate 1" + the log:**
(a) a shared body has exactly one source; a duplicate copy is a defect the health chain asserts and its count is published with its
rule (the phase's invariant); (b) every commit that advances a task carries its log line and the 🛑 headline; a checkpoint older than
HEAD is a dead session's checkpoint; (c) a tool that restores files restores from its own snapshot, never `git checkout` on a tree it
did not commit; (d) a failure-cause extractor is negative-controlled against the compiler's real message forms, not the word "error";
(e) build the disagreeing oracle (R34) BEFORE the batch runs and measure its disagreement before believing either side — the text
oracle found in one run what the byte join could never class; (f) a registry lists a member only after the gate has spoken for it
(never ahead of the source); (g) a policy taken on a remembered precedent is a belief — read the target project's tree.
6. **DIGEST:** append the P35 synopsis to §2, the ratified R96–R99 are already in §3 (T0), refresh §0's "where the project stands" (Phase 35
closed, v2.1.0, Gen3's next = Phase 36 pins per Drew's order dedup → pins → structs → names), §4 already has the P35 map (T8).
7. `git mv phase-ends/CURRENT_PHASE.md phase-ends/logs/Phase35.md` (R19); `make kit-corpus` so the kit's record copies (the DIGEST, the new
PhaseEnd, the log) stay equal; leave PhaseEnd + DIGEST + the moved log + the kit corpus UNCOMMITTED with the commit message written in
the PhaseEnd; stop the Ghidra MCP; final message per P8; HARD STOP — no Phase 36 preview.
### 3. Numbers to re-derive, never trust
Every figure in §2 step 2 has its command there; the S1 line and the registry counts come from `share_census --check` /
`dedup_integrate --check`; the digest from `tools/progress.py --fleet`; the header count from `find`; the frozen/retired counts from
`config/tool_dictionary.tsv` (`grep -cP '\tFROZEN\t'`, the sunset README rows).
### 4. Gotchas that cost something this phase (the log has each with its verify line)
1. `share_body.py` runs on a COMMITTED tree, one batch per invocation; its bisect restores from an in-memory snapshot (never `git checkout`).
2. gcc 2.7.2 error lines carry no "error" token; `Error 33` is cc1's fatal exit — read the `file:line: message` form.
3. A twin's instance resolves to its primary's TU — dedupe edits by (tu, line); the census counts distinct TUs.
4. `add_members_surgical` refuses the verbose `members:` form; `--repair-registry` converts and completes; matches on (binary, vram).
5. `make kit-corpus` belongs in every commit that edits a tool, the DIGEST, a PhaseEnd, the cookbook or the how-to; the kit's lint refuses
rule ids (`R42`) and project names in authored kit text.
6. The timeline's last row follows the COMMITTED digest: commit `docs/progress.json` first, regenerate the timeline in the next commit.
7. `make tools-health` in the foreground (~440–550 s); `share_census` cache is keyed by mtime/size — `--no-cache` after a scanner edit.
8. A `grep` guard in a `&&` chain exits 1 on zero matches (twice this session); `;` before `git commit` let a partial commit through once (R97).