Files
BFM-decomp/tools/macro_draft.py
T
Drew T 9ab9120e04 feat(phase-30 S47-B): conform 8 declaration axes (~10,930 sites); 3 guard defects fixed; 213/213
Task B, re-scoped from evidence. The 129 dedup_extend failures are 106 conflicting-types /
21 CC1-FAIL / 4 undefined-ref / 3 DIFF — real byte divergence is 2%, and memcpy is 17 of 106,
not the story. Direction reversed too: the byte-true DEF of func_80128ED8 is what the target
.c files already declare; engine_core.h's macro-local extern was the stub-era guess.

Conformed 8 axes to byte-truth (func_8012F14C 2843, func_8012E5CC 2052, func_8012F038 2214,
func_8014C568 1816, func_80128ED8 1524, func_8012C750 406, func_8012C0EC 50, func_80144A04 25).
R22 clean-fleet: check-all 213 passed / 0 failed of 213. Zero functions banked by design.

Tooling (R33/R35) — three guards that asserted completeness over a narrowed population:
- NEW tools/macro_draft.py: a deduped fn has no definition in any .c (body lives in a DEFINE_
  macro), so conform_decls had been refusing the largest class it was built for.
- conform_decls skipped engine_core.h wholesale as "a defining TU": 10 stale externs survived
  while 1,514 fleet sites moved, and it still printed "axis complete". Skip now scoped to the
  defining macro's span.
- Return-axis compare was literal: typedef int/s32 and a missing `extern` faked a return change.
  Now compares normalized types.
- §85 consumer scan under-reported (the dangerous direction): a cast between `=` and the call
  hid `s0 = (s32 *)func_80144A04(...)`. Now classified by position, validated both ways.

Corrections to my own predictions (R14): the documented scalar-narrowing hazard was benign
across 2,052 sites; the breaks were arity (6 call sites, fixed with §17a-1 fn-ptr casts) and
the consumer-guard gap. A header-only first probe broke ov_SC01_000 — §85 is literal.

Not done, named: memcpy (builtin codegen), ApplyMatrixSV (no DEF), gte_SetRotMatrix (link bug),
func_80147364 (unparseable macro), D_800AE620/D_80126CC4 (data axis). Cookbook §159.
2026-08-10 16:01:49 -06:00

81 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""macro_draft.py — materialize a `DEFINE_func_XXXX()` macro body as a compilable draft .c
WHY THIS EXISTS (Phase 30 S47)
==============================
`conform_decls.py` needs a **byte-true DEFINITION** to conform the fleet's declarations to, and it
refuses (correctly) when handed a file that holds only a declaration. But for a DEDUPED function the
definition does not live in any .c file at all — it lives inside a `#define DEFINE_func_X() \\ ...`
macro in `src/shared/engine_core.h`, where two things defeat every definition parser:
* every line ends in a backslash continuation, and
* the definition line is INDENTED, so `^([A-Za-z_]...)` never matches.
So the single largest class of `conflicting types for func_X` — a deduped callee whose macro-local
`extern` disagrees with the fleet's — was unreachable by the tool built to fix exactly that class.
This bridges the two: it emits the macro body verbatim (dedented, continuations stripped) so the
existing conformer can read the signature it already knows how to read.
VERBATIM IS THE POINT. The draft is evidence, not a rewrite: nothing is reformatted, no types are
"cleaned up". If this file ever starts editing the body it stops being byte-truth (R33/R35).
Usage:
tools/macro_draft.py --fn func_80128ED8 [--out .run/draft.c] # default: .run/macro_draft_<fn>.c
"""
import argparse, os, re, sys, textwrap
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
HDR = os.path.join(REPO, "src", "shared", "engine_core.h")
def extract(fn, header=HDR):
"""(body_text, first_line, last_line) of DEFINE_<fn>()'s macro body, 1-indexed; None if absent."""
lines = open(header, errors="replace").read().split("\n")
for i, l in enumerate(lines):
if re.match(rf"\s*#define\s+DEFINE_{re.escape(fn)}\(\)", l):
j = i
while j < len(lines) and lines[j].rstrip().endswith("\\"):
j += 1
raw = lines[i:j + 1]
# strip the trailing backslash continuation from every line, drop the #define line
body = [x.rstrip()[:-1].rstrip() if x.rstrip().endswith("\\") else x for x in raw][1:]
return textwrap.dedent("\n".join(body)), i + 1, j + 1
return None
def main():
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--fn", required=True)
ap.add_argument("--out")
ap.add_argument("--header", default=HDR)
a = ap.parse_args()
got = extract(a.fn, a.header)
if not got:
print(f"macro_draft: no DEFINE_{a.fn}() in {a.header}", file=sys.stderr)
return 2
body, lo, hi = got
# R32: prove the emitted text actually contains a DEFINITION of the requested function, at
# column 0, where a definition parser can see it. A dedent that leaves it indented is a silent
# no-op that would surface later as "no DEFINITION" from the conformer.
if not re.search(rf"^[A-Za-z_][\w \t\*]*\b{re.escape(a.fn)}\s*\(", body, re.M):
print(f"macro_draft: DEFINE_{a.fn}() body carries no column-0 definition of {a.fn} "
f"(header lines {lo}-{hi}) — refusing to emit a draft that cannot be parsed",
file=sys.stderr)
return 2
out = a.out or os.path.join(REPO, ".run", f"macro_draft_{a.fn}.c")
os.makedirs(os.path.dirname(out), exist_ok=True)
with open(out, "w") as f:
f.write(f'/* verbatim body of DEFINE_{a.fn}() — {a.header} lines {lo}-{hi} */\n')
f.write('#include "engine_types.h"\n')
f.write(body.rstrip() + "\n")
print(out)
return 0
if __name__ == "__main__":
sys.exit(main())