86 KiB
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 touchessrc/— the clean fleet runmake 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 insrc/st/<name>.h, instantiated per stage by a.cstub that#includes it; it does not write duplicates.
Milestone (gate 2 — what Drew confirms, each with its literal output)
git grep -c '^#define DEFINE_func_' -- srcempty; noDEFINE_func_X()site;src/shared/engine_core.habsent; every registered body a plain-C header undersrc/shared/<space>/.tools/share_census.py --checkexit 0: same-vram duplicate copies 0 (or each ledgered with a reason inconfig/dedup_exceptions.tsv); the five twin overlays built from one source directory each; cross-vram classes published as a deferred count.make tools-healthOK with the S1 invariant strict and the macro-form guard; every consumer updated, frozen or retired per its dictionary row.make clean && make extract-all && make check-all→218 passed, 0 failed of 218(R22).- 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 maxat 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 touchsrc/).
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 cleancheck-all: 218 passed, 0 failed of 218in 157 s wall;make tools-healthOK (exit 0, 474 s — after two red runs fixed at their cause: the kit's record copies regenerated bymake kit-corpus, and R96–R99 dispositioned inconfig/kit_coverage_map.tsv); the probe on ov_SC06_033: 34/34 objects byte-identical in the include form, the binaryBYTE-IDENTICALboth 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),.d11.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_defsthe 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 --checkfresh · 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 bymake 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 undersrc/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.hdeleted;--verify OK; R22check-all: 218 passed, 0 failed of 218in 145 s. - ☐ T5 — IN PROGRESS (S94; state recovered by S95 from the transcript — see the log and the 🛑 block).
tools/share_body.pybuilt + probed (commit0bccef901); 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 → bucketnew(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 --checksame-vram unregistered = 0 or ledgered. - ☐ T6 — consumers: freeze 7 (main()-time refusal, FROZEN rows), retire 3 to
tools/sunset/, theastmacro-form guard intool_census --check(negative-controlled),make kit-corpus, SETUP rows. - ☐ T7 — S1 strict +
--selftest+ C2c/C2d inmake tools-health;progress.pyfields + README block; the +219 correction stated; the gate negative-controlled in a worktree. - ☐ T8 — 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 — 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.
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/timewall 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 bygit 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 themDEFINE_func_) ·sha1 1e91d46e… == config/check.ov_SC06_033.sha (BYTE-IDENTICAL)both ways ·obj A/B: 34/34 byte-identical, 0 differing·.dbytes 11,632 → 182,252 (1,813 header dependencies; harmless) · restore verified (git statusshows 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/.svgregenerated 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_MATCHINGhalves 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 throughdedup_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-linevoid f(a, b) void *a; s16 b; {form); a second definition head on the same line after anextern …;; the alias regex spanning a previous macro site's parentheses;asm(as well as__asm__(; implicit-return-type heads with no type line;static inlinehelpers (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
1dbffee87says "kit corpus regenerated" whilemake kit-corpushad exited 2 andtool_census --checkhad 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 in72a77e7d3(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_OFin the Makefile andconfig/*.mk;corpus.src_filesando0_subseggo through it;compile_only.src_dirsdelegates to it;progress.set_binarytakes its dir from it (the table'ssrcis only a cross-check);dedup_integrate._src_pathsusescorpus.src_files. The include form:share_census.header_defs()is the one reader ([(name, empty)], masked — a macro-only header defines nothing);progress.classifycounts an include site as the function's definition (REAL/EMPTY by the header body) with the#define SHARED_FN/#undefstate 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_splitgains theincludeitem kind (anchor by the header's defined name,#undef SHARED_FNtravels with the item;split_headerno longer swallows a leading body include;macro_externs/macro_protoread the header's parts; a macro invocation with no table entry is a loud refusal, R43) andjr_isolate_alladmits the kind;dedup_integrateC2a′ requires a plain-C source to DEFINEfunc;audit_binariesacceptsengine_prelude.h, resolves the main TU through the oracle, and gains CHECK 3b (twin citizenship: primary onboarded and not a twin, same src dir, nosrc/<twin>/, equal contracts, equal carves);cdecl.audit_differentialreports "not applicable" when the macro header is gone instead of crashing;lint_symbol_refs/family_remap/fix_arity_callersglobsrc/shared/**/*.h;blocker_probe.macro_scopeanddemacroize.macro_bodiestolerate the header's absence;shared_lockdocstring;harvest_verifycomment. Verify:py_compile16 tools OK;share_census --selftest7/7;audit-binaries: OK;dedup-check: 2220 validated, 0 failed; the split tool onsrc/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.cincludes 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 bymake 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 --check17 gaps) —make kit-corpusbelongs 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). Commits6cb056c93(the tools + the +7 numbers),4ec752e68(the timeline),24e8ff72d(the close). T2 ☑. - S94 — T3 probe pair SC01_005/006 (Max). The mechanism:
config/overlays.mkov_SC01_006_TWIN_OF := ov_SC01_005+ov_SC01_006_SRC_DIR := src/ov_SC01_005; the Makefile's twin block (static pattern rulesbuild/src/<twin>/<twin>%.o ← src/<primary>/<primary>%.c+ the bare-name rule, the same recipe;TWIN_O0_OBJSkeep-O0; the twin's ownOBJS/C_DEPS);config/splat.ov_SC01_006.yamlcreate_c_files: False(src_path stays the twin's name → the .ld's 120 object paths underbuild/src/ov_SC01_006/);git rm -r src/ov_SC01_006(30 files). Verify:make extract BINARY=ov_SC01_006exit 0, nosrc/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 checkfor 005 and 006 run CONCURRENTLY → both BYTE-IDENTICAL; consumers:progress.py --binary ov_SC01_0062503/2503 (of which dedup-shared 1838, unchanged),dedup-check --binary ov_SC01_0061838 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 fromsrc/ov_SC01_005/(2 at -O0). One instrument slip caught by the census's R32 gate: my Makefile variableTWIN_SRC_DIRmatched the_SRC_DIRoracle regex as a binary namedTWIN→ renamedTWIN_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'scopiesnow counts DISTINCT private sites (a twin's instance resolves to its primary's TU — one source),collapsible= copies − 1. Commit9b0816e74. - S94 — T3 pairs 2–3 (equal carves, mechanical):
ov_SC03_119TWIN_OFov_SC03_118(31 files removed; both77ee78dd…BYTE-IDENTICAL),ov_SC02_003TWIN_OFov_SC02_000(37 files; both5ece4bca…);audit-binaries: OK; dedup-check per twin 0 failed. Commite79cfcc06. - S94 — T3 pairs 4–5 (carve alignment, R60): the twin's yaml := the primary's with the alias and
FILE_nnnsubstituted (+ 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 assemblyonov_SC04_019_jr_8017AE2C.o): the per-objectJTBL_PADSlines 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.pybuiltsrc/<b>/<tu>.cby 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_018BYTE-IDENTICAL (fe9b413f…),ov_SC03_015+ov_SC03_014(d84b01a2…);interleave_checkALIGNED (n=51, n=46);pads_auditok on every carve object; dedup-check 0 failed;audit-binaries: OK(CHECK 3b incl. equal carves). Commits2648aa5a9,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-corpusforpads_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.--planon 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}.--applyover all 213 non-twin binaries (.run/P35/convert/batch2.sh: snapshot objects → one apply → per binarymake 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 (commitc53a9e1a9, 6,036 files).--finalize: the three legacy sites (SETTER/RETCONST×3 in SC01_005 →ov/func_8012AD64.hetc.;CLEAR_TBL40×2 insrc/800_c.c→ the#define SHARED_FN/#include "shared/main/func_80037004.h"/#undefform — the cross-address control), the whale moved toov/func_80144B9C.h(guard removed) with every-O0includer 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,224source:/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.hdeleted;--verify: OK — 0 macro sites, no macro header, the prelude present. Gates: main143dbb89…, 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 census362,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 issrc/, a prefix test matched the fleet) — restored withgit 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-filematch_one-onlyincludes with trailing comments and (4) seven whale includers in_o0c.c/_o0d.c(a*_o0b.cglob) were missed — the census's coverage line (7 unaccounted) and--verifyfound them; (5)verifylisted 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.mdP35 T3–T4 section + the dictionary row (P10, PROJECT-ONLY). T4 ☑. - S94 — T5
tools/share_body.py(Max) + the R37 probe (commit0bccef901, 18:12 local). Built as designed (411 lines):census(jobs)=share_census.classify(…, keep_instances=True)(the census now exports per-instanceline/end/nlinesand the class'sgroups; a census with unaccounted or multi-form instances is a refusal, R32);candidates()→extend= verdict-B classes (registered, some instance still a privatedef),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 definefuncor the class is refused; the extension list = instances not yet listed; new: headerfunc_<VRAM>[__h8].hviamacro_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> -j16by 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 — neveryaml.safe_dump),dedup_extend.add_members_surgicalfor extensions;batch_<label>.jsonper batch (classes,refused,resultsper binary,registered,extended,rejected,exemplars).--planon 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 inshare_census.classify. The probe (R37): classc1c085b28d16c2417f2d9ce46553d16fe45b4508=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; headersrc/shared/ov/func_801681FC__c1c085b2.h(suffixed: the vram hosts >1 class fleet-wide), groupS_func_801681FC, ledger rows 0.bind_alias_headerfactored out ofmacro_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 102binaries: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 containingerror/conflicting— the fleet-wide benignwarning: 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_surgicalextended 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'sapply_selected()restores the TU and applies the selected classes in ONE bottom-up pass from the original text; rejected extend classes ledgeredTU-CONFLICTwith 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-binarymake 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): 38src/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 (allTU-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 146check_*.log(each endsBYTE-IDENTICAL— the final gate of each binary), untrackedrun_extend2.logand.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:
- 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>.logreads[ 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). - 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'sbinaries:lines).E_func_80168B70alone: 7 members before the first pass (the 7 late overlays), 141 after, 134 of them private copies.share_census --checkwill 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. - The cause extractor still misses gcc-2.7.2's error lines (
tools/share_body.py:204-206requires\berror\b|Error\b|undefined reference|already defined|multiple definition|parse error|\[FAIL\]): cc1 prints errors asfile:line: messagewith NO "error" token (too many arguments to function,conflicting types for,redeclaration of, …), so 254 of the 303 rejection lines read onlymake: *** [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 offunc_80168B70in 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 THEIRextern void func_80146C3C(u8 *a0)); the 134 main overlays declareextern void func_80146C3C(void)and their private copies call((void (*)(s32))func_80146C3C)(a0)— same bytes (h_exact93d5fccdcc…), incompatible source environments. The header carries the MINORITY spelling because the group was created for the 7. (The first pass'ssymbol 'func_80168B70' is already definedfor 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'. - A cross-batch edit-loss defect (R42 class — a gate destroying banks):
restore()(share_body.py:221-223) isgit checkout -- <tus>, andBatch.apply_editsuses 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 bisectgit 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.clost extend1's six kept sitesfunc_80149374, func_801493D0, func_80149450, func_801494CC, func_80149544, func_8014964C(present in no diff); ov_MAIN_012 / ov_SC02_037 / ov_SC03_107 lostfunc_80146128, func_80146FC4, func_80153800, func_801539F8the 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 beforeapply_editsand restore from the snapshot; re-derive the edit positions per batch (re-census, ~30 s) or run ONE batch per invocation and commit between. - 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_80168B70case; (b) suspected census definition-range defects —parse error before 'if'at<b>_jr_8015C32C.c:3388-3390(d743c21bd9=E_func_8016163C) andbefore '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_00xfor7529ad8f17=E_func_801376E8(an alias-form header,aF801376E8) in SC07_006/007/010/011; (e)c9866fcb86=E_func_8012F49Cin 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. - T6 recon the session had done (read-only, reproduced by S95): 30 live-tree tools carry a non-docstring string constant naming
DEFINE_func_orengine_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_sweepmain:878/1060,gen_harvest_targets154/294,p16_improve68/136,recover_giant71/81,restore_dropped_decls73/129,normalize_self_decls193/210,o0_subsplit64/197; dictionary status today: all LIVE except restore_dropped_decls + macro_draft STILL-NEEDED. Nothing else changed on disk in S95 except this file.
- 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
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:
- Shared bodies as macros. The dedup engine instantiates one body in every location overlay as a
DEFINE_func_XXXXXXXX()macro fromsrc/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. - 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.yamlviadedup_integrate.group_members): 2,220 groups / 255,708 members, allh_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'sclearTbl40pair). Sources:engine_core.h2,215 ·ov_setters.h3 (name-parameterized SETTER/RETCONST, the SC01_005≡006 pair) ·clearTbl40.h1 ·func_80144B9C.h1 (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_SHBblock 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 carryexternpreambles, 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 lineDEFINE_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 itsexterns 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.ofiles (1,632 B, identical relocations); cc1 adds only a.filedirective thatasconsumes. A separate.cobject 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.-MMDtracking (: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_typesuffixes, extern scoping, 9 with pins) → one chosen text re-gated per member; type-carrying bodies viatools/lift_types.pyfirst. - 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 intarget_path(+ a carve delta: SC04_019 has one extra_jr_80181804split, SC03_015 two extra);overlays.mkblocks 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-healthon the header's deletion:cdecl.audit_differential(tools/cdecl.py:1292-1323opensengine_core.hunconditionally;make audit-cdeclis in the chain) andoverlay_src_split(:667 degrades to a silently EMPTY macro table). 51 tools buildsrc/<binary>paths themselves; onlycompile_only.pyreads the Makefile's<alias>_SRC_DIR;corpus.py:312maps link objects tosrc/{binary}/{subseg}.c.progress.pyundercounts ov_SC03_015 by 219 (single-site macro sites invisible toclassify()) — 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 definingvoid 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.hdeleted;func_80144B9C.hmoved toov/, its guard removed (one shape). End state:src/shared/= the prelude,engine_types.h, the per-function tree. Nothing else. - Twin binaries:
config/overlays.mkgetsov_B_TWIN_OF := ov_Aandov_B_SRC_DIR := src/ov_A; the Makefile maps a twin's objects to its OWNbuild/src/ov_B/from the primary's sources (-O0object lists and.dpaths twin-aware;check-all's per-binary parallelism never races); the twin's yaml = the primary's carve with its owntarget_path/sha1; itscheck.shastill proves it;src/ov_B/deleted. One oracle "binary → source dir" (Makefile-derived, R33) replaces the hardcodedsrc/<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_membersoracle, shorthand kept;source:= the header path;func:= the defined token (SHARED_FNfor the parameterized form, asCLEAR_TBL40is today); optionalform:; text-edited only, neveryaml.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 --checkgains C2c (the source is undersrc/shared/, defines exactly one function, andfuncis the token it uses — viacdecl) 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); noDEFINE_func_token insrc/; and a second, sig-blind oracle: no name-blind normalized definition text appears in >1 file undersrc/outsidesrc/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)
git grep -c '^#define DEFINE_func_' -- srcempty; noDEFINE_func_X()site;src/shared/engine_core.habsent; every registered body a plain-C header undersrc/shared/<space>/.tools/share_census.py --checkexit 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.make tools-healthOK with S1 strict and the macro-form guard; every consumer updated, frozen or retired per its dictionary row.make clean && make extract-all && make check-all→218 passed, 0 failed of 218(R22).- 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 — S95 recovery (2026-09-08): Phase 35 OPEN at gate 1; T0–T4 ☑; T5 IN PROGRESS (share_body.py built; bucket 0 run twice — first pass committed, second run's 170 shares UNCOMMITTED on disk; 55 classes / 325 pairs ledgered, 317 of them still listed as registry members; three tool defects named); NEXT = inside T5: bank run 2 → fix the tool → repair the registry → replay the suspect rejections → decide E_func_80168B70 → bucket new
0. How to use this block
A fresh session (S96) reads CLAUDE.md's load order, replays THIS block verbatim, and resumes at §2 step 1. This block was written by S95, a
recovery session that ran nothing (Drew: capture only) — every claim in it is read from the S94 transcript, the two T5 commits, the batch
records under .run/P35/share/ and the working tree; the S94 log entries above carry the literal lines. Effort: T5's remaining tool work and
the two decisions in §2 are Max (the plan: T5's tool is Max; the mechanical batch runs xHigh) — ask Drew to /effort max at session
start. Autonomous between the gates (P3); stop only on P5's conditions — §2 step 6 is a P5(d)-shaped decision and is written as one.
Commit per task; a bank the moment it is green (R42); Drew pushes (R6). Read §4's first gotcha before running share_body.py at all.
1. Where things stand (S95, 2026-09-08 ~19:30 local)
- HEAD
571374b97= "T5 bucket 0 (first pass)" (18:30 local). Phase-35 commits so far, in order: T0 …, T11dbffee87/72a77e7d3, T26cb056c93/4ec752e68/24e8ff72d, T39b0816e74/e79cfcc06/2648aa5a9/6ebb3dac3/0e38b51a3, T4c53a9e1a9/a8457663f, T50bccef901(the tool + probe) and571374b97.origin/mainis behind by every Phase-35 commit (Drew pushes). - The tree is DIRTY with run 2's bank (uncommitted, bankable — S94 log entry "SECOND RUN" + S95 finding 1): 38
src/files in 8 overlays (170 private definitions → shared includes),config/dedup_exceptions.tsv(+55 TU-CONFLICT rows),.run/P35/share/ batch_extend1.json+batch_extend2.json(run 2's records), 139 modified.run/P35/share/check_*.log, untracked.run/P35/share/ run_extend2.logand.run/P35/baseline/kit_corpus_t5a.log. Every binary's finalcheck_<b>.logends(BYTE-IDENTICAL); the run exited 0.git diff --stat -- src= 38 files, +225/−3,057. Do not runshare_body.py, any gate,make check-allor anything that rewritessrc/before step 1 commits this (R42; and §4 gotcha 1 — the tool's bisectgit checkouts TUs). - Last clean fleet run: T4's,
.run/P35/baseline/r22_t4.log(18:04 local,check-all: 218 passed, 0 failed of 218, 145 s) — BEFORE any T5 edit. R22 is owed for T5 (both the committed first pass and run 2 were gated only by per-binary incrementalmake check+ object comparison).build/holds objects from those incremental gates, not from a clean run. - Bucket 0 state: started at 183 registered-incomplete classes (
share_body --plan, 18:09). First pass: 754 + 34 = 788 members added to the registry (wholesale, before the "only members that passed" fix); run 2 found 150 still incomplete, shared 170 sites (on disk) and rejected 55 classes in 325 (class, binary) pairs — all 55 ledgeredTU-CONFLICT. Of the 325 rejecting binaries, 317 are listed as members of the class's group inconfig/dedup.us.yaml(registry ahead of source). An estimated 50–63 further kept sites were wiped by the cross-batch defect (S95 finding 4) — they are NOT ledgered and will reappear as verdict-B classes in the next census. - Bucket
new(untouched): 915 unregistered same-vram classes / 3,567 private sites; by band 32+ 446 · 16–31 255 · 8–15 204 · 1–7 11; 27 differing-text (verdict D), 139 with pins, 6 alias-form. Cross-vram classes (E flag, 3,826 at the T4 census) are DEFERRED (plan). - Ghidra MCP: launched headless by the SessionStart hook each session (
.run/ghidra-mcp.log; S95's connection attempt failed at start-up and was not needed — no RE work this phase). Stop it via the sentinel (tools/ghidra_mcp_stop.sh/.run/mcp-stop.req) before the close commit (R23). Disk: 32 GB free of 73 GB;.run/≈ 21 GB. The S94 transcript:~/.claude/projects/-home-musashi-bfm-decomp/ e902c34e-e4b3-41a6-b1e4-7d2ef019a371.jsonl(6.4 MB; S94 ran 14:29–18:42 local). The dead session's replay residue (cc1.err, out.s, bak_074.c) is at/tmp/claude-1000/-home-musashi-bfm-decomp/e902c34e-e4b3-41a6-b1e4-7d2ef019a371/scratchpad/(volatile).
2. The resume sequence for T5 (in this order; each step's verify line quoted in the log; one commit per step)
- Bank run 2 (R42/R66; the tool's success lines are quoted in the S94 "SECOND RUN" entry). From the repository root:
git add -u src config/dedup_exceptions.tsv .run/P35/share && git add .run/P35/share/run_extend2.log .run/P35/baseline/kit_corpus_t5a.logthengit status --short | grep -v '^[MA] 'must print nothing else, then commit:src(phase-35): T5 bucket 0 (second run, finished after S94 died — banked by S96) — 170 private copies in 8 overlays replaced by the shared include (ov_SC07_006 49, ov_SC07_007 29, ov_SC07_011 29, ov_SC07_010 28, ov_SC01_077 19, ov_SC02_037 6, ov_MAIN_012 5, ov_SC03_107 5); 141/141 binaries green per binary (make check + object A/B); 55 classes rejected in 325 (class, binary) pairs, ledgered TU-CONFLICT in config/dedup_exceptions.tsv; run_extend2.log + the batch records. (If Drew prefers to see the fleet first:make clean && make extract-all JOBS=16 && make check-all JOBS=16→218 passed, 0 failed of 218, read by exit code — then commit. Either order is defensible; R42 says commit first.) - Fix
tools/share_body.py(Max; one commit;make kit-corpus+tool_census --checkin the same commit — the kit holds a verbatim copy): (a)gate()lines 201–207: the cause = the first stderr line of the form^\S+:\d+:that is notwarning:/note:/In function/At top level/In file included, elseundefined reference/multiple definition/{standard input}:.*Error/\[FAIL\], else the make line — gcc 2.7.2 errors carry no "error" token (S95 finding 3); negative-control on.run/P35/share/check_*.logfrom the two runs (R39: the memcpy warning must never be picked). (b) Cross-batch edit loss (finding 4): take an in-memory snapshot of every TU's text inrun_batchBEFOREb.apply_edits()and makerestore()write those snapshots back (nevergit checkout); AND make batch positions valid — simplest: one batch per invocation (--batch= the whole pool, or exit after the first batch) with the caller committing between runs, so every run's census sees committed text; the more general fix re-runscensus()before each batch (~30 s). (c) Ledger reasons: RANGE-DEFECT (parse error at/near the site), SYMBOL-NAME (undefined reference), GATE-REJECT ([FAIL]= bytes differ), TU-CONFLICT (cc1 prototype/type conflict) — the reason column inconfig/dedup_exceptions.tsvis documented at its head; add the new codes there. (d) The docstring's promise "the class is registered with the members that passed" is only true since the first-pass fix — keep it, and add the §4 gotcha 1 sentence to the docstring. Verify:py_compile; a--only <h> --bucket extendrun on ONE ledger-free class after step 3. - Repair the registry (Max decision, mechanical edit): remove the 317 listed-but-private members — derive them, never type them: for
each ledger row, the group id is in its note (
extend of <gid> rejected in [...]), the binaries are the list; the registry members are thebinaries: [...]line of that group (config/dedup.us.yaml, shorthand form). Edit by TEXT (neveryaml.safe_dump, H5); thentools/dedup_integrate.py --check→dedup-check: N validated, 0 failed, andtools/share_census.py --no-cache→ the 55 classes still verdict B + EXCEPTED,0 UNACCOUNTED. Alternative Drew may prefer: keep the members and let T7's C2d exempt ledgered classes — S95's recommendation is removal ("the registry never runs ahead of the source" is the rule the tool now enforces). Also drop the 55 ledger rows for any class you intend to retry in step 5/6 —candidates()(share_body.py:75) SKIPS excepted classes, so a ledgered class is never re-attempted while its row stands (use--only <h_exact>after deleting its row). - Replay the suspect rejections (xHigh; read-only until a fix is designed). The replay recipe (S94's, works): from the census's instance
record (
share_body.census(16)['classes']withkeep_instances, orshare_census.scan_text(text, rel, shared_defs={})on one TU) takeline/endof the private copy, replacelines[line-1:end]with the include line, thenmipsel-linux-gnu-cpp -lang-c -Iinclude -undef -fno-builtin -Dmips <tu> | tools/bin/gcc-2.7.2-psx/cc1 -quiet -O2 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker 2>cc1.errand READ cc1.err's non-warning lines (grep -vE 'warning:|In function|In file included|At top level'); restore the TU from a copy (cp, notgit checkout, if the tree is dirty). Targets:E_func_8016163C(d743c21bd9) atsrc/ov_SC01_005/ov_SC01_005_jr_8015C32C.c:3390(parse error before 'if'),E_func_80161774(3576d059c2) atsrc/ov_SC04_018/ov_SC04_018_jr_8015C32C.c:3438(before 'not'),E_func_80138C30(9bda7673e9) atsrc/ov_SC03_014/ov_SC03_014_jr_801380E0.c:1050; if the include landed inside or short of the definition, the defect isshare_census's range detection (end) — fix it there, re-census, retry those classes. The link-timeundefined referencecases (func_80161A90,func_8012A68C,func_80161B18,func_8012A598) needgrep -n <name> config/symbols.*for the rejecting binary — a callee named differently in that binary's symbol stack is a names-phase item; ledger it SYMBOL-NAME.E_func_801376E8(7529ad8f17, bytes differ in SC07_006/007/010/011): compare the private copy's text with the alias-form header andobjdump -dboth objects' function; ledger GATE-REJECT with the differing instruction. - Decide
E_func_80168B70(93d5fccdcc…; 134 of the 325 pairs — P5(d)-shaped, ask Drew with the recommendation first): the header's text is the 7 late overlays' spelling (func_80146C3C((u8 *)a0)against theirextern void func_80146C3C(u8 *a0)); 134 overlays declareextern void func_80146C3C(void)and call((void (*)(s32))func_80146C3C)(a0)— same bytes. Options: (A) re-exemplar the header from the MAJORITY private text (the 134's) and re-gate the 7 (they may then reject → 7 private copies instead of 134) — S95's recommendation, and the general rule for any B class whose header is a minority spelling:share_bodyshould treat a B class whose header text is rejected by most members as a re-exemplar candidate (a--reexemplarmode: the majoritydeftext becomes the header, EVERY member re-gated); (B) a header spelling compatible with both environments (a cast at the call already IS the 134's spelling; the 7 would need the same(void)extern — a TU edit outside the class, which the tool does not do); (C) leave 134 private copies ledgered (defeats the phase). The same policy question applies to every late-overlay conflict in 5(a): the 7 late overlays + ov_SC01_077 were matched by the P30/P31 waves with their own extern spellings (R95's story), and a header written from the 134's macro text conflicts with THEIR declarations — for those the right side is the header (majority) and the fix is the TU's declaration, which is the types phase's work; ledger them TU-CONFLICT with the message and move on. - Bucket
new(xHigh runs): after steps 2–5,tools/share_body.py --apply --bucket new --batch 120 --limit 120ONE batch per invocation (until the cross-batch fix is proven), read thegated N/N binaries green · registered G groups · … · rejected classes Rline, commit (src(phase-35): T5 new batch k — …), then R22 (make clean && make extract-all JOBS=16 && make check-all JOBS=16→check-all: 218 passed, 0 failed of 218, exit 0, log under.run/P35/baseline/r22_t5_<k>.log) after every batch or every few batches (3–5 min each). Bands in order: 32+ (446 classes) → 16–31 (255) → 8–15 (204) → 1–7 (11); differing-text classes (27) go through the majority-text exemplar and the gate; a class rejected everywhere is ledgered, never forced. Twin-covered and cross-vram classes are skipped bycandidates()— do not touch them. After the last batch:tools/share_census.py --no-cache --check→ S1's line with0 unregistered, N excepted;tools/dedup_integrate.py --check0 failed;make audit-binariesOK; then the T5 log entry with the before/after table (183 + 916 classes → registered / ledgered by reason) and T5 ☑. - Then T6 (the freeze/retire/guard; the 30-tool hit list in the S95 entry — whitelist share_census/macro_to_header/share_body, which name the retired form legitimately), T7, T8, T9 per the plan; rules check (P6) after T7.
3. Numbers to re-derive, never trust (their commands)
share_body --plan (the two buckets and the bands; ~30 s); share_census --no-cache → .run/P35/census/share_census.json (verdicts, S1,
same-vram backlog); the registry member count = grep -c of names in binaries: lines; the ledger = grep -vcE '^#|^h_exact' config/dedup_exceptions.tsv (55 now); the listed-but-private pairs = the S95 script's logic (ledger notes × registry lines; 317 now); the
lost-site estimate (50–63) is superseded by the next census's verdict-B list. T4-era figures: 2,215 headers (86 suffixed), 246,347 sites
converted, 1,300 dead macros dropped, fleet 218/218 in 145 s, census 362,389/362,389 with 0 macro sites.
4. Gotchas (each cost something)
share_body.py's bisect restores TUs withgit checkout --(line 223): running it on a tree with UNCOMMITTEDsrc/edits DESTROYS them for every bisected binary (S95 finding 4). Commit first, always; and fix the tool (step 2b) before any multi-batch run.- The tool's failure "detail" is not the cause until step 2a lands —
Error 33means "cc1 reported an error whose message has no 'error' word"; read the binary'scheck_<b>.log— but note the log holds only the LAST gate of that binary (the final green one), so the message is gone after the bisect; the replay recipe (step 4) recovers it. candidates()skips EXCEPTED classes: a ledger row is a permanent skip until deleted;--onlyneeds the row gone too.- The census cache (
.run/P35/census/cache/) is keyed by file mtime/size, not by the scanner's version —--no-cacheafter anyshare_census.pyedit.share_census --checkexits 1 on unaccounted instances;share_bodyrefuses to run on such a census (R32). make kit-corpusbelongs in EVERY commit that edits a tool or a PhaseEnd/DIGEST/how-to/cookbook file (tool_census --checkis a tools-health rung; it failed twice in S94 on exactly this);kit_coverageneeds aconfig/kit_coverage_map.tsvrow for any new rule.make tools-healthregenerates every sig first (~8 min; run in the FOREGROUND with a 600 s timeout, never as a harness background task); two red rungs in S94 were the kit's copies and the coverage map, both fixed at their cause.- The registry is text-edited only (
dedup_extend.add_members_surgical,share_body.registry_append); ayaml.safe_dumpdecimalizes every vram and deletes comments while every gate stays green (H5 precedent). - A twin alias (SC01_006, SC03_119, SC02_003, SC04_019, SC03_015) has no source dir: its TUs are its primary's;
share_bodygates twins with their primary and the bisect maps a twin's TUs to the primary's directory (run_batch, lines 270–273). - Transcript recovery method (used by S95): dump the session's
.jsonlto text (per record: user text, assistant text, tool_use name + input, tool_result truncated), grep forSESSION CHECKPOINTto find the last block written, read forward from there; the commits, the tool's own batch records and the tree are the ground truth the transcript is checked against (R14).