joeyagi/scripts/oracle.sh
2026-10-06 16:59:41 -05:00

124 lines
5.1 KiB
Bash
Executable file

#!/usr/bin/env bash
# oracle.sh - play the test probes (tests/probes.py) and the King's Quest
# III sessions (tests/kq3/*.scr) on Sierra's own interpreter and on
# JoeyAGI, and compare the frames.
#
# Sierra's interpreter is the black box the probes were checked against:
# each probe game is written beside the interpreter files of the copy of
# King's Quest III in games/kq3 (AGI, AGIDATA.OVL, the *.OVL drivers,
# KQ3.COM -- never distributed), run in DOSBox Staging at 386SX-16 speed
# under a private Xvfb and fed the probe's script with xdotool; JoeyAGI
# plays the same script on the host (build/tests/agiHost, built by
# scripts/test-agi.sh). tests/tools/compareFrames.py then compares each
# shot, reading text with each interpreter's own font.
#
# A probe whose script records (shake) is compared by what
# tests/tools/shakeMeasure.py reads out of the two recordings.
#
# DOSBox runs in real time and Sierra's interpreter starts a moment later
# than the host's virtual clock, so shots of things in motion can be a
# cycle or two apart; the probes that matter record their results into
# variables and print them, which does not depend on timing.
#
# The follow probe's second follower takes random detours, so its x2/y2
# rows differ from run to run on Sierra's side; everything else matches.
#
# Usage: scripts/oracle.sh [PROBE | kq3-SESSION ...] every probe and
# session when none is named
# Output: build/oracle/PROBE/{oracle,joey}/ and a diff image per shot
# Needs: games/kq3 with Sierra's interpreter, Xvfb, xdotool, python3 +
# PIL, JoeyLib's DOSBox Staging (./install.sh install emulators)
set -uo pipefail
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
JOEYLIB=$(cd "${JOEYLIB:-$repo/../joeylib}" && pwd) || { echo "oracle: no JoeyLib checkout (set JOEYLIB)" >&2; exit 2; }
export JOEYLIB
sierra=$repo/games/kq3
tools=$repo/tests/tools
build=$repo/build/oracle
agiHost=$repo/build/tests/agiHost
parallel=${ORACLE_JOBS:-4}
[[ -f $sierra/AGI && -f $sierra/KQ3.COM ]] || { echo "oracle: needs Sierra's interpreter in games/kq3" >&2; exit 2; }
for tool in Xvfb xdotool python3; do
command -v "$tool" >/dev/null || { echo "oracle: needs $tool" >&2; exit 2; }
done
[[ -x $agiHost ]] || "$repo/scripts/test-agi.sh" font >/dev/null || true
[[ -x $agiHost ]] || { echo "oracle: build/tests/agiHost missing (scripts/test-agi.sh builds it)" >&2; exit 1; }
sessions=$(for f in "$repo"/tests/kq3/*.scr; do [[ -f $f ]] && echo "kq3-$(basename "${f%.scr}")"; done)
probes=${*:-$(python3 "$repo/tests/probes.py" list) $sessions}
mkdir -p "$build"
# One probe on both interpreters.
one() {
local name=$1
local game=$build/$name/game
local run
local script
rm -rf "${build:?}/$name"
mkdir -p "$build/$name/oracle" "$build/$name/joey"
if [[ $name == kq3-* ]]; then
cp -r "$sierra" "$game"
script=$repo/tests/kq3/${name#kq3-}.scr
else
python3 "$repo/tests/probes.py" build "$build/$name/.probe" "$name" || return 1
mv "$build/$name/.probe/$name" "$game"
rmdir "$build/$name/.probe"
cp "$sierra"/AGI "$sierra"/*.OVL "$sierra"/HGC_FONT "$sierra"/KQ3.COM "$game/"
script=$game/probe.scr
fi
timeout -s KILL 900 python3 "$tools/dosDrive.py" "$game" KQ3.COM "$build/$name/oracle" "$script" \
--set joysticktype=none > "$build/$name/oracle.log" 2>&1
run=$(mktemp -d "$build/run.XXXXXX")
ln -s "$game" "$run/DATA"
mkdir "$run/SAVES"
(cd "$run" && timeout -s KILL 900 "$agiHost" "$script" "$build/$name/joey") > "$build/$name/joey.log" 2>&1
rm -rf "$run"
}
export -f one
export build repo sierra tools agiHost
# The fonts first: every comparison reads text with them.
one font
printf '%s\n' $probes | grep -vx font | xargs -P "$parallel" -I{} bash -c 'one {}'
fontArgs=(--fonts "$build/font/oracle/font.png" "$build/font/joey/font.ppm")
total=0
differ=0
for name in $probes; do
echo "=== $name"
for shot in "$build/$name"/oracle/*.png; do
[[ -f $shot ]] || continue
base=$(basename "${shot%.png}")
joey=$build/$name/joey/$base.ppm
total=$((total + 1))
if [[ ! -f $joey ]]; then
echo " $base: JoeyAGI took no such shot"
differ=$((differ + 1))
continue
fi
if result=$(python3 "$tools/compareFrames.py" "$shot" "$joey" "$build/$name/diff-$base.png" "${fontArgs[@]}"); then
echo " $base: same"
else
echo " $base: DIFFERS $result"
differ=$((differ + 1))
fi
done
# Recordings (shake.screen): the shakes read out of both.
for video in "$build/$name"/oracle/*.avi; do
[[ -f $video ]] || continue
base=$(basename "${video%.avi}")
total=$((total + 1))
if result=$(python3 "$tools/shakeMeasure.py" "$video" "$build/$name/joey/$base.frames"); then
echo " $base (recording): same"
else
echo " $base (recording): DIFFERS"
differ=$((differ + 1))
fi
echo "$result" | sed 's/^/ /'
done
done
echo
echo "oracle: $((total - differ)) of $total shots match (diff images in $build/<probe>/)"
[[ $differ == 0 ]]