#!/usr/bin/env bash # oracle.sh - play the test probes (tests/probes.py) and the game sessions # (tests/GAME/*.scr, for each game in games/GAME) 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), or of the game ORACLE_INTERP names. A # session plays a copy of its own game with that game's interpreter, # started by the launcher it shipped with (GAME.COM, SIERRA.COM, or the # game's .EXE). Each runs in DOSBox Staging as a fast 386 (dosDrive.py) # 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. dosDrive.py keeps every step of a script at # its own time from the moment DOSBox switches to the game's graphics mode # (the host harness's frame 0), so the two sides stay in step however long # the screenshots take; a shot of something in motion can still fall in a # different part of a cycle. 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 | GAME-SESSION ...] every probe and # session when none is named # Env: ORACLE_INTERP=GAME runs the probes on games/GAME's interpreter # (a v2 game; default kq3) # Output: build/oracle/PROBE/{oracle,joey}/ and a diff image per shot # Needs: games/kq3 (or games/ORACLE_INTERP) 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 games=$repo/games sierra=$games/${ORACLE_INTERP:-kq3} tools=$repo/tests/tools build=$repo/build/oracle agiHost=$repo/build/tests/agiHost parallel=${ORACLE_JOBS:-4} # The launcher Sierra shipped with game $2 (games/$2) in folder $1. A v3 # game runs as .COM, the name its installer gives SIERRA.COM (the # interpreter takes its file prefix, GR of GRDIR, from it), so in a copy # of one that file is made. launcher() { local dir=$1 local id=$2 local exe local d if [[ ! -f $dir/LOGDIR && -f $dir/SIERRA.COM ]]; then for d in "$dir"/*DIR; do if [[ -f $d ]]; then cp "$dir/SIERRA.COM" "${d%DIR}.COM" basename "${d%DIR}.COM" return 0 fi done fi for exe in "$dir/${id^^}.COM" "$dir/SIERRA.COM" "$dir"/*.[Ee][Xx][Ee]; do if [[ -f $exe ]]; then basename "$exe" return 0 fi done return 1 } [[ -f $sierra/AGIDATA.OVL ]] && launcher "$sierra" "$(basename "$sierra")" >/dev/null || { echo "oracle: needs Sierra's interpreter in $sierra" >&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; } # Every session of a game that ships Sierra's interpreter (AGIDATA.OVL; # Space Quest 0's is a Windows program and has no part here), but those # that use the mouse, which Sierra's DOS interpreters have none of, and # those marked "# JoeyAGI only" (where JoeyAGI skips copy protection). sessions=$(for f in "$repo"/tests/*/*.scr; do g=$(basename "$(dirname "$f")") [[ -f $f && -f $games/$g/AGIDATA.OVL ]] || continue grep -qE '^((mouse|click)( |$)|# JoeyAGI only)' "$f" && continue echo "$g-$(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 local exe rm -rf "${build:?}/$name" mkdir -p "$build/$name/oracle" "$build/$name/joey" script=$repo/tests/${name%%-*}/${name#*-}.scr if [[ $name == *-* && -f $script ]]; then cp -r "$games/${name%%-*}" "$game" exe=$(launcher "$game" "${name%%-*}") else python3 "$repo/tests/probes.py" build "$build/$name/.probe" "$name" || return 1 mv "$build/$name/.probe/$name" "$game" rmdir "$build/$name/.probe" exe=$(launcher "$sierra" "$(basename "$sierra")") cp "$sierra"/*.OVL "$sierra"/HGC_FONT "$sierra/$exe" "$game/" if [[ -f $sierra/AGI ]]; then cp "$sierra/AGI" "$game/" fi script=$game/probe.scr fi timeout -s KILL 900 python3 "$tools/dosDrive.py" "$game" "$exe" "$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 launcher export build repo games 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//)" [[ $differ == 0 ]]