diff --git a/config/dedup.us.yaml b/config/dedup.us.yaml index 7aeedb9b4..6b4978d52 100644 --- a/config/dedup.us.yaml +++ b/config/dedup.us.yaml @@ -13557,3 +13557,10 @@ groups: func: DEFINE_func_8017D174 vram: 0x8017D174 binaries: [ov_SC03_006, ov_SC02_016, ov_SC02_017, ov_SC03_029] + - id: E_func_80168B70 + tier: h_exact + hash: 93d5fccdcc7fc5cecee94cd364670dbcf8bb920d + source: src/shared/engine_core.h + func: DEFINE_func_80168B70 + vram: 0x80168B70 + binaries: [ov_SC07_006, ov_SC07_007, ov_SC07_010, ov_SC07_011, ov_MAIN_012, ov_SC02_037, ov_SC03_107] diff --git a/src/ov_MAIN_012/ov_MAIN_012.c b/src/ov_MAIN_012/ov_MAIN_012.c index 40d91d017..e79b3848f 100644 --- a/src/ov_MAIN_012/ov_MAIN_012.c +++ b/src/ov_MAIN_012/ov_MAIN_012.c @@ -11095,7 +11095,7 @@ void func_80168AA8(void *a0) { DEFINE_func_80168AE4() /* dedup: shared engine-core @0x80168ae4 (src/shared) */ -INCLUDE_ASM("asm/ov_MAIN_012/nonmatchings/ov_MAIN_012", func_80168B70); +DEFINE_func_80168B70() /* dedup: shared engine-core @0x80168B70 (src/shared) */ extern void func_800D2318(void); extern void RotMatrixYXZ(void *a0, void *a1); diff --git a/src/ov_SC02_037/ov_SC02_037.c b/src/ov_SC02_037/ov_SC02_037.c index b11cbeace..7b90aa1a7 100644 --- a/src/ov_SC02_037/ov_SC02_037.c +++ b/src/ov_SC02_037/ov_SC02_037.c @@ -11407,21 +11407,7 @@ DEFINE_func_80168AE4() /* dedup: shared engine-core @0x80168ae4 (src/shared) */ void func_80168BDC(s32 param_1, s32 param_2, s32 param_3, s32 param_4); void func_80146C3C(u8 *a0); -void func_80168B70(s32 a0) -{ - s32 v0; - s32 *ptr = (s32 *)a0; - - v0 = ptr[7] - 1; - ptr[7] = v0; - - if (v0 != -1) { - func_80168BDC(a0, 9, 3, 1); - } else { - func_80168BDC(a0, 9, 2, 1); - func_80146C3C((u8 *)a0); - } -} +DEFINE_func_80168B70() /* dedup: shared engine-core @0x80168B70 (src/shared) */ extern void func_800D2318(void); diff --git a/src/ov_SC03_107/ov_SC03_107.c b/src/ov_SC03_107/ov_SC03_107.c index a85acb8d2..f09fbdeb7 100644 --- a/src/ov_SC03_107/ov_SC03_107.c +++ b/src/ov_SC03_107/ov_SC03_107.c @@ -11095,7 +11095,7 @@ void func_80168AA8(void *a0) { DEFINE_func_80168AE4() /* dedup: shared engine-core @0x80168ae4 (src/shared) */ -INCLUDE_ASM("asm/ov_SC03_107/nonmatchings/ov_SC03_107", func_80168B70); +DEFINE_func_80168B70() /* dedup: shared engine-core @0x80168B70 (src/shared) */ extern void func_800D2318(void); extern void RotMatrixYXZ(void *a0, void *a1); diff --git a/src/ov_SC07_006/ov_SC07_006_jr_8015C32C.c b/src/ov_SC07_006/ov_SC07_006_jr_8015C32C.c index 2e6408e00..ec59f9bd1 100644 --- a/src/ov_SC07_006/ov_SC07_006_jr_8015C32C.c +++ b/src/ov_SC07_006/ov_SC07_006_jr_8015C32C.c @@ -7608,20 +7608,7 @@ DEFINE_func_80168AE4() /* dedup: shared engine-core @0x80168ae4 (src/shared) */ -extern void func_80168BDC(s32 a0, s32 a1, s32 a2, s32 a3); -/* Conform to the TU's canonical decl (jr_8015C32C.c:5546 `extern void ((void (*)(void))func_80146C3C)(void);`) - * and cast at the use site — same escape the TU already uses at :6388. Codegen-neutral. */ -extern void func_80146C3C(u8*); - -void func_80168B70(s32 a0) { - s32 *p = (s32 *)a0; - if (--p[0x1C / 4] != -1) { - func_80168BDC(a0, 9, 3, 1); - } else { - func_80168BDC(a0, 9, 2, 1); - ((void (*)(s32))func_80146C3C)(a0); - } -} +DEFINE_func_80168B70() /* dedup: shared engine-core @0x80168B70 (src/shared) */ extern void func_800D2318(void); diff --git a/src/ov_SC07_007/ov_SC07_007_jr_8015C32C.c b/src/ov_SC07_007/ov_SC07_007_jr_8015C32C.c index fe282eb9e..c277f2016 100644 --- a/src/ov_SC07_007/ov_SC07_007_jr_8015C32C.c +++ b/src/ov_SC07_007/ov_SC07_007_jr_8015C32C.c @@ -7546,20 +7546,7 @@ DEFINE_func_80168AE4() /* dedup: shared engine-core @0x80168ae4 (src/shared) */ -extern void func_80168BDC(s32 a0, s32 a1, s32 a2, s32 a3); -/* Conform to the TU's canonical decl (jr_8015C32C.c:5546 `extern void ((void (*)(void))func_80146C3C)(void);`) - * and cast at the use site — same escape the TU already uses at :6388. Codegen-neutral. */ -extern void func_80146C3C(u8*); - -void func_80168B70(s32 a0) { - s32 *p = (s32 *)a0; - if (--p[0x1C / 4] != -1) { - func_80168BDC(a0, 9, 3, 1); - } else { - func_80168BDC(a0, 9, 2, 1); - ((void (*)(s32))func_80146C3C)(a0); - } -} +DEFINE_func_80168B70() /* dedup: shared engine-core @0x80168B70 (src/shared) */ extern void func_800D2318(void); diff --git a/src/ov_SC07_010/ov_SC07_010_jr_8015C32C.c b/src/ov_SC07_010/ov_SC07_010_jr_8015C32C.c index e366be81f..ac27c2fde 100644 --- a/src/ov_SC07_010/ov_SC07_010_jr_8015C32C.c +++ b/src/ov_SC07_010/ov_SC07_010_jr_8015C32C.c @@ -6953,20 +6953,7 @@ DEFINE_func_80168AE4() /* dedup: shared engine-core @0x80168ae4 (src/shared) */ -extern void func_80168BDC(s32 a0, s32 a1, s32 a2, s32 a3); -/* Conform to the TU's canonical decl (jr_8015C32C.c:5546 `extern void ((void (*)(void))func_80146C3C)(void);`) - * and cast at the use site — same escape the TU already uses at :6388. Codegen-neutral. */ -extern void func_80146C3C(u8*); - -void func_80168B70(s32 a0) { - s32 *p = (s32 *)a0; - if (--p[0x1C / 4] != -1) { - func_80168BDC(a0, 9, 3, 1); - } else { - func_80168BDC(a0, 9, 2, 1); - ((void (*)(s32))func_80146C3C)(a0); - } -} +DEFINE_func_80168B70() /* dedup: shared engine-core @0x80168B70 (src/shared) */ DEFINE_func_80168BDC() /* dedup: shared engine-core @0x80168bdc (src/shared) */ diff --git a/src/ov_SC07_011/ov_SC07_011_jr_8015C32C.c b/src/ov_SC07_011/ov_SC07_011_jr_8015C32C.c index e64d8d923..e964c7dbe 100644 --- a/src/ov_SC07_011/ov_SC07_011_jr_8015C32C.c +++ b/src/ov_SC07_011/ov_SC07_011_jr_8015C32C.c @@ -7547,20 +7547,7 @@ DEFINE_func_80168AE4() /* dedup: shared engine-core @0x80168ae4 (src/shared) */ -extern void func_80168BDC(s32 a0, s32 a1, s32 a2, s32 a3); -/* Conform to the TU's canonical decl (jr_8015C32C.c:5546 `extern void ((void (*)(void))func_80146C3C)(void);`) - * and cast at the use site — same escape the TU already uses at :6388. Codegen-neutral. */ -extern void func_80146C3C(u8*); - -void func_80168B70(s32 a0) { - s32 *p = (s32 *)a0; - if (--p[0x1C / 4] != -1) { - func_80168BDC(a0, 9, 3, 1); - } else { - func_80168BDC(a0, 9, 2, 1); - ((void (*)(s32))func_80146C3C)(a0); - } -} +DEFINE_func_80168B70() /* dedup: shared engine-core @0x80168B70 (src/shared) */ extern void func_800D2318(void); diff --git a/src/shared/engine_core.h b/src/shared/engine_core.h index 134d8e4ab..40b922dc5 100644 --- a/src/shared/engine_core.h +++ b/src/shared/engine_core.h @@ -550,6 +550,21 @@ return; \ } +#define DEFINE_func_80168B70() \ + void func_80168B70(s32 a0) \ + { \ + s32 v0; \ + s32 *ptr = (s32 *)a0; \ + v0 = ptr[7] - 1; \ + ptr[7] = v0; \ + if (v0 != -1) { \ + func_80168BDC(a0, 9, 3, 1); \ + } else { \ + func_80168BDC(a0, 9, 2, 1); \ + func_80146C3C((u8 *)a0); \ + } \ + } + #endif #define DEFINE_func_80128EA8() \ @@ -30505,4 +30520,19 @@ return; \ } +#define DEFINE_func_80168B70() \ + void func_80168B70(s32 a0) \ + { \ + s32 v0; \ + s32 *ptr = (s32 *)a0; \ + v0 = ptr[7] - 1; \ + ptr[7] = v0; \ + if (v0 != -1) { \ + func_80168BDC(a0, 9, 3, 1); \ + } else { \ + func_80168BDC(a0, 9, 2, 1); \ + func_80146C3C((u8 *)a0); \ + } \ + } + #endif diff --git a/tools/dedup_propagate.py b/tools/dedup_propagate.py index 8e5935fbd..2e6e8c14b 100644 --- a/tools/dedup_propagate.py +++ b/tools/dedup_propagate.py @@ -633,6 +633,29 @@ def main(): # So on a byte-gate failure: isolate the culprit(s) for the failing overlay (per-fn trial), drop # them (they stay matched ×1 in the source), and retry the batch with the survivors. The byte-gate # stays the sole arbiter (G3/P9) — a dropped fn is never banked anywhere it isn't byte-identical. + # P30 S45p7 — RECONCILE LEDGER (the 141/213 breakage, root-caused 2026-08-07). + # Part B below deliberately LEAVES its caller-extern reconcile on disk when it buys the match + # ("keep the reconcile on disk"). That is correct only while the fn ultimately survives. A fn can + # still be dropped by a LATER iteration (a different fail_ov), and when `plan` finally empties the + # `sys.exit` at the bottom used to leave every kept reconcile orphaned — a no-proto'd caller extern + # for a function that was never propagated. Measured cost: dedup_propagate exited 1 leaving + # ov_SC07_* rewritten, and 141 of 213 binaries failed check-all (the wave-2 propagation, this + # session). The byte-gate never mis-banked — it fails closed — but every SUBSEQUENT gate then + # reports `near` against the broken tree, so its verdicts are void (R35), which is how two whole + # batches (4/4 and 20/20) were mis-read as draft failures. + # Fix: ledger every kept reconcile against its fn, and undo it the moment that fn leaves `plan`. + kept_reconciles = [] # [(addr, snapshot)] in apply order + + def _undo_reconciles(addrs): + """Restore reconciles for addrs that did not survive. Reverse order: each snapshot is the + file text captured BEFORE its own edit, so replaying newest->oldest ends on the original.""" + drop = [r for r in kept_reconciles if r[0] in addrs] + for _a, _s in reversed(drop): + restore_snapshot(_s) + if drop: + kept_reconciles[:] = [r for r in kept_reconciles if r[0] not in addrs] + return len(drop) + while plan: touched, changed = apply_plan(plan) struct_check(plan, changed, touched) @@ -665,7 +688,9 @@ def main(): pok2 = byte_gate(fail_ov)[0] if fail_ov in c3 else True restore(t3) # -> the RECONCILED text (t3 snapshot is post-reconcile) if pok2: - survivors.append(p); recovered.append(p); continue # keep the reconcile on disk + survivors.append(p); recovered.append(p) + kept_reconciles.append((p["addr"], snap)) # LEDGERED — undone if p later drops + continue # keep the reconcile on disk restore_snapshot(snap) # reconcile didn't buy the match -> undo it # Part A — exclude ONLY fail_ov from p's members (keep p for the rest); drop iff reach<2. p["members"] = [m for m in p["members"] if m != fail_ov] @@ -689,9 +714,19 @@ def main(): print(f"[drop] {fail_ov}: byte-gate fails with no single-fn culprit (interaction) — " f"dropping its {len(tofail)} fn(s), kept ×1") survivors = [p for p in plan if fail_ov not in p["members"]] + # Any fn that just left `plan` must give back its kept reconcile — otherwise a no-proto'd + # caller extern survives for a function that was never propagated (see the ledger note above). + _gone = {p["addr"] for p in plan} - {p["addr"] for p in survivors} + _n = _undo_reconciles(_gone) + if _n: + print(f"[restore] undid {_n} kept caller-extern reconcile(s) for dropped fn(s)") plan = survivors if not plan: - sys.exit("[error] all candidates dropped — no cleanly-shareable function") + # Nothing survived ⇒ NOTHING may remain edited. Restore every outstanding reconcile before + # exiting, so a failed propagation leaves the tree exactly as it found it (fail-closed). + _n = _undo_reconciles({a for a, _ in kept_reconciles}) + sys.exit("[error] all candidates dropped — no cleanly-shareable function" + + (f" (restored {_n} kept reconcile(s); tree unchanged)" if _n else " (tree unchanged)")) # ---- register groups (compact shorthand: position-locked -> vram + binaries list) groups = [dict(id=f"E_{sym(p['addr'])}", tier=a.tier, hash=p["hash"],