Files
BFM-decomp/cookbook/C0224.md
T

3.7 KiB

§203 — A DEDUPED TYPEDEF MUST PRECEDE EVERY SPLICE POINT, NOT JUST ITS OWN (P31 S56)

The defect. strip_provided_typedefs (harvest_verify:225) removes from a draft every typedef the destination TU already provides. That is right, and it is address-blind — which is wrong when a slate banks two functions that share a type.

Wave Z's md_MAIN_034 group banked 6 of 7. The drop, func_800CC310, failed with:

PLUMBING: src/md_MAIN_034/md_MAIN_034.c:547: parse error before `D_800CCAD0'

func_800CC4E8 — same slate, same batch — banked a Quad4_800CCB14 typedef into the TU. The stripper then correctly deleted func_800CC310's duplicate copy as "already provided". But func_800CC310 splices EARLIER in address order (0x800CC310 < 0x800CC4E8), so the surviving definition sat ~20 lines BELOW the externs that needed it. The type was provided, just not yet.

The wrong fix, and why the gate caught it. Renaming the draft's typedef so the stripper spares it (Quad4_800CCB14 -> Quad4_800CCAD0) moves the error rather than removing it: the draft then declares extern Quad4_800CCAD0 D_800CCB14; while the TU declares extern Quad4_800CCB14 D_800CCB14; — one symbol, two types, and the failure simply walks to the second line. A dedup fix that introduces a second name for one type is not a fix.

The fix. Hoist the shared typedef to the top of the TU, above every splice point, and let the stripper delete the draft's copy as designed:

#include "common.h"

/* HOISTED: defined here rather than beside its first banker, because an
 * EARLIER-addressed function's draft declares externs of this type. */
typedef struct { u8 f0; u8 f1; u8 f2; u8 f3; } Quad4_800CCB14;

Banked on the next gate (commit fd7d26e3c).

Correction, recorded because it bit the writer of this section (P31 S56). The text that banked is the RENAMED variant, not the original draft — gate_stage calls backlog.save_draft() on failure, so the failed rename attempt OVERWROTE .run/backlog_drafts/func_800CC310.c, and copying "the original" back copied the rename. The BYTES are correct (whole-binary gate + R22 213/213), but the TU now carries two names for one 4-byte shape, and a symbol-rewriting transform mangled prose inside a comment (not (*(Quad4_800CCAD0 *)&D_800CCB14)). Two lessons: a backlog draft path is not a stable original — snapshot the text you mean to re-gate; and a transform that rewrites symbols must skip comments (H5).

The general law. A type shared by two functions in one TU belongs at the TOP of that TU, not beside whichever of them happened to bank first. pregate_check already hoists for main's gate_main driver (it hoisted one for ov_SC03_094 in this very wave) — it does not for harvest_verify's module/overlay driver, which is the third-driver gap the S55 checkpoint warned about. Until it does, a slate with two functions sharing a typedef needs the hoist by hand.

Diagnostic order for this class (R38, learned the expensive way). The verdict was already on disk in .run/harvest_failed.<binary>.classified.txt before any investigation started. Reading it first would have cost one command. Instead: reloc_identity (AGREE, 13 relocs — correct and irrelevant), then a disassembly of the built .elf showing 69/69 instructions identical — a clean-looking result that was pure artifact, because the draft never compiled and the .elf therefore still held the original INCLUDE_ASM bytes. A byte-diff against a build that failed to include your draft is a diff against the target and itself: it always reads MATCH. Check the classified ledger before any oracle, and confirm a build actually consumed your source before believing any diff taken from it.