- 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
112 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 (S94–S96) —
tools/share_body.py(+--repair-registry,--reexemplar,share_body_cycle.sh): bucket 0 183 classes → 135 shared / 48 ledgered; bucketnew915 → 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.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 (S96) — consumers: 14 macro-era tools FROZEN (
tools/frozen.py, main()-time refusal; the plan's 7 + 7 the guard census found), 4 retired totools/sunset/(dedup_propagate, dedup_extend → share_body.py; macro_draft; test_reconcile_ledger), 12 live tools' dead macro branches dropped, theastmacro-form guard intool_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) +--selftestinmake tools-health; C2c/C2d indedup_integrate --check;progress.pyfields (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-memberh_exactin the verbose member form),dedup_integrateC1 per member;share_censustext 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/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
-
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: 37src/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 finalcheck_<b>.logis the tool's success line, R66); R22 still owed for T5. -
S96 — T5 step 2:
tools/share_body.pyfixed (the three S95 defects):failure_cause()= the firstfile:line: message(orfile:(.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 fromtu_snapshot()(the pre-edit text of every touched TU, in memory) —git checkoutis gone;--batches(default- stops after one batch with a "COMMIT, then re-run" line; the per-class cause is kept (
causesin 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 redcc1.err(64 warning lines) the cause isfunc_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.
- stops after one batch with a "COMMIT, then re-run" line; the per-class cause is kept (
-
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 bydedup-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_80168B70back to its 7 late-overlay members.dedup-check: 2221 validated, 0 failed | C1 coverage 256120/256120; census362,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 controlA/141/missing 0..run/P35/share/repair_registry.jsonis 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-3426covers all offunc_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) orundefined 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_classdedupes 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 callsfunc_80139BE0(obj), and those TUs declare the callees32 (s32)— the header text allocates registers differently there (replayed:[FAIL], the one object differs insidefunc_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 chaineda.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-check0 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_80168B70from 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_exemplarover the private copies, the current header's text counted once per includer),bind_alias_headerif 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-check0 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 extendrun at the end. -
S96 — T5 bucket
new, batch 2 (run_new2.log; record copied tobatch_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
newbatch 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
newbatch 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
newbatch 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
newdone in 8 batches (share_body_cycle.sh3–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 VERBOSEmembers:groups, whichdedup_extend.add_members_surgicalskipped silently (nobinaries:line) — the inverse defect, the registry BEHIND the source. Fixes:add_members_surgicalrefuses a verbose group loudly (R43);--repair-registrygained 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 atlasruns 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 toshare_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_censusaccepts FROZEN. Verify: every touched file compiles;family_sweep.py --source x→FROZEN since Phase 35 … Successor: tools/share_body.pyrc 1;import share_body, family_remap, gen_harvest_targets, validate_targetsOK; no live tool names a retired file;tool_census --check: OKaftermake 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_reffinds 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.pyparsed withast; a non-docstring string constant carryingDEFINE_func_orengine_core.his 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 DIRruns 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 commit0e38b51a3→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-healthwent 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 BOTHrealandempty: the include rule classifies an empty header body as EMPTY whilereport()'s foldreal ∪= sharedre-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 excludesempty. 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 correcteddocs/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-healthred three times, each an instrument finding fixed at its cause, green on the fourth: (1) theprogress.pyfold (above); (2)doc_links:docs/wiki/The-dedup-engine.mdlinked the retiredtools/dedup_propagate.py— repointed toshare_body.pywith a note (T8 rewrites the page); (3)kit_lintLEAK: my dictionary row forshare_body_cycle.shcarried 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) withmacro-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: afteraudit-text-sources, beforereport BINARY=main, two rungs —share_census.py --selftest(7/7) andshare_census.py --check --strict-macros --strict-text --quiet(exit code, R97) — soprogress.pyreads a fresh census json.dedup_integrate --checkgained C2c (one source undersrc/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; adefsite = 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 datedcorrectionslist (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 (anh_texttier 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 classS_func_801681FC(2 members): ov_SC05_009's include reverted to the 19-line private copy →S1 … 10,179 satisfied … 1 VIOLATION(S) — FAILnamingB - 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 missingosimport (C2d's first run), the--checkpath 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) withS1: … 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.yamlgainstier: h_text(the group hash = the normalized text; verbose members each with their ownh_exact);dedup_integrateC1 checks an h_text member against ITS recorded byte hash (TIERS += h_text);share_censusindexes h_text groups by site (a byte class whose sites they list is registered by TEXT — noextra), exportstext_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 textshares them (headerfunc_<VRAM>__t<hash8>.h, bannerh_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 censusS1 … 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 oracle0 same-address definition texts duplicatedand381 … inside the deferred cross-address classes (1,668 sites); R22check-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) withS1 … 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.mdrewritten 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.mdP35 (R31: the reversal with the sotn evidence, the session death and recovery, the six decisions, the measurements, four hindsights);docs/accelerators.mdP35 (five); DIGEST §4 (the Phase-35 doc map); the memorydedup-backlog-leave-itrewritten 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) andcookbook-index OK·kit_coverage: OK· kit corpus +tool_census --check: OK. T8 ☑. (tools/sunset/README.mdrows 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-healthrun 1 (467 s) was green through S1 / dedup-check / the guard and red only attool_census --check— the kit's record copies ofDIGEST.mdand the newPhaseEnd_Phase35.md(both written while the chain ran);make kit-corpusregenerated 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.mdwritten (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) withS1 … 10,180 satisfied … 0 VIOLATION(S) — OK,dedup-check: 3173 validated, 0 failed,macro-form guard: 0 LIVE …. The log archived tophase-ends/logs/Phase35.md(R19);make kit-corpus+tool_census --checkafter 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:
- 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 — 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.hdeleted · T5share_body(bucket 0: 183 classes → 135 shared / 48 ledgered; bucket new: 915 → 914 shared / 3 ledgered; the E_func_80168B70 re-exemplar) · T5b theh_texttier (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 open48170fd7f:git log --oneline 48170fd7f..HEAD(≈50). - The last gates on this tree: R22
check-all: 218 passed, 0 failed of 218from clean atac8c29edb(.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: OKatac8c29edb(tools_health_t5b.log, 439 s) withS1: 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_linksstrict 0 broken,wiki_render32/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-REJECTE_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 parameterizedSHARED_FNform is their shape). The Ghidra MCP: launched by the hook, unused all phase — stop it viatools/ghidra_mcp_stop.shbefore 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)
/effort maxconfirmed.make clean && make extract-all JOBS=16 && make check-all JOBS=16→check-all: 218 passed, 0 failed of 218by exit code (.run/P35/baseline/r22_t9.log);make tools-healthin the FOREGROUND (≤ 600 s; ~440–550 s) →tools-health: OKwith the S1 line, dedup-check 3173/0, the guard line (tools_health_t9.log).- 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_' -- srcempty;git grep -cP '^\s*DEFINE_func_' -- srcempty);src/shared/engine_core.h8.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 (38h_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_bodies103,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.jsoncorrections(3 entries). - The milestone (gate 2), item by item with literal output: (1)
git grep -c '^#define DEFINE_func_' -- srcempty, noDEFINE_func_X()site,engine_core.habsent, every registered body a header undersrc/shared/<space>/(dedup_integrate --checkC2c); (2)tools/share_census.py --check --strict-macros --strict-textexit 0 — same-vram copies 0 or ledgered, the five twins from one directory (make audit-binariesCHECK 3b), the cross-vram count published; (3)make tools-healthOK 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. - 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. - 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, nevergit checkouton 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. - 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).
git mv phase-ends/CURRENT_PHASE.md phase-ends/logs/Phase35.md(R19);make kit-corpusso 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)
share_body.pyruns on a COMMITTED tree, one batch per invocation; its bisect restores from an in-memory snapshot (nevergit checkout).- gcc 2.7.2 error lines carry no "error" token;
Error 33is cc1's fatal exit — read thefile:line: messageform. - A twin's instance resolves to its primary's TU — dedupe edits by (tu, line); the census counts distinct TUs.
add_members_surgicalrefuses the verbosemembers:form;--repair-registryconverts and completes; matches on (binary, vram).make kit-corpusbelongs 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.- The timeline's last row follows the COMMITTED digest: commit
docs/progress.jsonfirst, regenerate the timeline in the next commit. make tools-healthin the foreground (~440–550 s);share_censuscache is keyed by mtime/size —--no-cacheafter a scanner edit.- A
grepguard in a&&chain exits 1 on zero matches (twice this session);;beforegit commitlet a partial commit through once (R97).