Files
goldeneye-pc-port/tools_pc/release_gate.sh
T
James Dansereau 162691a3f5 v0.6.0: issue fixes and v0.7.0 roll-ins before release
Issue fixes:
- D643 (#145 b/c, #149): Original control style is a strict virtual N64 pad
  (native buttons in stage/menu/watch/tank, per-seat Input.N64Pad remap,
  Input.N64StickFeel)
- D605 + D645 (#145 a): Invert look reaches the original styles; in Original
  it is the game's own Look up/down, synced with the F10 row
- D606 (#144): optional always-visible mission timer
- D644 (#146): IA4 decodes as the RDP's I3:A1 (file-select crest drew on a
  dark square)
- D581 (#151): open on the real primary monitor (SDL display 0) and centre
  every window placement in the display's global bounds; the earlier
  "display at (0,0)" rule still picked the left monitor
- D580 (#153, #156): runtime check of the propDef type/end-of-list fix
- D646: F10 dropdown popup stays inside the settings box

Frame rate (#148):
- D578: Auto follows the display up to 240 (4 presents per sim tick, the
  renderer's ceiling); 165 and 240 in the Frame rate cap row

Rolled in from release/v0.7.0:
- D632: release the GL context + window before exit (BSOD 0x119)
- D626: Windows per-monitor DPI awareness
- D639: exclusive fullscreen uses the desktop mode
- verify.sh: a frame-size mismatch fails; demobatch/flicker_scan harness

Release gate, licenses, docs:
- NOTICE: macOS packaging and pi/ coverage; runtime libraries bundled in
  the downloads (SDL2/SDL3, zlib, MinGW runtime, macOS GCC runtime)
- macOS package ships SDL3, GCC COPYING.RUNTIME and the GPL-3.0 text
  (tools_pc/dist/GPL-3.0.txt)
- README: release date 2026-10-13, "Why play this port", Legal (Apple,
  runtime licenses), frame-rate text; site date tokens
- tools_pc/release_gate.sh: one-command win/deck/mac release checks
- release notes, findings D578/D580/D581/D643-D646 + index, porting notes
2026-10-13 09:37:19 -04:00

167 lines
8.1 KiB
Bash

#!/usr/bin/env bash
# release_gate.sh -- run the automatable release checks per platform and print
# one PASS/FAIL/SKIPPED table. Reuses verify.sh (pixel gate) and levelbench.sh
# (scripted run + clean-quit + crash-log detection); invents no new harness.
#
# release_gate.sh [--dry-run] [--secs N] [--out DIR] <win|deck HOST|mac HOST|all>
#
# win verify.sh sweep --platform win (pixel gate vs goldens/<lvl>/win)
# + scripted-fire smoke on -level_09 (GE_INPUTSCRIPT MLB1/MLB0,
# the D595/D597 regression) + exit-path check (rc 0 through
# GE_QUITFRAME, "quit requested" log line, no ge007.crash.log)
# deck HOST queue 21 levelbench jobs through deckrunner (Steam shortcut must
# be running on the Deck), crash-free + clean-quit + fps summary.
# Pixel compare is SKIPPED (deck goldens 640x480 vs 1280x800 dumps).
# mac HOST over ssh: optional build, 21-level crash-free sweep + fire smoke.
# No goldens exist -> crash-free only.
# all every platform that is configured (HOSTs from env), else SKIPPED.
#
# Environment (no defaults are machine-specific):
# GE_WIN_DIR game dir for the Windows runs (default: <repo>/build-pc, has data/)
# GE_DECK_HOST / GE_MAC_HOST ssh targets for `all` (or pass as argument)
# GE_MAC_DIR remote game dir on the Mac (binary + data/); required for mac
# GE_MAC_EXE remote binary name inside GE_MAC_DIR (default: ge007)
# GE_MAC_BUILD_CMD optional command run in GE_MAC_DIR before the sweep
# GE_GATE_SSH_OPTS extra ssh/scp options
# Every run ends through GE_QUITFRAME (levelbench sets it); nothing is hard-killed.
# Remote temp/result files are removed after results are pulled.
set -u
ROOT=$(cd "$(dirname "$0")/.." && pwd)
DRY=0; SECS=30; OUT=; MODE=; HOST=
while [ $# -gt 0 ]; do
case $1 in
--dry-run) DRY=1; shift;;
--secs) SECS=$2; shift 2;;
--out) OUT=$2; shift 2;;
-h|--help) sed -n 2,26p "$0"; exit 0;;
win|all) MODE=$1; shift;;
deck|mac) MODE=$1; HOST=${2:-}; shift; [ $# -gt 0 ] && shift;;
*) echo "unknown argument: $1" >&2; exit 2;;
esac
done
[ -n "$MODE" ] || { sed -n 2,6p "$0" >&2; exit 2; }
[ -n "$OUT" ] || OUT="$ROOT/scratch/release-gate-$(date +%Y%m%d-%H%M%S)"
LEVELS="33 34 35 36 09 20 26 43 27 22 24 29 30 25 37 23 39 41 28 32 54"
FIRE='1500:START;6000:MLB1;14000:MLB0'
SSHO=${GE_GATE_SSH_OPTS:-}
ROWS=(); FAILS=0
row() { [ $DRY = 1 ] && [ "$3" = PASS ] && set -- "$1" "$2" DRY "$4"; ROWS+=("$1|$2|$3|$4"); [ "$3" = FAIL ] && FAILS=$((FAILS + 1)); }
run() { if [ $DRY = 1 ]; then echo "+ $*"; return 0; fi; "$@"; }
if [ $DRY = 1 ]; then OUT=$(mktemp -d); trap 'rm -rf "$OUT"' EXIT; else mkdir -p "$OUT"; fi
# judge_summary FILE -> "PASS" or "FAIL:<why>" from a levelbench summary.txt
judge_summary() {
local f=$1
[ -s "$f" ] || { echo "FAIL:no summary"; return; }
grep -q '^exit=0$' "$f" || { echo "FAIL:rc $(grep -m1 '^exit=' "$f" | cut -d= -f2)"; return; }
grep -q 'CRASH LOG PRESENT' "$f" && { echo "FAIL:crash log"; return; }
grep -q 'no clean quit line' "$f" && { echo "FAIL:no clean quit"; return; }
echo PASS
}
gate_win() {
local d=${GE_WIN_DIR:-$ROOT/build-pc} v rc
echo "== win: pixel sweep"
if [ $DRY = 1 ]; then
echo "+ $ROOT/tools_pc/verify.sh sweep --platform win"; rc=0
else
"$ROOT/tools_pc/verify.sh" sweep --platform win | tee "$OUT/win-sweep.txt"; rc=${PIPESTATUS[0]}
fi
if [ $rc = 0 ]; then row win "verify.sh sweep (21 lvls vs win goldens)" PASS ""
else row win "verify.sh sweep (21 lvls vs win goldens)" FAIL "rc $rc, see win-sweep.txt"; fi
echo "== win: scripted-fire smoke + exit path (level 09)"
run "$ROOT/tools_pc/levelbench.sh" --dir "$d" --secs "$SECS" --script "raw:$FIRE" \
--out "$OUT/win-fire" 09
if [ $DRY = 1 ]; then v=PASS; else v=$(judge_summary "$OUT/win-fire/summary.txt"); fi
if [ "$v" = PASS ]; then
row win "fire smoke -level_09 (MLB1/MLB0)" PASS ""
row win "exit path (rc 0 via GE_QUITFRAME, no crash log)" PASS ""
else
row win "fire smoke / exit path -level_09" FAIL "${v#FAIL:}"
fi
}
gate_mac() {
local h=${HOST:-${GE_MAC_HOST:-}} dir=${GE_MAC_DIR:-} exe=${GE_MAC_EXE:-ge007} l v bad=0 badl=
if [ -z "$h" ] || [ -z "$dir" ]; then
row mac "all checks" SKIPPED "no host (arg/GE_MAC_HOST) or GE_MAC_DIR"; return
fi
# shellcheck disable=SC2086 # SSHO is an option list
run scp $SSHO "$ROOT/tools_pc/levelbench.sh" "$h:$dir/levelbench.sh"
# shellcheck disable=SC2086
[ -n "${GE_MAC_BUILD_CMD:-}" ] && run ssh $SSHO "$h" "cd '$dir' && $GE_MAC_BUILD_CMD"
for l in $LEVELS; do
# shellcheck disable=SC2086
run ssh $SSHO "$h" "cd '$dir' && bash levelbench.sh --dir . --exe './$exe' --secs $SECS --script 'raw:$FIRE' --out gate-$l $l >/dev/null 2>&1; cat gate-$l/summary.txt; rm -rf gate-$l" \
> "$OUT/mac-$l.txt" 2>&1
[ $DRY = 1 ] && continue
v=$(judge_summary "$OUT/mac-$l.txt")
[ "$v" = PASS ] || { bad=$((bad + 1)); badl="$badl $l(${v#FAIL:})"; }
done
# shellcheck disable=SC2086
run ssh $SSHO "$h" "cd '$dir' && rm -rf levelbench.sh gate-* ppm ge007.crash.log"
if [ $bad = 0 ]; then row mac "21-level crash-free + fire smoke" PASS ""
else row mac "21-level crash-free + fire smoke" FAIL "$bad bad:$badl"; fi
row mac "pixel compare" SKIPPED "no mac goldens exist"
}
gate_deck() {
local h=${HOST:-${GE_DECK_HOST:-}} l n=0 v bad=0 badl= name t pre last max
if [ -z "$h" ]; then row deck "all checks" SKIPPED "no host (arg/GE_DECK_HOST)"; return; fi
echo "== deck: checking for duplicate deckrunner instances"
if [ $DRY = 1 ]; then echo "+ ssh $h pgrep -af deckrunner.sh"; else
# shellcheck disable=SC2086
ssh $SSHO "$h" 'pgrep -af deckrunner.sh' || true
echo "(if more than one deckrunner.sh is listed, stop the extras before continuing)"
fi
# shellcheck disable=SC2086
run scp $SSHO "$ROOT/tools_pc/levelbench.sh" "$h:levelbench.sh"
pre="gate$(date +%H%M%S)"
for l in $LEVELS; do
n=$((n + 1)); name=$(printf '%s-%03d-%s' "$pre" "$n" "$l")
# shellcheck disable=SC2086
run ssh $SSHO "$h" "mkdir -p ~/lb/queue && echo '--secs $SECS --script fight $l' > ~/lb/queue/$name.job"
done
# the runner is serial; wait for the last job's .finished marker
last=$(printf '%s-%03d-%s' "$pre" 21 54); t=0; max=$((21 * (SECS + 60)))
if [ $DRY = 1 ]; then echo "+ poll: ssh $h test -e ~/lb/$last.finished (timeout ${max}s)"; else
# shellcheck disable=SC2086
until ssh $SSHO "$h" "test -e ~/lb/$last.finished" 2>/dev/null; do
sleep 10; t=$((t + 10))
[ $t -ge $max ] && { echo "deck: timed out waiting (is the deckrunner shortcut running?)" >&2; break; }
done
fi
n=0
for l in $LEVELS; do
n=$((n + 1)); name=$(printf '%s-%03d-%s' "$pre" "$n" "$l")
# shellcheck disable=SC2086
run ssh $SSHO "$h" "cat ~/lb/$name/summary.txt" > "$OUT/deck-$l.txt" 2>&1
[ $DRY = 1 ] && continue
v=$(judge_summary "$OUT/deck-$l.txt")
[ "$v" = PASS ] || { bad=$((bad + 1)); badl="$badl $l(${v#FAIL:})"; }
done
# shellcheck disable=SC2086
run ssh $SSHO "$h" "rm -rf ~/lb/$pre-* ~/lb/done/$pre-* ~/levelbench.sh"
if [ $bad = 0 ]; then row deck "21-level crash-free + clean quit" PASS ""
else row deck "21-level crash-free + clean quit" FAIL "$bad bad:$badl"; fi
row deck "levelbench fps summary" INFO "D578 cadence / D583 SLOW lines in deck-<lvl>.txt"
row deck "pixel compare" SKIPPED "deck goldens 640x480 vs 1280x800 dumps (known v0.7.0 item)"
}
case $MODE in
win) gate_win;;
deck) gate_deck;;
mac) gate_mac;;
all) gate_win; gate_deck; gate_mac;;
esac
echo
echo "=== RELEASE GATE ($([ $DRY = 1 ] && echo dry-run || echo "$OUT")) ==="
printf '%-5s %-52s %-8s %s\n' PLAT CHECK RESULT NOTE
for r in "${ROWS[@]}"; do
IFS='|' read -r a b c d <<<"$r"
printf '%-5s %-52s %-8s %s\n' "$a" "$b" "$c" "$d"
done
if [ $FAILS = 0 ]; then echo "OVERALL: no failures"; else echo "OVERALL: $FAILS FAIL"; fi
[ $FAILS = 0 ]