Files
Sylpheed/docker/decoder/bin/run-canary
MechaCat02 1fdbb5f197
Some checks failed
CI / Native — linux (pull_request) Failing after 1h1m35s
CI / WASM — Web (pull_request) Successful in 24m37s
CI / Formatting (pull_request) Successful in 26s
fix(decoder): give the container the Canary it is supposed to run
The database mount below was one of three ways the decoder could not reach its
own oracle. The other two are here.

`run-canary` never looked in `Checked/`. It tried `Release/` then `Debug/`, and
both of those exist on this box -- an Aug 28 binary and a Jul 19 one. They boot
the game perfectly well and carry NO `audit_61` branch probe, so a probe run
against either returns zero hits that read as a finding about the game rather
than as a stale binary. Configuration is now the outer loop and location the
inner one, so a `Checked` build anywhere beats a `Release` build anywhere;
`$XENIA_BIN` still overrides everything. Measured here: `Checked` has
`audit_61_branch_probe_pcs`, `Release` and `Debug` do not.

The launcher also now says which instrumentation is missing BEFORE the run,
because the alternative is reading an empty log afterwards and guessing.

`build-canary` built `$PROJECT_DIR/xenia-canary`, which does not exist in this
container -- the source is bind-mounted at `/canary` and the launcher already
exports `XENIA_SRC=/canary`. CONTAINER-NOTES has carried that defect since
2026-08-29 with a symlink workaround and a warning to remember to delete the
symlink afterwards. It now reads `$XENIA_SRC` first, so there is nothing to
remember. Its default configuration moves Release -> Checked to match what
`run-canary` picks; the old default spent a full build on a binary nothing ran.

Two documented blockers are refuted rather than deleted, since the sequence of
wrong readings is what makes the right one checkable: the CONTAINER-NOTES
symlink dance (the warm build volume it was configured against is gone too,
removed in the 2026-09-18 cleanup, so the next build configures cleanly against
`/canary`), and `upstream-baseline.md`'s "`version.h` is never generated" --
`CMakeLists.txt` generates it at configure time now, with a stub fallback.

`decoder-loop.md` claimed the oracle was at `Linux/Release/` and that the probe
was on two side branches; both were true when written and neither is now.

Verified: five pick_bin cases against the extracted function body, and `strings`
on all three real binaries.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 18:08:10 +02:00

118 lines
5.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# Launch Xenia Canary with the settings this title actually needs.
#
# Four of these are not preferences — they are measured requirements, and every
# one of them cost a debugging session before it was pinned down:
#
# --apu=sdl + SDL_AUDIODRIVER=dummy
# There is no PulseAudio here, so `--apu=nop` looks like the safe muted
# choice. It is not: the log then fills with "CreateDriver failed for
# index=0", the guest never gets past the intro movie, and the window
# stays black for 8+ minutes. The SDL driver against a dummy device is
# both silent AND lets the title advance.
#
# NO --audio flag
# The RE notes say "--audio --apu=sdl". `--audio` is NOT a cvar in this
# tree, and an unknown argument is not a friendly error: xenia calls
# ShowSimpleMessageBox from ParseLaunchArguments, BEFORE logging is
# initialised, and that SDL dialog blocks on XIfEvent forever. Headless,
# the symptom is a 10x10 window, an empty log, and no guest memory —
# which reads like a hang deep in the emulator rather than a typo.
# If this ever appears to hang at startup, suspect a bad flag first.
#
# --hid=file --pad_file=...
# The old vgamepad path made its device through /dev/uinput, which is NOT
# namespaced — a pad created inside a container registers with the HOST's
# input stack and every scripted press leaks to the user's desktop. This
# driver reads a text file instead. Drive it with tools/re-capture/pad.py.
# Trap worth remembering: 360 menus poll XamInputGetKeystrokeEx, not
# GetState, so a stubbed GetKeystroke looks like a completely dead pad.
#
# one instance at a time
# Two emulators (or ours + canary) at once perturbs both and the box.
# Enforced with a lockfile rather than left to discipline.
#
# Usage: run-canary [extra xenia flags...]
# ISO from $SYLPH_ISO, else the first *.iso under $PROJECT_DIR.
# Binary from $XENIA_BIN, else the container build, else the repo build.
set -u
LOCK=/tmp/xenia-canary.lock
exec 9>"$LOCK"
if ! flock -n 9; then
echo "run-canary: an emulator is already running (lock $LOCK)." >&2
echo " Only one at a time — kill it first: pkill -x xenia_canary" >&2
exit 1
fi
PROJECT_DIR="${PROJECT_DIR:-/work}"
# ── Binary ───────────────────────────────────────────────────────────────────
# `Checked` FIRST. All three configurations can be built, but `Checked` is the
# one this project actually builds, so it is the one carrying our
# instrumentation; the `Release/` and `Debug/` binaries beside it are months-old
# leftovers that still run and still boot the game, which is exactly what makes
# them dangerous -- a probe run against one reports zero hits and reads as a
# finding about the game. Measured on this box 2026-09-21: `Checked` has
# `audit_61_branch_probe_pcs`, `Release` (Aug 28) and `Debug` (Jul 19) do not.
pick_bin() {
[ -n "${XENIA_BIN:-}" ] && { echo "$XENIA_BIN"; return; }
# Configuration is the OUTER loop, location the inner one: a `Checked` build
# anywhere beats a `Release` build anywhere. The other order picks a stale
# container `Release` over a fresh instrumented repo `Checked`, which is the
# exact mistake this is here to stop.
for cfg in Checked Release Debug; do
for c in \
"${XENIA_BUILD_DIR:-/sylph-home/re/canary-build}/bin/Linux/$cfg/xenia_canary" \
"$PROJECT_DIR/xenia-canary/build/bin/Linux/$cfg/xenia_canary"; do
[ -x "$c" ] && { echo "$c"; return; }
done
done
}
BIN="$(pick_bin)"
if [ -z "${BIN:-}" ]; then
echo "run-canary: no xenia_canary binary found. Build one with: build-canary" >&2
exit 1
fi
# Say what is in the binary before the run, not after reading an empty log.
# A missing probe is a build that predates it, never a quiet game.
for sym in audit_61_branch_probe_pcs RE-DRAW; do
grep -aqm1 -- "$sym" "$BIN" \
|| echo "run-canary: ⚠ $BIN has NO '$sym' -- it predates that instrumentation. Rebuild with: build-canary" >&2
done
# ── ISO ──────────────────────────────────────────────────────────────────────
ISO="${SYLPH_ISO:-}"
if [ -z "$ISO" ]; then
# Prefer a REAL file over a symlink and take the largest: the tree carries
# `xenia-rs/sylpheed.iso` as a symlink to the retail image, and a symlink has
# already cost a session once (Wine could not resolve it -> "path invalid").
ISO="$(find "$PROJECT_DIR" -maxdepth 2 -type f -iname '*.iso' -printf '%s\t%p\n' 2>/dev/null \
| sort -rn | head -1 | cut -f2-)"
fi
if [ -z "$ISO" ] || [ ! -f "$ISO" ]; then
echo "run-canary: no ISO. Set SYLPH_ISO=/path/to/game.iso" >&2
exit 1
fi
ISO="$(readlink -f "$ISO")"
export SDL_AUDIODRIVER="${SDL_AUDIODRIVER:-dummy}"
export DISPLAY="${DISPLAY:-:98}"
PAD="${XENIA_PAD_FILE:-/tmp/xenia_pad.txt}"
: > "$PAD"
# Guest memory is backed by /dev/shm; a stale file from a killed run confuses
# the memory readers (gmem.py finds two candidates and picks the dead one).
rm -f /dev/shm/xenia_memory_* /dev/shm/xenia_code_cache_* 2>/dev/null || true
echo "run-canary: $BIN" >&2
echo " iso: $ISO" >&2
echo " pad: $PAD display: $DISPLAY shm: $(df -h /dev/shm | awk 'NR==2{print $2}')" >&2
exec "$BIN" "$ISO" \
--apu=sdl \
--hid=file --pad_file="$PAD" \
--mute=true \
"$@"