Files
SplitBit-Emulator/Tests/replay.sh
T
AnachronautandClaude Opus 5 a163c670d0 A demo recorder: --record-pad writes what --pad reads
One byte a frame, in exactly the format the player takes, so a recording
needs no conversion and there is no second format to keep in step. That
symmetry is the feature, and it makes the strongest form of the claim
testable: a recording is made OF a playback, and the bytes coming out have
to be the bytes that went in.

It exists because some inputs cannot sensibly be written by hand. Flying a
lander from one base to another is a few hundred frames of steering that
has to arrive somewhere eight cells wide, and several attempts at
authoring one got within two columns and no closer. That is a piloting
exercise rather than a test. Playing it once and keeping what happened is
the answer.

A BYTE FOR EVERY FRAME, written inside the loop that advances the
recordings rather than after it, so a machine that jumped several frames
at once still writes one for each. A recording is a timeline: one that
skipped the frames nobody looked at would play back faster than it was
flown.

What is recorded is what the DEVICE WOULD REPORT, not the live state - a
recording of a playback that wrote the live state would be a file of
noughts. And it is flushed as it goes, because a recording is usually
stopped by whoever is playing rather than by the program ending, and a
demo lost to a buffer is a demo flown twice.

Tests/replay.sh is where this and whatever follows it are checked. Twelve
scripts now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-09-03 12:38:43 -04:00

104 lines
4.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# Checks that what the machine records is what it plays back.
#
# --record-pad writes what a controller held, one byte a frame, in exactly the format --pad
# reads. That symmetry is the whole feature and it is the whole of what is checked here: a
# recording is only worth having if playing it back does what was recorded.
#
# ---- Why it exists ----
#
# Some inputs cannot sensibly be written by hand. Flying a lander from one base to another is
# a few hundred frames of steering that has to arrive somewhere eight cells wide, and several
# attempts at hand-authoring one got within two columns and no closer. That is a piloting
# exercise rather than a test. Playing it once and keeping what happened is the answer, and
# this is what says the keeping works.
#
# Written by Anachronaut
set -u
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
BUILD="$ROOT/Tests/build/replay"
ASM="$ROOT/Assembler"
EMU="$ROOT/SplitBit"
for tool in "$ASM" "$EMU"; do
[ -x "$tool" ] || { echo "$(basename "$tool") is not built."; exit 1; }
done
rm -rf "$BUILD"; mkdir -p "$BUILD"
PASS=0
FAIL=0
FAILED_NAMES=()
GREEN=$'\033[32m'; RED=$'\033[31m'; RESET=$'\033[0m'
[ -t 1 ] || { GREEN=""; RED=""; RESET=""; }
result() {
if [ "$1" = "ok" ]; then
PASS=$((PASS + 1)); printf " [%sok %s] %-40s %s\n" "$GREEN" "$RESET" "$2" "$3"
else
FAIL=$((FAIL + 1)); FAILED_NAMES+=("$2")
printf " [%sFAIL%s] %-40s %s\n" "$RED" "$RESET" "$2" "$3"
fi
}
echo "Checking that a recording plays back as itself."
# A program that reads a pad every frame and stops. It is the one in testPrograms rather than
# a new one, because what is being checked is the recording and not the reading.
"$ASM" "$ROOT/Programs/testPrograms/padTest.asm" -o "$BUILD/padTest.bin" > "$BUILD/asm.log" 2>&1 \
|| { echo "padTest did not assemble."; exit 1; }
# ---- Played and recorded at the same time ----
#
# The strongest form of the claim: the recording is made OF a playback, so if the two formats
# ever drift apart the bytes coming out stop matching the bytes going in.
"$EMU" --fast --pad "$ROOT/Tests/input/padTest.pad" \
--record-pad "$BUILD/roundtrip.pad" "$BUILD/padTest.bin" > "$BUILD/roundtrip.out" 2>&1
SAME="$(python3 -c "
first = open('$ROOT/Tests/input/padTest.pad', 'rb').read()
again = open('$BUILD/roundtrip.pad', 'rb').read()
print('yes' if again[:len(first)] == first else 'no')
" 2>/dev/null || echo error)"
[ "$SAME" = "yes" ] \
&& result ok "a recording of a playback is the playback" "byte for byte, in and out" \
|| result no "a recording of a playback is the playback" "the bytes differ"
# ---- One byte a frame, and the frame is the machine's ----
#
# A recording is a TIMELINE. If it were a byte a read, a program that polled twice in one
# frame would record twice as fast as it flew; if it skipped frames nobody looked at, it would
# play back faster than it was flown. So the count is the number of frames the machine ran,
# which at 16,667 cycles a frame is arithmetic rather than a guess.
"$EMU" --fast --cycles 500000 --record-pad "$BUILD/timeline.pad" \
"$BUILD/padTest.bin" > "$BUILD/timeline.out" 2>&1
WROTE="$(wc -c < "$BUILD/timeline.pad" | tr -d ' ')"
# The machine halts of its own accord well before the limit, so what is checked is that the
# count is frames-of-something rather than bytes-of-nothing: more than none, and fewer than
# the limit could possibly hold.
[ "$WROTE" -gt 0 ] && [ "$WROTE" -le 30 ] \
&& result ok "a byte for every frame and no more" "$WROTE frames of it" \
|| result no "a byte for every frame and no more" "$WROTE bytes, which is not a frame count"
# ---- And a machine with no controller records that honestly ----
#
# Nothing held is nought, which is what a pad nobody is touching reports and what a machine
# with no pad at all reports. A recorder that wrote something else would make a fixture that
# pressed buttons nobody pressed.
QUIET="$(python3 -c "
data = open('$BUILD/timeline.pad', 'rb').read()
print('yes' if set(data) <= {0} else 'no')
" 2>/dev/null || echo error)"
[ "$QUIET" = "yes" ] \
&& result ok "and nothing held is written as nothing" "no buttons nobody pressed" \
|| result no "and nothing held is written as nothing" "the recording holds something"
echo
if [ "$FAIL" -eq 0 ]; then
echo "All $PASS replay checks passed."
exit 0
fi
echo "$PASS passed, $FAIL failed: ${FAILED_NAMES[*]}"
exit 1