From 6aa31e52ba42000496873a3f635496f4fa45ade5 Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Wed, 26 Aug 2026 22:55:58 +0000 Subject: [PATCH] tools: reject degenerate captures in screen_id instead of classifying them Every statistic in screen_id is an AREA FRACTION, so a capture that is not a game frame still produces clean numbers. Measured 2026-08-26: a guard shot came back 10x710 -- a sliver -- and classified as `menu` with green=0.0000, white=0.0157. The guard passed, the fixed key sequence went out anyway, and the run loaded a TUTORIAL instead of the save's Stage 02. This is the second time this failure has been paid for. bin/screenshot's own header records the first (2026-08-18): a second window of class "xenia_canary" meant grabs came back as slivers and "a whole session's screen ids were noise". That fix hardened the CAPTURE side only, so the same failure still reached the oracles by any other path. Reject it at the point the answer is consumed too: features() now returns None below 640x360 and classify() reports `none`. Verified: the 10x710 sliver -> `none`; readyroom, flight and the briefing capture all still classify as before. --- tools/re-capture/screen_id.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/tools/re-capture/screen_id.py b/tools/re-capture/screen_id.py index 5a09dad3..8de3e198 100755 --- a/tools/re-capture/screen_id.py +++ b/tools/re-capture/screen_id.py @@ -35,7 +35,34 @@ import sys W, H = 320, 180 +def frame_size(path): + """(w, h) of the capture, or None if it cannot be read.""" + out = subprocess.run(["identify", "-format", "%w %h", path], + capture_output=True, text=True).stdout.split() + try: + return int(out[0]), int(out[1]) + except (IndexError, ValueError): + return None + + +# A capture that is not a plausible game frame must be REJECTED, not classified. +# MEASURED 2026-08-26: a guard shot came back 10x710 -- a sliver -- and every +# statistic below is an area fraction, so the sliver classified cleanly as +# `menu`. The guard passed, the fixed key sequence was issued anyway, and the run +# loaded a TUTORIAL instead of the save's Stage 02. +# +# This is the SECOND time this has been paid for. `bin/screenshot`'s own header +# records the first (2026-08-18): a second window of class "xenia_canary" meant +# grabs came back as slivers and "a whole session's screen ids were noise". That +# fix hardened the CAPTURE side only, so the same failure still reaches the +# oracles by any other path. Reject it here too, where the answer is consumed. +MIN_W, MIN_H = 640, 360 + + def features(path): + size = frame_size(path) + if size is None or size[0] < MIN_W or size[1] < MIN_H: + return None raw = subprocess.run( ["convert", path, "-alpha", "off", "-resize", f"{W}x{H}!", "-depth", "8", "rgb:-"], capture_output=True).stdout @@ -57,7 +84,7 @@ def features(path): def classify(f): if f is None: - return "none" + return "none" # unreadable OR not a plausible frame -- never a screen # Flight first: the HUD paints far more green than any menu. # (READY ROOM is checked before the menu rule because it is also dark-ish # and strongly blue, and would otherwise be swallowed by it.)