Files
Sylpheed/tools/port/contract-check
Sylpheed port agent f1e249a606 port: correct a check that asserted an absence of measurement as a finding
The pair I shipped this iteration -- focus_persists on for main_menu, off
everywhere else -- reported both halves as agreement with the contract. Nothing
measured that extras does not persist. The corpus has EXTRAS' opening item from
one entry and (B) restoring the PARENT's focus 4/4; neither says what a submenu's
own cursor does on re-entry. Caught by the Decoder.

It is the mirror of the trap it was written to avoid. I refused to let a derived
menu-wide rule overwrite a measured value, then let 'not measured here' become a
positive assertion of the negative. Both treat a gap in the corpus as if it
carried information and differ only in which direction they fill it. And the
failure mode was the bad one: if the game does persist EXTRAS, the check holds
the port to the wrong behaviour and passes while doing it.

check_focus_persists now asserts only the measured half. The scope became a
separate guard with its own outcome word -- 'only main_menu, AUTHORED DEFAULT,
unmeasured elsewhere' -- which still fails if widened, since that should be a
deliberate edit, but can no longer be read as the game being known to reset.
focus_persists_why records the correction rather than being rewritten.

It also weakens a label. EXTRAS' initial_focus is marked measured and was taken
on a single entry; now that the main menu is known to remember its cursor, a
one-entry reading of any screen may be measuring history rather than what the
screen opens on -- the same objection that reframed the TUTORIAL/NEW GAME
disagreement. The observation stands, its reading as an initial focus does not.
Caveat attached, kind left as measured with a note that it changes if EXTRAS
turns out to persist.

Not building on the non-persistence half until their EXTRAS re-entry run returns.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
2026-08-30 21:39:08 +00:00

341 lines
15 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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.
"""
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 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, 1), real()[1])
try:
fn(None)
finally:
nav = real
def control(h):
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]:
src = h if (fn, old, new) in CONTROLS else nav()[0]
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:
fn(h.replace(old, new, 1))
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.
"""
want = bool(re.search(r"the main menu remembers its cursor; re-entry is not a reset", h))
got = (((jload("authored/flow.json") or {}).get("screens") or {})
.get("main_menu", {}).get("focus_persists"))
report("menu remembers its cursor", want or None, got, want and got is True)
def guard_focus_scope(_h):
"""NOT a contract check. A regression guard on an AUTHORED DEFAULT.
No screen but `main_menu` persists its cursor in this port, and that is the
port's choice, not a finding: it preserves EXTRAS' measured opening item and
invents the least. Guarded so that widening it is a deliberate edit with a
`why`, and labelled so a passing run cannot be read as the game being known
to reset.
"""
global FAIL
on = sorted(n for n, v in ((jload("authored/flow.json") or {}).get("screens") or {}).items()
if isinstance(v, dict) and v.get("focus_persists"))
if on == ["main_menu"]:
print(f" {'focus_persists scope':<30} guard only main_menu"
f" -- AUTHORED DEFAULT, unmeasured elsewhere")
else:
FAIL += 1
print(f" {'focus_persists scope':<30} 🔴 GUARD {on} -- widened past the"
f" one screen measured; needs a why and a measurement")
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 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 "--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):
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())