From 01025ab494d988948ab7faebb0aef4b70f98d786 Mon Sep 17 00:00:00 2001 From: sylph-decoder Date: Mon, 31 Aug 2026 06:11:50 +0000 Subject: [PATCH] tools: capture the title's blend states, and replicate the menu in a second session Completes the reach of ui-blend-mode-measured.md, which was explicitly two screens and one session and explicitly not the title. Three things it does differently from menu_blend_capture.sh, each one paid for yesterday: a 1200 s title deadline instead of 420 (the 420 fired while the game was minutes from the settled title); the title arm is gated on the plate pulse because build 2's PRESS (A) plate is part of what is being measured; and on timeout it leaves the emulator up and says to attach rather than reboot. Committed before it is run, per METHOD.md. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v --- tools/re-capture/title_blend_capture.sh | 87 +++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100755 tools/re-capture/title_blend_capture.sh diff --git a/tools/re-capture/title_blend_capture.sh b/tools/re-capture/title_blend_capture.sh new file mode 100755 index 00000000..75c73daf --- /dev/null +++ b/tools/re-capture/title_blend_capture.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# Boot -> TITLE -> arm the UI draw/blend capture there -> (A) -> MAIN MENU -> arm again. +# +# Completes the blend table started in docs/re/structures/ui-blend-mode-measured.md, +# whose stated reach was "two screens, one session" and explicitly NOT the title. +# The second arm at the menu is deliberate: it replicates the main-menu result in a +# SECOND session, which is the part of that page's reach a rerun can actually widen. +# +# Differences from menu_blend_capture.sh, each one paid for: +# +# * DEADLINE defaults to 1200 s, not 420. On 2026-08-31 a 420 s wait reported +# "NEVER REACHED THE TITLE" and the emulator, left running, was at the settled +# title minutes later and took (A) first try. A timeout measures the timeout. +# * The title arm is gated on the PLATE PULSE (wait_plate_pulse.py), not on +# screen_id alone: build 2's `PRESS (A)` plate is part of what is being +# measured, and it pulses. +# * On timeout it LEAVES THE EMULATOR UP and says so, so the run can be rescued +# by attaching instead of rebooting. +# * No --log_ui_draws. Canary's own source records that arming is unconditional +# now and that launching with the flag correlates with the title refusing (A), +# 0 of 7 runs against 4 of 5 without. +# +# F10 opens Xenia's menu bar, and any Xenia UI makes IsUIActive() true, after +# which XamInputGetKeystrokeEx returns SUCCESS with a zeroed keystroke and the +# guest's unbounded pump queues one entry per poll -- the crash in +# docs/re/structures/title-a-press-fault.md. So the surface is clicked +# immediately after every F10, and stdout is size-checked afterwards. +# +# Usage: title_blend_capture.sh [out_dir] +set -u +export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98 +SD="$(cd "$(dirname "$0")" && pwd)" +OUT="${1:-/sylph-home/re/titleblend}" +DEADLINE="${DEADLINE:-1200}" +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}'; } +shot(){ screenshot "$1" >/dev/null 2>&1; } +screen(){ shot /tmp/tbc.png; python3 "$SD/screen_id.py" /tmp/tbc.png | awk '{print $1}'; } +arm(){ # $1 = label + xdotool windowactivate --sync "$win"; sleep 1 + xdotool key F10; sleep 2 + xdotool mousemove 900 400 click 1; sleep 2 + echo "ARMED ($1) at ${SECONDS}s -- stdout $(stat -c%s "$OUT/canary.stdout") bytes" +} + +( cd "$OUT" && nohup run-canary --mem_watch=false \ + --ui_draw_capture_frames="${FRAMES:-4}" --ui_draw_capture_max="${MAXDRAWS:-4000}" \ + --logged_profile_slot_0_xuid=B13EBABEBABEBABE \ + >"$OUT/canary.stdout" 2>"$OUT/canary.stderr" & ) +sleep 8 +until xdotool search --name "Xenia-canary" >/dev/null 2>&1; do + [ -n "$(alive)" ] || { echo "EMULATOR GONE"; exit 4; }; sleep 1 +done +win="$(xdotool search --name "Xenia-canary" | tail -1)" +echo "WINDOW=$win" + +s="" +until [ "$s" = "title" ]; do + [ $SECONDS -lt $DEADLINE ] || { echo "TIMEOUT at ${SECONDS}s, screen=$s -- EMULATOR LEFT RUNNING, attach rather than reboot"; exit 1; } + s="$(screen)"; echo "t=${SECONDS}s $s" + [ "$s" = "title" ] || sleep 4 +done +echo "TITLE at ${SECONDS}s" + +# The plate is part of the title. Gate on its pulse before arming. +timeout 180 python3 "$SD/wait_plate_pulse.py" || echo " (plate gate did not fire; arming anyway)" +shot "$OUT/title.png" +arm title +ls -l "$OUT"/xenia_re_ui_draws_*.log 2>/dev/null || echo "NO TITLE LOG -- F10 was lost" + +# One tap. The title that ENDS the boot accepts a single (A); repeating is pointless. +python3 "$SD/pad.py" tap A 0.3 +for _ in 1 2 3 4 5 6; do + sleep 4; s="$(screen)"; echo " after A: $s" + [ "$s" = "menu" ] && break +done +shot "$OUT/menu.png" +if [ "$s" = "menu" ]; then + arm menu +else + echo "NO MENU (screen=$s) -- title capture kept" +fi +ls -l "$OUT"/xenia_re_ui_draws_*.log 2>/dev/null +grep -i "UI-CAP" "$OUT/canary.stdout" | tail -6 +echo "stdout $(stat -c%s "$OUT/canary.stdout") bytes (a guest fault dumps ~100 MB/30 s)" +pkill -x xenia_canary 2>/dev/null; sleep 2 +echo "DONE at ${SECONDS}s"