From bdc2dd4e4e28bb517420f8781769595660da2686 Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Wed, 19 Aug 2026 10:59:35 +0000 Subject: [PATCH] tools: a reliability harness, and a title test that also requires a static frame resume_reliability.sh boots N times and records, per boot, whether the title's loader thread was created and whether it ever became the CALLER of a kernel call. That is the thing the threading fix targets, and it is binary; "did the menu appear" is a worse test because it needs the game to get further and the attract loop confuses it. Two defects fixed on the way, both found by the harness disagreeing with itself: * is_title.py's glyph count alone still fired at 113s and 173s, during the intro movie, and the single press was wasted there. Measured, not guessed: on those runs NO loader thread was created at all, and the title handler always creates one - even on the boots where it then fails to run. The title check now also requires the frame to be STATIC, reusing the RMSE between the two grabs 0.6s apart that the movie branch already computes, and raises the glyph threshold to 800 (a real title measures ~1450-1520, the SQUARE ENIX logo 0). * `grep -ac ... || echo 0` corrupted every CSV field it guarded: grep exits 1 on zero matches, so the fallback APPENDED a second value and the field came out "0 0". Retrospective before/after from the logs already on disc, using ResolvePath after the resume (logged at every verbosity, so it is comparable across all runs): before the fix 1 of 5 boots that created a loader thread went on to load assets; after it, 2 of 2. n is small and a proper count is running. --- tools/re-capture/resume_reliability.sh | 45 ++++++++++++++++++++++++++ tools/re-capture/skip_intro.sh | 7 +++- 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100755 tools/re-capture/resume_reliability.sh diff --git a/tools/re-capture/resume_reliability.sh b/tools/re-capture/resume_reliability.sh new file mode 100755 index 00000000..3707328b --- /dev/null +++ b/tools/re-capture/resume_reliability.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Count how often the title screen's loader thread actually RUNS. +# +# Pressing (A) at the title makes the game ExCreateThread(CREATE_SUSPENDED) + +# NtResumeThread. Before canary a60fe7d11 that resume could be lost, leaving a +# thread with zero kernel calls and zero CPU (docs/re/canary-scripted-input-traps.md). +# The menu appearing is a poor test — it needs the game to get further, and the +# attract loop confuses it — so this measures the thing the fix targets directly: +# does the slot-(1F) thread appear as the CALLER of any kernel call? +# +# Needs kernel logging, which is off by default: LOG_MASK=12 LOG_LEVEL=3. +# +# resume_reliability.sh [runs] [tag] +set -u +export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98 +SD="$(cd "$(dirname "$0")" && pwd)" +RUNS="${1:-4}" +TAG="${2:-rel}" +OUT=/sylph-home/re/resume-reliability +mkdir -p "$OUT" +CSV="$OUT/$TAG.csv" +echo "run,title_at_s,thread_created,thread_ran_calls,resolvepath_after,screen" > "$CSV" + +for i in $(seq 1 "$RUNS"); do + pkill -9 -x xenia_canary 2>/dev/null; sleep 3 + rm -f /tmp/xenia-canary.lock + LOG_MASK=12 LOG_LEVEL=3 BOOT_MENU_LOG="$OUT/$TAG-$i.stdout" \ + timeout 700 "$SD/boot_menu.sh" "$TAG-$i" > "$OUT/$TAG-$i.boot" 2>&1 + sleep 20 + L="$OUT/$TAG-$i.stdout" + t=$(grep -ao 'TITLE at [0-9]*s' "$OUT/$TAG-$i.boot" 2>/dev/null | head -1 | tr -dc '0-9') + created=$(grep -ac '(1F) Stack' "$L" 2>/dev/null); created=${created:-0} + h=$(grep -a '(1F) Stack' "$L" 2>/dev/null | tail -1 | grep -ao 'XThread[0-9A-F]*' | sed 's/XThread//') + calls=0; rp=0 + if [ -n "${h:-}" ]; then + calls=$(grep -ac "^[dik]> $h" "$L" 2>/dev/null); calls=${calls:-0} + n=$(grep -an "NtResumeThread($h" "$L" 2>/dev/null | tail -1 | cut -d: -f1) + if [ -n "${n:-}" ]; then rp=$(tail -n +"$n" "$L" | grep -ac 'ResolvePath'); rp=${rp:-0}; fi + fi + screenshot "$OUT/$TAG-$i.png" >/dev/null 2>&1 + s=$(python3 "$SD/screen_id.py" "$OUT/$TAG-$i.png" 2>/dev/null | awk '{print $1}') + echo "$i,${t:-none},$created,$calls,$rp,${s:-none}" | tee -a "$CSV" +done +pkill -9 -x xenia_canary 2>/dev/null +echo "--- $CSV ---"; cat "$CSV" diff --git a/tools/re-capture/skip_intro.sh b/tools/re-capture/skip_intro.sh index 6c7d43fe..b8f9a5d7 100755 --- a/tools/re-capture/skip_intro.sh +++ b/tools/re-capture/skip_intro.sh @@ -50,7 +50,12 @@ while [ $SECONDS -lt $deadline ]; do # then over-matched the other way: it called the SQUARE ENIX publisher logo # "title" 151s into a boot, and this script spent its one press there. # is_title.py counts glyph pixels, which only the interactive title has. - if python3 "$SD_SI/is_title.py" /tmp/f2.png >/dev/null 2>&1; then + # AND the frame must be STATIC. A glyph count alone still fired at 113-173s, + # during the intro movie, and the press was wasted — measured by the loader + # thread that the title handler ALWAYS creates being absent entirely on those + # runs. The resting title barely changes between two grabs 0.6s apart; a movie + # does, and `$d` is already computed above for exactly that distinction. + if [ "$d" -le 1500 ] && python3 "$SD_SI/is_title.py" /tmp/f2.png 800 >/dev/null 2>&1; then echo "TITLE at ${SECONDS}s -> A"; tapA; exit 0 fi # DO NOT tap through the movies. This branch used to, and for a long time it