tools: capture targets, and a Z-aware draw-order decoder

`ui_draw_capture.sh` grows three knobs the second iteration needed:

* ARM=early presses F10 before the title exists, so a long window contains the
  frames in which a screen is BUILT (it turns out none are — the title screen
  submits the same 11 draws every frame and never rebuilds);
* TARGET=menu taps A once on the title and arms on the main menu, skipping
  attract movies on the way — it does not get there, but the blocker it hits is
  documented rather than worked around;
* EXTRA_FLAGS passes emulator cvars through (--create_profile_if_none,
  --mem_watch=false, --log_level).

`ui_draw_order.py` follows the capture's new vertex format (x, y, z) and reports
the Z it now has.
This commit is contained in:
Sylpheed RE agent
2026-08-18 20:15:48 +00:00
parent 53642ee3d6
commit 088fcce724
2 changed files with 30 additions and 3 deletions

View File

@@ -29,7 +29,7 @@ wide(){ [ "$(identify -format '%w' "$1" 2>/dev/null || echo 0)" -gt 1000 ]; }
xsetroot -solid black 2>/dev/null || true xsetroot -solid black 2>/dev/null || true
# ATTACH=1 watches an emulator that is already up instead of launching one. # ATTACH=1 watches an emulator that is already up instead of launching one.
if [ "${ATTACH:-0}" != "1" ]; then 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" & ) ( cd "$OUT" && nohup run-canary --log_ui_draws=true --ui_draw_capture_frames="${FRAMES:-3}" --ui_draw_capture_max="${MAXDRAWS:-20000}" ${EXTRA_FLAGS:-} >"$OUT/canary.stdout" 2>"$OUT/canary.stderr" & )
# Grace period before the liveness check: run-canary is a shell that execs the # 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 # binary, and polling `ps -C xenia_canary` in the first moments reports GONE
# for a process that is merely not exec'd yet. # for a process that is merely not exec'd yet.
@@ -66,6 +66,33 @@ while [ $SECONDS -lt $deadline ]; do
d=$(compare -metric RMSE /tmp/u1.png /tmp/u2.png null: 2>&1 | sed 's/ .*//' | cut -d. -f1); d=${d:-0} 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}') s=$(python3 "$SD/screen_id.py" /tmp/u2.png | awk '{print $1}')
echo "t=${SECONDS}s screen=$s rmse=$d" echo "t=${SECONDS}s screen=$s rmse=$d"
# TARGET=menu walks one step further: tap A ONCE on the title, then arm on the
# main menu. One tap, and only from a full-width grab — the run that tapped
# repeatedly (off a 10-pixel sliver it had classified) crashed the guest in the
# save-data probe behind that button.
if [ "${TARGET:-title}" = "menu" ]; then
if [ "$s" = "title" ] && [ "${tapped:-0}" = "0" ]; then
echo "TITLE at ${SECONDS}s -> one A"; python3 "$SD/pad.py" tap A 0.25; tapped=1
elif [ "$s" = "menu" ]; then
cp /tmp/u2.png "$OUT/menu-before-f10.png"
xdotool windowactivate "$win" 2>/dev/null
xdotool key --window "$win" F10
xdotool key F10
sleep 3
screenshot "$OUT/menu-after-f10.png" >/dev/null 2>&1
if ls "$OUT"/xenia_re_ui_draws_*.log >/dev/null 2>&1; then
echo "MENU CAPTURED at ${SECONDS}s"; break
fi
echo "F10 pressed on the menu; no log yet"
elif [ "$d" -gt 1500 ]; then
# Still in the attract loop. Without this the menu target waits forever:
# the attract movie returns to the title and away again, and a run that
# only ever watches never gets there.
echo "movie (rmse $d) -> skip A"; python3 "$SD/pad.py" tap A 0.25; sleep 3
fi
sleep 1
continue
fi
if [ "$s" = "title" ] && [ "${ARM:-title}" = "early" ]; then if [ "$s" = "title" ] && [ "${ARM:-title}" = "early" ]; then
cp /tmp/u2.png "$OUT/title-reached.png" cp /tmp/u2.png "$OUT/title-reached.png"
echo "TITLE REACHED at ${SECONDS}s (capture was armed early)" echo "TITLE REACHED at ${SECONDS}s (capture was armed early)"

View File

@@ -19,7 +19,7 @@ import glob
from collections import defaultdict from collections import defaultdict
W, H = 1280, 720 W, H = 1280, 720
VERT = re.compile(r"\[([0-9A-F]{8}),([0-9A-F]{8})=(-?\d+\.\d+),(-?\d+\.\d+)\]") VERT = re.compile(r"\[(-?\d+\.\d+),(-?\d+\.\d+),z=(-?\d+\.\d+)\]")
def sprite_index(texdir): def sprite_index(texdir):
@@ -78,7 +78,7 @@ def main():
continue continue
if "vb=0x" in line and pending: if "vb=0x" in line and pending:
if want is None or frame == want or frame is None: if want is None or frame == want or frame is None:
verts = [(float(a), float(b)) for _, _, a, b in VERT.findall(line)] verts = [(float(a), float(b)) for a, b, _z in VERT.findall(line)]
if verts: if verts:
# Two conventions in one log: the UI sprite shader emits NDC, # Two conventions in one log: the UI sprite shader emits NDC,
# the full-screen pass emits pixels already (-0.5 .. 1279.5). # the full-screen pass emits pixels already (-0.5 .. 1279.5).