Files
Sylpheed/tools/re-capture/footer_and_locked_rows.py
sylph-decoder f6d49da612 re: two menu facts re-measured from the committed captures, no disc needed
Refutation attempt, per the adversarial duty. Target: this page's own row "B on
the main menu goes to the title". Chosen because it is one of only two Q5 rows
with an empty evidence cell, and because it is the only exit from the main menu,
so the port will build on it.

Whole-frame colour test for the pad-glyph discs. The main menu carries ZERO
red-B pixels anywhere in the frame, on two independent captures, while the same
unchanged detector finds 514 on EXTRAS and 518 on DIFFICULTY. The control passes
twice: the A glyph reads 438/438/440/438 across all four screens, so it is one
asset at one size and a B of that family could not have slipped under a
threshold. The main menu's legend is "Select / OK"; every submenu adds "Back".

The claim SURVIVES -- a legend is not behaviour, and an absent glyph cannot
refute an observed press -- but it is downgraded to amber. The observation is
uncited and single, it is now the only Q5 row the game's own text contradicts,
and there is a named confound: the title-side screens auto-return after ~8-10 s
idle, which looks exactly like what was described. Reading 0x828A690C while
pressing B would separate them in one run; that run needs a disc this container
does not have.

Second finding, same method. MISSION SELECT's "sixteen d-pad presses never left
Stage 01" was a LOCKED stage list, not a broken one. The labels have three
brightnesses, not two -- locked 104, unlocked 183, focused 254 -- and the
all-story-unlocked capture is the control that separates the lower two while
holding row 1 at an identical 254. On that save the cursor reaches Stage16 at the
bottom of a scrolled list. The list is 16 long and shows 8 at a time.

Regenerator committed beside the finding; it reads only files already in git.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013UxPvE5cz7zekXBKi7Xw2r
2026-08-29 10:43:01 +00:00

93 lines
3.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""Re-measure two menu facts from the COMMITTED oracle captures — no disc needed.
1. Which screens advertise Ⓑ in their footer legend.
The pad glyphs are saturated green (Ⓐ) and red (Ⓑ) discs on a blue field,
so a colour test finds them without knowing where the footer is.
2. Whether a dim MISSION SELECT row is LOCKED or merely UNFOCUSED.
Three brightness levels discriminate; the all-unlocked capture is the
control that separates them.
Usage: python3 tools/re-capture/footer_and_locked_rows.py [repo-root]
"""
import sys
import pathlib
import numpy as np
from PIL import Image
ROOT = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else ".").resolve()
CAP = ROOT / "docs/re/captures"
def glyph_masks(rgb):
r, g, b = rgb[:, :, 0], rgb[:, :, 1], rgb[:, :, 2]
green = (g > 110) & (g > r + 45) & (g > b + 45)
red = (r > 110) & (r > g + 45) & (r > b + 45)
return green, red
def blobs(mask, gap=20):
ys, xs = np.nonzero(mask)
if len(xs) == 0:
return []
o = np.argsort(xs)
xs, ys = xs[o], ys[o]
out, start = [], 0
for i in range(1, len(xs) + 1):
if i == len(xs) or xs[i] - xs[i - 1] > gap:
s = slice(start, i)
out.append((int(xs[s].min()), int(xs[s].max()),
int(ys[s].min()), int(ys[s].max()), i - start))
start = i
return out
def footers():
print("== 1. footer legends: does the screen advertise Ⓑ? ==")
print(f"{'capture':46} {'Ⓐ px':>7} {'Ⓑ px':>7} verdict")
shots = [
("title-builds/live-main-menu.png", "main menu"),
("title-builds/live-main-menu-options-focused.png", "main menu (OPTIONS focused)"),
("title-builds/live-extras.png", "EXTRAS"),
("difficulty-screen.png", "DIFFICULTY"),
]
for rel, _name in shots:
p = CAP / rel
if not p.exists():
print(f"{rel:46} MISSING")
continue
a = np.asarray(Image.open(p).convert("RGB")).astype(int)
g, r = glyph_masks(a) # WHOLE frame, not a guessed band
verdict = "no Ⓑ anywhere in frame" if r.sum() == 0 else f"Ⓑ at {blobs(r)[0][:2]}"
print(f"{rel:46} {g.sum():7d} {r.sum():7d} {verdict}")
ROW_Y0, ROW_PITCH, ROW_X = 201, 50, (190, 320)
def stage_rows():
print("\n== 2. MISSION SELECT rows: locked, or just unfocused? ==")
shots = [
("mission-select-stage01-only.png", "save with only Stage01 cleared"),
("mission-select-all-story-unlocked.png", "save with the story unlocked"),
("mission-select-ends-at-stage16.png", "unlocked, scrolled to the end"),
]
for rel, note in shots:
p = CAP / rel
if not p.exists():
print(f"{rel:44} MISSING")
continue
a = np.asarray(Image.open(p).convert("L")).astype(float)
p95 = []
for i in range(8):
y = ROW_Y0 + ROW_PITCH * i
p95.append(np.percentile(a[y - 14:y + 14, ROW_X[0]:ROW_X[1]], 95))
print(f"{rel:44} {note}")
print(" row p95: " + " ".join(f"{v:5.0f}" for v in p95))
if __name__ == "__main__":
footers()
stage_rows()