mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-28 06:49:47 -04:00
fix(gate_main): the uncommitted-work guard belonged OUTSIDE the bisection loop
I added the guard to try_batch() an hour ago. try_batch runs REPEATEDLY during
bisection, and its own first substitution makes main's TUs dirty -- so on
iteration two the guard could not tell the operator's unsaved work from the
gate's own in-flight edit, and aborted the run:
M src/800c3.c
gate_main: aborting with an UNVERIFIED substitution in main's TUs — reverting
It failed safely (reverted, no bank lost, and said so), but it made the gate
unusable for any batch larger than one.
Hoisted to assert_main_tus_clean(), called ONCE from main() before any
substitution. The lesson is worth the line it costs: A GUARD MUST BE ABLE TO
DISTINGUISH THE STATE IT PROTECTS FROM THE STATE IT CREATES. Placed inside the
loop it was checking its own footprints.
Negative-controlled both directions: a genuinely dirty src/800c3.c is refused by
name before anything is substituted, and a clean tree now proceeds into the
bisection (currently running 21 drafts).
This commit is contained in:
+23
-17
@@ -715,26 +715,30 @@ def _preserve_and_localize(entries, got):
|
||||
verdict, msg = MDL.classify(per, fn, ndiff)
|
||||
print(f" VERDICT {fn}: {verdict} — {msg}")
|
||||
|
||||
def try_batch(entries):
|
||||
# REFUSE TO DESTROY UNCOMMITTED WORK (P31 S75). The `git checkout` below is correct for the
|
||||
# normal flow -- restore the stubs, re-extract, substitute the drafts -- and it is CATASTROPHIC
|
||||
# for anything uncommitted in main's TUs, because it reverts them without asking.
|
||||
#
|
||||
# Measured twice this session: (1) the SaveLoadRoutine decompile (1,179 ins, byte-identical)
|
||||
# sat uncommitted in src/800_b.c while a gate ran, and only survived because it was committed
|
||||
# first; (2) a §265 verbatim body converted to a stub is UNCOMMITTED BY CONSTRUCTION, so this
|
||||
# line restored the __asm__ block NEXT TO the substituted C -- the TU then carried 9 jump
|
||||
# tables instead of 5, `jtbl_rodata_pads --derive` refused, and the gate REJECTED a
|
||||
# byte-identical bank. gate_main could not bank anything in the verbatim class, by construction.
|
||||
#
|
||||
# A destructive step that cannot be undone must ASK, not assume (R42: never blind-revert a dirty
|
||||
# src/). Commit or stash first; `--allow-dirty` is the deliberate override.
|
||||
def assert_main_tus_clean():
|
||||
"""Refuse to START if main's TUs carry uncommitted work this gate would destroy.
|
||||
|
||||
CALLED ONCE, BEFORE ANY SUBSTITUTION — never inside try_batch. `try_batch` runs repeatedly
|
||||
during bisection, and its OWN first substitution makes the tree dirty, so a check placed there
|
||||
cannot distinguish the operator's unsaved work from the gate's own in-flight edit: it aborts the
|
||||
bisection on iteration two. (P31 S75 — I put it there first and did exactly that.) A guard must
|
||||
be able to tell the state it PROTECTS from the state it CREATES.
|
||||
|
||||
What it protects against, measured twice the same session: the SaveLoadRoutine decompile (1,179
|
||||
ins, byte-identical) sat uncommitted in src/800_b.c while a gate ran; and a §265 verbatim body
|
||||
converted to a stub is UNCOMMITTED BY CONSTRUCTION, so the checkout restored the __asm__ block
|
||||
NEXT TO the substituted C — 9 jump tables instead of 5, jtbl_rodata_pads refused, and the gate
|
||||
REJECTED a byte-identical bank. A destructive step that cannot be undone must ASK (R42)."""
|
||||
dirty = run("git status --porcelain -- " + " ".join(main_tus())).stdout.strip()
|
||||
if dirty and not os.environ.get("GATE_MAIN_ALLOW_DIRTY"):
|
||||
sys.exit("gate_main: main's TUs have UNCOMMITTED changes and this gate is about to `git\n"
|
||||
"checkout` them, which would DESTROY that work:\n\n" + dirty +
|
||||
"\n\nCommit it (R42 -- banked work is committed the moment it exists) or stash it.\n"
|
||||
sys.exit("gate_main: main's TUs have UNCOMMITTED changes and this gate will `git checkout`\n"
|
||||
"them before substituting, which would DESTROY that work:\n\n" + dirty +
|
||||
"\n\nCommit it (R42 — banked work is committed the moment it exists) or stash it.\n"
|
||||
"Pass --allow-dirty / GATE_MAIN_ALLOW_DIRTY=1 only if you intend to discard it.")
|
||||
|
||||
|
||||
def try_batch(entries):
|
||||
# (uncommitted-work guard: assert_main_tus_clean(), called once from main())
|
||||
run("git checkout -- " + " ".join(main_tus()))
|
||||
run("make extract BINARY=main") # regenerate .s for the reverted stubs (hazard 2)
|
||||
substitute(entries)
|
||||
@@ -757,6 +761,8 @@ def main():
|
||||
a = ap.parse_args()
|
||||
if getattr(a, 'allow_dirty', False):
|
||||
os.environ["GATE_MAIN_ALLOW_DIRTY"] = "1"
|
||||
if not a.assert_baseline:
|
||||
assert_main_tus_clean() # ONCE, before any substitution
|
||||
if not a.assert_baseline and not a.slate:
|
||||
ap.error('a slate file is required unless --assert-baseline')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user