diff --git a/phase-ends/CURRENT_PHASE.md b/phase-ends/CURRENT_PHASE.md index affaf5a33..770e51908 100644 --- a/phase-ends/CURRENT_PHASE.md +++ b/phase-ends/CURRENT_PHASE.md @@ -2132,4 +2132,40 @@ conditional) · main-EXE/B9 + GLM/B6 + resident's 14 walls (P30) · behemoths B7 **Smoke test (3 fns, `-v`): both oracles AGREE 3/3, 0 static-only, 0 cc1-only.** Not yet evidence about the population — that is T2. +- **✅ 2026-07-24 (SESSION-16, Max) — Task 16 / T2 = S0: KILL GATE A PASSED ON MEASURED NUMBERS, and + the §20 "DEF-conflict is unrecoverable by text transform" doctrine is REFUTED for the per-overlay case.** + Whole probe over all 36 stranded drafts: **9.2 s**. Both oracles agree 36/36 (0 static-only, 0 cc1-only). + **THE POPULATION, corrected (R14).** The SESSION-15 audit's "~92% of drafts are byte-correct" is a + WHOLE-WAVE figure. Among the STRANDED residue, `match_one` says **24/36 MATCH · 11 near · 1 ERR = 67%**. + The 11 `near` are unfinished drafts, NOT integration problems — and they are exactly the 4 that + compile in their real TU and DIFF. So "we strand ~16 paid-for CORRECT functions per wave" is ~11, + not 16, and one third of the "stranded correct" premise was never correct. + **BLOCKERS (per-blocker counts; they STACK — cc1 reveals only the first):** `self_decl_hdr` **21** · + `callee_decl` 19 · `data_decl` 16 · `self_decl_tu` 5 · `local_type` 5. **Per function by MAX tier: + T0=6 · T1=26 · not-an-integration-problem=4.** The dominant blocker is the SHARED-HEADER self-decl — + far bigger than the pre-probe static inference (6–7 of 16) predicted. + **T3's baseline, measured early and for free.** Running the EXISTING draft-side ladder transforms + (`cast_call_sites` + `reconcile_tu`) over all 36 clears what they target — `callee_decl` 19→3, + `data_decl` 16→**0** — and converts exactly **1 of 36** from CC1-FAIL to compiling, which then + **DIFFs**. That is §61d reproducing verbatim: dissolving the plumbing reveals what the plumbing hid. + The existing ladder cannot bank this population; the wave's gate orchestration was NOT broken. + **THE ESCAPE, BUILT AND BYTE-PROVEN — `tools/demacroize.py` (NEW).** The conflicting `extern` lives + INSIDE a `DEFINE_func_*` macro BODY in `engine_core.h`, so it exists only where the macro is + INSTANTIATED. Replacing those instantiations, in this overlay's own TU, with the macro's own + expansion — correcting ONLY the conflicting decl to the draft's byte-true sig, never dropping it + (§57a-1) — dissolves the conflict while writing nothing outside `src/ov_SC07_006/`. This is the + "per-overlay-local decl" §63's own note named as the unexplored alternative. + **Measured on the 14 clean candidates** (match_one MATCH, `self_decl_hdr` their only remaining + blocker): **13 MATCH / 1 DIFF** in the REAL TU (1–12 macro instantiations each; `func_8014F3E8` + DIFFs at 28-vs-32 ins — a short draft, not an integration failure). + **END-TO-END PROOF (the arbiter, not the proxy):** `func_8012CC88` (6 instantiations) + → `harvest_verify --chunk 1` → **verified 1 / failed 0, BYTE-IDENTICAL**; stub gone, confirmed by + `grep INCLUDE_ASM` on the source, never the report (§55b trap 4). + **THE TIER CLAIM, VALIDATED EMPIRICALLY:** full R22 `make clean && extract-all && check-all` → + **140 passed, 0 failed of 140** (80 s). A `src//**`-confined edit does not perturb the + fleet — which is precisely the difference from `fix_header_decl`, whose `src/shared/` write broke + 139/140 under the same per-binary green light (§63 UPDATE). + **GATE A:** cond-1 ≥12/36 with a real decl/type error → **32/36 PASS**; cond-2 ≥12/36 rtu-MATCH + once the blocker is simulated → **13/36 PASS**. Proceed. + diff --git a/src/ov_SC07_006/ov_SC07_006.c b/src/ov_SC07_006/ov_SC07_006.c index c30a1a5f8..3a7547261 100644 --- a/src/ov_SC07_006/ov_SC07_006.c +++ b/src/ov_SC07_006/ov_SC07_006.c @@ -1206,25 +1206,101 @@ DEFINE_func_8012CAE4() /* dedup: shared engine-core @0x8012cae4 (src/shared) */ DEFINE_func_8012CB64() /* dedup: shared engine-core @0x8012cb64 (src/shared) */ -DEFINE_func_8012CBA4() /* dedup: shared engine-core @0x8012cba4 (src/shared) */ +/* de-macroized: per-overlay-local decl for func_8012CC88 (byte-true sig); do NOT re-macroize */ + extern s32 func_8012CC88(s32, s32, s32); + extern u8 D_800D3918[]; + void func_8012CBA4(s32 a0) { + func_8012CC88(a0, 0, (s32)D_800D3918); + } /* dedup: shared engine-core @0x8012cba4 (src/shared) */ -DEFINE_func_8012CBCC() /* dedup: shared engine-core @0x8012cbcc (src/shared) */ +/* de-macroized: per-overlay-local decl for func_8012CC88 (byte-true sig); do NOT re-macroize */ + extern s32 func_8012CC88(s32, s32, s32); + extern u8 D_800D3918[]; + void func_8012CBCC(s32 a0) { + func_8012CC88(a0, 1, (s32)D_800D3918); + } /* dedup: shared engine-core @0x8012cbcc (src/shared) */ -DEFINE_func_8012CBF4() /* dedup: shared engine-core @0x8012cbf4 (src/shared) */ +/* de-macroized: per-overlay-local decl for func_8012CC88 (byte-true sig); do NOT re-macroize */ + extern u8 D_800D3918[]; + extern s32 func_8012CC88(s32, s32, s32); + void func_8012CBF4(s32 a0) { + func_8012CC88(a0, 0x11, (s32)D_800D3918); + } /* dedup: shared engine-core @0x8012cbf4 (src/shared) */ -DEFINE_func_8012CC1C() /* dedup: shared engine-core @0x8012cc1c (src/shared) */ +/* de-macroized: per-overlay-local decl for func_8012CC88 (byte-true sig); do NOT re-macroize */ + extern s32 func_8012CC88(s32, s32, s32); + void func_8012CC1C(s32 arg0, s32 arg1) { + func_8012CC88(arg0, 0, arg1); + } /* dedup: shared engine-core @0x8012cc1c (src/shared) */ -DEFINE_func_8012CC40() /* dedup: shared engine-core @0x8012cc40 (src/shared) */ +/* de-macroized: per-overlay-local decl for func_8012CC88 (byte-true sig); do NOT re-macroize */ + extern s32 func_8012CC88(s32, s32, s32); + void func_8012CC40(s32 arg0, s32 arg1) { + func_8012CC88(arg0, 1, arg1); + } /* dedup: shared engine-core @0x8012cc40 (src/shared) */ -DEFINE_func_8012CC64() /* dedup: shared engine-core @0x8012cc64 (src/shared) */ +/* de-macroized: per-overlay-local decl for func_8012CC88 (byte-true sig); do NOT re-macroize */ + extern s32 func_8012CC88(s32, s32, s32); + void func_8012CC64(s32 a0, s32 a1) { + func_8012CC88(a0, 0x11, a1); + } /* dedup: shared engine-core @0x8012cc64 (src/shared) */ -INCLUDE_ASM("asm/ov_SC07_006/nonmatchings/ov_SC07_006", func_8012CC88); +// @class: other +// @stuck: none — MATCH (105 ins). BANK NOTE: engine_core.h declares `extern void func_8012CC88(s32,s32,s32)` at 5 sites; the return value is REAL (void-return probe = 102 ins / 13 mismatch, LENGTH-DRIFT -3), so the HEADER decl must be lifted void->s32 (§63 fix_header_decl.py), not the draft. +#include "common.h" + +extern s32 func_8012CEB0(s32 a0, s32 a1, s32 a2); + +/* a0 = actor/entity base, a1 = mode passed through to func_8012CEB0, a2 = s16[3] offset vector. + * Two 8-byte s16 vectors live at sp+0x10 and sp+0x18 and are passed by address; the second one + * is written back to the entity's hi-16 fixed-point position after the offset is removed. */ +s32 func_8012CC88(s32 a0, s32 a1, s32 a2) { + typedef struct { s16 vx, vy, vz, pad; } SV3_8012CC88; + SV3_8012CC88 sp10; + SV3_8012CC88 sp18; + s32 v0; + + sp10.vx = *(u16*)(a0 + 0x3A); + sp10.vy = *(u16*)(a0 + 0x3E); + sp10.vz = *(u16*)(a0 + 0x42); + sp10.vx += *(u16*)(a2 + 0); + sp10.vy += *(u16*)(a2 + 2); + sp10.vz += *(u16*)(a2 + 4); + + *(s32*)(a0 + 0x10) += *(s32*)(a0 + 0x44); + *(s32*)(a0 + 0x14) += *(s32*)(a0 + 0x48); + *(s32*)(a0 + 0x18) += *(s32*)(a0 + 0x4C); + *(s32*)(a0 + 0x04) += *(s32*)(a0 + 0x10); + *(s32*)(a0 + 0x08) += *(s32*)(a0 + 0x14); + *(s32*)(a0 + 0x0C) += *(s32*)(a0 + 0x18); + + sp18.vx = *(u16*)(a0 + 0x06); + sp18.vy = *(u16*)(a0 + 0x0A); + sp18.vz = *(u16*)(a0 + 0x0E); + sp18.vx += *(u16*)(a2 + 0); + sp18.vy += *(u16*)(a2 + 2); + sp18.vz += *(u16*)(a2 + 4); + + v0 = func_8012CEB0((s32)&sp10, (s32)&sp18, a1); + + sp18.vx -= *(u16*)(a2 + 0); + sp18.vy -= *(u16*)(a2 + 2); + sp18.vz -= *(u16*)(a2 + 4); + *(s16*)(a0 + 0x06) = sp18.vx; + *(s16*)(a0 + 0x0A) = sp18.vy; + *(s16*)(a0 + 0x0E) = sp18.vz; + + if (v0 & 0x6000) { + *(s32*)(a0 + 0x14) = 0; + } + return v0; +} DEFINE_func_8012CE2C() /* dedup: shared engine-core @0x8012ce2c (src/shared) */ diff --git a/tools/blocker_probe.py b/tools/blocker_probe.py index c225867e6..dae0c47c4 100644 --- a/tools/blocker_probe.py +++ b/tools/blocker_probe.py @@ -138,6 +138,11 @@ def static_verdict(fn, stub, draft_text): if d.is_definition and d.name == fn: out.append(('self_decl_hdr' if src.startswith('hdr') else 'self_decl_tu', '%s declares %s | def is %s' % (src, other.type, d.type))) + elif 'typedef' in (d.storage, other.storage): + # cc1 forbids redeclaring a typedef AT ALL, even identically (`compatible` encodes it). + # This is a TYPE conflict, not a data conflict: the fix is to rename/drop the draft's + # copy, which is draft-only — routing it as `data_decl` would send it at the wrong tool. + out.append(('local_type', '%s: %s declares %s | draft %s' % (d.name, src, other.type, d.type))) else: klass = 'callee_decl' if d.kind == 'func' else 'data_decl' out.append((klass, '%s: %s declares %s | draft %s' % (d.name, src, other.type, d.type))) diff --git a/tools/demacroize.py b/tools/demacroize.py new file mode 100644 index 000000000..1fd966816 --- /dev/null +++ b/tools/demacroize.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +"""demacroize.py — the per-overlay-local escape from a shared-header self-decl conflict. + +THE BLOCKER. A byte-correct draft for `func_X` fails the whole-binary gate with +`conflicting types for func_X`, because a `DEFINE_func_*` macro in `src/shared/engine_core.h` +forward-declares `func_X` with a caller-derived signature the byte-true definition cannot +satisfy (typically `void` where the real return is live). This is the single largest stranded +class. + +WHY THE OBVIOUS FIXES ARE WRONG. + * Rewriting the DRAFT to the header's signature compiles but byte-MISMATCHes — the body + genuinely needs its own signature (§20; §58b: forcing a `void` return dropped the return + computation). The draft is byte-truth; the header decl is a stub-era guess. + * Rewriting the SHARED HEADER decl (`fix_header_decl`) is fleet-blind: it passed the + per-binary gate on 3 of 3 and then failed R22 139/140, because that decl reaches ~137 other + overlays where those functions are ALREADY matched and a caller's codegen shifts under the + widened declaration (§63 UPDATE). Off-limits. + +THE ESCAPE. The conflicting `extern` lives INSIDE the macro BODY, so it only exists where the +macro is INSTANTIATED. Replacing that instantiation, in this overlay's own TU, with the macro's +own expansion — with only the conflicting declaration corrected to the draft's byte-true +signature — dissolves the conflict while writing nothing outside `src//`. Every other +overlay is textually untouched, so no R22 risk is created by construction. This is the +"per-overlay-local decl" §63's own note named as the unexplored alternative. + +THE PRICE, STATED HONESTLY. The de-macroized function can no longer be propagated ×138 from the +shared macro (`dedup_propagate` would re-macroize it). A bank recovered this way is ×1: it +credits the FULL distinct-code unit (progress.py marks an h_exact class matched if ANY instance +is) but only ~1/138 of the instruction-weighted headline. + +CAVEAT (why --apply must still be byte-gated). The macro's own function is ALREADY matched in +this binary. Correcting the declaration it sees can shift ITS codegen — the §63 failure mode, +relocated somewhere the per-binary gate can actually see it. The whole-binary byte-gate is the +arbiter (G3/P9); this tool only prepares the edit. + + tools/demacroize.py --binary ov_SC07_006 --fn func_8012CC88 --draft --emit-edits + tools/demacroize.py --binary ov_SC07_006 --fn func_8012CC88 --draft --apply +""" +import argparse, os, re, sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +REPO = os.path.dirname(HERE) +sys.path.insert(0, HERE) +import cdecl +import corpus + +ENGINE_CORE = 'src/shared/engine_core.h' + + +def macro_bodies(header=ENGINE_CORE): + """{macro: body_text} for every DEFINE_func_* in the shared header (continuations joined).""" + text = open(os.path.join(REPO, header), errors='replace').read() + out = {} + for m in re.finditer(r'^#define\s+(DEFINE_func_[0-9A-Fa-f]+)\s*\(\s*\)', text, re.M): + i, body = text.find('\n', m.end()), [] + if i < 0: + continue + i += 1 + while i < len(text): + nl = text.find('\n', i) + line = text[i:nl] if nl >= 0 else text[i:] + body.append(line.rstrip().rstrip('\\').rstrip()) + if not line.rstrip().endswith('\\') or nl < 0: + break + i = nl + 1 + out[m.group(1)] = '\n'.join(body) + return out + + +def draft_decl(fn, draft_text): + """`extern ;` reconstructed from the draft's own definition (cdecl renders it, + so the spelling is canonical rather than copied source text).""" + for d in cdecl.declarations(cdecl.split_statements(draft_text), path=''): + if d.name == fn and d.is_definition: + return d.declaration(storage='extern') + raise SystemExit('demacroize: no definition of %s in the draft' % fn) + + +def plan(binary, fn, draft_text): + """[(macro, old_instantiation, new_expansion)] — every macro instantiated in fn's home TU + whose body declares fn incompatibly with the draft's definition.""" + stub = {s.symbol: s for s in corpus.stubs(binary).values()}.get(fn) + if stub is None: + raise SystemExit('demacroize: %s is not an INCLUDE_ASM stub in %s (already banked?)' % (fn, binary)) + tu_path = os.path.join(REPO, stub.path) + tu_text = open(tu_path, errors='replace').read() + want = draft_decl(fn, draft_text) + want_d = cdecl.parse(want)[0] + + out = [] + for macro, body in sorted(macro_bodies().items()): + if not re.search(r'\b%s\s*\(\s*\)' % re.escape(macro), tu_text): + continue # not instantiated in THIS TU + hit = None + for stmt in re.findall(r'\bextern\b[^;{}]*;', body): + try: + ds = cdecl.parse(stmt) + except cdecl.CDeclError: + continue + for d in ds: + if d.name == fn and not (cdecl.compatible(d, want_d) and cdecl.compatible(want_d, d)): + hit = stmt + if hit is None: + continue + # Correct ONLY the conflicting declaration; never DROP it (§57a correction 1 — dropping a + # decl breaks the def-after-caller order the caller relies on). + out.append((macro, stub, hit, body.replace(hit, want, 1))) + return tu_path, out + + +def main(): + ap = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument('--binary', required=True) + ap.add_argument('--fn', required=True) + ap.add_argument('--draft', required=True) + g = ap.add_mutually_exclusive_group(required=True) + g.add_argument('--emit-edits', action='store_true', help='print //@EDIT lines for rtu_match (read-only)') + g.add_argument('--apply', action='store_true', help='rewrite the TU in place (T1; byte-gate after)') + a = ap.parse_args() + + draft_text = open(a.draft, errors='replace').read() + tu_path, items = plan(a.binary, a.fn, draft_text) + if not items: + print('demacroize: nothing to do for %s (no instantiated macro declares it incompatibly)' % a.fn) + return 1 + + if a.emit_edits: + for macro, _stub, _old, new in items: + # rtu_match applies these to the TU before splicing; \n is unescaped there. + print('//@EDIT %s()||%s' % (macro, new.replace('\\', '\\\\').replace('\n', '\\n'))) + print('// %d macro instantiation(s) in %s' % (len(items), os.path.relpath(tu_path, REPO)), + file=sys.stderr) + return 0 + + text = open(tu_path, errors='replace').read() + for macro, _stub, _old, new in items: + pat = re.compile(r'^([ \t]*)%s\(\s*\)' % re.escape(macro), re.M) + if not pat.search(text): + raise SystemExit('demacroize: instantiation of %s not found for in-place edit' % macro) + text = pat.sub(lambda m: '%s/* de-macroized: per-overlay-local decl for %s (byte-true sig); ' + 'do NOT re-macroize */\n%s' % (m.group(1), a.fn, new), text, count=1) + open(tu_path, 'w').write(text) + print('demacroize: expanded %d macro(s) in %s for %s' + % (len(items), os.path.relpath(tu_path, REPO), a.fn)) + return 0 + + +if __name__ == '__main__': + sys.exit(main())