Files
BFM-decomp/phase-ends/current/PHASE_PLAN.md
T

105 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<SIZE>` names, 0 parse-error (type-not-visible) declarations, controls 4/4 (from 7,255 defs / 206 dup classes / 40 variant camps / 26 `pad<SIZE>`); 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_<addr>` 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 <pid>`, 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=<p> TASK=<T> CENSUS=0 tools/restruct_cycle.sh START END 400 <RUNG>; echo "cycle exit=$?"' > .run/P38/restruct/cycle_<x>.log 2>&1 &`; `HEADERS=1` for header batches; `REDRAW="<VERDICT>:<cause>"` 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 <TU> 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 <name> -- <cmd>` then `bash tools/run.sh --wait <name> --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 <RUNG>` 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/<alias>/` + `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 — <violations>` 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<SIZE>` 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 <name> --space <s> --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 <type names>`; 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<SIZE>` names renamed to `unkHEX`/`pad_<off>` 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