Files
BFM-decomp/cookbook/C0068.md
T

4.4 KiB

§64a — VARIANT types: UNIQUIFY the camps, do not reconcile them (uniquify_type.py, Phase 29 SESSION-14)

The trap. §64 defers any type with 2+ distinct definitions as a VARIANT, and the obvious next move reads as "pick the canonical layout and reconcile the other camps' field access." For most of these names that is actively WRONG. Measured on this fleet:

Vec8 = { s32 w[8] }                 180 files  (32 bytes)
Vec8 = { s16 unk0,unk2,unk4,unk6 }  139 files  ( 8 bytes)
MATRIX = { s32 m[3][3]; s32 t[3] }  578 files  |  { short m[3][3]; long t[3] }  71 files
Buf    = { s16 h[8] }               578 files  |  a 0x20+ struct  6 files  |  { DrawEnv env; … }  1

These are not one type with two spellings — they are different types that happen to share an identifier in different TUs of the same overlay (each TU carried its own local guess, which is exactly why they diverged). "Reconciling" them MERGES two layouts and silently repoints the minority camp's TUs at the wrong struct — the same failure mode that broke 103 binaries on Prim (§64 Law 3).

The correct operation is the opposite: keep both layouts, give them distinct NAMES.

reconcile  -> merges two layouts     -> breaks the minority camp   WRONG
uniquify   -> preserves both layouts -> every camp becomes liftable  RIGHT

Renaming a type is byte-neutral (a type name emits no code) and TU-local by construction: the definition and all its uses live in the same file. Once each camp is uniquely named it has exactly ONE definition fleet-wide, so lift_types.py lifts them all by its ordinary rules and the §20 propagation cap lifts with them. tools/uniquify_type.py --type <T> [--apply] does it: deterministic camp ordering (file-count desc, then normalized text, so re-runs assign the same suffixes), majority keeps the name, camp n becomes <T>_c<n>, and it rewrites ONLY files that DEFINE that camp — a file that merely uses the name is getting the type from elsewhere and must keep referring to it (\bT\b word boundaries also keep Buf from matching Buf80153978).

Validated on Buf (the cheapest camp: 578 / 6 / 1 files). 11 identifiers rewritten across 7 files → 3 camps all LIFTABLE → lifted (585 local copies stripped) → R22 140/140 byte-identical → the blocked core queue fell 13 → 11 (0x8012ea90, 0x801749c8 freed, both Buf-blocked). Recipe: uniquify_type --apply → lift_types --candidates → --types <camps> --apply → pre-filter build → R22 → dedup_propagate --addr. Remaining camps by cost: MATRIX (578/71/6), Vec8 (180/139 — no clear minority, so expect to name BOTH camps), then Handler / Blk8 / V8 / Prim / Prim_8016E7C8.

Also fixed here (R32): dedup_propagate's skip line printed a COUNT and no names, and aggregated three unrelated causes into n_local — a body skipped merely for containing a // comment or a line continuation (macro-unsafe, a one-line fix) was reported identically to one genuinely using an overlay-local type. The queue is now named and split by cause, so the work it represents is visible.

§63 UPDATE (Phase 29 SESSION-14) — fix_header_decl's "SAFE" verdict is FLEET-BLIND; it MUST be R22-validated

The §63 header-decl reconcile (void→s32 widening of a shared engine_core.h caller decl) banked 3 of 3 fresh cracks under the PER-BINARY gate (gate_stage --no-propagate on ov_SC07_006) — then R22 clean-fleet FAILED (139/140): ov_SC01_077 broke. Reverting the biggest-fanout decl (func_8012CC88, 12 refs) did NOT fix it — at least one of the 2-ref void→s32 widenings (func_8014D12C/func_8014CF04) ALSO perturbs ov_SC01_077, where those functions are already matched and a caller's codegen shifts under the widened (even no-proto ()) shared decl. fix_header_decl --check's [SAFE] verdict only inspects the ONE caller's return-use; it is structurally blind to the other ~137 overlays the shared decl reaches. RULE: a fix_header_decl (or any engine_core.h decl) edit is a §61 shared-state mutation — validate it with a full R22 clean-fleet, NEVER the per-binary gate that authorised it. The per-binary gate is a NECESSARY-not-sufficient filter here. The byte-gate caught it; the whole recovery pass was reverted, 0 broken landed. The 3 drafts are byte-correct in isolation — they need a per-overlay-local decl or an alternate integration path (not a fleet-shared header widen), logged to the backlog.