spacetaxi/tools/stsim/gate.sh
2026-10-19 15:47:41 -05:00

155 lines
5.5 KiB
Bash
Executable file

#!/usr/bin/env bash
# Behavior gate for the Space Taxi simulation: every screen's ride --
# A..X and the 25th, Y -- checked against the real C64.
#
# stsim rides a screen for up to 4000 ticks and prints one line of state
# a tick: the cab, the fare machine, the demo playback, the RNG, the HUD,
# the sprites shown and hashes of screen RAM, color RAM and the charset.
# c64trace.py rides the same input on the original game in VICE and
# prints the same line from the C64's memory. gateHashes.txt holds the
# md5 of each C64 trace, so `check` -- which needs no emulator -- is a
# byte-for-byte comparison with the real machine.
#
# H, W, T and X ride the C64's own attract recordings; the other screens
# ride synthesised streams from gateStreams.h (tools/stsim/recorder).
#
# gate.sh check [dir] every ride's md5 against gateHashes.txt; a ride
# that differs is diffed against its golden in dir,
# if one was captured, to show where
# gate.sh c64 [L...] ride the C64 too (VICE, c64trace.py) and compare
# the two traces byte for byte, showing the first
# fields that differ; every screen by default
# gate.sh c64hashes the C64 traces' md5s in gateHashes.txt's form --
# the only way gateHashes.txt is made
# gate.sh capture [dir] record goldens from the current tree
# gate.sh hashes print the current tree's md5 per screen
#
# The rides run on the game's level bake, so build the game first
# (gateLib.sh finds it). The c64 modes keep their traces in
# build/c64trace/ and run C64_JOBS rides at once (default 4); they exit 2
# when VICE or the original disk is missing (see c64trace.py).
set -u
here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
mode=${1:-check}
source "$here/gateLib.sh"
traces=$here/../../build/c64trace/traces
# simTrace L NN FILE: the current tree's ride.
simTrace() {
"$here/stsim" "$levels/level$2.dat" "$1" "$ticks" > "$3" 2>/dev/null
}
# c64Ride L NN: ride screen L on the C64 into $traces/L.jsonl and compare
# it with stsim's; prints one verdict line, returns 1 on a difference. A
# ride that could not be traced leaves $traces/L.failed behind.
c64Ride() {
local l=$1 n=$2
rm -f "$traces/$l.failed"
STSIM_DEMOBUF=1 "$here/stsim" "$levels/level$n.dat" "$l" 0 > "$traces/$l.buf.json"
simTrace "$l" "$n" "$traces/$l.sim.jsonl"
if ! timeout -s KILL 900 python3 "$here/c64trace.py" "$l" "$ticks" "$traces/$l.buf.json" > "$traces/$l.jsonl"; then
echo " $l: *** the C64 ride failed ***"
touch "$traces/$l.failed"
return 1
fi
if cmp -s "$traces/$l.sim.jsonl" "$traces/$l.jsonl"; then
printf ' %s: IDENTICAL to the C64 (%s lines)\n' "$l" "$(wc -l < "$traces/$l.jsonl")"
return 0
fi
echo " $l: *** DIFFERS from the C64 ***"
python3 "$here/compare.py" "$traces/$l.sim.jsonl" "$traces/$l.jsonl" 3 | sed 's/^/ /'
return 1
}
# c64All [L...]: every named screen (all of them by default), C64_JOBS at
# a time.
c64All() {
local want=" ${*:-} " fail=0 p l n
python3 "$here/c64trace.py" --prepare || exit $?
mkdir -p "$traces"
for p in $pairs; do
l=${p%%:*}; n=${p##*:}
if [ -n "${*:-}" ] && [[ "$want" != *" $l "* ]]; then
continue
fi
while [ "$(jobs -rp | wc -l)" -ge "${C64_JOBS:-4}" ]; do
wait -n || fail=1
done
c64Ride "$l" "$n" &
done
while [ "$(jobs -rp | wc -l)" -gt 0 ]; do
wait -n || fail=1
done
return $fail
}
case "$mode" in
capture)
dir=${2:-$here/golden}
mkdir -p "$dir"
for p in $pairs; do
l=${p%%:*}; n=${p##*:}
simTrace "$l" "$n" "$dir/demo$l.jsonl"
printf ' %s: %s lines\n' "$l" "$(wc -l < "$dir/demo$l.jsonl")"
done
echo "gate: captured to $dir"
;;
check)
dir=${2:-$here/golden}
fail=0
for p in $pairs; do
l=${p%%:*}; n=${p##*:}
got=$(mktemp)
simTrace "$l" "$n" "$got"
want=$(awk -v l="$l" '$1 == l { print $2 }' "$here/gateHashes.txt")
have=$(md5sum < "$got" | cut -d' ' -f1)
if [ "$have" = "$want" ]; then
printf ' %s: IDENTICAL to the C64 (%s lines, md5 as gateHashes.txt)\n' "$l" "$(wc -l < "$got")"
else
echo " $l: *** DIFFERS from the C64 *** (md5 $have, gateHashes.txt ${want:-has none}; gate.sh c64 $l shows where)"
if [ -f "$dir/demo$l.jsonl" ]; then
python3 "$here/compare.py" "$dir/demo$l.jsonl" "$got" 3 | sed 's/^/ golden vs now: /'
fi
fail=1
fi
rm -f "$got"
done
[ $fail -eq 0 ] && echo "gate: PASS" || echo "gate: FAIL"
# The cel census rides the same streams: a cel a ride displays that its
# manifest does not bake is a mid-ride interpreted sprite.
"$here/celGate.sh" || fail=1
exit $fail
;;
c64)
shift
c64All "$@"
fail=$?
[ $fail -eq 0 ] && echo "gate c64: PASS" || echo "gate c64: FAIL"
exit $fail
;;
c64hashes)
c64All >&2
if compgen -G "$traces/*.failed" > /dev/null; then
echo "gate c64hashes: a C64 ride failed; no hashes" >&2
exit 1
fi
for p in $pairs; do
l=${p%%:*}
printf '%s %s\n' "$l" "$(md5sum < "$traces/$l.jsonl" | cut -d' ' -f1)"
done
;;
hashes)
for p in $pairs; do
l=${p%%:*}; n=${p##*:}
got=$(mktemp)
simTrace "$l" "$n" "$got"
printf '%s %s\n' "$l" "$(md5sum < "$got" | cut -d' ' -f1)"
rm -f "$got"
done
;;
*)
sed -n '2,33p' "$0"
exit 2
;;
esac