check-claims --control plants a revival in docs/port/ and requires exit 1. That the plant lands INSIDE a scanned directory was a property I checked manually, one time, and wrote up -- the exact pattern I had criticised in this same tool one iteration earlier. A fifth case now plants the identical text OUTSIDE the scanned root and requires 0, so the pair asserts the boundary is real: same text, 1 inside and 0 outside. Either half alone is consistent with the tool scanning everything, or nothing. Five cases: clean 0, unmarked 1, marked 0, outside-root 0, empty register 2. audit-kinds has always reported what it found and was never asked whether it can find anything, while its clean runs are cited as evidence that fifteen labels are grounded. --selftest pushes three synthetic rows through the real classifier and reads its verdict: citing nothing must read BARE, a real path ok, a missing path DANGLING. Verified two-directionally -- an extractor stubbed to accept everything returns exit 2. Asserting in check-all. All four submenus are now measured to reset -- LOAD GAME, TUTORIAL and OPTIONS joining EXTRAS -- and the main menu remains the only screen that remembers. Three of the four are not in this export, so no authored value changes. NOT promoted to a rule, deliberately. 'Submenus reset' at 4/4 is better evidence than the 2/2 that made wrap a menu-wide rule, and adopting it would change nothing today because the only submenu this port ships is already measured. What it would do is pre-decide the next screen from a generalisation instead of a measurement -- the trap that nearly let a derived rule overwrite EXTRAS' measured opening item. The guard prints the 4/4 finding beside its per-screen values so the evidence is visible without being load-bearing. MISSION-SELECT-versus-top-item stays open: none of the three separates it, each opens on its own first item, and NEW GAME is untested. Remaining without a harness self-test: verify-transcode-fidelity. Every asserting check passes, 13 of them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
516 lines
25 KiB
Python
Executable File
516 lines
25 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""Reconcile the numbers the CONTRACT states against the numbers the PORT ships.
|
||
|
||
`docs/port/HANDOFF.md` is the contract, and this port reads it from `main` --
|
||
where it is frozen at 926 lines while the live document, on the Decoder's branch,
|
||
is 4 111. Two days of deliveries addressed to the port landed on a page the port
|
||
does not open. Reading 70 unread sections by hand is how that gets missed again.
|
||
|
||
So the values are checked instead of read. Each check names a quantity, pulls it
|
||
OUT OF THE LIVE HANDOFF TEXT by pattern -- never restating it here, or this file
|
||
would be a third copy to go stale -- and compares it against the port's own
|
||
`export/` tree or `authored/` mapping.
|
||
|
||
Three outcomes, and the third is the point:
|
||
|
||
ok the contract and the port agree
|
||
MISMATCH they disagree; one of us is wrong and this says which values
|
||
ANCHOR the pattern no longer matches the contract -- the check has STOPPED
|
||
CHECKING. Reported as loudly as a mismatch, because a check whose
|
||
anchor has drifted passes forever while measuring nothing.
|
||
|
||
Reads the newest HANDOFF on ANY ref, not the working tree's, and says which.
|
||
|
||
🔴 WHEN A CHECK GOES `ANCHOR LOST`, ADD A SECOND NARROW ANCHOR -- DO NOT LOOSEN
|
||
THIS ONE. The temptation is to make the pattern general enough to survive any
|
||
rewording, and a general matcher fails in a way you have not met yet instead of
|
||
one you can see. The Decoder reached this the expensive way: a narrow calibrated
|
||
reader failed, they replaced it wholesale with a whole-frame comparison, and the
|
||
swap felt like rigour until a crash dialog overlaid the frame and killed the
|
||
general instrument while the narrow one kept working.
|
||
"""
|
||
import json, re, subprocess, sys, os
|
||
|
||
FAIL = 0
|
||
|
||
|
||
def git(*a):
|
||
return subprocess.run(["git", *a], capture_output=True, text=True).stdout
|
||
|
||
|
||
def contract():
|
||
"""The newest HANDOFF anywhere, and how far the working tree's copy is behind."""
|
||
sha = git("log", "--all", "--format=%h", "--", "docs/port/HANDOFF.md").split()[0]
|
||
mine = git("log", "-1", "--format=%h", "--", "docs/port/HANDOFF.md").strip()
|
||
text = git("show", f"{sha}:docs/port/HANDOFF.md")
|
||
behind = len(git("log", "--all", "--not", "HEAD", "--format=%h",
|
||
"--", "docs/port/HANDOFF.md").split())
|
||
print(f" contract: {sha} ({len(text.splitlines())} lines)")
|
||
print(f" my copy : {mine} ({len(git('show', f'{mine}:docs/port/HANDOFF.md').splitlines())} lines)"
|
||
f"{'' if behind == 0 else f' <- {behind} HANDOFF commit(s) unread'}")
|
||
return text
|
||
|
||
|
||
def report(name, want, got, ok):
|
||
global FAIL
|
||
if want is None:
|
||
FAIL += 1
|
||
print(f" {name:<30} 🔴 ANCHOR LOST -- the contract no longer states this")
|
||
elif ok:
|
||
print(f" {name:<30} ok contract {want} port {got}")
|
||
else:
|
||
FAIL += 1
|
||
print(f" {name:<30} 🔴 MISMATCH contract {want} port {got}")
|
||
|
||
|
||
def jload(p):
|
||
return json.load(open(p)) if os.path.exists(p) else None
|
||
|
||
|
||
def el(screen, prefix):
|
||
d = jload(f"export/screens/title/{screen}.json")
|
||
if not d:
|
||
return None
|
||
return next((e for e in d["elements"] if e["id"].startswith(prefix)), None)
|
||
|
||
|
||
# --- the checks ------------------------------------------------------------
|
||
|
||
def check_fade_quads(h):
|
||
"""The fade-in that a broken helper reported 5x too slow for years.
|
||
|
||
The contract prints the three builds' `pteff00` poses in one fence. The port
|
||
animates that quad from its OWN export, so agreement here is two readers of
|
||
the same bytes -- theirs rebuilt after the record-layout fix, mine the pinned
|
||
crate -- and a disagreement would mean one reader never got the fix.
|
||
"""
|
||
for screen, build in (("title", 4), ("main_menu", 5), ("extras", 6)):
|
||
m = re.search(rf"build {build} \([^)]*\)\s+pteff00\.prm\s+(.+)", h)
|
||
want = None
|
||
if m:
|
||
want = [(int(t), int(a)) for t, a in re.findall(r"t=\s*(\d+)\s*α=(\d+)", m.group(1))]
|
||
e = el(screen, "pteff00")
|
||
got = [(k["t"], int(k["fade_argb"][2:4], 16)) for k in e["keyframes"]] if e else None
|
||
report(f"fade quad, {screen}", want, got, want is not None and want == got)
|
||
|
||
|
||
def check_plate_period(h):
|
||
"""`+0x08` is the loop length: 120, and the port must not run the glow at 105."""
|
||
m = re.search(r"the plate's pulse period is (\d+), not (\d+)", h)
|
||
want = int(m.group(1)) if m else None
|
||
e = el("press_start", "ptbtn00")
|
||
got = (e.get("focus") or {}).get("loop_length_units") if e else None
|
||
report("plate glow cycle", want, got, want is not None and want == got)
|
||
a = jload("authored/timing.json") or {}
|
||
auth = a.get("looping_focus_records", {}).get("press_start/ptbtn00", {}).get("period_units")
|
||
report(" ... authored 2nd witness", want, auth, want is not None and want == auth)
|
||
|
||
|
||
def check_bgm_window(h):
|
||
"""The menu loop, as an ffmpeg window the contract states literally."""
|
||
m = re.search(r"the window is \*\*`-ss ([\d.]+) -t ([\d.]+)`\*\*", h)
|
||
want = (float(m.group(1)), float(m.group(2))) if m else None
|
||
a = ((jload("authored/audio.json") or {}).get("bgm") or {}).get("main_menu", {})
|
||
got = (a.get("loop_start_s"), a.get("loop_end_s"))
|
||
report("menu BGM loop window", want, got, want is not None and want == got)
|
||
|
||
|
||
def check_black_hold(h):
|
||
"""The gap between screens is not a load: the contract says keep it at 0."""
|
||
m = re.search(r"Keep `black_hold_units` at (\d+)", h)
|
||
want = int(m.group(1)) if m else None
|
||
got = (jload("authored/timing.json") or {}).get("black_hold_units")
|
||
report("black hold between screens", want, got, want is not None and want == got)
|
||
|
||
|
||
def check_menu_bank(h):
|
||
"""Which bank the menu plays -- the row the port once got wrong by authoring."""
|
||
m = re.search(r"`(BGM_\d+)` confirmed from the RUNTIME", h)
|
||
want = m.group(1) if m else None
|
||
got = (((jload("authored/audio.json") or {}).get("bgm") or {})
|
||
.get("main_menu", {}).get("bank", ""))
|
||
report("menu BGM bank", want, got, want is not None and got.startswith(want))
|
||
|
||
|
||
def check_fade_out(h):
|
||
"""The fade-OUT lengths, derived from the same poses the fade-in check reads.
|
||
|
||
Stated as prose rather than in the fence, so this parses the sentence. Split
|
||
from the fade-in deliberately: they came from the same broken helper, and a
|
||
single check covering both would let one wrong half hide behind a right one.
|
||
"""
|
||
m = re.search(r"Fade-out = (\d+) units, (\d+) units, and \*\*(\d+)\*\* on the title", h)
|
||
want = [int(m.group(i)) for i in (1, 2, 3)] if m else None
|
||
got = []
|
||
for screen in ("main_menu", "extras", "title"):
|
||
e = el(screen, "pteff00")
|
||
ks = [k["t"] for k in e["keyframes"]] if e else []
|
||
got.append(ks[-1] - ks[-2] if len(ks) >= 2 else None)
|
||
report("fade-out ramps", want, got, want is not None and want == got)
|
||
|
||
|
||
def check_splash_dwell(h):
|
||
"""The two boot splashes' dwell -- the retraction the port's recomputation caused.
|
||
|
||
The contract gives 190 and 145 as the widest gap in each entry's own times.
|
||
The port plays the declared timeline, so the same gap must come out of the
|
||
export. This is the retracted claim re-derived from a third reading.
|
||
"""
|
||
m = re.search(r"the splashes are (\d+) and (\d+)", h)
|
||
want = [int(m.group(1)), int(m.group(2))] if m else None
|
||
got = []
|
||
for screen in ("publisher_logo", "developer_logos"):
|
||
d = jload(f"export/screens/title/{screen}.json")
|
||
ts = sorted({k["t"] for e in d["elements"] for k in e["keyframes"]}) if d else []
|
||
got.append(max((b - a for a, b in zip(ts, ts[1:])), default=None))
|
||
report("boot splash dwells", want, got, want is not None and want == got)
|
||
|
||
|
||
def check_splash_times(h):
|
||
"""The splash's ABSOLUTE keyframe times, not just the gap between two of them.
|
||
|
||
🔴 Added 2026-08-30 because the dwell check above is a DIFFERENCE, and a
|
||
difference is blind to the origin: a reader whose times were all shifted by a
|
||
constant would produce the same 190 and pass. That is not hypothetical -- the
|
||
Decoder's own control asserted "two DOWNs move two items", which a constant
|
||
offset preserves exactly, and it passed for a whole session on a reader that
|
||
was two items wrong. Ground truth caught it; the control could not.
|
||
|
||
The contract prints entry 10's times in full, so the origin is checkable.
|
||
"""
|
||
m = re.search(r"entry 10's times are\s*`\[([0-9, ]+)\]`", h)
|
||
want = [int(x) for x in m.group(1).split(",")] if m else None
|
||
d = jload("export/screens/title/publisher_logo.json")
|
||
got = sorted({k["t"] for e in d["elements"] for k in e["keyframes"]}) if d else None
|
||
report("splash absolute times", want, got, want is not None and want == got)
|
||
|
||
|
||
def check_initial_focus(h):
|
||
"""What the menu opens on FROM A FRESH BOOT -- measured, and it was authored.
|
||
|
||
Anchored on the measurement rather than on the value, so that if the reading
|
||
is corrected again this fails instead of silently agreeing.
|
||
"""
|
||
want = "NEW GAME" if re.search(
|
||
r"\*\*Initial focus on a fresh boot is `NEW GAME`\*\*", h) else None
|
||
scr = ((jload("authored/flow.json") or {}).get("screens") or {}).get("main_menu", {})
|
||
bid = scr.get("initial_focus")
|
||
got = (scr.get("buttons") or {}).get(bid, {}).get("label")
|
||
kind = scr.get("initial_focus_kind")
|
||
report("menu opens on (fresh boot)", want, f"{got} [{kind}]",
|
||
want is not None and got == want and kind == "measured")
|
||
|
||
|
||
|
||
|
||
|
||
def fn_nav_perturbed(fn, old, new):
|
||
"""Run a walk-anchored check against a perturbed copy of the walk.
|
||
|
||
`nav()` reads from git, so the perturbation is injected by swapping the
|
||
function out rather than by editing a file -- nothing on disk is touched.
|
||
"""
|
||
global nav
|
||
real = nav
|
||
nav = lambda: (real()[0].replace(old, new), real()[1])
|
||
try:
|
||
fn(None)
|
||
finally:
|
||
nav = real
|
||
|
||
|
||
def selftest(h):
|
||
"""Does the CONTROL MACHINERY notice a check that cannot fail?
|
||
|
||
🔴 THE GAP THIS CLOSES, named by me and prioritised by the Decoder: every
|
||
`--control` run asserts that each check FAILS on a perturbed contract. None
|
||
of them asserted that a **broken control reports broken**. That is the same
|
||
shape as printing a verdict without asserting it, one level up — and a
|
||
control harness that silently approves a dead check is exactly as useless as
|
||
a check that silently approves a dead value.
|
||
|
||
So a stub check that can never fail is fed to the machinery, and the
|
||
machinery must flag it. If the stub comes back "✅ fails as it must", the
|
||
harness is broken and says so with its own exit code.
|
||
|
||
Exit codes follow the Decoder's convention, which distinguishes the two
|
||
failures that matter: **0** all good, **1** a real check failed, **2** the
|
||
HARNESS is broken and nothing it reported can be trusted.
|
||
"""
|
||
import io, contextlib
|
||
|
||
def always_ok(_h):
|
||
# Prints a verdict and asserts nothing -- the exact defect shipped in
|
||
# `verify-transcode-fidelity`'s unconditional `return 0`.
|
||
print(" stub: everything is fine")
|
||
|
||
# 🔴 RUN THE REAL MACHINERY OVER THE STUB. A first version of this checked
|
||
# that the stub left FAIL at zero and then ARGUED that `control` would
|
||
# therefore flag it. That is reasoning where a measurement was available --
|
||
# the error this whole thread has been about -- so the stub goes through the
|
||
# same `control()` loop the real checks do, and its verdict is read.
|
||
with contextlib.redirect_stdout(io.StringIO()) as buf:
|
||
verdict = control(h, extra=[(always_ok, "120", "121")])
|
||
out = buf.getvalue()
|
||
stub_line = [l for l in out.splitlines() if "always_ok" in l]
|
||
if verdict is not False or not stub_line:
|
||
print(" 🔴 HARNESS BROKEN: the control machinery did not flag a check that")
|
||
print(" cannot fail. Nothing any `--control` run has reported is trustworthy.")
|
||
print(f" stub verdict: {verdict!r}; line: {stub_line}")
|
||
return 2
|
||
if "PASSES A WRONG CONTRACT" not in stub_line[0]:
|
||
print(f" 🔴 HARNESS BROKEN: stub flagged, but not as a dead check: {stub_line[0].strip()}")
|
||
return 2
|
||
print(" harness self-test: a check that cannot fail is flagged by the machinery ✅")
|
||
print(f" {stub_line[0].strip()}")
|
||
print(" Exit codes: 0 all good, 1 a real check failed, 2 the HARNESS is broken.")
|
||
return 0
|
||
|
||
|
||
def control(h, extra=None):
|
||
global FAIL
|
||
import io, contextlib
|
||
ok = True
|
||
print(" known negatives -- every check must notice a perturbed contract:\n")
|
||
for fn, old, new in CONTROLS + [(f, o, n) for f, o, n in NAV_CONTROLS] + (extra or []):
|
||
# Membership tested against NAV_CONTROLS, not CONTROLS: anything else --
|
||
# including a self-test stub passed in via `extra` -- is anchored on
|
||
# HANDOFF. Written the other way round, the stub was routed at the walk
|
||
# and flagged "the control's own anchor is gone", a real failure for a
|
||
# fabricated reason.
|
||
src = nav()[0] if (fn, old, new) in NAV_CONTROLS else h
|
||
if old not in src:
|
||
print(f" {fn.__name__:<22} 🔴 the control's own anchor is gone")
|
||
ok = False
|
||
continue
|
||
before, FAIL = FAIL, 0
|
||
with contextlib.redirect_stdout(io.StringIO()):
|
||
if src is h:
|
||
# 🔴 EVERY occurrence, not the first. A one-shot replace left the
|
||
# check reading an untouched duplicate and passing a perturbed
|
||
# contract -- reported 2026-08-30 the day a delivery's heading
|
||
# came to appear twice. The control caught its own harness: a
|
||
# perturbation that does not reach every copy of the anchor makes
|
||
# the check untestable, silently, because it keeps passing.
|
||
fn(h.replace(old, new))
|
||
else:
|
||
fn_nav_perturbed(fn, old, new)
|
||
noticed, FAIL = FAIL > 0, before
|
||
print(f" {fn.__name__:<22} {'✅ fails as it must' if noticed else '🔴 PASSES A WRONG CONTRACT -- it checks nothing'}")
|
||
ok = ok and noticed
|
||
return ok
|
||
|
||
|
||
def nav():
|
||
"""The player's-eye walk, from the newest ref that carries it.
|
||
|
||
A second unreachable document: `docs/game/navigation.md` was filled in from
|
||
the committed oracle frames and, like HANDOFF, is not on `main`. The port's
|
||
`authored/flow.json` is the executable form of that walk, so the two must not
|
||
drift -- and the drift would be invisible, because nothing in the port fails
|
||
when a label is wrong.
|
||
"""
|
||
sha = git("log", "--all", "--format=%h", "--", "docs/game/navigation.md").split()[0]
|
||
return git("show", f"{sha}:docs/game/navigation.md"), sha
|
||
|
||
|
||
def flow_buttons(screen):
|
||
d = jload("authored/flow.json") or {}
|
||
b = ((d.get("screens") or {}).get(screen) or {}).get("buttons") or {}
|
||
return [v.get("label") for _, v in sorted(b.items())]
|
||
|
||
|
||
def check_focus_persists(h):
|
||
"""The menu remembers its cursor -- MEASURED, on the main menu, one screen.
|
||
|
||
🔴 This checked a PAIR until 2026-08-30: on for `main_menu`, off everywhere
|
||
else. The second half asserted that `extras` does NOT persist, and **nothing
|
||
measured that**. What the corpus has is EXTRAS' initial focus from a single
|
||
entry and Ⓑ restoring the PARENT's focus 4/4 — neither says what a submenu's
|
||
own cursor does on re-entry. So one measured behaviour and one absence of a
|
||
measurement were being reported identically, and if the game does persist
|
||
EXTRAS the check would have held the port to the wrong behaviour AND PASSED.
|
||
|
||
The mirror of the trap it was written to avoid: refusing to let a derived
|
||
rule overwrite a measured value, then letting "not measured here" become a
|
||
positive assertion of the negative. Now only the measured half is asserted
|
||
against the contract; the scope is a guard, below.
|
||
"""
|
||
heading = bool(re.search(r"the main menu remembers its cursor; re-entry is not a reset", h))
|
||
# 🔴 SECOND NARROW ANCHOR, added 2026-08-30 on the Decoder's advice, and it
|
||
# repairs a weakness I had already identified and not acted on. The heading
|
||
# anchor is on the CONCLUSION; when they corrected the run's item names --
|
||
# `TUTORIAL → EXTRAS → EXTRAS` was actually `NEW GAME → TUTORIAL → TUTORIAL`
|
||
# -- this check sailed past it, because the conclusion was above the part
|
||
# that was wrong. It survived by luck, not by design.
|
||
#
|
||
# So the check now also rests on the EVIDENCE: the ring at y 384.0 before the
|
||
# round trip and 385.5 after. That pair is the geometry-free equality the
|
||
# conclusion actually stands on, and it is what a future correction to the
|
||
# measurement would have to touch.
|
||
#
|
||
# Two narrow anchors, NOT one loosened one. Their words: after a specific
|
||
# instrument fails the general one feels safer, and its failure mode is only
|
||
# one you have not met yet.
|
||
evidence = bool(re.search(r"ring sits at y 384\.0 before the round trip and 385\.5 after", h))
|
||
want = heading and evidence
|
||
got = (((jload("authored/flow.json") or {}).get("screens") or {})
|
||
.get("main_menu", {}).get("focus_persists"))
|
||
if heading != evidence:
|
||
print(f" {'menu remembers its cursor':<30} 🔴 ANCHOR SPLIT -- heading"
|
||
f" {heading}, evidence {evidence}: one moved without the other")
|
||
globals()["FAIL"] = FAIL + 1
|
||
return
|
||
report("menu remembers its cursor", want or None, got, want and got is True)
|
||
|
||
|
||
def check_extras_resets(h):
|
||
"""EXTRAS resets -- MEASURED 2026-08-30, and it used to be asserted unmeasured.
|
||
|
||
For one iteration the port asserted this with nothing behind it, which the
|
||
Decoder flagged; it then measured it and the assertion was right. That does
|
||
not make the assertion evidence, so the check is rewritten to rest on the
|
||
measurement rather than being left to look vindicated.
|
||
"""
|
||
want = False if re.search(r"EXTRAS resets, the main menu persists", h) else None
|
||
ex = ((jload("authored/flow.json") or {}).get("screens") or {}).get("extras", {})
|
||
got = ex.get("focus_persists")
|
||
report("extras resets its cursor", want, f"{got} [{ex.get('focus_persists_kind')}]",
|
||
want is not None and got is False and ex.get("focus_persists_kind") == "measured")
|
||
|
||
|
||
def guard_focus_scope(_h):
|
||
"""NOT a contract check. A guard over the screens NOBODY HAS LOOKED AT.
|
||
|
||
Two screens are now measured and disagree -- `main_menu` persists, `extras`
|
||
resets -- so there is no menu-wide rule to state. What this guards is the
|
||
rest: `OPTIONS`, `LOAD GAME` and `TUTORIAL` are untouched, and their absent
|
||
`focus_persists` is the port defaulting, not a finding.
|
||
|
||
📌 The absent key and a measured `false` behave identically and mean opposite
|
||
things. That is why `extras` now spends a key on saying `false` out loud.
|
||
"""
|
||
global FAIL
|
||
scr = ((jload("authored/flow.json") or {}).get("screens") or {})
|
||
stated = {n: v.get("focus_persists") for n, v in scr.items()
|
||
if isinstance(v, dict) and "focus_persists" in v}
|
||
silent = sorted(n for n, v in scr.items()
|
||
if isinstance(v, dict) and "focus_persists" not in v)
|
||
ok = stated == {"main_menu": True, "extras": False}
|
||
if ok:
|
||
# ✅ 2026-08-31: all FOUR submenus are now measured to reset -- EXTRAS,
|
||
# LOAD GAME, TUTORIAL and OPTIONS -- and the main menu remains the only
|
||
# screen that remembers. Three of those four are not in this export, so
|
||
# no authored value changes.
|
||
#
|
||
# 🔴 NOT PROMOTED TO A RULE, deliberately. "Submenus reset" at 4/4 is
|
||
# better evidence than the 2/2 that made `wrap` a rule -- and adopting it
|
||
# would change nothing today, because the only submenu this port ships is
|
||
# already measured. What it WOULD do is pre-decide the next screen from a
|
||
# generalisation instead of a measurement, which is the trap that nearly
|
||
# let a derived rule overwrite EXTRAS' measured opening item.
|
||
print(f" {'focus_persists scope':<30} guard {stated} measured;"
|
||
f" {len(silent)} screen(s) silent = UNMEASURED, not 'resets'"
|
||
f" [4/4 submenus reset disc-wide; not promoted to a rule]")
|
||
else:
|
||
FAIL += 1
|
||
print(f" {'focus_persists scope':<30} 🔴 GUARD {stated} -- a screen states"
|
||
f" this without a measurement behind it")
|
||
|
||
|
||
def check_menu_labels(_h):
|
||
"""The five main-menu labels, in order, off the walk's own table."""
|
||
n, sha = nav()
|
||
rows = re.findall(r"^\| [1-5] \| \*\*([A-Z ]+)\*\* \|", n, re.M)
|
||
want = rows or None
|
||
report(f"main menu labels ({sha})", want, flow_buttons("main_menu"),
|
||
want is not None and want == flow_buttons("main_menu"))
|
||
|
||
|
||
def check_extras_labels(_h):
|
||
"""EXTRAS' three items, written as prose rather than a table."""
|
||
n, _ = nav()
|
||
m = re.search(r"Three items: `([A-Z ]+)` · `([A-Z ]+)` · `([A-Z ]+)`", n)
|
||
want = [m.group(i) for i in (1, 2, 3)] if m else None
|
||
report("extras labels", want, flow_buttons("extras"),
|
||
want is not None and want == flow_buttons("extras"))
|
||
|
||
|
||
def check_wrap(_h):
|
||
"""The cursor wraps, and it is a MENU rule -- the walk says so in two places."""
|
||
n, _ = nav()
|
||
want = True if re.search(r"one item, and it \*\*wraps\*\* at both ends", n) else None
|
||
got = ((jload("authored/flow.json") or {}).get("navigation") or {}).get("wrap")
|
||
report("cursor wraps", want, got, want is not None and want == got)
|
||
|
||
|
||
# Each check paired with a one-token edit to the CONTRACT that must break it.
|
||
# A check that has never been observed to fail is not evidence -- it may be
|
||
# reading nothing, comparing a value to itself, or anchored on a pattern that
|
||
# matches anything. `--control` perturbs the contract and requires every check to
|
||
# notice. This is the same discipline the checks themselves enforce: an
|
||
# instrument goes through a known negative before its clean run is believed.
|
||
CONTROLS = [
|
||
(check_fade_quads, "pteff00.prm t= 0 α=255 t= 12", "pteff00.prm t= 0 α=255 t= 13"),
|
||
(check_fade_out, "Fade-out = 10 units, 10 units", "Fade-out = 11 units, 10 units"),
|
||
(check_plate_period, "pulse period is 120, not 105", "pulse period is 121, not 105"),
|
||
(check_bgm_window, "`-ss 9.44 -t 61.87`", "`-ss 9.45 -t 61.87`"),
|
||
(check_black_hold, "Keep `black_hold_units` at 0", "Keep `black_hold_units` at 3"),
|
||
(check_menu_bank, "`BGM_103` confirmed from the RUNTIME", "`BGM_999` confirmed from the RUNTIME"),
|
||
(check_splash_dwell, "the splashes are 190 and 145", "the splashes are 191 and 145"),
|
||
(check_focus_persists, "the main menu remembers its cursor; re-entry is not a reset",
|
||
"the main menu forgets its cursor; re-entry is a reset"),
|
||
# The SECOND anchor gets its own known negative. Perturbing only the evidence
|
||
# must trip ANCHOR SPLIT -- otherwise the second anchor is decorative and the
|
||
# check is still resting on the conclusion alone.
|
||
(check_focus_persists, "ring sits at y 384.0 before the round trip and 385.5 after",
|
||
"ring sits at y 384.0 before the round trip and 999.9 after"),
|
||
# The list sits on the line AFTER "times are", so the perturbation has to
|
||
# carry the newline the check's `\s*` spans. A control whose own anchor is
|
||
# written from memory of the prose rather than from the prose is the same
|
||
# class of error the checks exist to catch.
|
||
(check_splash_times, "times are\n`[0,15,30,45,235,239,251,255]`",
|
||
"times are\n`[1,16,31,46,236,240,252,256]`"),
|
||
(check_extras_resets, "EXTRAS resets, the main menu persists",
|
||
"EXTRAS persists, the main menu persists"),
|
||
(check_initial_focus, "**Initial focus on a fresh boot is `NEW GAME`**",
|
||
"**Initial focus on a fresh boot is `TUTORIAL`**"),
|
||
]
|
||
|
||
# The walk's controls perturb `navigation.md` instead of HANDOFF, so they are
|
||
# applied to a different document and kept separate rather than folded in.
|
||
NAV_CONTROLS = [
|
||
(check_menu_labels, "| 1 | **NEW GAME**", "| 1 | **NEW GAMES**"),
|
||
(check_extras_labels, "`MISSION SELECT` · `MOVIE THEATER`", "`MISSION SELECTS` · `MOVIE THEATER`"),
|
||
(check_wrap, "one item, and it **wraps** at both ends", "one item, and it stops at both ends"),
|
||
]
|
||
|
||
|
||
def main():
|
||
if not os.path.exists("export/manifest.json"):
|
||
sys.exit("no export/ -- run the exporter first; this check reads what is shipped")
|
||
h = contract()
|
||
print()
|
||
if "--selftest" in sys.argv:
|
||
return selftest(h)
|
||
if "--control" in sys.argv:
|
||
return 0 if control(h) else 1
|
||
for fn in (check_fade_quads, check_fade_out, check_plate_period,
|
||
check_bgm_window, check_black_hold, check_menu_bank,
|
||
check_splash_dwell, check_menu_labels, check_extras_labels,
|
||
check_wrap, check_focus_persists, guard_focus_scope,
|
||
check_splash_times, check_initial_focus, check_extras_resets):
|
||
fn(h)
|
||
print()
|
||
print(" A passing run means the port agrees with the contract ON THESE VALUES.")
|
||
print(" It is not a statement about the 70 sections nobody has reduced to a")
|
||
print(" check -- those are still read by hand, or not read at all.")
|
||
if FAIL:
|
||
print(f"\n🔴 {FAIL} disagreement(s) or lost anchor(s) with the contract")
|
||
else:
|
||
print("\nthe port agrees with the contract on every value checked")
|
||
return 1 if FAIL else 0
|
||
|
||
|
||
sys.exit(main())
|