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.