From 619858104f8a7c4def54038bc0103021ccd84bb7 Mon Sep 17 00:00:00 2001 From: sylph-decoder Date: Mon, 31 Aug 2026 05:38:31 +0000 Subject: [PATCH] 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) Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v --- tools/re-capture/menu_blend_capture.sh | 88 ++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100755 tools/re-capture/menu_blend_capture.sh diff --git a/tools/re-capture/menu_blend_capture.sh b/tools/re-capture/menu_blend_capture.sh new file mode 100755 index 00000000..e950b5b9 --- /dev/null +++ b/tools/re-capture/menu_blend_capture.sh @@ -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)"