re: a menu/EXTRAS discriminator that failed its control, and the fix
sylpheed-port's BLOCKED.md ask #1 -- does (B) from EXTRAS also show no black interval, or is "(B) has no black" one screen pair -- needs the harness to know it is on EXTRAS. screen_id.py cannot tell: both are dark blue GP_TITLE screens and it reports `menu` for either. which_title_screen.py correlates a grab against our build 5 / build 6 renders. First version FAILED its control: it called live-main-menu.png "extras" and live-extras.png "main_menu", both backwards, margins under 1.1 on RMSE ~33. Cause: it applied the y=45 game-surface offset unconditionally, but only a full 1280x720 display frame has the menu bar -- a 1279x675 grab IS the surface, and two of the three reference captures are surface-sized. Offset made conditional; the control now passes 4/4 with margins 9.9-11.7 against ~18 within-class. The tool is a navigation aid for driving the emulator and says so: it identifies a screen by agreeing with our own renders, so nothing measured may rest on it. Also records the METHOD entry sylpheed-port offered from their own wedged check script: an absence of output is not a status. They reported "still running, two lines, both ok" for three iterations while the first attempt had died silently under its own timeout with block-buffered output -- zero information, reported as patience. An orphan from an older form of the same script was found running after 9.5 hours. Line-buffer and bound long jobs, and check the artifact rather than the exit code: the artifact reached its correct duration while the process never returned. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
This commit is contained in:
@@ -203,6 +203,19 @@ agent's loop prompt, i.e. nowhere durable. See [`README.md`](README.md) for the
|
|||||||
or `-` is that bug's signature. Grep the corpus for readers of a structure
|
or `-` is that bug's signature. Grep the corpus for readers of a structure
|
||||||
before calling its fix done.
|
before calling its fix done.
|
||||||
|
|
||||||
|
* **An absence of output is not a status.** `sylpheed-port` reported "still
|
||||||
|
running, two lines, both ok" for three consecutive iterations of a check script.
|
||||||
|
The first attempt had already died silently under its own timeout with
|
||||||
|
block-buffered output — so there had been *zero* information from it, and "still
|
||||||
|
two lines" was being read as patience rather than as the alarm it was. The
|
||||||
|
underlying process was wedged on an ffmpeg that finishes its work and never
|
||||||
|
exits (an orphan from an older form of the same script was found still running
|
||||||
|
after **9.5 hours**). Two rules fall out, and this corpus has now paid for both:
|
||||||
|
a long-running job needs **line-buffered output and a bound**, and *"no new
|
||||||
|
output"* must be treated as **no information**, never as progress. Check the
|
||||||
|
artifact, not the exit code — the artifact reached its correct 8.0 s duration
|
||||||
|
while the process it came from never returned.
|
||||||
|
|
||||||
## Runtime / emulator
|
## Runtime / emulator
|
||||||
|
|
||||||
* **Look at the PNG** — and check its dimensions.
|
* **Look at the PNG** — and check its dimensions.
|
||||||
|
|||||||
@@ -23,6 +23,9 @@
|
|||||||
#
|
#
|
||||||
# WHERE=menu (default) arms on the main menu and presses (B) -> menu -> title.
|
# WHERE=menu (default) arms on the main menu and presses (B) -> menu -> title.
|
||||||
# WHERE=title arms on the boot title and presses (A) -> title -> menu.
|
# WHERE=title arms on the boot title and presses (A) -> title -> menu.
|
||||||
|
# WHERE=extras navigates to EXTRAS, arms there, presses (B) -> menu.
|
||||||
|
# Tests whether "(B) has no black interval" is a rule or
|
||||||
|
# one screen pair -- sylpheed-port's BLOCKED.md ask #1.
|
||||||
# The two transitions have DIFFERENT declared fade-ins for the incoming screen --
|
# The two transitions have DIFFERENT declared fade-ins for the incoming screen --
|
||||||
# build 4 is 0->16 (16 units, 8 frames), build 5 is 0->12 (12 units, 6 frames) --
|
# build 4 is 0->16 (16 units, 8 frames), build 5 is 0->12 (12 units, 6 frames) --
|
||||||
# which is what makes the pair a discriminator rather than a fit.
|
# which is what makes the pair a discriminator rather than a fit.
|
||||||
@@ -68,6 +71,31 @@ if [ "$WHERE" = "menu" ]; then
|
|||||||
done
|
done
|
||||||
[ "$s" = "menu" ] || { echo "NO MENU (screen=$s)"; exit 2; }
|
[ "$s" = "menu" ] || { echo "NO MENU (screen=$s)"; exit 2; }
|
||||||
BTN=B
|
BTN=B
|
||||||
|
elif [ "$WHERE" = "extras" ]; then
|
||||||
|
python3 "$SD/pad.py" tap A 0.5
|
||||||
|
for _ in 1 2 3 4 5 6; do
|
||||||
|
sleep 4; s="$(screen)"; echo " after A: $s"
|
||||||
|
[ "$s" = "menu" ] && break
|
||||||
|
done
|
||||||
|
[ "$s" = "menu" ] || { echo "NO MENU (screen=$s)"; exit 2; }
|
||||||
|
# EXTRAS is the only main-menu button that stays inside GP_TITLE, and initial
|
||||||
|
# focus varies boot to boot, so search: press (A), ask which screen we got, and
|
||||||
|
# if it is not EXTRAS come back with (B) and step the cursor. screen_id.py
|
||||||
|
# cannot tell EXTRAS from the main menu -- both are dark blue GP_TITLE screens
|
||||||
|
# -- so the check is which_title_screen.py, whose control separates them by
|
||||||
|
# ~11 RMSE against ~18 within-class.
|
||||||
|
found=""
|
||||||
|
for try in 1 2 3 4 5 6; do
|
||||||
|
python3 "$SD/pad.py" tap A 0.5; sleep 5
|
||||||
|
shot "$OUT/try$try.png"
|
||||||
|
w="$(python3 "$SD/which_title_screen.py" "$OUT/try$try.png")"
|
||||||
|
echo " try $try: $w"
|
||||||
|
case "$w" in extras*) found=1; break;; esac
|
||||||
|
python3 "$SD/pad.py" tap B 0.5; sleep 5
|
||||||
|
python3 "$SD/pad.py" dpad UP 0.3; sleep 2
|
||||||
|
done
|
||||||
|
[ -n "$found" ] || { echo "NEVER REACHED EXTRAS"; exit 3; }
|
||||||
|
BTN=B
|
||||||
else
|
else
|
||||||
# arm on the title itself and press (A): the incoming screen is then build 5,
|
# arm on the title itself and press (A): the incoming screen is then build 5,
|
||||||
# whose declared fade-in is 12 units where build 4's is 16.
|
# whose declared fade-in is 12 units where build 4's is 16.
|
||||||
|
|||||||
44
tools/re-capture/which_title_screen.py
Executable file
44
tools/re-capture/which_title_screen.py
Executable file
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Is this screenshot the MAIN MENU or EXTRAS? screen_id.py cannot tell them apart.
|
||||||
|
|
||||||
|
Both are dark-blue `GP_TITLE` screens, so `screen_id.py` reports `menu` for
|
||||||
|
either. This correlates the grab against our own build 5 / build 6 renders and
|
||||||
|
reports which is closer, plus the margin -- a small margin means "cannot tell",
|
||||||
|
not "the closer one".
|
||||||
|
|
||||||
|
⚠️ These are OUR renders, so this identifies a screen by agreeing with our
|
||||||
|
decoding. It is a navigation aid for driving the emulator, NOT evidence about the
|
||||||
|
game. Nothing measured is allowed to rest on it.
|
||||||
|
|
||||||
|
which_title_screen.py <grab.png> [ref_dir]
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
def load(p, shape=None):
|
||||||
|
im = Image.open(p).convert("RGB")
|
||||||
|
a = np.asarray(im, dtype=float)
|
||||||
|
return a
|
||||||
|
|
||||||
|
def main():
|
||||||
|
grab = load(sys.argv[1])
|
||||||
|
d = sys.argv[2] if len(sys.argv) > 2 else "/sylph-home/re/ref"
|
||||||
|
# The game surface sits at y=45 ONLY in a full 1280x720 display frame; a
|
||||||
|
# 1279x675 grab is the surface already. Hard-coding the 45 made this tool fail
|
||||||
|
# its own control -- it called the main menu "extras" and extras "main_menu",
|
||||||
|
# because two of the three reference captures are surface-sized.
|
||||||
|
off = 45 if grab.shape[0] >= 716 else 0
|
||||||
|
best = []
|
||||||
|
for b, name in ((5, "main_menu"), (6, "extras")):
|
||||||
|
r = load(f"{d}/build{b}.png")
|
||||||
|
h = min(grab.shape[0] - off, r.shape[0]); w = min(grab.shape[1], r.shape[1])
|
||||||
|
g = grab[off:off + h, :w]; rr = r[:h, :w]
|
||||||
|
best.append((float(np.sqrt(((g - rr) ** 2).mean())), name))
|
||||||
|
best.sort()
|
||||||
|
margin = best[1][0] - best[0][0]
|
||||||
|
print(f"{best[0][1]} rmse={best[0][0]:.2f} (other {best[1][1]} {best[1][0]:.2f}, margin {margin:.2f})")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user