# 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 ` 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 `_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=` 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()` 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()`, 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_