Files
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

112 KiB
Raw Permalink Blame History

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 #includes 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/, 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 checkouts 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, #included 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 externs 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).