Files
Sylpheed/tools/re-capture/screen_id.py
Sylpheed RE agent 673fdaaf56 tools: a working route to the menu, and the classifier that could not see it
screen_id.py called the main menu "other". Its menu rule required a near-white
fraction above 1.5%, measured in 2026-07; the menu reached from the boot title
measures 0.03% (mean 13,26,59 — dark, strongly blue, essentially green-free).
That is worse than a cosmetic miss: a script that waits for "menu" and never
sees it reports the navigation as failed while the menu is on screen, which is
exactly what happened here. Both measured signatures are now documented in the
code and both classify.

menu_draw_capture.sh now taps ONCE on the first title rather than up to 40 times:
repeating was measured to be useless (the attract title accepts nothing) and the
first title accepts a single press.

title_states_capture.sh is new — it captures the draw list in both title states
in one run, which is what refuted the "the attract title omits the button plate"
theory.
2026-08-18 23:10:29 +00:00

100 lines
3.8 KiB
Python
Executable File

#!/usr/bin/env python3
"""Identify which game screen a screenshot shows, without fixed pixel positions.
The nav scripts used to test named pixels ("648,221 is white"). That works only
while the game image sits at a known place on the root window, and it does not:
xenia's GTK window has a menu bar, so on some displays the image is ~25 px lower
and every constant reads the wrong row. The failure is silent and expensive — a
run sat on a plainly visible MAIN MENU for 300 s reporting "no main menu", and a
later one missed the title screen so the attract movie looped for ten minutes.
So identify screens by WHOLE-IMAGE statistics instead, which no vertical shift
(or scale, or letterbox) can move:
green fraction of pixels that are the game's green UI text/HUD colour
white fraction of near-white pixels
mean per-channel mean
Measured on known-good captures of each screen (1280x720, no menu bar):
title green 0.11% white 7.4% mean (62, 75, 84) blue-ish, bright
main menu green 0.04% white 3.5% mean (25, 38, 70) dark, strongly blue
in flight green 1.2% white 3.4% mean (54, 39, 37) green HUD everywhere
movie green 0% white varies no green at all
Usage: screen_id.py <png> [--json]
Prints one of: title | menu | flight | other, plus the features.
"""
import json
import subprocess
import sys
# Downscaled first: the features are area fractions, so 320x180 gives the same
# answer for a fraction of the work (a full-size pure-python pass costs seconds,
# and these oracles are polled once a second).
W, H = 320, 180
def features(path):
raw = subprocess.run(
["convert", path, "-alpha", "off", "-resize", f"{W}x{H}!", "-depth", "8",
"rgb:-"], capture_output=True).stdout
n = len(raw) // 3
if not n:
return None
green = white = 0
sr = sg = sb = 0
for i in range(0, n * 3, 3):
r, g, b = raw[i], raw[i + 1], raw[i + 2]
sr += r; sg += g; sb += b
if g > 130 and g - r > 45 and g - b > 45:
green += 1
if r > 230 and g > 230 and b > 230:
white += 1
return {"green": green / n, "white": white / n,
"r": sr / n, "g": sg / n, "b": sb / n}
def classify(f):
if f is None:
return "none"
# Flight first: the HUD paints far more green than any menu.
if f["green"] > 0.004:
return "flight"
# The main menu is dark and strongly blue. TWO measured signatures, not one:
#
# white 3.5% mean (25, 38, 70) the 2026-07 reference at the top
# white 0.03% mean (13, 26, 59) measured 2026-08-18, twice, on the menu
# reached from the boot title
#
# The second one classified as "other" under the original rule's
# `0.015 < white` floor, which is worse than it sounds: a script that waits
# for "menu" then never sees it reports the navigation as FAILED while the
# menu is plainly on screen. Both variants are dark, strongly blue and
# essentially green-free, so key on that and let the near-white fraction be
# anything below the title's 3.8%.
if f["b"] - f["r"] > 30 and f["r"] < 45 and f["white"] < 0.075:
return "menu"
# The title is brighter, still blue-ish, and carries the green PRESS A text.
if f["green"] > 0.0004 and f["b"] > 60 and f["white"] > 0.03:
return "title"
return "other"
def main():
path = sys.argv[1]
f = features(path)
name = classify(f)
if "--json" in sys.argv:
print(json.dumps({"screen": name, **(f or {})}))
elif f:
print(f"{name} green={f['green']:.4f} white={f['white']:.4f} "
f"mean=({f['r']:.1f},{f['g']:.1f},{f['b']:.1f})")
else:
print(name)
return 0
if __name__ == "__main__":
raise SystemExit(main())