Files
BFM-decomp/phase-ends/current/PHASE_PLAN.md
T
2026-10-02 21:33:37 -06:00

40 KiB

Phase 3.39 — Casts to declared data (implements GENERATION_PLAN.md phase 3.39)

Milestone: 0 uncovered raw address casts in the 4 census forms: every raw P/I/X/M site is a member access, a declared symbol, a reinterpret macro backed by its KEPT row, or covered by a latest restruct ledger RESIDUAL row naming its cause (reading A, REPLAN 2026-10-02; the canonical-types campaign that removes TYPE-NOT-CANONICAL residue is 3.40's input, sized by that ledger); lever_census --check --strict --residue config/lever_residue.tsv 0 (reading A, 2026-10-01; literal --strict 0 moves to 3.40); lying-decl census 0 outside ledgered exceptions; R22 218/218 — verified by: PY tools/type_census.py --check-casts --residue exit 0 (T10's gate: uncovered raw P+I+X+M = 0, unbacked macros 0, types_floor_lying 0 outside config/lying_exceptions.tsv), PY tools/restruct.py --rung S --check-residue --only <seg> exit 0 for each of ov_*, md_*, main, src/shared/*, PY tools/lever_census.py --check --strict --residue config/lever_residue.tsv exit 0, and make clean && make extract-all JOBS=16 && make check-all JOBS=16 → check-all: 218 passed, 0 failed of 218, all at the closing commit Approved: 2026-10-02 Planner: claude-opus-5-5/medium Plan-hash: 6ca6c442da914fdccccf68d4512af29ae92e09cc772eb7a0ec98ce2f9c2ad84e

Context

  • Product: the matched C of Brave Fencer Musashi (218/218 binaries byte-identical). Gen 3 makes it legible; source may be rewritten freely, the object bytes are the gate (R22). This phase = legacy T6 "the cast campaign".
  • State at 3.38 close: 1,650 struct defs in src/shared/engine_types.h + include/struct_types.h (via include/common.h); type_census --check-structs 0; parse_error_decls 0; R22 218/218.
  • Raw casts (instrument tools/type_census.py, regexes :134-137): 503,016 at P37 T1 = P *(T *)(base+k) 409,007 + I *(T *)ident / &D_x / 0x80… 60,666 + X ((T *)e)[i] 13,800 + M M2C_FIELD( 19,543; 502,983 in 69,492 bodies at P38 close. Counted apart, not gated: A address-of (T *)(base+k) 18,912 (:138), C typed cast-member 36,681 (:139), ABS_ADDR :140. Struct map explains 99.2% of sites by base; legacy estimate of the floor: ~4% kept casts (S2) + up to ~24% the current layouts cannot hold (misaligned, pointer fields needing the pointee, sign-mismatch loads, non-struct bases). Per-form/per-binary split lives in .run/P37/census/* (regenerate in T1).
  • type_census --check (:1905-1918) already fails on any raw deref and any lying decl, but also on block-scope defs (629 block_frames, non-gating under the ratified 3.38 reading) — hence a new --check-casts gate (T2), not a re-reading of --check.
  • Only reinterpret macro today: M2C_FIELD(expr, type_ptr, offset) include/common.h:43, which IS form M (raw). Legacy also proved LOBU/LOH/HIH/LOHU/HIHU/LOW/LOWU byte-equal (PhaseEnd_Phase37:36-37); retriever found none of them in include/ — T2 confirms and decides.
  • Byte law behind kept casts (C0507 §458): in gcc 2.7.2 *(T*)(p+k) carries no MEM_IN_STRUCT_P; p->f, ((S*)p)->f, q[k] do (expr.c:4568-4577); sched.c:837-865 and the cse kill table differ, so cast → member IS a byte edit. Ladder: rung S1 = convert every cast on one base to member spelling (126/139 bodies IDENTICAL in the probe); S2 = greedy leave-one-out/revert in ≤ 2n+1 compiles, minimal kept set ledgered KEPT (27 of 716 kept). Recipes for globals: R1 pointer global as struct member, R2 extern T X[], R3 walked pointer (restruct --try … --recipes). A parameter cannot be retyped while its body still has *(T*)(a0+k) (pointer arithmetic scales): signature change is a per-callee S → T → D unit.
  • Oracle hazard: the whole-object oracle reports DIFFERS on a correct global-block edit (relocation spelling only); use the linked oracle (R110, delever_oracle --linked-control, SHA vs config/check.*.sha).
  • Header economics: a header is judged on every includer (~35 s per judgement at -j16, tools/restruct_cycle.sh:19); a canonical-header edit re-fans ~3,900 objects (~6 min full). Header edits are batched: dozens of field additions per fan-out, never one per site.
  • Levers (tools/lever_census.py): --check (:1188-1210) fails on any class A/B site (pins + asm statements) without a // !FAKE: marker (FAKE_MARK :83) and on orphan markers; --strict (:1190-1204) fails on ANY pin/asm site, any direct GTE statement in a body, any per-TU asm macro definition. 3,730 = pins 1,868 + asm 1,862 over 1,838 bodies (docs/levers.md:127-128); also GTE levers 450, per-TU asm macros 314; direct GTE statements 6,717 (decision-log:3843). P36 composition: 691 parked signature/carve, ~1,150 in five proven head classes, 450 GTE clobber variants, 94 missing-parameter pins, 1,548 drawable singletons. P37 T4 removed 280.
  • GTE: include/gte_inline.h = one definition per signature (51 #defines, majority spelling, generated by tools/gte_consolidate.py --header); a def differing only by an extra clobber lives in its TU as <name>_m (~60 files, e.g. src/ov_SC06_030/ov_SC06_030_jr_801380E0.c). Hazards: C0209 §187 (inline sqr emits two hazard nops, SDK Square0 one) and C0217 §195-J (call vs inline is a per-site source fact; tell = target opcodes lwc2/sqr/swc2 vs jal). Never unify by TU style; prove per site.
  • Lying decls (type_census.py:1354-1362, via tools/argcheck.py): lying = argcheck rows not K&R-marked and not cross-binary; 229 at 3.38 close over 24 callees (K&R-empty 216, narrow 13); func_80146C3C left K&R-marked (zero-arg $a0 callers in shared headers). Declaration byte facts C0508 §459: "pin was missing argument" = unit of def + all callers; a def in a shared header is per-definition. 40 TU-CONFLICT rows already ledgered in config/dedup_exceptions.tsv (PsyQ ApplyMatrixSV 8, RotTransPers 5, …).
  • Carry from 3.38: 18 stale conflicting types ledger rows (func_800D20C0 15 .c + 1 .h, func_8012C098 1 .c + 1 .h; sources canonical; restruct writes no row for a hand-fixed unit); restruct --selftest --real is red after any commit until R22 → delever_oracle --snapshot-baseline → --calibrate ov_SC04_011 ov_SC03_015 md_SC07_004 main -j 16; lever_progress --check goes red after a census-moving commit until --snapshot; timeline and README regenerate after the previous phase's last commit (README Levers should read 3,729; timeline.py --check reports STALE).
  • Gates: make check BINARY=<alias> per touched binary; main only through tools/gate_main.py (never incremental); R22 clean fleet run after any shared body/header/executable edit; read exit codes (R53); restruct_cycle.sh runs R22 itself and commits each green batch (R42), detached via setsid nohup (R115), batches < 10 min.
  • Mutation discipline: restruct writes inflight.json (originals) under a lock; recover with restruct --restore, never git checkout (R102). Every latest-verdict reader goes through restruct.ledger_latest (R100).
  • Hand-edited: src/**, include/common.h, config/*.tsv. Generated (regenerate, never hand-edit): include/struct_types.h between head_types:begin/end (tools/head_types.py), include/gte_inline.h (gte_consolidate.py --header), docs/levers.md, docs/readability.md series, docs/struct-twins.md (struct_twins.py --write).
  • Replan 2026-10-02 (T4 blocked, critic needs-developer, developer chose option 1 = reading A): T1-T4 done. Rung S has drawn every base the tools can draw (ov T3, md/main/shared T4; byte-identical, R22 218/218, restruct --rung S --check-residue uncovered 0 for ov_*, md_*, main, src/shared/*). Raw at T4 close: P=257,464 I=51,505 X=13,790 M=15,130; unbacked 0; lying 229 (tasks/T4.md Verified). P/X residue cause is overwhelmingly TYPE-NOT-CANONICAL (struct-map type with no canonical definition; ov 234,400 sites, md 4,815, main 1,712, shared 11,353; 18,526 map types vs 1,650 defined); T3 binding: residual for rung S, ledger-only RESIDUAL rows. Other ov causes: NO-TYPE-IN-MAP 2,444, NO-FIELD (IN-LEAF, needs union/split, deferred by 3.38) 1,910, NO-EDIT 482, INDEX 471, FILE-SCOPE 167.
  • Coverage gap the replan closes (T10): --check-residue (restruct.py:3521) counts a P/X body covered when ANY latest rung-S row exists for it (residue_sites :3456 skips on key presence, any verdict); it does not match sites to per-site RESIDUAL(<cause>) entries, and no code covers I/M sites with ledger rows. --check-casts (type_census.py:1982) fails on every raw site. Reading A needs a per-site covered/uncovered split.
  • Out of scope: tools/ harness files of Project Architect, .claude/, hooks, templates (harness gotchas only). The decomp's own tools under tools/ (type_census, restruct, lever_census, delever, gte_consolidate) are in scope.

Rationale

  • Order: instruments first (T2), then the biggest form by leverage (P on struct-map bases, T3/T4), then globals and absolute addresses (I, M; T5), declarations (T6), GTE (T7), then pins (T8) — the pins "the unified types now explain" only become removable after the bodies are re-spelled, so T8 runs last on the rewritten tree. Legacy order (S → S+A → X → R, largest leverage first) kept.
  • Kept casts: the milestone allows reinterpret macros counted apart, each with its instrument. A kept cast is respelled as a named macro whose expansion is the identical cast (preprocessor-identical, so the respelling is byte-neutral by construction, still gated), and every macro site must match a restruct ledger row (KEPT + cause) — the instrument. A macro site without its row fails --check-casts. This keeps "kept only where the gate proves the spelling moves bytes" machine-checked rather than a relabel. Unions stay deferred (3.38 binding decision); a width-conflict loser offset is a kept-cast cause, not a union.
  • T2 is effort: high (expert-fable): the judgment is the design of the reinterpret-macro set, the cause classes and the gate's reading of "raw" — a definition no test can arbitrate, and every later task inherits it. It is the only high mark (1 of 9).
  • Split T3/T4 by fleet segment (overlays vs md_*/main/shared) so each expert's context holds one segment's batches; main needs gate_main.py and shared bodies re-fan the fleet, which makes T4 the riskier half, run second with T3's ledger as its model.
  • Lying decls are drawn here (the milestone needs 0 outside a ledger), not left to 3.40; what cannot be drawn mechanically is ledgered with cause in a new config/lying_exceptions.tsv and becomes 3.40 residue. Kept apart from dedup_exceptions.tsv, which keys TU conflicts, not callee lies.
  • --strict 0 conflicts with the phase's own scope ("the surviving pins that the unified types now explain") and with 3.40's existence: removing all 3,730 levers is the agent-priced lane legacy P36 measured at 9 sessions / ~15-20M tokens, and the Standing constraints allow agent waves only at the developer's cap. That is money and scope — the one Developer-decides item. The plan is drafted for the recommended reading (A); reading B changes only T8's cap and the milestone line.
  • No new dependency. New code only in the decomp's own tools: type_census --check-casts (the existing census already computes every count; delta = the macro/ledger cross-check and the lying ledger) and, under reading A, a lever_census --residue exclusion keyed by a ledger.
  • Coders: opus55 everywhere code or sweeps run; T9 is the record and gate, still coder opus55 (the clean R22 and the snapshots are coder-run loops).
  • Replan (developer, option 1): the milestone reads raw casts as covered or uncovered, the same reading A already approved for levers; the literal 0 needs canonical definitions for thousands of map types (several sessions, header fan-outs) and becomes 3.40's input, sized by the RESIDUAL ledger T10 renders. T4 closes done on its swept work. T10 is a new id (not T4.1: T4 is not reopened) and runs between T4 and T5 so T5 can ledger undrawable I/M sites in the shape T10 defines. T10 is medium: covered/uncovered is arbitrated by its selftest controls (a covered and an uncovered site per form), not by judgment. High marks stay 1 of 10 (T2).
  • Per-site coverage, not per-body: a drawn body (MEMBERS/KEPT) can still hold raw sites whose own entry is RESIDUAL(<CLS>), and a body may hold a raw site its row never listed; counting the body as covered would hide an un-ledgered site. T10 matches each raw census site to a site entry of its body's latest ledger row (key chosen by T10 from form/base/off/type; line numbers drift after edits, so line alone is not the key) and reads the entry's cause; no matching entry, or an entry without a cause, is uncovered.
  • T5-T9 unchanged except: T5 deps T10 and ledgers what no recipe draws (the amended milestone needs it on I/M); T9 done-when and verify carry the new gate.
  • Triage P3.38-1 lands in T1: the previous phase's last commit has happened, and the INBOX asks for it first.

Interfaces

  • tools/type_census.py: forms FORM_P :134, FORM_I :135, FORM_X :136, FORM_M :137, FORM_A :138, FORM_C :139, ABS_ADDR :140; parse_base(expr) :189; find_sites(masked, rel, span_of_line, line_of, params_of) :275; walk_file(raw, rel) :593; run_census(jobs, use_cache=True, out_dir, want_sites=False) :1171; lying :1354-1362; summary['casts'] = {deref_total, by_form{P,I,X,M}, addr_form, typed_cast_member, bodies, by_bclass, coverage_ok} :1425; render :1635, :1669; main() :1879, flags -j --out-dir(.run/P37/census) --no-cache --sites --check --check-structs --selftest --quiet :1881-1888; --check verdict :1905-1918; --check-structs :1919-1926 (check_structs_inputs(), check_structs_verdict(summary, pad, parse)); struct map JSON written :1468 (.run/P37/census/struct_map.json).
  • tools/argcheck.py: definitions() :71; scan(defs, only_needed_argpins=False) :164; main :230.
  • tools/lever_census.py: classify_asm_body :176; walk_file :434; run_census :774; controls :991; selftest :1109; main :1159; --check :1188-1210; --strict :1190-1204; FAKE_MARK :83; C_FAKE_RX :84; NON_LEVER_KINDS {gte, verbatim-body, gte-unsigned} :85; GTE_LEVER_KIND "gte-lever" :86; GTE_HEADER_DEFAULT include/gte_inline.h :67; out .run/P36/census :53. Flags --check --strict --gte-header --out-dir --sites -j --no-cache --selftest --quiet.
  • tools/restruct.py: docstring :1-52 (rungs S struct spelling, D declaration redraw, L definition fold; residual classes :31-33); RUN=.run/P37/restruct :85; LEDGER=RUN/ledger.jsonl :86, row {rung, tu, unit, verdict, cause, before/after hashes}, key (rung, tu, unit); INFLIGHT :87 (fcntl lock :429-452); ledger_latest(rows) :402 → {(rung,tu,unit): row}; ledger_index :411; argparse main() :4490-4532: --try TU FN :4492, --plan/--apply :4495-4496, --rung S|D|L :4497, --batch --label --only --headers, --redraw :4503, --try-file :4504, D flags --callee --signature --body --all-defs --map-data --with --tu --unalias :4505-4512, --restore :4513, --status :4514, --check-ledger :4515, --audit-types --write-types --type --top :4516-4519, --selftest [--real] :4523 (body :4054-4484), --fanout-cost :4528, -j :4532.
  • tools/restruct_cycle.sh:14: [LABEL_PREFIX=] [TASK=] [REDRAW=] [CENSUS=0] START END [BATCH=50] [RUNG=D|S|L] [ONLY] [HEADERS=1]; clean src/config required :36; calibrate when red :38-43; apply -j 12 :44, needs final N/N :45-50; skips R22 if src unchanged :52-54; R22 :56 (.run/P37/baseline/r22_<label>.log); FLEET = count of config/check.*.sha.
  • tools/delever_oracle.py: main() :608; --recipes :610, --calibrate <aliases> :611/:639, --status :612, --snapshot-baseline :613 (:241-280, .run/P36/delever/baseline/), --linked-control :615, -j :616.
  • tools/delever.py: main() :6002; --plan --apply --batch --headers --only --rejudge --redraw --restore --scrub --propagate --recipes [--cap --control --limit] --apply-body :6027 --rung (default E) :6028 --allow-residue; apply refuses without current calibration :1115, :5872 (R56).
  • tools/gte_consolidate.py: inventory :246; canonical_table :303; render_header :398; write_header :421; plan_files :457; file_edits :532; apply_batch :627; sweep :783; remark :915; status :979; selftest :987; main :1115. Flags --inventory --header --apply --sweep --remark --status --selftest --batch N(400) --label --only --rejudge --dirty-ok -j(12).
  • tools/head_types.py: resolve :124, main :202 (writes include/struct_types.h region from struct_map.json). tools/struct_layout.py:370 field_offsets(res, d).
  • include/common.h:43 M2C_FIELD(expr, type_ptr, offset). include/gte_inline.h:1-7 header contract.
  • Gates: Makefile kit-corpus :255, tools-health :269, extract-all :1219 (JOBS ?= 16 :1215), check-all :1241, check :1262 (make check BINARY=<alias>). tools/gate_main.py main() :870: gate_main.py <slate.json> [--apply] [--no-bisect] [--no-resume] [--allow-dirty] [--assert-baseline].
  • Also used: tools/cast_call_sites.py main :293; tools/conform_decls.py main :209 (--fn --draft --check --apply --cast-zero-arg-calls); tools/decl_from_use.py main :1178.
  • Post-T2/T3 lines (retriever, 2026-10-02): type_census.py check_casts_counts :1203; run_census :1211 (counts :1367; casts dict with check_raw, macro_sites :1470); selftest :1811 (check-casts controls :1885-1921); BACKED_S_VERDICTS :1963; check_casts_inputs() :1966 (import restruct as rs; latest = rs.ledger_latest(rs.load_ledger()) :1969); check_casts_verdict(raw_by_form, macro_sites, latest, lying_rows, exceptions) :1982 → (rc, line), format :2005-2006; main :2010; --check-casts :2018, use :2059-2064. Site dict (sites.jsonl): form, tu, fn, line, pos, end, base, bclass, off, width, type, cls.
  • restruct.py: load_ledger :391; ledger_latest :407; site_row :1053 → {form, line, base, off, width, sign, access, type, field, verdict MEMBER|KEPT()|RESIDUAL(), cause}; S_KEPT_MACRO :1061; site_class :1066 (MISALIGNED, SIGN, WIDTH, SCHED-ALIAS, INDEX, NEGATIVE-OFFSET, NON-STRUCT, else NO-FIELD/NO-EDIT/POINTEE); S_NO_EDIT_VERDICTS :1189; matches_only(only, tu, unit, extra=()) :3366 (main = top-level src/*.c + src/resident/); RESIDUE_FILE_UNIT "<file-scope>" :3436; RESIDUE_DEFAULT_ONLY ["ov_*"] :3437; residue_cause :3440 (FILE-SCOPE, NO-TYPE-IN-MAP, TYPE-NOT-CANONICAL, TYPE-NOT-VISIBLE); residue_sites :3456 (P/X only, filter :3462); residue_rows :3472, row :3504-3506 = {ts, label, rung "S", calib, tu, unit, fn, addr, header, nhash_before, nhash_after, bases[{base,type,status}], skips, sites[{form,line,base,off,type,verdict "RESIDUAL()",cause}], verdict (shared TNC/TNV cause or "RESIDUAL")}: cause per site, no row-level cause; residue(a) :3510; check_residue(a) :3521 prints uncovered <n> sites / <m> bodies; argparse --only :5104, --residue :5119, --check-residue :5120.
  • New in the replan (T10 owns the exact shape): type_census.py --check-casts --residue prints check-casts: raw P=<n> I=<n> X=<n> M=<n> (uncovered P=<n> I=<n> X=<n> M=<n>); macros … (unbacked <n>); lying=<n> (ledgered <n>) plus one by-cause line; writes .run/P37/census/residue_census.{json,txt} (by form, cause, segment; top 50 map types by covered sites with body counts); exits 1 on uncovered > 0, unbacked > 0 or unledgered lying > 0. Without --residue, T2 behaviour unchanged. I/M residue rows: ledger-only rows from restruct --residue extended to forms I and M (or a sibling flag T10 names), same per-site {form, …, verdict "RESIDUAL(<cause>)", cause} entries, read through ledger_latest (R100).
  • New in this phase (T2 owns the exact shape; later tasks read T2's summary):
    • type_census.py --check-casts → prints check-casts: raw P=<n> I=<n> X=<n> M=<n>; macros <name>=<n>… (unbacked <n>); lying=<n> (ledgered <n>) and exits 1 on any raw > 0, unbacked > 0 or unledgered lying > 0.
    • reinterpret macros in include/common.h, each expanding to the identical cast; instrument = restruct ledger row {rung:"S", verdict:"KEPT", cause:<class>} keyed (binary, address, body) per R99.
    • config/lying_exceptions.tsv: callee \t def_file \t kind \t cause \t task.
    • Reading A only: lever_census.py --strict --residue config/lever_residue.tsv (file \t func \t kind \t pass \t instrument \t cause), rows excluded from --strict, still counted.

Cookbook

C0507 (§458 struct spelling is a per-access dial; S1/S2 ladder), C0508 (§459 declaration byte facts), C0503 (§454 rungs D/R), C0504 (§455 rung G), C0505 (§456 lever-removal move catalog), C0506 (§457 S105 TU batches), C0420 (§379 MEM_IN_STRUCT_P member vs cast), C0392 (§351 /s per access), C0209 (§187 GTE nops SDK vs game), C0217 (§195-J GTE call vs inline per site), C0368 (§328 volatile alias must be an object), C0383 (§342 void* param cast not byte-neutral), C0502 (§453 one source per unique function), C0510-C0513 (phase 3.38 struct entries), C0036 (§33 reconcile_decls fleet-majority oracle), C0081/C0086 (§73/§75c decl + call-site cast pair). Rules: R22, R40 (retry a fan-out failure once), R42, R53, R56, R99, R100, R102, R110, R112, R113 (marker names pass and instrument), R115, R122, G53 (producer census before a spelling sweep), G61.

Research

R3.38-001 (remaining work after P37; Gen3 constraints), R3.38-002 (type_census / lift_types mechanics). Pending, for T1 to adopt: phase-ends/current/research/pending/retriever-code-p39-cast-instruments.md (cast forms, lever_census flags, lying source, gte_consolidate CLI).

Developer decides

(none open) Answered: lever gate reading A (--strict --residue, 2026-10-01); cast gate reading A (--check-casts --residue, REPLAN option 1, 2026-10-02).

Triage

  • P3.38-1: T1 -- the 3.38 close has landed; regenerating the timeline and README is phase-start bookkeeping (INBOX asks it first)

Tasks

  • T1 | done | expert-opus55 | title: phase-start health, carry cleanup and baselines | coder: opus55 | effort: medium | files: docs/story-timeline.md, README.md, .run/P37/restruct/ledger.jsonl, docs/readability-progress.tsv | done-when: timeline.py --check fresh and README Levers reads the live lever count; the 18 stale conflicting-types ledger rows removed through restruct (--check-ledger clean); make tools-health OK; R22 218/218 on the start commit; delever_oracle --snapshot-baseline + --calibrate ov_SC04_011 ov_SC03_015 md_SC07_004 main -j 16 green and restruct --selftest --real green; fresh type_census (--sites) and lever_census baselines recorded in the summary: raw per form, per binary, per base class (struct-map-explained vs not, top 20 bases), lever split (pins/asm/GTE statements/asm macros/gte-levers), lying 229 by callee; pending report adopted | verify: PY tools/timeline.py --check && make tools-health && PY tools/restruct.py --check-ledger && PY tools/type_census.py --check-structs --quiet | reads: R3.38-001, phase-3.38/tasks/T6.md, phase-3.38/tasks/T7.md | deps: — | est-ctx: 90k | review: no | wait-for: — Timeline: PY tools/timeline.py then PY tools/progress.py --readme; commit before R22. The census baselines are the denominators every later task and the closing record quote; key sites by (binary, address, body) (R99).
  • T2 | done | expert-fable | title: cast and lying gate instruments, reinterpret-macro set | coder: opus55 | effort: high | files: tools/type_census.py, tools/lever_census.py, include/common.h, config/lying_exceptions.tsv, config/lever_residue.tsv, docs/gen3-standards.md | done-when: type_census --check-casts exists with the Interfaces output line; it FAILS on the T1 tree with T1's counts; selftest has positive/negative controls for each form, for a macro site with and without its ledger row, and for a ledgered vs unledgered lying callee; the reinterpret-macro set (names, expansion = identical cast, one cause class each: at minimum SCHED-ALIAS, SIGN, WIDTH/overlap loser, MISALIGNED, NON-STRUCT base) is defined in include/common.h and documented in docs/gen3-standards.md, with one byte-proof per macro (an existing kept site respelled → make check IDENTICAL); M2C_FIELD stays raw form M; under reading A, lever_census --strict --residue with its selftest; the census caches invalidated; type_census --check-structs still 0 | verify: PY tools/type_census.py --selftest && PY tools/lever_census.py --selftest && PY tools/type_census.py --check-structs --quiet && (PY tools/type_census.py --check-casts; test $? -eq 1) | reads: T1 | deps: T1 | est-ctx: 130k | review: no | wait-for: — Judgment: what counts as raw vs a backed reinterpret macro, and the cause classes; decide whether legacy LOBU/LOH/HIH… are reinstated as macros of this set. Header edit to common.h re-fans the fleet: land the macro set in ONE edit + one R22. The gate cross-checks macro sites against restruct.ledger_latest rows (R100), never a second filter.
  • T3 | done | expert-opus55 | title: rung S sweep, overlays (forms P and X) | coder: opus55 | effort: medium | files: src/ov_/**/.c, include/struct_types.h, src/shared/engine_types.h, .run/P37/restruct/ledger.jsonl | done-when: every ov_* body with a P or X site on a struct-map base processed by restruct --rung S (S1 then S2) through restruct_cycle.sh batches, each batch R22-green and committed; kept casts respelled with T2 macros backed by KEPT rows; missing fields added to the canonical types in batched header edits (≤ 1 fan-out per batch of field additions); residual P/X on ov_* each ledgered with a cause class; summary records before/after per form, kept count by cause, bodies refused and why | verify: PY tools/type_census.py --check-casts --quiet; PY tools/type_census.py --check-structs --quiet && make clean && make extract-all JOBS=16 && make check-all JOBS=16 | reads: T1, T2 | deps: T2 | est-ctx: 160k | review: no | wait-for: — Isolate on one TU first (C0507 exemplar func_801814AC, ov_SC05_010_jr_80180F84.c:3030: the +0x34 store must stay cast). Parameter retyping is a per-callee S → T → D unit (C0507). Use the linked oracle for global-block edits (R110). Retry a fan-out failure once (R40). Run cycles detached (skill restruct-cycle-watch). --check-casts still exits 1 here (other segments); read its P/X counts for ov_*.
  • T4 | done | expert-opus55 | title: rung S sweep, md_, main and shared bodies (forms P and X) | coder: opus55 | effort: medium | files: src/md_//*.c, src/main//.c, src/shared/**, include/struct_types.h, src/shared/engine_types.h | done-when: as T3 for md_, main and shared bodies; main gated only by tools/gate_main.py; shared-body edits followed by R22; P and X raw = 0 fleet-wide except sites ledgered with a cause that T5 owns (non-struct base) | verify: PY tools/type_census.py --check-casts --quiet; make clean && make extract-all JOBS=16 && make check-all JOBS=16 | reads: T1, T2, T3 | deps: T3 | est-ctx: 160k | review: no | wait-for: — Shared bodies (C0502 §453 include-at-site share) re-fan every includer: batch them, one R22 per batch. Closed done by the 2026-10-02 replan: swept work stands; the fleet-wide P/X clause is superseded by the reading-A milestone (residue covered per site, T10).
  • T10 | done | expert-opus55 | title: residue coverage gate (--check-casts --residue) and 3.40 sizing | coder: opus55 | effort: medium | files: tools/type_census.py, tools/restruct.py, .run/P37/restruct/ledger.jsonl, docs/ops/, HOW_WE_WORK.md | done-when: type_census --check-casts --residue exists with the Interfaces line; a raw P/I/X/M site counts covered only when its body's latest ledger row (via restruct.ledger_latest) has a matching site entry RESIDUAL(<cause>) with a non-empty cause; selftest controls per form: one covered site passes, one uncovered site (no row; row present but site absent; entry without cause) fails, plus a KEPT macro site still counted under macros, not residue; restruct --residue can write ledger-only I/M rows with causes (selftest control) and --check-residue matches per site, not per body (selftest control: a MEMBERS body with an unlisted raw site reports uncovered); any P/X gap the per-site match exposes on the T4 tree is closed by a ledger-only --residue rerun (no source edit); on the T4 tree the gate reads P/X uncovered 0 and fails only on I/M uncovered and lying, counts in the summary; residue_census.{json,txt} written and its by-cause / top-50-type tables quoted in the summary as 3.40's sizing; --help and docs/ops note updated, HOW_WE_WORK tools row updated | verify: PY tools/type_census.py --selftest && PY tools/restruct.py --selftest && PY tools/restruct.py --rung S --check-residue --only "ov_" && PY tools/restruct.py --rung S --check-residue --only "md_" && PY tools/restruct.py --rung S --check-residue --only main && PY tools/restruct.py --rung S --check-residue --only "src/shared/*" && (PY tools/type_census.py --check-casts --residue; test $? -eq 1) | reads: T2, T3, T4 | deps: T4 | est-ctx: 110k | review: no | wait-for: — No source or header edit: tool code and ledger-only rows. Key sites by (binary, address, body) (R99); read verdicts only through ledger_latest (R100), never a second filter. Ledger writes go through restruct's lock (inflight/fcntl); commit the ledger before and after any --residue rerun. Keep --check-casts without --residue byte-for-byte as T2 left it (T2 selftest stays green). The top-50-type table is what the 3.40 planner sizes the canonical-types campaign from: report sites and bodies per type, and how many of the 18,526 map types cover 50/80/95% of TNC sites.
  • T5 | done | expert-opus55 | title: globals and absolute addresses to declared symbols (forms I and M, residual non-struct bases) | coder: opus55 | effort: medium | files: src/**/.c, src/shared/engine_types.h, include/common.h, config/symbols..txt | done-when: form I (*(T *)D_x, &D_x reinterpret, absolute 0x80…) and form M (M2C_FIELD) at 0 uncovered: each site is a typed extern / declared symbol (recipes R1 pointer global as struct member, R2 extern T X[], R3 walked pointer), a member access, a T2 macro backed by its row, or (no recipe draws it) a ledger-only RESIDUAL entry with a specific cause in T10's I/M row shape; absolute addresses become symbols named in the binary's symbol file (unnamed D_<addr> form, no invented names, G5); every touched binary make check / gate_main green and R22 green | verify: PY tools/type_census.py --check-casts --residue --quiet; make clean && make extract-all JOBS=16 && make check-all JOBS=16 | reads: T1, T2, T3, T4, T10 | deps: T10 | est-ctx: 150k | review: no | wait-for: — Verify reads I/M uncovered from --check-casts --residue (still exit 1 on lying until T6). Ledger RESIDUAL only after the recipe ladder refused the site; cause names the recipe that failed. restruct --try TU FN --base global:D_x --recipes per class before fanning out; a global's declared type change is a header/def edit seen by every user — batch. Symbol-file edits follow the symbol-file path (R15), never generated asm.
  • T11 | done | expert-opus55 | coder: opus55 | effort: medium | title: progress snapshot and chart catch-up | files: docs/readability-progress.tsv,docs/story-timeline.md,docs/story-timeline.svg,README.md,docs/progress.json | done-when: docs/readability-progress.tsv has a P39 row at this commit; timeline.py --check fresh; README Types reads the live check-casts numbers and the uncovered split | verify: PY tools/readability_progress.py --check && PY tools/timeline.py --check | reads: — | deps: — | est-ctx: — | review: no | wait-for: —
  • T6 | done | expert-opus55 | title: lying declarations to 0 outside the ledger | coder: opus55 | effort: medium | files: src//*.c, src//*.h, config/lying_exceptions.tsv | done-when: types_floor_lying 0 outside config/lying_exceptions.tsv; each of the 24 callees either drawn by restruct --rung D (--callee/--signature/--all-defs, def + all callers as one unit, C0508) and R22-green, or ledgered with kind and cause (e.g. func_80146C3C zero-arg $a0 callers); --check-structs still 0 | verify: PY tools/type_census.py --check-casts --quiet; PY tools/type_census.py --check-structs --quiet | reads: T1, T2 | deps: T2 | est-ctx: 110k | review: no | wait-for: T5 Runs after T5 so declarations are drawn on the final symbol types (wait-for keeps the tree single-writer; no file overlap with T3-T5 otherwise). Shared-header prototypes conform in every includer at once.
  • T7 | superseded | expert-opus55 | title: GTE to one spelling | coder: opus55 | effort: medium | files: include/gte_inline.h, src/**/*.c, tools/gte_consolidate.py | done-when: 0 direct GTE statements in bodies and 0 per-TU asm macro definitions (lever_census --strict counts); every _m variant either replaced by its canonical macro (byte-proven IDENTICAL) or kept as a header-level variant in include/gte_inline.h with a // !FAKE: marker naming pass and instrument (R113), one definition per variant; call vs inline left per site (§195-J); gte_inline.h regenerated by gte_consolidate --header, not hand-edited; R22 green | verify: PY tools/gte_consolidate.py --selftest && PY tools/lever_census.py --check && make clean && make extract-all JOBS=16 && make check-all JOBS=16 | reads: T1, T2 | deps: T6 | est-ctx: 130k | review: no | wait-for: — If gte_consolidate cannot express a header-level variant, extend it (decomp tool, in scope). gte-lever sites after this task are marked levers; under reading A they enter T8's residue ledger, under B T8 removes them.
  • T7.1 | done | expert-opus55 | coder: opus55 | effort: medium | title: GTE to one spelling (predicate read as the census documents it) | files: include/gte_inline.h, src/**/*.c, tools/gte_consolidate.py, tools/lever_census.py | done-when: 0 GTE asm statements in bodies outside calls of include/gte_inline.h macros: lever_census --strict direct_gte (:1133) gains the via filter its own docstring (:1127-1128, 'spelled inline instead of as the header's macro call') describes, i.e. kind gte/gte-unsigned sites not via a gte_inline.h macro, with selftest controls (a via-macro call passes --strict, an inline statement fails; the 207 inline + 270 gte-unsigned sites reach 0 by respelling, each batch byte-proven); 0 per-TU asm macro definitions of kind gte (150 -> 0; the 164 launder/instruction/barrier defs are T8's, counted apart in the summary); every _m/_v variant either replaced by its canonical macro (byte-proven IDENTICAL) or kept as a header-level variant in include/gte_inline.h with a // !FAKE: marker naming pass and instrument (R113), one definition per variant (extend gte_consolidate render_header, JSON-only variants become header definitions); gte-lever sites (434) marked, left for T8's residue ledger; call vs inline left per site (§195-J); gte_inline.h regenerated by gte_consolidate --header, not hand-edited; lever_census via-macro/direct/pt-by-kind counts before and after in the summary; R22 green | verify: PY tools/gte_consolidate.py --selftest && PY tools/lever_census.py --selftest && PY tools/lever_census.py --check && make clean && make extract-all JOBS=16 && make check-all JOBS=16 | reads: T1, T2, T7 | deps: T6 | est-ctx: 130k | review: no | wait-for: —
  • T8 | done | expert-opus55 | title: pins and asm the unified types explain; residue ledger | coder: opus55 | effort: medium | files: src/**/*.c, config/lever_residue.tsv, docs/levers.md | done-when: delever (rungs A/B strip, R recipes with --propagate) run over every lever-carrying body rewritten by T3-T7, removals R22-green and committed; reading A: every remaining pin/asm/gte-lever site has a config/lever_residue.tsv row (pass, instrument, cause; agent-shaped ones flagged for 3.40) and lever_census --check --strict --residue config/lever_residue.tsv exits 0; reading B: literal --strict 0 under the developer's cap; lever_census --check 0 UNMARKED | verify: PY tools/lever_census.py --check && PY tools/lever_census.py --check --strict --residue config/lever_residue.tsv | reads: T1, T2, T7 | deps: T7 | est-ctx: 140k | review: no | wait-for: — Calibration current before delever --apply (R56). Seed rung D only with the tree's own lever-free body (C0503). The residue ledger is 3.40's input: the 3.40 planner reads it, so causes must be specific (head class, missing parameter, carve, singleton).
  • T9 | queued | expert-opus55 | title: milestone gate and record | coder: opus55 | effort: medium | files: docs/readability.md, docs/readability-progress.tsv, docs/levers.md, docs/story-timeline.md, README.md, cookbook/ | done-when: on one commit from a clean tree: type_census --check-casts --residue 0, restruct --rung S --check-residue 0 for ov_/md_/main/src/shared/, --check-structs 0, lever_census --check --strict (as the developer's reading) 0, R22 218/218, make tools-health OK; delever snapshot + calibrate and restruct --selftest --real green; lever_progress --snapshot taken; readability and lever series rendered; timeline/README regenerated; cookbook entries for the phase's byte facts added; summary states before/after per form, covered residue by form and cause (from residue_census, the 3.40 canonical-types input), kept casts by cause, lying ledger size, lever residue size | verify: PY tools/type_census.py --check-casts --residue && PY tools/restruct.py --rung S --check-residue --only "ov_" && PY tools/restruct.py --rung S --check-residue --only "md_" && PY tools/restruct.py --rung S --check-residue --only main && PY tools/restruct.py --rung S --check-residue --only "src/shared/" && PY tools/lever_census.py --check --strict --residue config/lever_residue.tsv && make clean && make extract-all JOBS=16 && make check-all JOBS=16 && make tools-health | reads: T1, T2, T3, T4, T5, T6, T7, T8 | deps: T8 | est-ctx: 100k | review: no | wait-for: — Both gates read A (levers 2026-10-01, casts 2026-10-02). Do not edit the plan between the R22 and the commit.

Risks

  • The ~24% of sites the layouts cannot hold blows up kept-cast counts or header churn. Detection: T1's per-base split and T3's first batches; if kept > ~25% of a segment, T3 returns question with the cause histogram before fanning out.

  • A reinterpret macro reads as relabeling. Mitigation: every macro site needs its KEPT row (T2 selftest proves an unbacked site fails); the closing summary reports kept by cause.

  • False DIFFERS from the whole-object oracle on global edits (relocation spelling) — use the linked oracle (R110); a correct edit refused shows in the ledger as REFUSED with a relocation-only diff.

  • Header fan-out cost (~6 min per canonical-header edit): unbatched field additions multiply wall time. Detection: R22 logs per cycle in .run/P37/baseline/.

  • Main regresses silently if gated incrementally — gate_main.py only.

  • --selftest --real and lever_progress --check read red after commits by design; a task that treats that as a failure stalls. Each task refreshes snapshot/calibration after its R22.

  • Reading B without a cap leaves T8 open-ended; the critic should stop T8 at the cap and ledger the rest.

  • GTE respellings that differ only in hazard nops (C0209) pass a careless check on one binary and fail another; T7 gates fleet-wide.

  • Per-site matching (T10) may expose P/X sites T3/T4 never listed in any row (e.g. sites added by a later respell, or bodies whose row predates the census). Mitigation: T10 closes them by ledger-only --residue reruns; a gap that needs a source edit returns question.

  • Covered residue can grow silently in T5-T8 (a site ledgered instead of drawn). Detection: T9 quotes covered-by-cause against T10's numbers; growth in a cause without a recorded refusal is a defect.

Changes

  • 2026-10-01 router: T1 next -> done
  • 2026-10-01 router: T2 next -> done
  • 2026-10-02 router: T3 next -> done
  • 2026-10-02 router: T10 queued -> next
  • 2026-10-02 router: T10 next -> done
  • 2026-10-02 router: T5 queued -> next
  • 2026-10-02 developer: added T11 — progress snapshot and chart catch-up
  • 2026-10-02 developer: D1 2026-10-02: every census-moving task closes with readability_progress --snapshot, timeline.py, progress.py --readme in its commit and a 'progress:' line in its summary Done
  • 2026-10-02 router: T5 next -> done
  • 2026-10-02 router: T11 queued -> next
  • 2026-10-02 router: T11 next -> done
  • 2026-10-02 router: T6 next -> done
  • 2026-10-02 critic: reopened T7 as T7.1 — GTE to one spelling (predicate read as the census documents it)
  • 2026-10-02 critic: 2026-10-02 critic (T7 question): T7 -> T7.1, done-when clauses read as lever_census's own docstring defines them (direct = not via a gte_inline.h macro; per-TU defs of kind gte). T8 done-when gains: per-TU asm macro definitions (lever_census pt, the 164 non-GTE launder/instruction/barrier defs left after T7.1) reach 0 by deletion or by a residue-row shape for definitions (config/lever_residue.tsv kind asm-macro, func = macro name, strict_failures :1142-1143 matches rows instead of the synthetic entry, selftest control). Milestone gate wording unchanged: lever_census --check --strict --residue config/lever_residue.tsv exit 0.
  • 2026-10-02 router: T7.1 next -> done
  • 2026-10-02 router: T8 next -> done