Files
vt1200/harness/mame/run-vidstream.sh

265 lines
12 KiB
Bash
Executable File

#!/bin/sh
# run-vidstream.sh -- build the streaming payload and run its MAME check.
#
# harness/mame/run-vidstream.sh # build, then mode alloc
# BENCH=0 harness/mame/run-vidstream.sh # use the existing .bin
# VT1200_MODE=rx harness/mame/run-vidstream.sh # alloc + the receive path
# VT1200_MODE=draw harness/mame/run-vidstream.sh # alloc + rx + the blit
# VT1200_MODE=stream harness/mame/run-vidstream.sh # the status back-channel
# VT1200_MODE=ring harness/mame/run-vidstream.sh # the RX ring's structure
# VT1200_MODE=dict harness/mame/run-vidstream.sh # dictionary upload + kinds
# VT1200_MODE=tiles harness/mame/run-vidstream.sh # the 32x32 tile blit
# VT1200_MODE=delta harness/mame/run-vidstream.sh # deltas and keyframes
# VT1200_MODE=replay harness/mame/run-vidstream.sh # the make_tiles.py clip
# STREAM_POOL_BYTES=0x7FFF000 harness/mame/run-vidstream.sh # force fallback
#
# Mode rx delivers one valid frame and one corrupted frame through the payload's
# own LANCE RX ring and asserts RX_OK == 1 && RX_BAD == 1 (see vidstream.lua and
# the task-7 report).
#
# Mode draw delivers one 128-row strip frame (stretch 8 = 1024 screen rows),
# lets the payload blit it, and dumps the screen and the delivered frame; this
# script then runs harness/mame/vidstream_check.py, which compares every screen
# row against its source row. The pixel verdict is the checker's, not the Lua's.
#
# Mode stream plays the sender: 200 strip frames, then RX_EOF so the terminal
# reports the final count. The payload takes the LANCE over for receive and
# transmit and builds a KIND_STATUS frame every 64 accepted frames; the Lua
# reads those frames back from the payload's own transmit buffer and this
# script runs harness/mame/vidstream_status_check.py, which decodes them with
# the host sender's wire format. There is no network in this run: the emulated
# LANCE has no interface (ruling R11), so the evidence is the buffer's bytes
# plus the completed descriptor, and the wire path needs the unit.
#
# Mode ring asserts the transport's structure: 16 descriptors armed with OWN set
# for slot A, a completed frame moving them to slot B, a header claiming more
# than SLOT_BYTES rejected without a blit (RX_BAD moves, RX_OK does not, the
# screen does not change), and a datagram the chip flags bad (RMD3 = 0, so it
# carries no bytes) counted in RX_FCS_DROP with its frame abandoned. The verdict
# is in the Lua; the evidence is $LOGDIR/ring-status.txt.
#
# Mode dict asserts the dictionary assembler and the kind dispatch: a TILES_FULL
# frame before the dictionary is rejected without painting, a 32 KB upload in 23
# KIND_DICT_CHUNK frames (with a duplicate offset and an overrunning tail chunk
# refused) makes DICT_READY 1 and DICT_BYTES 32,768, and the stored bytes are
# compared with what was sent. The verdict is in the Lua; the evidence is
# $LOGDIR/dict-status.txt.
#
# Mode tiles asserts the 32x32 tile blit: a 32 KB dictionary of row-varying
# entries is uploaded and read back, then one TILES_FULL frame (1,280 indices,
# a different dictionary entry at every corner) is delivered and painted. The
# Lua's verdict is the dispatch and the record; the PIXEL verdict is
# harness/mame/tile_check.py, which renders the screen the indices and the
# dictionary require and compares it with the VRAM dump. A wrong SPTCH in
# vram_tile.asm is the negative control: the frame is accepted and the pixels
# are wrong, which only the checker can see.
#
# Mode delta asserts the changed-tile list: a TILES_FULL keyframe is painted,
# then a one-tile TILES_DELTA must change exactly that 32x32 block and nothing
# else, with RX_OK/TILES_DROP/RX_LAST_LEN saying so. Two negative controls
# follow -- a delta naming a position off the 1,280-tile grid, and one whose
# count disagrees with its length -- and each must be refused (TILES_DROP, no
# RX_OK) with ZERO bytes of the screen changed; a good delta after them proves
# the refusal left no latch behind. The Lua holds the before/after dumps in
# memory and compares them itself: the assertion is a relation between two
# screens, not a rendering to re-derive, and it is why the first negative
# control's valid first entry matters (a decoder that painted as it decoded
# would have painted it). A last delta changes all 1,280 tiles (3,842 payload
# bytes: three datagrams), which is the only case where the count, the position
# list and the value list straddle a fragment boundary; every tile is compared
# with its new entry. Evidence: $LOGDIR/vram-delta-*.bin and delta-status.txt.
#
# Mode replay is Task 6's end-to-end check: it plays the cache make_tiles.py
# wrote (dictionary.bin + stream.bin) through the payload's own RX ring, one
# VS frame per clip frame, and dumps the 160 active bytes of every screen row
# after each one. This script then runs tile_check.py --replay, which decodes
# stream.bin with its own code, renders every frame through the dictionary and
# compares it with the dump. The pixels are the checker's verdict; the Lua's is
# the transport record (every frame accepted, none dropped, dictionary intact).
# The clip is synthetic: no Bad Apple source exists on this machine, and the
# script says so rather than implying otherwise.
#
# The run uses VT1200_SKIP_LEVEL1=1: the payload's heap is only initialised in
# the OS phase. In the diagnostics menu the free counter (0x1E8BA0) and the
# free-list head (0x1E8AC0) both read 0, so the malloc success path cannot be
# tested there. The skip is a documented divergence (harness/mame/README.md).
#
# Exit status: 0 only if vidstream.lua printed "VID: RESULT PASS" and, in draw
# or stream mode, the checker for that mode passed too.
set -eu
HERE=$(cd "$(dirname "$0")" && pwd)
ROOT=$(cd "$HERE/../.." && pwd)
MAME=${MAME:-/home/arti/code/k-space/mame0289/vt1200}
BENCH=${BENCH:-1}
MODE=${VT1200_MODE:-alloc}
LOGDIR=${LOGDIR:-/tmp/opencode}
LOG="$LOGDIR/vidstream-mame.log"
if [ ! -x "$MAME" ]; then
echo "run-vidstream.sh: no MAME binary at $MAME (set MAME=...)" >&2
exit 1
fi
if [ "$BENCH" != "0" ]; then
STREAM_POOL_BYTES=${STREAM_POOL_BYTES:-249856} \
"$ROOT/harness/udpstream/build-stream.sh"
fi
if [ ! -f "$ROOT/harness/udpstream/build/vidstream.bin" ]; then
echo "run-vidstream.sh: no build/vidstream.bin (run without BENCH=0)" >&2
exit 1
fi
# POOL_BYTES / STATIC_PTR come from the image that was just built, so the check
# compares against the linked fallback address rather than a guess.
POOL_BYTES=249856
STATIC_PTR=0
ENTRY_PTR=0x018C0000
ENVFILE="$ROOT/harness/udpstream/build/vidstream.env"
if [ -f "$ENVFILE" ]; then
# shellcheck disable=SC1090
. "$ENVFILE"
fi
mkdir -p "$LOGDIR"
# Ruling R15: draw-mode evidence must come from THIS run. The Lua removes its
# own dump files at startup; removing them here as well means a run that dies
# before the script starts (or before it dumps) leaves no previous screen
# behind for vidstream_check.py to bless.
if [ "$MODE" = "draw" ]; then
rm -f "$LOGDIR/vram-draw.bin" "$LOGDIR/frame-draw.bin" \
"$LOGDIR/draw-status.txt"
fi
if [ "$MODE" = "stream" ]; then
rm -f "$LOGDIR/stream-status.bin" "$LOGDIR/stream-status.txt"
fi
if [ "$MODE" = "ring" ]; then
rm -f "$LOGDIR/ring-status.txt"
fi
if [ "$MODE" = "dict" ]; then
rm -f "$LOGDIR/dict-status.txt"
fi
if [ "$MODE" = "tiles" ]; then
rm -f "$LOGDIR/vram-tiles.bin" "$LOGDIR/dict-tiles.bin" \
"$LOGDIR/indices-tiles.bin" "$LOGDIR/tiles-status.txt"
fi
if [ "$MODE" = "delta" ]; then
rm -f "$LOGDIR/vram-delta-before.bin" "$LOGDIR/vram-delta-after.bin" \
"$LOGDIR/vram-delta-reject.bin" "$LOGDIR/vram-delta-recover.bin" \
"$LOGDIR/vram-delta-large.bin" "$LOGDIR/dict-delta.bin" \
"$LOGDIR/indices-delta.bin" "$LOGDIR/delta-status.txt"
fi
if [ "$MODE" = "replay" ]; then
# One dump per clip frame: the count comes from the stream, so a glob is
# the only way to be sure no previous run's frame survives (ruling R15).
rm -f "$LOGDIR"/vram-replay-*.bin "$LOGDIR/replay-status.txt" \
"$LOGDIR/dictionary.bin" "$LOGDIR/stream.bin" \
"$LOGDIR/tiles-manifest.json"
fi
# Absolute: the Lua runs with MAME's cwd, so a relative default only resolves
# when the caller happens to be at the repo root.
VID_PAYLOAD="$ROOT/harness/udpstream/build/vidstream.bin"
VID_TILE_DIR=${VID_TILE_DIR:-$ROOT/harness/udpstream/tiles}
if [ "$MODE" = "replay" ]; then
# The run's directory must be checkable on its own: the checker renders
# from these exact bytes, and a stale copy would be evidence of another
# stream (ruling R15).
cp "$VID_TILE_DIR/dictionary.bin" "$VID_TILE_DIR/stream.bin" "$LOGDIR/"
if [ -f "$VID_TILE_DIR/manifest.json" ]; then
cp "$VID_TILE_DIR/manifest.json" "$LOGDIR/tiles-manifest.json"
fi
fi
set +e
VT1200_SKIP_LEVEL1=1 \
VT1200_MODE="$MODE" \
VID_PAYLOAD="$VID_PAYLOAD" \
VID_DUMP_DIR="$LOGDIR" \
VID_POOL_BYTES="$POOL_BYTES" \
VID_STATIC_PTR="$STATIC_PTR" \
VID_ENTRY_PTR="$ENTRY_PTR" \
VID_TILE_DIR="$VID_TILE_DIR" \
"$MAME" vt1200 -rompath "$HERE/roms" -video none -sound none -nothrottle \
-skip_gameinfo -debug -debugger none \
-seconds_to_run "${VID_SECONDS:-120}" \
-autoboot_script "$HERE/vidstream.lua" -autoboot_delay 0 \
> "$LOG" 2>&1
STATUS=$?
set -e
grep '^VID:' "$LOG" || true
if [ "$STATUS" -ne 0 ]; then
echo "run-vidstream.sh: MAME exited $STATUS; log $LOG" >&2
tail -20 "$LOG" >&2
exit "$STATUS"
fi
if ! grep -q '^VID: RESULT PASS' "$LOG"; then
echo "run-vidstream.sh: check failed (log $LOG)" >&2
exit 1
fi
# The pixel comparison is a separate, independent reading of the geometry
# (vidstream_check.py); a transport pass with the wrong picture is a failure.
if [ "$MODE" = "draw" ]; then
set +e
python3 "$HERE/vidstream_check.py" \
--vram "$LOGDIR/vram-draw.bin" \
--frame "$LOGDIR/frame-draw.bin" \
--status "$LOGDIR/draw-status.txt"
STATUS=$?
set -e
if [ "$STATUS" -ne 0 ]; then
echo "run-vidstream.sh: pixel check failed (log $LOG)" >&2
exit "$STATUS"
fi
fi
# The status frames are decoded by the host side's wire format, so the payload's
# encoder and frame.py cannot drift apart without failing here.
if [ "$MODE" = "stream" ]; then
set +e
python3 "$HERE/vidstream_status_check.py" \
--bin "$LOGDIR/stream-status.bin" \
--status "$LOGDIR/stream-status.txt" \
--frames "${VID_STREAM_FRAMES:-200}"
STATUS=$?
set -e
if [ "$STATUS" -ne 0 ]; then
echo "run-vidstream.sh: status check failed (log $LOG)" >&2
exit "$STATUS"
fi
fi
# The tile picture is a separate, independent reading of the geometry
# (tile_check.py renders the indices through the dictionary); a dispatch pass
# with the wrong picture is a failure. This is the check the SPTCH negative
# control is run against.
if [ "$MODE" = "tiles" ]; then
set +e
python3 "$HERE/tile_check.py" \
--vram "$LOGDIR/vram-tiles.bin" \
--dict "$LOGDIR/dict-tiles.bin" \
--indices "$LOGDIR/indices-tiles.bin" \
--status "$LOGDIR/tiles-status.txt"
STATUS=$?
set -e
if [ "$STATUS" -ne 0 ]; then
echo "run-vidstream.sh: tile check failed (log $LOG)" >&2
exit "$STATUS"
fi
fi
# The replay's picture is checked the same way: tile_check.py --replay reads
# the stream itself and renders every frame. A dispatch pass with a wrong
# picture is a failure, and so is a run whose dumps do not match the stream.
if [ "$MODE" = "replay" ]; then
set +e
python3 "$HERE/tile_check.py" --replay "$LOGDIR"
STATUS=$?
set -e
if [ "$STATUS" -ne 0 ]; then
echo "run-vidstream.sh: replay check failed (log $LOG)" >&2
exit "$STATUS"
fi
fi
exit 0