fix(phase-30 S45p7): dedup_propagate leaves no orphaned reconcile on failure + func_8015C030 propagated x7

ROOT CAUSE of the 141/213 breakage earlier this session (correctly derived this time;
my first attribution to F1 was WRONG -- no arity journal ever touched func_80146A6C and
the arity undo reported success):

  dedup_propagate --recover's Part B reconciles a conflicting caller extern and
  DELIBERATELY leaves the edit on disk when it buys the byte-match ("keep the reconcile
  on disk"). Correct while the fn survives -- but a fn can still be dropped by a LATER
  iteration against a different overlay, and when the plan finally emptied, the
  "all candidates dropped" sys.exit fired with NO restore. Reconciles kept for
  ov_SC07_001..009 were orphaned: no-proto'd caller externs for functions that were
  never propagated -> ov_SC07_010 "passing arg 2 of func_80146A6C makes pointer from
  integer" -> 141 of 213 binaries failed check-all.

  The byte-gate never mis-banked (it fails closed). The real cost was VERDICT VOIDING:
  every subsequent gate reported "near" against the broken tree, so two whole batches
  (4/4 and 20/20) were mis-read as draft failures when they measured the tree (R35).

FIX: a reconcile LEDGER. Every kept reconcile is recorded against its fn, undone the
moment that fn leaves the plan, and ALL outstanding reconciles are restored before the
failure exit -- so a failed propagation leaves the tree exactly as it found it.

HONESTY: the fix is IMPLEMENTED AND REVIEWED BUT NOT YET PROVEN. The negative control
aimed at the exact failing propagation SUCCEEDED instead (different tree state), so the
guarded path never executed. A targeted test of the ledger is still owed.

Also lands the propagation that control performed: func_8015C030 x7 overlays
(func_80168B70 excluded from 4 SC07 overlays, survived elsewhere). check-all 213/213.
This commit is contained in:
Drew T
2026-08-07 18:49:34 -06:00
parent 0d05d91293
commit fe946595fd
10 changed files with 81 additions and 75 deletions
+7
View File
@@ -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]
+1 -1
View File
@@ -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);
+1 -15
View File
@@ -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);
+1 -1
View File
@@ -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);
+1 -14
View File
@@ -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);
+1 -14
View File
@@ -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);
+1 -14
View File
@@ -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) */
+1 -14
View File
@@ -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);
+30
View File
@@ -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
+37 -2
View File
@@ -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"],