ROADMAP P5. The loader moved in session 21 and the frame clock in 22; the ring producer was the last policy living outside the machine. src/player/ring.i does `aligned` placement, the descriptor ring, a prefill, 51.2's slack rule and a seek, and the host keeps only the transport. It needed a container change. `aligned` asks whether the next record fits before the end of the ring -- a length asked BEFORE the record is fetched -- and every reader in this tree answered that by walking the frame stream, which is exactly what a player streaming off a disc cannot do. DLX4 carries nframes u16 record lengths in the scene header. Frame payloads are byte-identical to the DLX3 encode, so no fitted constant moves; the scene header goes 5,920 to 6,164 B. The producer reproduces the host's tiling exactly: 18 wraps, 14.7 KB mean hole, pixel-exact, a third independent implementation of the same policy. What it exposed is bigger than the item. A channel only moves bytes while it has a request and only the CPU can issue one, so the disc stands still between records by an amount the PLAYER sets, not the medium -- and no host-filled run could see it. At 488 KB/s in a 256 KB ring a one-deep request queue gives away 6.8% of the pipe and underruns 59 of 120 frames; two-deep gives away 3.4% and underruns none. The container's whole surplus over the wire is 8.7%, so the player's own loop was spending most of the slack a branch point saves up. Prefill is the weaker lever: six records of it still leaves 24 underruns. Three silent bugs are recorded in FINDINGS 55.7 -- all produced wrong pixels or a desync rather than a fault -- plus a rig one: MAME renders a screen line by line, so snapshotting the frame the decoder finished in captures a tear that reads exactly like a decoder bug. check.sh gains the machine-owned ring and a seek with the decode after it. decode.bin is unchanged at 1,296 B and a host-filled run executes none of the new code, so every FINDINGS 49/51 figure stands. ALL GREEN before and after. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
83 lines
4.6 KiB
Bash
Executable File
83 lines
4.6 KiB
Bash
Executable File
#!/bin/bash
|
|
# One paced ring-buffer run (STATUS item 4, FINDINGS 49.7.2).
|
|
#
|
|
# tools/bench/check.sh runs the ring pass FREE-RUNNING, which is right for what
|
|
# it gates -- wrap correctness at a fixed ring size, delivery removed as a
|
|
# variable by an unlimited pipe. It cannot answer the buffering question,
|
|
# because a free-running decoder never lets the ring back up.
|
|
#
|
|
# This runs the same rig with the decoder held to the container's frame rate,
|
|
# so the ring fills and FR_HEAD-FR_TAIL means "frames the decoder could still
|
|
# draw with the pipe dead". Every run is verified PIXEL-EXACT: a paced decode
|
|
# that drops a pixel is not a slack measurement, it is a bug.
|
|
#
|
|
# tools/bench/pace_run.sh <ring_kb> <kbps> [cut_at_tick] [cut_frames]
|
|
#
|
|
# kbps 0 = unlimited pipe. There is no default rate anywhere in this tree
|
|
# (FINDINGS 50) and there is none here either.
|
|
#
|
|
# DLX_RINGOWN=1 hands the RING to the 68000 as well (ROADMAP P5,
|
|
# src/player/ring.i): this script's Lua stops placing records and becomes a
|
|
# transport that answers one request at a time. DLX_PREFILL_FR is then the
|
|
# prefill policy, in whole records. It needs a DLX4 container, because the
|
|
# machine cannot learn a record's length by walking a stream it has not fetched.
|
|
#
|
|
# DLX_ITER=2 runs the scene TWICE, which under DLX_RINGOWN means a real seek
|
|
# between the passes: the channel goes quiet, the ring is declared empty and the
|
|
# whole accumulated lookahead is thrown away and rebuilt from the prefill. It
|
|
# needs DLX_PACE=2, because rebasing the frame clock across a pass is the
|
|
# machine's to do and a host-written tick would carry on counting.
|
|
#
|
|
# DLX_PACE selects WHO KEEPS THE TIME: 1 (default) is the host writing the tick,
|
|
# 2 is the 68000 writing it off the CRTC's V-DISP (ROADMAP P3, FINDINGS 54).
|
|
# Everything else about the run is identical, which is the whole point -- the
|
|
# pace gate in src/player/stream.s cannot tell them apart, so a difference in
|
|
# the result is a difference in the CLOCK and not in the rig.
|
|
set -e
|
|
cd "$(dirname "$0")/../.."
|
|
RING=${1:?ring KB}; KBPS=${2:?pipe KB/s, or 0 for unlimited}
|
|
CUT_AT=$3; CUT_FR=${4:-1}
|
|
DLX=${DLX:-tmp/rc_fr_singe_scsi_span.dlx}
|
|
PACE=${DLX_PACE:-1}
|
|
OWN=${DLX_RINGOWN:-0}
|
|
ITERS=${DLX_ITER:-1}
|
|
if [ "$OWN" = 1 ] && [ "$ITERS" != 1 ] && [ "$PACE" != 2 ]; then
|
|
echo "DLX_ITER>1 needs DLX_PACE=2: the frame clock is rebased per pass by"
|
|
echo "src/player/stream.s, and a host-written tick would go on counting"
|
|
echo "through the seek and open every slot of the second pass at once."
|
|
exit 2
|
|
fi
|
|
TAG="r${RING}_k${KBPS}${CUT_AT:+_cut${CUT_AT}x${CUT_FR}}"
|
|
# The default tag is left ALONE when the host keeps the time: tools/bench/
|
|
# pace_sweep.sh reads tmp/pace_r<ring>_k<kbps>.log by name, and renaming the
|
|
# host-paced logs would break a sweep that has nothing to do with this option.
|
|
if [ "$PACE" != 1 ]; then TAG="${TAG}_p$PACE"; fi
|
|
if [ "$OWN" = 1 ]; then TAG="${TAG}_own"; fi
|
|
if [ "$ITERS" != 1 ]; then TAG="${TAG}_x$ITERS"; fi
|
|
|
|
tools/vasm/vasmm68k_mot -Fbin -o tmp/stream.bin src/player/stream.s > /dev/null
|
|
[ -f tmp/stream_disk.bin ] || python3 tools/bench/prep_stream.py "$DLX" > tmp/prep_stream.log
|
|
mkdir -p "tmp/snap_pace_$TAG"; rm -f "tmp/snap_pace_$TAG/x68000"/*.png
|
|
# `env` rather than an assignment prefix: an empty ${CUT_AT:+...} in the middle
|
|
# of a prefix is not an assignment token, so bash takes the next word as the
|
|
# command and the run dies with "SDL_VIDEODRIVER=dummy: command not found".
|
|
CUTENV=(); [ -n "$CUT_AT" ] && CUTENV=(DLX_CUT_AT="$CUT_AT" DLX_CUT_FR="$CUT_FR")
|
|
( cd tmp && env DLX_PACE=$PACE DLX_RING_KB=$RING DLX_STREAM_KBPS=$KBPS \
|
|
DLX_RINGOWN=$OWN DLX_ITER=$ITERS \
|
|
${DLX_PREFILL_FR:+DLX_PREFILL_FR=$DLX_PREFILL_FR} \
|
|
"${CUTENV[@]}" DLX_SLACK_CSV="slack_$TAG.csv" \
|
|
SDL_VIDEODRIVER=dummy stdbuf -oL timeout -k 5 900 \
|
|
mame x68000 -bios ipl10 -ramsize 2M -video soft -window -sound none \
|
|
-nothrottle -plugins -autoboot_script ../tools/bench/stream.lua \
|
|
-snapshot_directory "./snap_pace_$TAG" -snapview native -seconds_to_run 90 \
|
|
> "pace_$TAG.log" 2>&1 )
|
|
# The completion marker is not optional: a run killed mid-decode compares a
|
|
# half-drawn screen and reads as a wrap bug rather than as a truncated run.
|
|
grep -q "snapshot taken" "tmp/pace_$TAG.log" || {
|
|
echo "FAIL($TAG): no snapshot marker -- the pass did not complete."
|
|
tail -6 "tmp/pace_$TAG.log"; exit 1; }
|
|
echo "=== $TAG"
|
|
grep -aE "decoder (SELF-PACED|PACED|FREE)|FRAME CLOCK|ring: |UNDERRUNS|NO IDLE|SEEK SLACK|RING-BOUND|RATE-BOUND|BUILD TIME|PIPE CUT|DEADLINE|REQUIRED|MACHINE-OWNED|PREFILL:|CHANNEL IDLE|MISPLACED|SEEK PASS" \
|
|
"tmp/pace_$TAG.log" | sed "s/\[STR\] / /"
|
|
python3 tools/bench/verify_decode.py "$DLX" --snap "tmp/snap_pace_$TAG" | tail -2
|