`ui_draw_capture.sh` boots to the title screen and arms Canary's new `log_ui_draws` there — deliberately WITHOUT tapping A on the title, which is the subject and which sends the guest into a save-data probe. ARM=early presses F10 before the title exists, for the frames in which a screen is built. A grab that is not full-width is a hard error rather than something to classify. `ui_draw_order.py` turns the capture into a named paint order: the UI shader emits NDC, so a quad's pixel rect is exact, and the disc's sprites have near-unique decoded sizes, so the rect's SIZE names the sprite. Matching is nearest-within-6px with the distance printed, because a quad comes back a few pixels under its sprite for a reason that is not yet measured.
97 lines
4.4 KiB
Bash
Executable File
97 lines
4.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ONE blocking session: boot -> title screen -> arm the UI draw-order capture.
|
|
#
|
|
# Unlike skip_intro.sh this deliberately does NOT tap A on the title: the title
|
|
# screen IS the subject, and a stray A there sends the guest into the save-data
|
|
# probe (which crashed the guest on run 1 of 2026-08-18). It taps A only while a
|
|
# movie is playing, and the instant the title is classified it presses F10,
|
|
# which arms `log_ui_draws`' 3-frame submission-order dump.
|
|
#
|
|
# The log lands in the emulator's CWD, so the emulator is launched from $OUT.
|
|
#
|
|
# A grab that is not full-width is treated as a HARD ERROR rather than
|
|
# classified. Run 1 spent 13 minutes acting on a 10-pixel sliver: the screenshot
|
|
# wrapper had picked a 10x10 helper window that shares the class "xenia_canary"
|
|
# with the real one. Nothing failed loudly; the classifier just returned noise.
|
|
#
|
|
# Usage: ui_draw_capture.sh [timeout_s] [out_dir]
|
|
set -u
|
|
export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98
|
|
SD="$(cd "$(dirname "$0")" && pwd)"
|
|
TIMEOUT="${1:-900}"
|
|
OUT="${2:-/sylph-home/re/uicap}"
|
|
mkdir -p "$OUT"
|
|
rm -f "$OUT"/xenia_re_ui_draws_*.log
|
|
|
|
alive(){ ps -o pid=,stat= -C xenia_canary 2>/dev/null | awk '$2 !~ /^Z/ {print $1}'; }
|
|
wide(){ [ "$(identify -format '%w' "$1" 2>/dev/null || echo 0)" -gt 1000 ]; }
|
|
|
|
xsetroot -solid black 2>/dev/null || true
|
|
# ATTACH=1 watches an emulator that is already up instead of launching one.
|
|
if [ "${ATTACH:-0}" != "1" ]; then
|
|
( cd "$OUT" && nohup run-canary --log_ui_draws=true --ui_draw_capture_frames="${FRAMES:-3}" --ui_draw_capture_max="${MAXDRAWS:-20000}" >"$OUT/canary.stdout" 2>"$OUT/canary.stderr" & )
|
|
# Grace period before the liveness check: run-canary is a shell that execs the
|
|
# binary, and polling `ps -C xenia_canary` in the first moments reports GONE
|
|
# for a process that is merely not exec'd yet.
|
|
sleep 8
|
|
fi
|
|
|
|
until xdotool search --name "Xenia-canary" >/dev/null 2>&1; do
|
|
[ -n "$(alive)" ] || { echo "EMULATOR GONE before the window appeared"; exit 4; }
|
|
sleep 1
|
|
done
|
|
win="$(xdotool search --name "Xenia-canary" | tail -1)"
|
|
echo "WINDOW=$win"
|
|
xdotool windowactivate "$win" 2>/dev/null; xdotool windowfocus "$win" 2>/dev/null
|
|
|
|
# ARM=early presses F10 before the title exists, so a long window contains the
|
|
# frame in which the screen is BUILT. Armed at the title instead, a capture only
|
|
# ever sees the steady state — and on this title screen the steady state is 11
|
|
# draws a frame that never mention a sprite.
|
|
if [ "${ARM:-title}" = "early" ]; then
|
|
xdotool windowactivate "$win" 2>/dev/null
|
|
xdotool key --window "$win" F10
|
|
xdotool key F10
|
|
echo "ARMED EARLY at ${SECONDS}s"
|
|
fi
|
|
|
|
deadline=$(( SECONDS + TIMEOUT ))
|
|
while [ $SECONDS -lt $deadline ]; do
|
|
rm -f /tmp/u1.png /tmp/u2.png
|
|
screenshot /tmp/u1.png >/dev/null 2>&1; sleep 0.6
|
|
screenshot /tmp/u2.png >/dev/null 2>&1
|
|
[ -s /tmp/u1.png ] && [ -s /tmp/u2.png ] || { echo "SCREENSHOT FAILED at ${SECONDS}s"; exit 5; }
|
|
wide /tmp/u2.png || { echo "GRAB IS NOT THE GAME SURFACE ($(identify -format '%wx%h' /tmp/u2.png)) at ${SECONDS}s"; exit 6; }
|
|
[ -n "$(alive)" ] || { echo "EMULATOR GONE at ${SECONDS}s"; exit 4; }
|
|
d=$(compare -metric RMSE /tmp/u1.png /tmp/u2.png null: 2>&1 | sed 's/ .*//' | cut -d. -f1); d=${d:-0}
|
|
s=$(python3 "$SD/screen_id.py" /tmp/u2.png | awk '{print $1}')
|
|
echo "t=${SECONDS}s screen=$s rmse=$d"
|
|
if [ "$s" = "title" ] && [ "${ARM:-title}" = "early" ]; then
|
|
cp /tmp/u2.png "$OUT/title-reached.png"
|
|
echo "TITLE REACHED at ${SECONDS}s (capture was armed early)"
|
|
break
|
|
elif [ "$s" = "title" ]; then
|
|
cp /tmp/u2.png "$OUT/title-before-f10.png"
|
|
xdotool windowactivate "$win" 2>/dev/null
|
|
xdotool key --window "$win" F10
|
|
xdotool key F10
|
|
sleep 2
|
|
screenshot "$OUT/title-after-f10.png" >/dev/null 2>&1
|
|
sleep 2
|
|
if ls "$OUT"/xenia_re_ui_draws_*.log >/dev/null 2>&1; then
|
|
echo "ARMED and writing at ${SECONDS}s"
|
|
# A long window is deliberately NOT waited out here: the caller polls the
|
|
# log, because the interesting frames may be many seconds away.
|
|
[ "${WAIT_DONE:-0}" = "1" ] || break
|
|
fi
|
|
echo "F10 pressed; window still open"
|
|
elif [ "$d" -gt 1500 ]; then
|
|
echo "movie (rmse $d) -> skip A"; python3 "$SD/pad.py" tap A 0.25; sleep 3
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
grep -i "UI-CAP" "$OUT/canary.stdout" 2>/dev/null | tail -5
|
|
ls -l "$OUT"/xenia_re_ui_draws_*.log 2>/dev/null || echo "NO UI DRAW LOG"
|
|
echo "UI DRAW CAPTURE SESSION DONE (emulator left running on purpose)"
|