tools+docs: locate the lost resume, and a title test that is neither too narrow nor too loose

The stalled loader thread is a lost wakeup in Xenia's POSIX threading, fixed on
the canary branch as a60fe7d11 and written up here. A thread created suspended
publishes state_ and suspend_count_ in two separate lock scopes, and Resume()
waits only for state_ before testing suspend_count_ == 0 - so a resumer in the
gap drops the resume and the thread waits forever. The Linux XThread::Resume
discards that false, which is why the guest saw success.

On the first clean boot after the fix the loader thread is the CALLER on 20
kernel-call lines and issues 4 ResolvePath reads. Every failed boot before it had
exactly zero of both.

Stated plainly as not shown: that boots now reach the menu RELIABLY. One post-fix
boot, and it is confounded by the harness.

Which is the second half. skip_intro.sh's title test has now been wrong twice in
opposite directions: originally one absolute pixel (625,618) - a 1280x720
coordinate against the 1279x675 game surface, so it read the copyright line and
timed out with the title on screen - and then my replacement, screen_id.py, which
is too loose and called the SQUARE ENIX publisher logo "title" 151s into a boot,
spending the script's single press there. is_title.py now counts the green (A)
glyph over the whole frame: geometry-independent and specific, measured at 0
pixels on the logo and 1520 on a real title.
This commit is contained in:
Sylpheed RE agent
2026-08-19 10:41:43 +00:00
parent 24c1c940e6
commit ae3f37d3ec
4 changed files with 130 additions and 10 deletions

26
tools/re-capture/is_title.py Executable file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env python3
"""Is this frame the interactive title screen? Exit 0 if yes.
Counts the pixels of the **green (A) glyph** in "PRESS (A) BUTTON" anywhere in
the frame. That is the one thing only the interactive title has, and counting is
geometry-independent — unlike the single absolute pixel this replaced, which was
a 1280x720 coordinate being sampled against the 1279x675 game surface and always
read the copyright line.
`screen_id.py` is too loose for this job on its own: it called the SQUARE ENIX
publisher logo "title" 151 s into a boot, and skip_intro spent its one press
there. Measured on a real title frame: 1442 glyph pixels, centred near (622,572).
is_title.py FRAME.png [min_pixels]
"""
import sys
import numpy as np
from PIL import Image
a = np.asarray(Image.open(sys.argv[1]).convert("RGB"), dtype=int)
r, g, b = a[:, :, 0], a[:, :, 1], a[:, :, 2]
n = int(((g > 130) & (g - r > 45) & (g - b > 45)).sum())
need = int(sys.argv[2]) if len(sys.argv) > 2 else 400
print(f"green-glyph px {n} (need {need})")
sys.exit(0 if n >= need else 1)

View File

@@ -43,13 +43,14 @@ while [ $SECONDS -lt $deadline ]; do
[ -n "$(alive)" ] || { echo "EMULATOR GONE at ${SECONDS}s"; exit 4; }
d=$(compare -metric RMSE /tmp/f1.png /tmp/f2.png null: 2>&1 | sed 's/ .*//' | cut -d. -f1)
d=${d:-0}
# Classify the whole frame, do NOT probe one absolute pixel. The old test
# sampled (625,618) for the green (A) glyph, which is a coordinate from a
# 1280x720 grab; `screenshot` now returns the 1279x675 GAME SURFACE, where
# that point lands on the copyright line and reads (8,17,31). The symptom was
# a 600s TIMEOUT with the title sitting on screen the entire time — measured
# 2026-08-19, with screen_id.py calling it "title" on the same frames.
if [ "$(python3 "$SD_SI/screen_id.py" /tmp/f2.png | awk '{print $1}')" = "title" ]; then
# COUNT the green (A) glyph over the whole frame; do not probe one absolute
# pixel and do not trust screen_id.py here. The pixel probe used a 1280x720
# coordinate against the 1279x675 game surface and always read the copyright
# line (600s TIMEOUT with the title on screen). screen_id.py fixed that and
# then over-matched the other way: it called the SQUARE ENIX publisher logo
# "title" 151s into a boot, and this script spent its one press there.
# is_title.py counts glyph pixels, which only the interactive title has.
if python3 "$SD_SI/is_title.py" /tmp/f2.png >/dev/null 2>&1; then
echo "TITLE at ${SECONDS}s -> A"; tapA; exit 0
fi
# DO NOT tap through the movies. This branch used to, and for a long time it