# Phase 3.38 — Struct unification (implements GENERATION_PLAN.md phase 3.38) Milestone: `make tools-health` OK and `make kit-corpus` clean at phase start (T1); `PY tools/type_census.py --check-structs` → 0 duplicate-layout classes, 0 definitions outside the canonical type files, 0 dead names, 0 variant names, 0 legacy `pad` names, 0 parse-error (type-not-visible) declarations, controls 4/4 (from 7,255 defs / 206 dup classes / 40 variant camps / 26 `pad`); R22 `check-all: 218 passed, 0 failed of 218` — verified by: `.venv/bin/python tools/type_census.py --check-structs` exit 0 and `make clean && make extract-all JOBS=16 && make check-all JOBS=16` exit 0 at the closing commit Approved: 2026-09-29 Planner: claude-opus-5-5/medium Plan-hash: 81ea169f9e3057048c934c68dd6547dfa092140d241f125f7a0d1baa3bd7bcd7 ## Context - Project: BFM (PS1) matching decomp, 100 % byte-identical since P32. Gen 3 = readability. P35 dedup, P36 levers off, P37 (legacy) = the structs phase part one: census, struct map, `restruct` engine, the declaration layer to its floor. This phase = legacy P38 / P37-carry T5 ("rung L"): one canonical definition per TYPE. - Seed sources (not to be re-read by experts): `phase-ends/PhaseEnd_Phase37.md:75-99` (carry list), `phase-ends/logs/Phase37.md` S108 checkpoint (~L820-965), cookbook C0507/C0508. Digest below is what they say. - Oracle = the byte gate. Every edit batch is judged by `tools/restruct.py` (whole-object byte compare; LINKED mode for global-block edits whose relocation spelling moves but linked bytes do not) and committed only after a green R22 (`make clean && make extract-all JOBS=16 && make check-all JOBS=16`, ~95 s, read the exit code; green = `extract-all: 217 extracted, 0 failed of 217` + `check-all: 218 passed, 0 failed of 218`). - Interpreter: BFM tools run under `.venv/bin/python` (Makefile `VENV_PY`), `-j 16` / `JOBS=16`, repo on ext4. PA tools (`plan_edit`, `task_log`, `research_add`) run under `/usr/bin/python3`. `tools/type_census.py`, `tools/restruct.py`, `tools/restruct_cycle.sh`, `tools/lift_types.py`, `tools/delever_oracle.py`, `tools/lever_progress.py` are BFM's own product tooling (not Project Architect's) and are in scope to edit. - Census state: `.run/P37/census/type_census.txt` is STALE (2026-09-13 at bd7d32ec2, before T4). Its DEFINITIONS line: 7,255 bodies = canonical header 1,179 · `.c` file-scope 4,085 · `.c` block-scope 1,904 · per-function shared headers 63 · other headers 24 · include/ 0; 3,143 names, 527 layouts, 206 duplicate classes over 2,823 names (tier 1 122 same-named, tier 2 84 opaque; 46 layout-twin classes / 1,867 names kept apart); 40 variant camps. T4-close figures: 1,796 lying declarations / 76 callees (`K&R-empty` 1,373, `narrow` 423), 5,262 K&R sites counted apart (the original's calling convention, not debt), 418 cross-binary apart. T1 re-baselines. - Canonical type files: `CANON_HEADERS = ("src/shared/engine_types.h",)` at `tools/type_census.py:55`; comment there says T3/T5 add `src/shared/main/types.h` and the slots'. Existing trees: `src/shared/{main,ov,slot_800CAE08,slot_801E25E8,slot_801E7B28,slot_801ED988,slot_801EF468}/`, `src/shared/engine_prelude.h`, `src/800_shared.h` (main). Only `include/{common.h,gte_inline.h,include_asm.h}` in `include/`. - Head types: the three names `Unkstruct_80126B58` (entity/actor, 0x24C, 146 fields, preview skipped {overlap 28, misaligned 1}), `Unkstruct_800B5CB8` (0x10C, 90 fields, skipped {overlap 44}), `Unkstruct_80078E00` (the player block, instance at 0x80078E00; `docs/actor-struct.md:3` "~154 fields over 0x24C") exist ONLY in the generated preview `.run/P37/restruct/types_preview/ov.h` (from `restruct.py --write-types --top 6 --space ov`); 0 occurrences in `src/`, `include/`, `docs/`. The overlaps are the struct map's width conflicts: the union-vs-split call is per offset. Source map: `.run/P37/census/struct_map.json` (18,760 types, explains 498,896/503,016 sites, 99.2 %), rendered `docs/struct-map.md` (generated, never hand-edited). Parked P36 work: `.run/P37/census/parked.tsv` (24 classes, 453 bodies; struct 7). - Naming law (G62): `Unkstruct_` placeholders, `unkHEX` fields with `/* 0xNN */` comments and `// size = 0x..`; a field gets a name only with cited evidence (`docs/memory-map.md` §3.4, `docs/actor-struct.md`); PsyQ layouts take Sony's SDK names. Layout twins without evidence stay apart (S106 "per type", not "per layout"). - Byte facts (C0507 §458): struct spelling is a per-access dial — `*(T*)(p+k)` carries no `MEM_IN_STRUCT_P`, a member access does, so a cast→member rewrite can move scheduling. An opaque-tier fold renames members (T2 probe: 8 of 20 TUs), so rung L rewrites accesses too, and every fold is judged, never assumed neutral. (C0508 §459): a K&R site is per-TU, but a definition in a shared header makes it per-definition; a return type is observable in a delay slot; what a prototype cannot hide: types the TU cannot see (1,120 `parse error` → `DECL-KEPT` "type not visible", `tools/restruct.py:1741-1745`), `conflicting types` (417, `:1737-1739`), jr-merged callers (10, → 3.44), void defs whose callers use `$v0` (5, → 3.40), zero-arg callers passing their own `$a0` (→ 3.40). - Owed from P37: `make tools-health` (last green at P36 close; >15 min, `audit-cdecl` ~787 s) and `make kit-corpus` (the kit's `tool_census --check` may read red until then); `docs/actor-struct.md`: +0x108/+0x10C are 4-byte and `D_80078F08/0C` are code-width 4, not 2 (the census control prints them as `doc-disputed`); the 45 TU-CONFLICT rows; the 13 `syntax error` units in `src/800.c` (600 KB; the marker lives in the restruct ledger / census logs, not the source); the cookbook §396(a) correction. - Tree at plan time: `config/kit_coverage_map.tsv` modified (uncommitted), `phase-ends/PhaseEnd_Phase37.md` untracked (the router's migration ceremony owns it; tasks never stage it). Disk: ~22 GB free; `.run/P32/` (6.6 GB) and parts of `.run/P36/` are prunable scratch, but `.run/P36/delever/baseline/` and `.run/P36/delever/ledger.jsonl` are live inputs of `delever_oracle` / `lever_progress`. - Gotchas carried (S108, binding for every task): - Stop a judge with `kill -INT `, WAIT for exit (~2 min, finishes in-flight units), only then `--restore`; an early restore consumes `inflight.json` (S108: 6 files hand-reverted, 197 orphan rows). - Never put a literal of your own command line in `pgrep -f`/`pkill -f`; bracket a char (`restruct_cycl[e]`). - Never edit tracked files the cycle rewrites between a cycle's R22 and its commit; check `ps` for `make`. - `delever_oracle --snapshot-baseline` only after a GREEN clean fleet run; `--apply` refuses a stale snapshot; `restruct_cycle.sh` refreshes snapshot + calibration itself when HEAD moved. - `DECL-NONE` is a DONE verdict: redraw with `--redraw VERDICT:cause`, never bare `DECL-NONE`. - A flat yield is not "nothing drawable": bucket the remainder by ledger verdict before stopping (`md_*` hid 3,700 never-judged pairs). - A pack's "one caller" is a claim: `grep -rnE '\bfunc_X\s*\(' src` before a signature unit. - Census blind spots that moved counts by thousands: include graph not followed, name-keyed maps (6,415 names carry >1 body), column-0 definition regex, one-sided redraw key, snapshot stamped fresh over empty `build/`. Key every map by (binary, address, body). - Shared headers are judged serially (~35 s per judgement over 141 includers): budget header batches accordingly. - Cycle launch form (detached, outlives the agent): `setsid nohup bash -c 'LABEL_PREFIX=

TASK= CENSUS=0 tools/restruct_cycle.sh START END 400 ; echo "cycle exit=$?"' > .run/P38/restruct/cycle_.log 2>&1 &`; `HEADERS=1` for header batches; `REDRAW=":"` to redraw. Scratch for this phase: `.run/P38/`. - `type_census --selftest` (21 tests) and `restruct --selftest` (54; `--real` adds calibration + known-true + linked controls) re-run after any engine change; known-true controls: `restruct --try func_801814AC --base global:D_801C7E30 --recipes` → DIFFERS, S2 keeps 1, R1 IDENTICAL. - Long commands: one tool call stays under 285 s. Anything longer: `bash tools/run.sh --bg -- ` then `bash tools/run.sh --wait --max 280` (repeat). R22 (~95 s) fits one foreground call. ## Rationale - Instrument first (T2). `type_census --check` (`tools/type_census.py:1621-1634`) also fails on raw cast sites (~503k, phase 3.39) and lying declarations (K&R-empty/narrow residue, phase 3.40), so it cannot go green this phase; it does not gate dead names at all, and no mode counts parse-error declarations. The generation milestone's content is kept exactly; T2 adds the phase-scoped `--check-structs` that asserts it, and the closing expert runs that. `--check` itself stays unchanged for 3.39/3.41. Rejected: reading numbers off the census text by hand (R85: a check compares derived figures; G58: generated, never typed). - The parse-error count comes from the restruct ledger's latest verdict per unit, read through restruct's own latest-verdict function (imported), never a second filter (P37 proposed rule (c): one "done" filter used by every side). - Head types before the long tail (gen-plan order): the two big types own most of the 206 classes' members and the 1,120 type-not-visible declarations; folding them first makes the long tail mostly mechanical `lift_types.py` / rung L work, and T6's redraw depends on the types being visible. - `effort: high` on T3 only (1 of 7): the union-vs-split decision per overlapping offset (28+1 on the entity, 44 on `Unkstruct_800B5CB8`) and the canonical-file layout are judgments the byte gate cannot arbitrate — both spellings can be byte-identical; the choice rests on reading the access evidence in `struct_map.json` (which functions read which width at which offset) and on what a reader of the C should see. T4 then applies that design mechanically at medium. - T3 designs + proves on exemplars; T4 applies fleet-wide. Splitting keeps the Fable context on the judgment and the rollout on cycles at medium. - T5 (long tail) separate from T4: different instrument (`lift_types.py` + rung L cycles vs a designed layout), different evidence, and a natural checkpoint where `--check-structs` shows the head types gone from the dup classes. - T6 (declarations) after T5: the gen plan's "most of the 417 `conflicting types` fall to the canonical files" and the 1,120 `parse error` redraw only pay once every type is in a canonical file every TU includes. The 13 `src/800.c` syntax-error units and 45 TU-CONFLICT rows are the same class (declaration environment) and ride along. - `docs/actor-struct.md` fix goes in T2: the census control `player_block_widths` reads it and currently prints two `doc-disputed` rows; the instrument task owns making its controls clean. - Coder tier opus55 throughout (the only tier). Test design: no new suites; the gates are R22, the restruct judge, `type_census --selftest` + `--check-structs`, and T2's known-true case (the current tree must FAIL `--check-structs` with its current counts before any fold lands). - No `## Developer decides` items: naming follows G62, file layout and union/split are engineering (T3), agent caps do not apply (no wave lanes this phase). ## Interfaces - `tools/type_census.py` (BFM): `main()` :1596 — flags `-j/--jobs`, `--out-dir` (default `OUT_DIR_DEFAULT=".run/P37/census"` :53), `--no-cache`, `--sites`, `--check` (:1621-1634: violations = `definitions.dup_layout_classes`, `in_c_file_scope+in_c_block_scope+in_shared_fn_headers+other_headers`, `casts.deref_total`, `decls.lying`; prints `type_census --check: OK|FAIL — …`), `--selftest`, `--quiet`. Always-on: `casts.coverage_ok` (COVERAGE FAIL), `controls.ok == controls.n` (CONTROL FAIL). `CANON_HEADERS` :55. Summary keys built :1271 (`variant_names`, `dead_canon_names`, `shadowed_canon_names`); dead names computed :1142-1154. `run_census(jobs, use_cache=True, out_dir=OUT_DIR_DEFAULT, want_sites=False) -> (summary, txt)` :1029; `controls(defs_all, sites_all, extern_data, by_text, name_layouts)` :1409; `render(s, dup_classes, variants, types)` :1455; `render_struct_map_md(s, types, path)` :1508; `selftest()` :1559; `find_definitions` :66, `find_decls_and_flows` :440, `build_struct_map` :735. - `tools/restruct.py` (BFM) argparse :3622-3661: `--try TU FN`, `--base bclass:name`, `--recipes`, `--plan`, `--apply`, `--rung {S,D,L}` (default S), `--batch N` (50), `--label`, `--headers`, `--only …`, `--redraw VERDICT[:cause] …`, `--try-file`, `--callee`, `--signature`, `--body`, `--all-defs`, `--map-data`, `--with REL=FILE`, `--tu`, `--unalias`, `--restore`, `--status`, `--check-ledger`, `--audit-types`, `--write-types --type … --top N --space ov|… [--pointees]`, `--selftest [--real]`, `--probe`, `--audit-layouts`, `--probe-defs` (fold a TU's duplicate file-scope definitions onto the canonical header). Declaration verdict causes :1737-1745 (`conflicting types …`, `type not visible: … (T5 canonicalises it)`). - `tools/restruct_cycle.sh START END 400 ` env `LABEL_PREFIX`, `TASK`, `CENSUS`, `HEADERS`, `REDRAW`; stops on "nothing drawable". - `tools/lift_types.py` (BFM): `main()` :120, flags :122-127 `--types a,b`, `--candidates`, `--apply`, `--exclude a,b`; dry-run by default (`[dry-run] would lift N type(s) to engine_types.h …` :219); `HEADER = src/shared/engine_types.h` :29; `scan(names=None)` :36, `candidates()` :67; imports `build_engine_types` (brace-aware `find_defs`, `find_typedefs`). - `tools/delever_oracle.py` (BFM): `--snapshot-baseline` flag :613 → `snapshot_baseline()` :218 (writes `build/**/*.o` + `_link//` + `links.json` to `.run/P36/delever/baseline/`, records HEAD; refuses a shrunken `build/` :241 or non-reproducing objects :271); `--calibrate [BIN …]` :611 → `calibrate(aliases, jobs, recipes)` :532 (untouched compile == build/, stale head rejected :602). - `tools/lever_progress.py` (BFM) :158-161: `--snapshot MILESTONE`, `--render` (docs/levers.md block), `--table`, `--check`. - Makefile: `kit-corpus` :255-256 (`$(VENV_PY) tools/tool_census.py --all`); `tools-health` :269-360 (last line `tools-health: OK …`); `check-all` :1236 (`check-all: N passed, M failed of 218`, exit 1 on shortfall); R22 recipe :166/:1209. - Records: `.run/P37/census/{type_census.json,type_census.txt,struct_map.json,struct_map_top.json,sites.jsonl,parked.tsv}`; `.run/P37/restruct/types_preview/ov.h`; restruct ledger (read via `restruct.py --status` / `--check-ledger`). - New in T2: `type_census.py --check-structs` → prints `type_census --check-structs: OK|FAIL — ` and one counts line `dup_classes=… outside_canon=… dead=… variants=… pad_names=… parse_error_decls=… controls=a/b` plus non-gating report `conflicting_types=… tu_conflict=… types_floor_lying=…`; exit 1 on any gating violation. ## Cookbook C0507 (§458 struct spelling per-access dial), C0508 (§459 declaration-layer byte facts), C0067 (§64 lift_types' three laws: fold the tagged typedef, check visibility, strip only textually identical), C0224 (§203 a deduped typedef must precede every splice point), C0012 (§8c a TU's declaration environment), C0013 (§8d templating must not change the declaration environment), C0103 (§92 conforming a declaration: pointer changes caller-neutral, width changes not), C0114 (§103 a file-scope extern constrains every later function), C0257 (§236 the declaration layer's nine gate failures). ## Research - (no new reports) — planning digest of `PhaseEnd_Phase37.md` §Carried, `logs/Phase37.md` S108 checkpoint, C0507/C0508, and a code retrieval of the tool interfaces above; no report ids were allocated. ## Developer decides - (none) ## Triage - (none) ## Tasks - T1 | done | expert-opus55 | title: phase-start health and baselines | coder: opus55 | effort: medium | files: config/kit_coverage_map.tsv, .run/P38/baseline/ | done-when: `make tools-health` last line `tools-health: OK`; `make kit-corpus` run and its outputs committed (tree clean of kit drift, `config/kit_coverage_map.tsv` either regenerated-and-committed or explained); R22 green; `delever_oracle --snapshot-baseline` then `--calibrate ov_SC04_011 ov_SC03_015 md_SC07_004 main -j 16` OK; fresh `type_census.py --out-dir .run/P38/census` + `restruct.py --audit-types` baseline numbers recorded in the summary (defs, dup classes, variant camps, dead names, pad names, lying by kind, controls) | verify: `make clean && make extract-all JOBS=16 && make check-all JOBS=16 && .venv/bin/python tools/delever_oracle.py --calibrate ov_SC04_011 ov_SC03_015 md_SC07_004 main -j 16` | reads: — | deps: — | est-ctx: 70k | review: no | wait-for: — tools-health: `bash tools/run.sh --bg tools-health -- make tools-health` then `--wait tools-health --max 280` repeated (>15 min); delete leftover `src/shared/.cdecl_allmacros.c`; `ps` for orphaned workers after. A red item is a product-tool defect → coder fixes, commit, re-run; three distinct red items → `blocked` with the list. Order: kit-corpus → tools-health → commit → R22 → snapshot-baseline → calibrate (snapshot only after the green clean run). Record `df -h .` in the summary; do not prune scratch. - T2 | done | expert-opus55 | title: --check-structs instrument and actor-struct fix | coder: opus55 | effort: medium | files: tools/type_census.py, docs/actor-struct.md | done-when: `type_census.py --check-structs` exists per ## Interfaces; gates dup classes, outside-canon defs, dead names, variant names, legacy `pad` names (same rule as `restruct --audit-types`, imported not re-implemented), parse-error declarations (restruct ledger latest verdict `DECL-KEPT` cause `type not visible`, via restruct's own latest-verdict function), controls; known-true case: on the T1 tree it FAILS with exactly T1's baseline counts; `--selftest` gains ≥2 tests for the new mode and passes; `docs/actor-struct.md` +0x108/+0x10C and `D_80078F08/0C` corrected to 4-byte and the control line shows no `doc-disputed` rows | verify: `.venv/bin/python tools/type_census.py --selftest && .venv/bin/python tools/type_census.py --out-dir .run/P38/census --check-structs; test $? -eq 1` | reads: tasks/T1.md | deps: T1 | est-ctx: 80k | review: no | wait-for: — `CANON_HEADERS` (:55) becomes the list T3 decides; T2 makes it a single constant both census and `--check-structs` read. Do not change `--check` semantics (3.39/3.41 use it). - T3 | done | expert-fable | title: head-type layouts and canonical files | coder: opus55 | effort: high | files: src/shared/engine_types.h, src/shared/main/, src/800_shared.h, tools/type_census.py (CANON_HEADERS only), docs/struct-map-decisions.md | done-when: `Unkstruct_80126B58`, `Unkstruct_800B5CB8`, `Unkstruct_80078E00` defined once each in the canonical file(s) with every overlapping offset resolved (union or split) and a per-offset decision record citing the struct-map access evidence (`docs/struct-map-decisions.md`); canonical file set decided (ov / main / md_* / slots) and written into `CANON_HEADERS`; each layout proven on ≥2 known exemplar TUs per space via `restruct --try`/`--try-file` IDENTICAL (incl. one main and one `md_*` TU); `func_801814AC` known-true control unchanged; R22 green | verify: `.venv/bin/python tools/restruct.py --selftest --real && make clean && make extract-all JOBS=16 && make check-all JOBS=16` | reads: tasks/T1.md, tasks/T2.md | deps: T2 | est-ctx: 120k | review: no | wait-for: — Inputs: `.run/P37/restruct/types_preview/ov.h` (regenerate with `restruct.py --write-types --type --space --pointees`), `.run/P37/census/struct_map.json`, `docs/actor-struct.md`, `docs/memory-map.md` §3.4. Field names only with cited evidence (G62); else `unkHEX`. Decide whether main (`src/800_shared.h`) and `md_*` share the ov definition or carry a per-space canonical file, by the layouts' identity in the map, never by name. - T4 | done | expert-opus55 | title: fold the head types fleet-wide (rung L) | coder: opus55 | effort: medium | files: src/**/*.c, src/**/*.h (via restruct cycles) | done-when: every local definition and variant of the three head types folded onto T3's canonical definitions through judged rung-L batches (accesses rewritten where an opaque-tier fold renames members); `--check-structs` shows none of the three in any dup class or variant camp; remainder bucketed by ledger verdict (kept units listed with cause); each batch committed after its own R22 | verify: `.venv/bin/python tools/type_census.py --out-dir .run/P38/census --check-structs; .venv/bin/python tools/restruct.py --check-ledger && make clean && make extract-all JOBS=16 && make check-all JOBS=16` | reads: tasks/T2.md, tasks/T3.md | deps: T3 | est-ctx: 110k | review: no | wait-for: — Launch: cycle form in ## Context with `TASK=T4 LABEL_PREFIX=t38_4 … L`, `--only `; shared headers with `HEADERS=1` (serial). Onboard main and `md_*` in this task. `delever_oracle --calibrate` refresh when HEAD moved (the cycle does it). - T5 | superseded | expert-opus55 | title: long tail — one definition per type | coder: opus55 | effort: medium | files: src/shared/engine_types.h, src/shared/**, src/**/*.c, tools/lift_types.py | done-when: remaining duplicate classes folded via `lift_types.py --apply` and rung-L cycles; 40 variant camps uniquified; dead canonical names deleted; the 26 legacy `pad` names renamed to `unkHEX`/`pad_` form; PsyQ layouts carry Sony's SDK names; layout twins without evidence left apart and listed; `--check-structs` gating counts dup_classes/outside_canon/dead/variants/pad_names all 0 | verify: `.venv/bin/python tools/type_census.py --out-dir .run/P38/census --check-structs; make clean && make extract-all JOBS=16 && make check-all JOBS=16` | reads: tasks/T2.md, tasks/T3.md, tasks/T4.md | deps: T4 | est-ctx: 130k | review: no | wait-for: — `lift_types.py --candidates` → dry-run → `--apply` per type list; C0067's three laws and C0224 (typedef before every splice point). A tool defect in `lift_types.py` is fixed in this task (product tooling). Commit per batch (R42) before the next gate. - T5.1 | next | expert-opus55 | coder: opus55 | effort: medium | title: long tail — one definition per type (gate reading fixed) | files: src/shared/engine_types.h, include/struct_types.h, src/**, tools/lift_types.py, tools/type_census.py, docs/struct-twins.md, docs/struct-map-decisions.md | done-when: dead: type_census counts identifier uses inside the canonical headers (a canon type used as member/alias/typedef by a live canon type is not dead) and identifiers of any length; +1 selftest (member-only canon type not dead); residual dead names deleted or each listed with its invisible-use cause; dup_classes: tier-1 classes gate unchanged; a tier-2 (all-placeholder) class gates unless listed in docs/struct-twins.md with its lhash, names and a per-class cause citing struct_map base objects/addresses; census reads the file, fails on a stale lhash, prints non-gating twins_listed=N; outside_canon: a block-scope definition that declares an object in the same statement (stack frame) and sits in no gating dup class is reported non-gating as block_frames=N; every other body (ov/md/main file-scope, per-function shared headers, other headers) relocated to the canonical files (lift_types.py extended beyond ov file-scope); PsyQ layouts carry Sony SDK names; the Unkstruct_800B5CB8 split/union call made from T4's kept units (DIFFERS 29, pad3C 3) and recorded in docs/struct-map-decisions.md; --check-structs gating counts dup_classes/outside_canon/dead/variants/pad_names all 0; R22 green | verify: .venv/bin/python tools/type_census.py --selftest && .venv/bin/python tools/type_census.py --out-dir .run/P38/census --no-cache --check-structs; make clean && make extract-all JOBS=16 && make check-all JOBS=16 | reads: tasks/T2.md, tasks/T4.md, tasks/T5.md | deps: T4 | est-ctx: 130k | review: no | wait-for: — - T6 | queued | expert-opus55 | title: declaration layer on the canonical types | coder: opus55 | effort: medium | files: src/**/*.c, src/**/*.h, src/800.c | done-when: `restruct.py --redraw DECL-KEPT:type` (type-not-visible) and `DECL-KEPT:conflicting` redrawn to exhaustion and the remainder bucketed; `--check-structs` parse_error_decls = 0; the 13 `src/800.c` syntax-error units and the 45 TU-CONFLICT rows each resolved or kept with a named cause; summary reports before/after for types-floor lying (1,537), conflicting types (417), TU-CONFLICT (45) | verify: `.venv/bin/python tools/type_census.py --out-dir .run/P38/census --check-structs; .venv/bin/python tools/restruct.py --check-ledger && make clean && make extract-all JOBS=16 && make check-all JOBS=16` | reads: tasks/T1.md, tasks/T5.md | deps: T5 | est-ctx: 110k | review: no | wait-for: — Out of scope (other phases): jr-merged callers (3.44), void-def/`$v0` and zero-arg `$a0` callers, return-type lies (3.40), cast sites (3.39). Grep callers before any signature unit. - T7 | queued | expert-opus55 | title: milestone gate and record | coder: none | effort: medium | files: docs/struct-map.md, docs/levers.md, cookbook/, phase-ends/current/ | done-when: `--check-structs` exit 0 on a fresh census; R22 218/218 at the closing commit; `type_census --selftest`, `restruct --selftest --real`, `lever_progress --check` OK; `docs/struct-map.md` regenerated; cookbook §396(a) correction and the phase's generalizable gotchas added via `tools/cookbook_add.sh` | verify: `.venv/bin/python tools/type_census.py --out-dir .run/P38/census --no-cache --check-structs && make clean && make extract-all JOBS=16 && make check-all JOBS=16` | reads: tasks/T1.md, tasks/T4.md, tasks/T5.md, tasks/T6.md | deps: T6 | est-ctx: 60k | review: no | wait-for: — - T8 | queued | expert-opus55 | coder: opus55 | effort: medium | title: docs/formats.md: LIST.CD verified on US, runtime reader, repack note (issue #2) | files: docs/formats.md | done-when: docs/formats.md §2.3 marked verified on US (8 tables in order, 0xE40 bytes); §2.1 names LoaderInitFileTable (0x8001971C) and cdFileLocTable (0x800AE830) as the runtime reader; a note says repacking must rebuild LIST.CD and the ISO directory; docs link check passes; no ROM bytes added (R74) | verify: — | reads: — | deps: — | est-ctx: — | review: no | wait-for: — ## Risks - `tools-health` red on a stale tool after P36/P37 edits → T1 stalls. Detection: its log's failing stage; fix as product-tool work in T1, `blocked` if three distinct causes. - `--check-structs` measures the wrong scope (the P37 blind-spot class). Detection: T2's known-true requirement (current tree FAILS with T1's exact counts) and selftests; T7 re-runs with `--no-cache`. - An overlapping offset admits no single layout that keeps every TU IDENTICAL (union spelling changes codegen per C0507). Detection: T3's exemplar probes DIFFER; resolution in T3 is a split or a kept per-TU view, recorded; if the kept set is large, T3 returns `blocked` with counts (a scope question for REPLAN). - Header batches are serial (~35 s × 141 includers): T4/T5 wall-clock may be long. Detection: cycle logs; mitigate by batching per space, never by skipping the judge. - Disk (~22 GB free) under cycle scratch + snapshots. Detection: `df` in T1 and each cycle log; ENOSPC → stop, report; prune only `.run/P32/` scratch, never `.run/P36/delever/`. - `DECL-KEPT` remainder after T6 may be non-zero for reasons outside types (arity). Only the type-not-visible cause gates; others are reported and fall to 3.40. - Stale snapshot / early restore corrupts the tree (S108). Detection: `--apply` refusal; the SIGINT-wait-restore order in ## Context. ## Changes - 2026-09-29 developer: launch.py _audit_flag_line sort key made type-safe via natural_key (PhaseEnd_Phase3_5 crashed router seed after approval); fix applied outside the loop by a coder; upstream report filed by developer - 2026-09-29 developer: added T8 — docs/formats.md: LIST.CD verified on US, runtime reader, repack note (issue #2) - 2026-09-29 developer: T8 also corrects docs/formats.md §2.1 'length in bytes (unpadded)': lengths are multiples of 0x800 in SC01-SC07 (MAIN's are not); issue #2 reply drafted at .run/issue2-reply.md for the developer to rewrite and post - 2026-09-29 router: T1 next -> done - 2026-09-29 router: T2 next -> done - 2026-09-29 router: T3 next -> done - 2026-09-29 router: T4 next -> done - 2026-09-30 critic: reopened T5 as T5.1 — long tail — one definition per type (gate reading fixed) - 2026-09-30 router: developer: milestone reading for --check-structs: dup_classes gates tier-1 plus unlisted tier-2 (docs/struct-twins.md with per-class evidence); outside_canon excludes object-declaring block-scope stack frames (reported block_frames=N); dead counts intra-canon uses. Header line unchanged; verified by exit 0 under this reading - 2026-09-30 developer: T8 also corrects docs/formats.md §4.4 (the game does NOT expect a zero-filled LZSS ring: the ring lives in scratchpad 0x1F800000 and is never cleared, so a valid stream must not read an unwritten slot) and adds to §4.6 the rule that a type-4 stream's terminator must land in the same 2048-byte sector as in the retail file (the decoder drops the rest of that sector and the loader then checks the next sector for the PAC magic; e.g. SC01.CD sub-file 1 stream must be 407,553-409,600 bytes); evidence: src/800.c LzssDecodeSector ~5465-5515, sector callback ~7198/7315, discussion of issue #2