tools: capture the BLEND STATE of every UI draw on the main menu

Canary's CaptureUiDrawForRE now logs RB_BLENDCONTROL0, RB_COLORCONTROL and
RB_COLOR_MASK per draw, raw and decoded, alongside the shader hashes and bound
texture it already logged. This script drives the game to the main menu and arms
it there.

Unlike menu_draw_capture.sh it does NOT pass --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.

Committed before it is run, per METHOD.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
This commit is contained in:
sylph-decoder
2026-08-31 05:38:31 +00:00
parent dad9f2761a
commit e568a3494d

View File

@@ -0,0 +1,88 @@
#!/usr/bin/env bash
# Boot -> title -> MAIN MENU (or EXTRAS) -> arm the UI draw capture and read the
# BLEND STATE the guest set for each draw.
#
# Why this exists rather than `menu_draw_capture.sh`: that script launches with
# `--log_ui_draws=true`, and Canary's own source records why that is now a bad
# idea — `RequestUiDrawCapture()` in command_processor.cc says arming is
# unconditional since the flag "correlates, across 12 runs, with the title screen
# refusing (A) (0 of 7 with the flag, 4 of 5 without)". The flag is OBSOLETE and
# only kept so old command lines parse. So this one does not pass it.
#
# The measurement: `CaptureUiDrawForRE` now also logs RB_BLENDCONTROL0,
# RB_COLORCONTROL and RB_COLOR_MASK per draw, raw and decoded. That is what the
# GPU was actually told, which is the only thing that can settle whether the
# menu's `ptframe1`/`ptframe2` are composited with something other than
# alpha-over — nothing on the disc selects a per-element mode
# (docs/re/structures/t32-blend-mode-not-on-disc.md).
#
# Two traps this inherits from menu_draw_capture.sh, both measured:
# * (A) at the title is accepted only intermittently, but the title that ENDS
# the boot accepts a single tap; repeating is pointless.
# * F10 arms the capture AND opens the emulator's menu bar. Any Xenia UI makes
# IsUIActive() true, and then XamInputGetKeystrokeEx returns SUCCESS with a
# zeroed keystroke forever, which is the guest-side crash in
# docs/re/structures/title-a-press-fault.md. So click the game surface
# immediately after F10 to dismiss it.
#
# Usage: menu_blend_capture.sh [out_dir]
# SCREEN=extras walk one step right and (A) into EXTRAS before arming
set -u
export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98
SD="$(cd "$(dirname "$0")" && pwd)"
OUT="${1:-/sylph-home/re/blendcap}"
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/mbc.png; python3 "$SD/screen_id.py" /tmp/mbc.png | awk '{print $1}'; }
( 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"
# 1. WAIT for the title. Do not tap through the intro: it is ~3.5 minutes and it
# gets there on its own; a run that tapped every 4 s delivered 88 presses and
# ended on a black screen.
s=""
deadline=$(( SECONDS + 420 ))
while [ $SECONDS -lt $deadline ]; do
s="$(screen)"; echo "t=${SECONDS}s $s"
[ "$s" = "title" ] && break
sleep 4
done
[ "$s" = "title" ] || { echo "NEVER REACHED THE TITLE"; exit 1; }
# 2. ONE tap.
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"
[ "$s" = "menu" ] || { echo "NO MENU (screen=$s)"; exit 2; }
echo "MENU at ${SECONDS}s"
if [ "${SCREEN:-menu}" = "extras" ]; then
# EXTRAS is the second item; the menu opens on NEW GAME.
python3 "$SD/pad.py" tap DOWN 0.2; sleep 1
python3 "$SD/pad.py" tap DOWN 0.2; sleep 1
python3 "$SD/pad.py" tap A 0.3; sleep 4
s="$(screen)"; echo " after EXTRAS attempt: $s"; shot "$OUT/extras.png"
fi
# 3. arm, then dismiss the menu bar F10 opened
xdotool windowactivate --sync "$win"; sleep 1
xdotool key F10; sleep 3
xdotool mousemove 900 400 click 1; sleep 2
shot "$OUT/after-f10.png"
ls -l "$OUT"/xenia_re_ui_draws_*.log 2>/dev/null || echo "NO CAPTURE LOG"
grep -i "UI-CAP" "$OUT/canary.stdout" | tail -3
pkill -x xenia_canary 2>/dev/null; sleep 2
echo "DONE at ${SECONDS}s (emulator killed)"