mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 21:36:06 -04:00
fix(phase-29): the undo was EATING the tree — snapshot src too; kill the §58 label; giant func_8018F694 banked
THE DEFECT (byte-witnessed, and it is the §61c mechanism). `jr_isolate_all`
repartitions a code object by writing region 0 back over the ORIGINAL
src/<ov>/<nm>.c — TRUNCATED to just that region — and emitting the rest as new
_jr_<lo>.c files. `_jtbl_restore` undid only config/ + the new region files, so
every gate-REJECTED draft left the original TU permanently truncated and its
stubs gone. Nothing regenerates them (splat does not rewrite a committed overlay
.c). Measured across an 11-draft re-probe: live stubs 419 -> 414 -> 406 -> 395,
ending in `undefined reference to func_80191C50`.
It is INVISIBLE to the gate that causes it: the incremental build keeps linking
stale objects (§42b) so `make build` stays green while a CLEAN rebuild fails.
That is exactly the "139/140, twice" signature that became the §61c blocker —
the tree was being eaten by the undo meant to protect it. The §61c attribution
(batch _jtbl_prep residue) is now a demonstrated defect, not an inference.
FIXES (both negative-control-validated on a draft that fails):
- _jtbl_snapshot captures every src/<binary>/*.c; _jtbl_restore restores them and
removes exactly the files the attempt created (derived from the snapshot's file
set, not re-guessed from the _jr_* name shape, R33). Undo by SNAPSHOT-RESTORE,
never an inverse transform — §61's law one level deeper. Same failing draft that
previously broke the tree now leaves it byte-identical, git status clean.
- classify_fail ignores `warning:` lines. The benign `conflicting types for
built-in function 'memcpy'` warning was winning the match on 8 of 8 failures
across four different real causes — a label identical for every input, which the
cookbook had to work around by hand ("the gate label is useless here, splice
individually and read real cc1 stderr"). Now reports the real error, and falls
through to CC1-FAIL:<last error line> rather than guessing.
THE RE-PROBE THIS ENABLED (11 preserved t5wave cracks, one invocation each):
- BANKED: func_8018F694 (478 ins) — one of the wave's three giants, previously
recorded as part of "the gate banked ZERO".
- The other 10 now carry TEN DISTINCT diagnoses: 4 data-decl conflicts (D_80193B64
x2, D_8011D030, D_80126B5C), 3 callee-decl (func_80135480 x2, func_8012F14C),
3 self-decl/own-sig (§57). ZERO jtbl-drift, ZERO local-type redefinition, ZERO
codegen DIFF.
- So §61a's "§8e-2 jtbl table-count drift blocks 10 of 12" does NOT survive the
carve-follows-splice prep: the carve now succeeds and what is left is ordinary
decl plumbing the existing ladder already handles (cast_call_sites / reconcile_tu
/ normalize_self_decls / fix_arity_callers) = Task 14 stages 2-3, no new tooling.
- ov_SC06_018 BYTE-IDENTICAL cbbc4f44 with the giant banked; nothing committed broken
at any point (tree was restored from HEAD and re-verified before these fixes)
- cookbook: the `git add -u` complementary hole (an isolation's NEW region file is
untracked, so a carve/isolation bank needs `git add -A src/ config/`)
This commit is contained in:
@@ -4462,6 +4462,12 @@ diverged.
|
||||
**the moment it banks**. Fix: `git add -u src/` (every modified tracked file under src/), which also
|
||||
retires the `src/ov_*/*.c` filename glob that once omitted 4 R22-verified banks from a commit because a
|
||||
family's members do not all live in the same-named split.
|
||||
**COMPLEMENTARY HOLE (2026-07-22): `git add -u` misses the NEW files an ISOLATION creates.** A jtbl
|
||||
sweep cuts a fresh region file per sibling (`src/<ov>/<ov>_jr_<ADDR>.c`), which is UNTRACKED — so
|
||||
`-u` commits the modified TU and drops the file holding the banked body, i.e. a tree that cannot
|
||||
clean-rebuild. For any carve/isolation bank use **`git add -A src/ config/`**. `jtbl_family_bank`
|
||||
already refuses to sweep on an uncommitted `config/`+`src/` (its per-sibling revert restores from
|
||||
HEAD), and that guard is what caught this — a fail-loud precondition doing exactly its job.
|
||||
2. **The `_xform` ladder dirs accumulate.** `<drafts>-cn/-cast/-rc/-uni` are reused across runs and the
|
||||
transform tools only write the drafts they are handed, so every stale draft from every previous run
|
||||
survives and is re-submitted to the byte-gate. Measured: the grinder submitted **1** draft, the gate
|
||||
|
||||
@@ -4290,7 +4290,185 @@ INCLUDE_ASM("asm/ov_SC06_018/nonmatchings/ov_SC06_018_jr_8017C24C", func_8018EDB
|
||||
|
||||
INCLUDE_ASM("asm/ov_SC06_018/nonmatchings/ov_SC06_018_jr_8017C24C", func_8018F060);
|
||||
|
||||
INCLUDE_ASM("asm/ov_SC06_018/nonmatchings/ov_SC06_018_jr_8017C24C", func_8018F694);
|
||||
// @class: other
|
||||
// @stuck: none — MATCH (478 ins, relocation-masked byte-identical)
|
||||
|
||||
#define gte_ldv0(r0) __asm__ volatile ( \
|
||||
"lwc2 $0, 0( %0 );" \
|
||||
"lwc2 $1, 4( %0 )" \
|
||||
: \
|
||||
: "r"( r0 ) )
|
||||
|
||||
#define gte_rtps() __asm__ volatile ("nop;nop;rtps")
|
||||
|
||||
#define gte_stsxy(r0) __asm__ volatile ( \
|
||||
"swc2 $14, 0( %0 )" \
|
||||
: \
|
||||
: "r"( r0 ) \
|
||||
: "memory" )
|
||||
|
||||
#define gte_stszotz(r0) __asm__ volatile ( \
|
||||
"mfc2 $12, $19;" \
|
||||
"nop;" \
|
||||
"sra $12, $12, 2;" \
|
||||
"sw $12, 0( %0 )" \
|
||||
: \
|
||||
: "r"( r0 ) \
|
||||
: "$12", "memory" )
|
||||
|
||||
#define gte_stflg(r0) __asm__ volatile ( \
|
||||
"cfc2 $12, $31;" \
|
||||
"nop;" \
|
||||
"sw $12, 0( %0 )" \
|
||||
: \
|
||||
: "r"( r0 ) \
|
||||
: "$12", "memory" )
|
||||
|
||||
typedef struct { u16 vx, vy, vz, pad; } UVEC_8018F694;
|
||||
typedef struct { u32 addr : 24; u8 len; } PTag_8018F694;
|
||||
typedef struct {
|
||||
u32 addr : 24;
|
||||
u8 len;
|
||||
u8 r0, g0, b0, code;
|
||||
s16 x0, y0, x1, y1;
|
||||
} LineF2_8018F694;
|
||||
typedef struct {
|
||||
u32 addr : 24;
|
||||
u8 len;
|
||||
u32 code0;
|
||||
} TPage_8018F694;
|
||||
typedef struct {
|
||||
u8 r, g, b, pad0;
|
||||
u8 dr, dg, db, pad1;
|
||||
s16 vx, vy, vz, tm;
|
||||
} Blip_8018F694;
|
||||
|
||||
void func_8018F694(void)
|
||||
{
|
||||
extern void func_8004914C(void *);
|
||||
extern void func_800491AC(void *);
|
||||
extern void *func_80010A08(s32);
|
||||
extern u8 D_800AF648;
|
||||
extern u8 D_800A6610[];
|
||||
extern short D_800B9A02;
|
||||
extern Blip_8018F694 D_801D57F0[];
|
||||
|
||||
UVEC_8018F694 sxy;
|
||||
UVEC_8018F694 p0;
|
||||
UVEC_8018F694 p1;
|
||||
long flag;
|
||||
long otz;
|
||||
s32 i;
|
||||
PTag_8018F694 *ot;
|
||||
PTag_8018F694 *otp;
|
||||
Blip_8018F694 *bp;
|
||||
s32 t, c, d, z, x, n;
|
||||
|
||||
ot = (PTag_8018F694 *)&D_800A6610[(*(u16 *)&D_800B9A02) << 14];
|
||||
func_8004914C(&D_800AF648);
|
||||
func_800491AC(&D_800AF648);
|
||||
bp = D_801D57F0;
|
||||
|
||||
#define EMIT_8018F694() { \
|
||||
LineF2_8018F694 *pk = (LineF2_8018F694 *)func_80010A08(0x10); \
|
||||
pk->len = 3; \
|
||||
pk->code = 0x42; \
|
||||
pk->r0 = bp->r; \
|
||||
pk->g0 = bp->g; \
|
||||
pk->b0 = bp->b; \
|
||||
pk->x0 = p0.vx; \
|
||||
pk->y0 = p0.vy; \
|
||||
pk->x1 = p1.vx; \
|
||||
pk->y1 = p1.vy; \
|
||||
pk->addr = otp->addr; \
|
||||
otp->addr = (u32) pk; }
|
||||
|
||||
for (i = 0; i < 32; i++, bp++) {
|
||||
t = bp->tm;
|
||||
__asm__ __volatile__ ("" : "=r" (t) : "0" (t));
|
||||
if (t != 0) {
|
||||
gte_ldv0(&bp->vx);
|
||||
gte_rtps();
|
||||
gte_stsxy((long *) &sxy);
|
||||
gte_stflg(&flag);
|
||||
gte_stszotz(&otz);
|
||||
if ((u16) (sxy.vx + 0x200) < 0x401 &&
|
||||
(u16) (sxy.vy + 0x180) < 0x301 &&
|
||||
(u32) (otz - 0x10) < 0x1000) {
|
||||
z = otz - 0x10;
|
||||
c = t >> 6;
|
||||
d = (t * 3) >> 8;
|
||||
otp = (PTag_8018F694 *) ((z << 2) + (s32) ot);
|
||||
|
||||
p0.vx = sxy.vx - c;
|
||||
p0.vy = sxy.vy - c;
|
||||
p1.vx = p0.vx + d;
|
||||
p1.vy = p0.vy;
|
||||
EMIT_8018F694();
|
||||
|
||||
p1.vx = p0.vx;
|
||||
p1.vy = p0.vy + d;
|
||||
EMIT_8018F694();
|
||||
|
||||
p0.vx = sxy.vx + c;
|
||||
p1.vx = p0.vx - d;
|
||||
p1.vy = p0.vy;
|
||||
EMIT_8018F694();
|
||||
|
||||
p1.vx = p0.vx;
|
||||
p1.vy = p0.vy + d;
|
||||
EMIT_8018F694();
|
||||
|
||||
p0.vy = sxy.vy + c;
|
||||
p1.vx = p0.vx - d;
|
||||
p1.vy = p0.vy;
|
||||
EMIT_8018F694();
|
||||
|
||||
p1.vx = p0.vx;
|
||||
p1.vy = p0.vy - d;
|
||||
EMIT_8018F694();
|
||||
|
||||
p0.vx = sxy.vx - c;
|
||||
p1.vx = p0.vx + d;
|
||||
p1.vy = p0.vy;
|
||||
EMIT_8018F694();
|
||||
|
||||
p1.vx = p0.vx;
|
||||
p1.vy = p0.vy - d;
|
||||
EMIT_8018F694();
|
||||
|
||||
{
|
||||
TPage_8018F694 *tp = (TPage_8018F694 *) func_80010A08(8);
|
||||
tp->len = 1;
|
||||
tp->code0 = 0xE100002A;
|
||||
tp->addr = otp->addr;
|
||||
otp->addr = (u32) tp;
|
||||
}
|
||||
|
||||
n = bp->r;
|
||||
x = n - bp->dr;
|
||||
n = x;
|
||||
if (x < 0) { n = 0; }
|
||||
bp->r = n;
|
||||
n = bp->g;
|
||||
x = n - bp->dg;
|
||||
n = x;
|
||||
if (x < 0) { n = 0; }
|
||||
bp->g = n;
|
||||
n = bp->b;
|
||||
x = n - bp->db;
|
||||
n = x;
|
||||
if (x < 0) { n = 0; }
|
||||
bp->b = n;
|
||||
x = n | (bp->r | bp->g);
|
||||
if ((u8) x == 0) {
|
||||
bp->tm = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
INCLUDE_ASM("asm/ov_SC06_018/nonmatchings/ov_SC06_018_jr_8017C24C", func_8018FE0C);
|
||||
|
||||
|
||||
+53
-19
@@ -97,13 +97,23 @@ def classify_fail(got_sha):
|
||||
return 'SKIP'
|
||||
if got_sha is not None:
|
||||
return 'DIFF'
|
||||
m = _PLUMBING.search(_last_err)
|
||||
if m:
|
||||
# pull the offending line for the log (e.g. "redefinition of 's16'")
|
||||
for ln in _last_err.splitlines():
|
||||
if m.re.search(ln):
|
||||
return 'PLUMBING: ' + ln.strip()[:90]
|
||||
return 'PLUMBING: ' + m.group(0).lower()
|
||||
# §58 RED-HERRING GUARD (2026-07-22). The build log is full of BENIGN WARNINGS — chiefly
|
||||
# `warning: conflicting types for built-in function 'memcpy'`, which fires from an unrelated
|
||||
# TU position on essentially every overlay build. The old code searched the whole stderr and
|
||||
# returned the FIRST match, so that warning won: an 11-draft re-probe returned the SAME
|
||||
# 'PLUMBING: …conflicting types for built-in fu' label for 8 of 8 failures, across four
|
||||
# genuinely different causes. A label that is identical for every input carries no
|
||||
# information, and it is worse than none — the cookbook had to record "the gate label is
|
||||
# useless here, splice individually and read real cc1 stderr" as a manual step. Classify on
|
||||
# NON-warning lines only, and when nothing but warnings matched, surface the real error
|
||||
# instead of guessing (R32: report the gap, do not paper over it).
|
||||
lines = [ln for ln in _last_err.splitlines() if 'warning:' not in ln]
|
||||
for ln in lines:
|
||||
if _PLUMBING.search(ln):
|
||||
return 'PLUMBING: ' + ln.strip()[:90]
|
||||
errs = [ln for ln in lines if re.search(r'\berror\b|\bError \d', ln)]
|
||||
if errs:
|
||||
return 'CC1-FAIL: ' + errs[-1].strip()[:90]
|
||||
return 'CC1-FAIL'
|
||||
|
||||
|
||||
@@ -209,7 +219,22 @@ def _reload_corpus():
|
||||
|
||||
|
||||
def _jtbl_snapshot():
|
||||
"""Text of the config files a carve/isolation may rewrite (for an exact undo)."""
|
||||
"""Text of EVERY file a carve/isolation may rewrite — config AND the binary's sources.
|
||||
|
||||
SOURCES ARE NOT OPTIONAL (byte-witnessed 2026-07-22). `jr_isolate_all` repartitions a code
|
||||
object by writing region 0 back over the ORIGINAL `src/<ov>/<nm>.c` (truncated to just that
|
||||
region) and emitting the rest as new `_jr_<lo>.c` files. An undo that restores only config/
|
||||
and deletes the new region files therefore leaves the original TU PERMANENTLY TRUNCATED —
|
||||
its stubs are gone, and nothing regenerates them (splat does not rewrite a committed
|
||||
overlay .c). Measured on an 11-draft re-probe: live stubs fell 419 -> 414 -> 406 -> 395 as
|
||||
successive rejected drafts each ate a TU, ending in `undefined reference to func_80191C50`.
|
||||
|
||||
Worse, it is INVISIBLE to the gate that caused it: the incremental build keeps linking the
|
||||
stale objects (§42b), so `make build` stays green while a CLEAN rebuild fails. That is the
|
||||
mechanism behind the "139/140, twice" reading that became the §61c blocker — the tree was
|
||||
being eaten by the very undo meant to protect it.
|
||||
|
||||
So snapshot the whole source set, and undo by RESTORE, never by an inverse transform (§61)."""
|
||||
out = {}
|
||||
for q in (os.path.join(REPO, 'config/splat.%s.yaml' % a.binary),
|
||||
os.path.join(REPO, 'config/overlays.mk')):
|
||||
@@ -217,10 +242,15 @@ def _jtbl_snapshot():
|
||||
out[q] = open(q).read()
|
||||
except OSError:
|
||||
pass
|
||||
for q in glob.glob(os.path.join(REPO, 'src/%s/*.c' % a.binary)):
|
||||
try:
|
||||
out[q] = open(q).read()
|
||||
except OSError:
|
||||
pass
|
||||
return out
|
||||
|
||||
|
||||
def _jtbl_restore(snap, regions_before):
|
||||
def _jtbl_restore(snap):
|
||||
"""Undo a carve/isolation EXACTLY: restore the config text and delete only the region files
|
||||
this attempt created. Then re-extract and re-derive the stub map.
|
||||
|
||||
@@ -233,7 +263,11 @@ def _jtbl_restore(snap, regions_before):
|
||||
whole overlay for the rest of the batch (4 isolate-FAILs downstream)."""
|
||||
for q, txt in snap.items():
|
||||
open(q, 'w').write(txt)
|
||||
for rf in set(glob.glob('src/%s/%s_jr_*.c' % (a.binary, a.binary))) - regions_before:
|
||||
# Remove every source file the attempt CREATED. Derived from the snapshot (which holds the
|
||||
# exact pre-attempt file set), not from the `_jr_*` name shape — an isolation may emit a
|
||||
# region whose name that glob does not predict, and a leftover .c is picked up by the OBJS
|
||||
# glob at the next parse (R33: derive from the recorded state, do not re-guess it).
|
||||
for rf in set(glob.glob(os.path.join(REPO, 'src/%s/*.c' % a.binary))) - set(snap):
|
||||
try:
|
||||
os.remove(rf)
|
||||
except OSError:
|
||||
@@ -243,10 +277,10 @@ def _jtbl_restore(snap, regions_before):
|
||||
|
||||
|
||||
def _jtbl_prep_one(fn):
|
||||
"""Prep ONE table-bearing draft's carve. Returns (ok, snapshot, regions_before)."""
|
||||
"""Prep ONE table-bearing draft's carve. Returns (ok, snapshot)."""
|
||||
if not _fn_has_jtbl(fn):
|
||||
return True, None, None
|
||||
snap, regions_before = _jtbl_snapshot(), set(glob.glob('src/%s/%s_jr_*.c' % (a.binary, a.binary)))
|
||||
return True, None
|
||||
snap = _jtbl_snapshot()
|
||||
done = []
|
||||
for fn in [fn]:
|
||||
st = _stubs.get(fn)
|
||||
@@ -280,12 +314,12 @@ def _jtbl_prep_one(fn):
|
||||
print(' [jtbl] carve FAILED %s: %s' % (fn, last[0][:120])); continue
|
||||
done.append(fn)
|
||||
if not done:
|
||||
_jtbl_restore(snap, regions_before)
|
||||
return False, None, None
|
||||
_jtbl_restore(snap)
|
||||
return False, None
|
||||
_sh(['make', '--no-print-directory', 'extract', 'BINARY=%s' % a.binary])
|
||||
_reload_corpus()
|
||||
print(' [jtbl] carved %s' % done[0])
|
||||
return True, snap, regions_before
|
||||
return True, snap
|
||||
|
||||
|
||||
def _stub_line(fn):
|
||||
@@ -332,15 +366,15 @@ while i < len(items):
|
||||
# per-chunk jtbl prep (chunk==1 is the prescribed default, so this is per-function): carve the
|
||||
# table, and if the gate then REJECTS the draft, undo the carve — a stranded carve has no owner
|
||||
# and poisons every later isolation in the overlay (see _jtbl_restore).
|
||||
_jsnap = _jregions = None
|
||||
_jsnap = None
|
||||
if len(chunk) == 1:
|
||||
_ok, _jsnap, _jregions = _jtbl_prep_one(chunk[0])
|
||||
_ok, _jsnap = _jtbl_prep_one(chunk[0])
|
||||
if attempt(chunk):
|
||||
commit(chunk)
|
||||
print(' + chunk(%d): %s' % (len(chunk), ' '.join(chunk)))
|
||||
elif len(chunk) == 1:
|
||||
if _jsnap is not None:
|
||||
_jtbl_restore(_jsnap, _jregions) # stranded-carve undo (R32 ownership stays 1:1)
|
||||
_jtbl_restore(_jsnap) # stranded-carve + truncated-TU undo (R32/§61)
|
||||
# ATOMIC CHUNK — do NOT bisect (Phase-28 T6). The old code fell into the loop below and
|
||||
# re-ran attempt([fn]) on the SAME single element against the SAME baseline: a byte-identical
|
||||
# DUPLICATE build. classify_fail reads _last_sha/_last_err, which the failed attempt(chunk)
|
||||
|
||||
Reference in New Issue
Block a user