ROADMAP P3 said "needs MFP timer or VBL" and neither can do it. The MFP's timer clock is 16 MHz/4, its prescalers stop at 200 and its data register is 8 bits, so the slowest tick any single timer can make is 78.125 Hz -- 6.5x faster than a frame -- and 4e6/12 is not an integer, so no setting reaches 12 Hz at all. The raster has no whole divide near 12 either: 4 refreshes is 13.86 fps and 5 is 11.09. tools/analysis/23_frame_clock.py walks all 7x256 timer settings rather than asserting it. src/player/clock.i takes the V-DISP falling edge on MFP GPIP4 -- the start of vertical blanking, which is when a player would present -- and adds fps*VTOTAL per edge to a 16-bit accumulator, emitting a tick at 31,500 and keeping the remainder. The long-run rate is fps*VTOTAL/VTOTAL = 12.000000 fps exactly, and both constants are read out of the CRTC at init, so the clock is derived from the registers that generate the raster it counts. Measured over 3,000 refreshes: 3,000 interrupts, 649 ticks where 649.1429 were due. It costs 181.35 clocks per V-DISP, 838 per frame, 0.1006% of the budget -- timed by the 68000 itself, because the host's granularity is 17.64 ms and the interrupt is microseconds. The loop's own cost was calibrated rather than looked up and landed on 38.000002 clocks, which both licenses the subtraction and confirms buscost.py's model; the 181.35 then decomposes exactly, leaving 43.99 clocks for the interrupt exception -- the textbook 44, measured. THE ONE THAT MOVES SOMETHING: 12 fps on a 55.4577 Hz raster is 4.6215 refreshes, so a frame is shown for 4 refreshes (72.13 ms) or 5 (90.16 ms), 37.9% of them short. The 833,333-clock budget every figure in this project is priced against is the MEAN slot, and the short one is 13.4% under it. The cadence was already in the tree unnamed: stream.lua's tick is sampled at frame boundaries, so its gaps were always 4 or 5, and every host-paced result in FINDINGS 49/51 carried it. P3 moved who produces it onto the machine and made it visible. It is not a dropped frame -- the pace gate lets an overrun eat the next frame's idle -- and on the gate container it costs 4 frames of 120 their idle against 1 for the nominal model, most of that the frame-0 transient at 111% of budget. stream.s counts it now, and the rig matches an offline model of the divider exactly. Also struck: MAME's raster runs 2.22% fast. refresh_mode() builds the frame period from scr.max_x*scr.max_y with scr.max_x = m_htotal - 8, one character cell short and an inclusive bound used as a count, so it runs at 56.6901 Hz where the registers say 55.4577 -- agreeing to six digits with the arithmetic. Every "1/55.46 s granularity" note in this tree was wrong and is 1/56.69 s, corrected in six files with the derivation put once in crtc_mode.lua. No conclusion changes and no 68000 cycle figure moves; the CPU clock is unrelated to the screen. But anything paced by the raster runs fast under MAME, so the rig reports both rates and prices the interrupt against the hardware's. decode.s and frame.i are unchanged; decode.bin is still 1,296 B at the same MD5. The pace gate's wait loop is byte-for-byte the one FINDINGS 51 measured and the free-running path executes none of the new code. check.sh gains two stages: the clock's own measurement, and 120 frames decoded pixel-exact with nothing outside the machine deciding when a frame may start. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
342 lines
20 KiB
Bash
Executable File
342 lines
20 KiB
Bash
Executable File
#!/bin/bash
|
|
# Green-light check: re-runs both display regression tests AND the rate-control
|
|
# drift test, from the Blu-ray. ~2 min. Run from the repo root. Any non-zero
|
|
# exit means something drifted.
|
|
set -e
|
|
cd "$(dirname "$0")/../.."
|
|
# No media ships with this repo. Bring your own disc; DLX_BDROM overrides the
|
|
# mount point, and every tool that reads the disc honours the same variable.
|
|
DLX_BDROM=${DLX_BDROM:-/media/${USER:-$(id -un)}/BDROM}
|
|
export DLX_BDROM
|
|
[ -d "$DLX_BDROM" ] || {
|
|
echo "Blu-ray not mounted at $DLX_BDROM."
|
|
echo " udisksctl loop-setup -r -f DRAGONS_LAIR.iso"
|
|
echo " or set DLX_BDROM to where yours is mounted."; exit 2; }
|
|
|
|
python3 tools/encoder/extract.py 00020 tmp/fr_00020 12 crop
|
|
mkdir -p tmp/snap_verify tmp/snap256
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# THE ONE PLACE THE RETIRED PIPE FIGURE STILL LIVES. Session 18 removed it as
|
|
# a default from every analysis tool and from tools/bench/stream.lua, because it
|
|
# was never a bus measurement -- a user-supplied "4 Mbps" with no provenance,
|
|
# 10% of SCSI-1's asynchronous rating (FINDINGS 42.1) -- and a default let table
|
|
# after table be scored against it without anyone restating what it was.
|
|
#
|
|
# It survives HERE and only here because the gate container was ENCODED with it,
|
|
# and every per-block and span constant in FINDINGS 41/43/45/49 is fitted to that
|
|
# container. Changing this number is not an edit, it is a re-encode plus a
|
|
# re-measurement of all of them.
|
|
#
|
|
# It is a CONTAINER RECIPE, not a claim about any medium. Do not read a delivery
|
|
# rate out of it, do not copy it into a tool, and do not add a default anywhere
|
|
# that would resurrect it. When the pipe is finally measured, this becomes an
|
|
# ordinary encoder setting and the comment goes.
|
|
GATE_SPAN_KBPS=488
|
|
# ---------------------------------------------------------------------------
|
|
|
|
run() { # run <script> <snapdir>
|
|
rm -f "tmp/$2/x68000"/*.png
|
|
( cd tmp && SDL_VIDEODRIVER=dummy timeout -k 5 120 mame x68000 -bios ipl10 \
|
|
-video soft -window -sound none -nothrottle -plugins \
|
|
-autoboot_script "../tools/bench/$1" \
|
|
-snapshot_directory "./$2" -snapview native -seconds_to_run 6 >"$2.log" 2>&1 )
|
|
}
|
|
|
|
echo "--- session 3: 768-wide IPL timing (FINDINGS 22) ---"
|
|
python3 tools/bench/prep_frame.py tmp/fr_00020 tmp/frame.bin 0
|
|
run show_frame.lua snap_verify
|
|
python3 tools/bench/verify_frame.py
|
|
|
|
echo "--- session 4: real 256x256 mode (FINDINGS 23) ---"
|
|
python3 tools/bench/prep_frame.py tmp/fr_00020 tmp/frame256.bin 0 --reserve-black
|
|
run show_frame256.lua snap256
|
|
python3 tools/bench/verify_frame256.py
|
|
|
|
echo "--- session 6: rate-control drift (FINDINGS 26/27) ---"
|
|
# The codec is temporally recursive, so a rate controller can report quality for
|
|
# a reconstruction no decoder will ever produce -- silently. This asserts that a
|
|
# decoder replaying the emitted stream rebuilds exactly what the encoder
|
|
# recorded. ~55 s, nearly all of it k-means in H.build.
|
|
[ -d tmp/fr_singe ] || python3 tools/encoder/extract.py 00223 tmp/fr_singe 12 crop 539.4 10.0
|
|
# NOT piped into tail: a pipeline's exit status is the last command's, which
|
|
# would swallow the failure this whole script exists to catch.
|
|
python3 tools/analysis/09_ratectl_drift.py > tmp/drift_check.log 2>&1 \
|
|
|| { cat tmp/drift_check.log; exit 1; }
|
|
tail -9 tmp/drift_check.log
|
|
|
|
echo "--- session 12: the DLX3 span container round-trips (FINDINGS 41) ---"
|
|
# 09 above replays SKIP semantics in Python and never reads a container. A v7
|
|
# span breaks exactly that shortcut -- a spanned block reads SKIP in the mode
|
|
# header and is painted by the span section instead -- so this encodes, WRITES
|
|
# the container, reads it back with the reference decoder and compares. It also
|
|
# asserts that it emitted enough spans to have tested anything.
|
|
# --kbps is required now (session 18): the tool has no default rate, so the gate
|
|
# has to say which one it is testing at. Same recipe constant as the container.
|
|
python3 tools/analysis/16_span_roundtrip.py --kbps $GATE_SPAN_KBPS \
|
|
> tmp/span_roundtrip.log 2>&1 \
|
|
|| { cat tmp/span_roundtrip.log; exit 1; }
|
|
tail -4 tmp/span_roundtrip.log
|
|
|
|
echo "--- session 7: display-path coherency (FINDINGS 28.1) ---"
|
|
# 10_pathmix_drift.py is a COUNTEREXAMPLE, kept runnable: the dual-path plan of
|
|
# FINDINGS 24.5/25.6 must still be shown to corrupt frames, and the strategy the
|
|
# player actually uses must still be clean. A green light here means the reason
|
|
# decode.s has one display path is still demonstrable, not just asserted.
|
|
python3 tools/analysis/10_pathmix_drift.py > tmp/pathmix.log 2>&1 \
|
|
&& { echo "FAIL: the dual-path plan no longer reproduces its own defect"; \
|
|
cat tmp/pathmix.log; exit 1; }
|
|
grep -a "frames displaying pixels" tmp/pathmix.log
|
|
python3 tools/analysis/10_pathmix_drift.py --fix direct > tmp/pathmix_direct.log 2>&1 \
|
|
|| { echo "FAIL: direct-to-GVRAM is no longer coherent"; cat tmp/pathmix_direct.log; exit 1; }
|
|
|
|
echo "--- session 7: 68000 decoder is pixel-exact (FINDINGS 28) ---"
|
|
# The strongest display test in the tree: 120 frames decoded in sequence by
|
|
# 68000 code, every block mode, full temporal recursion. A SKIP block is a claim
|
|
# about the previous frame still being on screen, so the last frame is only
|
|
# right if all 120 were.
|
|
# The gate container is the HEAVIEST stream the encoder emits: the scsi mode
|
|
# decision (the only profile left after session 9 dropped sasi on capacity,
|
|
# FINDINGS 32) with the span pass drawing on a byte ceiling wide enough that
|
|
# every frame carries a span table and all four block modes are still exercised.
|
|
# That ceiling is GATE_SPAN_KBPS above -- a recipe, not a delivery rate.
|
|
# Spans are the newest and least-proven path in decode.s; gating on a container
|
|
# where they are rare would be gating on the old decoder. FINDINGS 41.
|
|
DLX=tmp/rc_fr_singe_scsi_span.dlx
|
|
[ -f "$DLX" ] || python3 tools/encoder/encode.py tmp/fr_singe "$DLX" --profile scsi \
|
|
--kbps 280 --span-kbps $GATE_SPAN_KBPS --spans all
|
|
# RIG_RAM is the EMULATED MACHINE's memory, and it is not a claim about the
|
|
# target. The rig preloads the whole container into RAM at 0x30000; the shipping
|
|
# player streams from disk into a ring buffer and never holds a window at once,
|
|
# so preloading is unlike the player at ANY size. At the 2 MB of a stock machine
|
|
# this gate covered 37 of 120 frames (FINDINGS 44.6.4) -- the span-heavy
|
|
# container is 5,261,814 B of stream, ending at 0x534BF6. 6 MB covers all 120.
|
|
#
|
|
# Raising it is licensed by measurement, not by convenience: at 2M and 6M the
|
|
# five synthetic anchors come out BIT-IDENTICAL (40,729 / 921,187 / 1,376,881 /
|
|
# 1,229,883 / 506,533 cycles) despite sitting at different addresses in the two
|
|
# layouts, so MAME's cycle model does not depend on ramsize over this range.
|
|
# FINDINGS 45. What is still NOT tested, at either size, is the streaming path.
|
|
RIG_RAM=${RIG_RAM:-6}
|
|
python3 tools/bench/prep_dlx.py "$DLX" --ram $((RIG_RAM * 0x100000)) > tmp/prep_dlx.log
|
|
# Verify against exactly the frame list prep_dlx emitted. It no longer truncates
|
|
# at the default RIG_RAM, but the guard stays: lower RIG_RAM, or a heavier
|
|
# container, brings truncation straight back and it must stay announced.
|
|
NF=$(sed -n 's/.*nframes=\([0-9]*\),.*/\1/p' tmp/decode_meta.lua)
|
|
grep -a "TRUNCATED" tmp/prep_dlx.log || true
|
|
tools/vasm/vasmm68k_mot -Fbin -o tmp/decode.bin src/player/decode.s > /dev/null
|
|
mkdir -p tmp/snap_decode
|
|
rm -f tmp/snap_decode/x68000/*.png
|
|
# stdbuf -oL: a FILE is block-buffered too, so without it a long MAME run is
|
|
# unobservable until it exits and a run that is merely finishing looks exactly
|
|
# like one that is wedged (FINDINGS 34.1).
|
|
# -seconds_to_run must cover the WHOLE sequential pass. The scsi container is
|
|
# 2.7x the payload of the session-7 one this gate used to run on, and at 20 s
|
|
# the pass was truncated -- MAME exited mid-decode and verify_decode.py then
|
|
# compared a partially drawn screen and reported 49,005 differing pixels, which
|
|
# reads as a decoder bug and is not one.
|
|
( cd tmp && DLX_VERIFY_ONLY=1 SDL_VIDEODRIVER=dummy stdbuf -oL timeout -k 5 300 mame x68000 \
|
|
-bios ipl10 -ramsize ${RIG_RAM}M -video soft -window -sound none -nothrottle -plugins \
|
|
-autoboot_script ../tools/bench/decode.lua \
|
|
-snapshot_directory ./snap_decode -snapview native -seconds_to_run 60 \
|
|
> decode_check.log 2>&1 )
|
|
# A truncated run must fail as a truncated run. Without this the only symptom is
|
|
# a pixel diff against a half-drawn frame.
|
|
grep -q "snapshot taken" tmp/decode_check.log || {
|
|
echo "FAIL: the 68000 sequential pass did not complete -- no snapshot marker."
|
|
echo " Raise -seconds_to_run; the pass needs the whole container decoded."
|
|
tail -5 tmp/decode_check.log; exit 1; }
|
|
python3 tools/bench/verify_decode.py "$DLX" --nframes "$NF"
|
|
|
|
echo "--- session 10: the same decode on a second CPU core (FINDINGS 37) ---"
|
|
# A SECOND emulator, and the cheapest strong test in the tree: seconds, no MAME,
|
|
# no ROMs. px68k's C68K core has its own cycle table and its own memory model,
|
|
# so a pass here says decode.s is pixel-exact under two independent cores and
|
|
# that the harness's byte-swapped RAM / high-byte-discarding GVRAM is right --
|
|
# which is what licenses its cycle and bus numbers.
|
|
# Skipped rather than failed when px68k is not checked out: it is an external
|
|
# tree, not part of this repo.
|
|
PX68K=${PX68K:-$HOME/src/px68k}
|
|
if [ -f "$PX68K/m68000/c68k.c" ]; then
|
|
make -s -C tools/bench/c68k PX68K="$PX68K"
|
|
bash tools/bench/c68k/run.sh tmp/c68k_frames.csv 2>tmp/c68k.log
|
|
grep -a "sequential pass" tmp/c68k.log
|
|
python3 tools/bench/c68k/verify_c68k.py "$DLX" --nframes "$NF"
|
|
|
|
echo "--- session 10: the bus model still matches the machine (FINDINGS 38) ---"
|
|
# 15_bus_occupancy.py derives instruction prefetch, which no emulator here can
|
|
# report, and validates itself against the DATA accesses the harness counts.
|
|
# If that check ever stops holding, every bus figure in FINDINGS 38/39 is
|
|
# unfounded -- so it is a gate, not a report.
|
|
python3 tools/analysis/15_bus_occupancy.py "$DLX" | sed -n '3,7p'
|
|
else
|
|
echo " SKIPPED: no px68k at $PX68K (set PX68K= to point at a checkout)"
|
|
fi
|
|
|
|
echo "--- session 20: the DMAC config, read out of the IPL ROM (FINDINGS 52) ---"
|
|
# The audio and disk per-byte debits are no longer a recollection about the
|
|
# HD63450: they are bytes at named addresses in the ROM MAME boots this rig
|
|
# with. This gate re-reads them. It is cheap, it needs no emulator, and if a
|
|
# different ROM revision is ever pointed at it, it says so rather than decoding
|
|
# some other code and reporting a number.
|
|
# Skipped rather than failed when the ROM is not where MAME keeps it: that is a
|
|
# path outside this repo.
|
|
IPLROM=${IPLROM:-$HOME/mame/roms/iplrom.dat}
|
|
if [ -f "$IPLROM" ]; then
|
|
python3 tools/analysis/21_iplrom_dmac.py "$IPLROM" > tmp/iplrom_dmac.log 2>&1 \
|
|
|| { cat tmp/iplrom_dmac.log; exit 1; }
|
|
grep -ac "^ OK " tmp/iplrom_dmac.log | xargs printf " %s evidence sites hold; "
|
|
sed -n 's/^ = \(.*clocks per audio byte\)/audio is \1/p' tmp/iplrom_dmac.log
|
|
else
|
|
echo " SKIPPED: no IPL ROM at $IPLROM (set IPLROM= to point at it)"
|
|
fi
|
|
|
|
echo "--- session 18: the shared-body split is a no-op (FINDINGS 49.7.5) ---"
|
|
# src/player/decode.s and src/player/stream.s assemble from ONE copy of the block
|
|
# loop and the span chain (src/player/frame.i) so that the two front-ends cannot
|
|
# drift apart. The drift would be silent -- both would still decode correctly,
|
|
# and only the cost model would be wrong, because the 66.0 clocks/span, 9.143
|
|
# clocks/coarse pixel and every per-block constant in FINDINGS 24/30/40/41 are
|
|
# fitted to those exact bytes. So the split is asserted to be a no-op rather than
|
|
# assumed to be one.
|
|
DECODE_MD5=7a7a06f8c6d097ee0041bca4aefa3eb2 # decode.bin before the split, 1296 B
|
|
GOT=$(md5sum tmp/decode.bin | cut -d" " -f1)
|
|
[ "$GOT" = "$DECODE_MD5" ] || {
|
|
echo "FAIL: decode.bin is $GOT, expected $DECODE_MD5 ($(stat -c%s tmp/decode.bin) B)."
|
|
echo " The block loop or the span chain changed. That is allowed -- but"
|
|
echo " every cycle constant in FINDINGS 24/30/40/41 is fitted to the old"
|
|
echo " bytes, so re-measure them and move this hash, do not just move it."
|
|
exit 1; }
|
|
echo " decode.bin unchanged at $(stat -c%s tmp/decode.bin) B ($DECODE_MD5)"
|
|
# Same argument for the loader maths, which prep_dlx.py and prep_stream.py now
|
|
# share via tools/bench/dlxload.py: a second copy of the palette packing would
|
|
# drift and the symptom would be wrong colours in one rig only.
|
|
python3 tools/bench/prep_dlx.py "$DLX" --ram $((RIG_RAM * 0x100000)) --out tmp/_pdchk > /dev/null
|
|
cmp -s tmp/_pdchk_data.bin tmp/decode_data.bin || {
|
|
echo "FAIL: prep_dlx.py is not reproducible"; exit 1; }
|
|
echo " prep_dlx.py blob reproducible ($(stat -c%s tmp/decode_data.bin) B)"
|
|
rm -f tmp/_pdchk_data.bin tmp/_pdchk_meta.lua
|
|
|
|
echo "--- session 18: 120 frames through a bounded RING (FINDINGS 49) ---"
|
|
# The gate above preloads the whole container into RAM and proves the DECODER.
|
|
# This proves the DELIVERY path: the same 120 frames decoded out of a 256 KB
|
|
# ring on a STOCK 2 MB machine, with the container in a host file. The block
|
|
# loop reads with a monotonically increasing a0 and no bounds check, so a record
|
|
# placed wrongly by the wrap policy corrupts pixels rather than faulting -- which
|
|
# is why this is gated on the same pixel-exact comparison and not on a checksum.
|
|
tools/vasm/vasmm68k_mot -Fbin -o tmp/stream.bin src/player/stream.s > /dev/null
|
|
python3 tools/bench/prep_stream.py "$DLX" > tmp/prep_stream.log
|
|
mkdir -p tmp/snap_stream
|
|
rm -f tmp/snap_stream/x68000/*.png
|
|
( cd tmp && DLX_STREAM_KBPS=0 SDL_VIDEODRIVER=dummy stdbuf -oL timeout -k 5 600 \
|
|
mame x68000 -bios ipl10 -ramsize 2M -video soft -window -sound none \
|
|
-nothrottle -plugins -autoboot_script ../tools/bench/stream.lua \
|
|
-snapshot_directory ./snap_stream -snapview native -seconds_to_run 90 \
|
|
> stream_check.log 2>&1 )
|
|
# Same truncation trap as the decode stage: without this, a run that exited
|
|
# mid-decode is compared against a half-drawn screen and reads as a wrap bug.
|
|
grep -q "snapshot taken" tmp/stream_check.log || {
|
|
echo "FAIL: the ring-buffer pass did not complete -- no snapshot marker."
|
|
tail -5 tmp/stream_check.log; exit 1; }
|
|
grep -a "ring: \|DEADLINE" tmp/stream_check.log | sed "s/\[STR\] / /"
|
|
python3 tools/bench/verify_decode.py "$DLX" --snap tmp/snap_stream
|
|
|
|
echo "--- session 19: the PACED ring, and what a branch point costs (FINDINGS 51) ---"
|
|
# The stage above runs the ring FREE-RUNNING, which is right for what it gates:
|
|
# an unlimited pipe removes delivery as a variable and leaves the wrap policy
|
|
# alone under test. It cannot see buffering, because a decoder that never waits
|
|
# never lets the ring back up -- 49.7.2, and it is why 48 KB passed while
|
|
# holding one record. This runs the same 120 frames with the decoder held to
|
|
# 12 fps, which is the only configuration in which FR_HEAD-FR_TAIL means what
|
|
# it is read to mean.
|
|
#
|
|
# Gated on: pixel-exact, zero UNDERRUNS, and a ceiling that has not moved. The
|
|
# ceiling is a property of THIS container in a 256 KB ring; it is asserted
|
|
# rather than printed because a change in it is a change in how much a branch
|
|
# point can afford, and that should not slip through as a line in a log.
|
|
bash tools/bench/pace_run.sh 256 0 > tmp/pace_check.log 2>&1 || {
|
|
echo "FAIL: the paced ring pass did not complete."; tail -8 tmp/pace_check.log
|
|
exit 1; }
|
|
grep -aE "SEEK SLACK|UNDERRUNS" tmp/pace_check.log
|
|
grep -q "UNDERRUNS: 0/120" tmp/pace_check.log || {
|
|
echo "FAIL: the paced decoder underran -- a frame's slot arrived before its"
|
|
echo " record did. Free-running this is earliness (49.6); paced it is not."
|
|
exit 1; }
|
|
grep -q "ceiling 8 frames" tmp/pace_check.log || {
|
|
echo "FAIL: the 256 KB seek-slack ceiling is no longer 8 frames (FINDINGS 51)."
|
|
echo " Re-run tools/bench/pace_sweep.sh and re-derive 51 before editing"
|
|
echo " this number -- it is what a branch point can spend."
|
|
exit 1; }
|
|
grep -q "^OK" tmp/pace_check.log || { echo "FAIL: paced pass not pixel-exact";
|
|
tail -4 tmp/pace_check.log; exit 1; }
|
|
|
|
echo "--- session 21: the 68000 builds its own codebooks and palette (FINDINGS 53) ---"
|
|
# ROADMAP P1+P2. Until now tools/bench/dlxload.py expanded the codebooks and
|
|
# packed the palette HOST-SIDE and the rigs pushed the result into emulated RAM.
|
|
# A player has no host. src/player/load.i does both on the 68000, out of the RAW
|
|
# container header, and this gates it byte-for-byte against dlxload.py -- which
|
|
# stays the reference, because what changed is where the transforms RUN, not
|
|
# what they produce.
|
|
#
|
|
# Byte-for-byte and not "close enough": a wrong codebook byte is a wrong colour
|
|
# in every block that uses that codeword, and a wrong shared LSB is a slightly
|
|
# wrong colour that looks like a codec artefact rather than a loader bug.
|
|
# The palette half is read back out of the PALETTE REGISTERS at $E82000, so
|
|
# "the words reached the hardware" is part of what passes.
|
|
#
|
|
# NOT gated on the cycle counts, and the reason is NOT the one blit.s has. These
|
|
# are emulated time and reproduce exactly run to run; what they are not is
|
|
# sharp, because MAME samples them on a 1/56.69 s clock and the job takes
|
|
# milliseconds. Nothing in the tree's cost models depends on them either. A
|
|
# change in them is a re-derivation in FINDINGS 53, not a red light here.
|
|
bash tools/bench/load_run.sh "$DLX" > tmp/load_gate.log 2>&1 || {
|
|
echo "FAIL: the load-time transforms did not pass."; tail -12 tmp/load_gate.log
|
|
exit 1; }
|
|
grep -aE "^ *OK|both CPU cores|SCENE CHANGE" tmp/load_gate.log | sed 's/^ *//;s/^/ /'
|
|
|
|
echo "--- session 22: the 68000 keeps its own frame clock (FINDINGS 54) ---"
|
|
# ROADMAP P3. Until now the 12 fps tick came from tools/bench/stream.lua -- a
|
|
# host writing a word into emulated RAM. A player has no host. src/player/
|
|
# clock.i derives the tick from the CRTC's own V-DISP output through the MFP,
|
|
# with a remainder-keeping divider whose two constants are READ OUT OF THE CRTC
|
|
# at init, so the clock and the raster it counts cannot disagree.
|
|
#
|
|
# WHAT IS GATED, and it is deliberately structural rather than numeric:
|
|
# * the interrupt count equals the raster frame count -- the tick IS the
|
|
# raster, not something that merely resembles it;
|
|
# * the divider does not accumulate drift, stated in TICKS (a remainder can
|
|
# hold back at most one) rather than in ppm, which would let a longer
|
|
# window advertise a tighter clock for free;
|
|
# * every frame tick waits 4 or 5 refreshes and nothing else, which is what a
|
|
# remainder-keeping divider can produce and a broken one cannot.
|
|
# The interrupt COST is printed and not gated, for the same reason FINDINGS 53's
|
|
# cycle counts are not: it is a measurement, and a change in it is a
|
|
# re-derivation in FINDINGS 54 rather than a red light here.
|
|
bash tools/bench/clock_run.sh 3000 12 > tmp/clock_gate.log 2>&1 || {
|
|
echo "FAIL: the frame clock did not pass."; tail -12 tmp/clock_gate.log
|
|
exit 1; }
|
|
grep -aE "INTERRUPT:|PER FRAME:|DRIFT:|CADENCE:" tmp/clock_gate.log
|
|
grep -q "V-DISP interrupts 3000" tmp/clock_gate.log || {
|
|
echo "FAIL: the tick is not the raster -- the interrupt count and the frame"
|
|
echo " count disagree. Everything else in FINDINGS 54 rests on that."
|
|
exit 1; }
|
|
|
|
echo "--- session 22: 120 frames decoded on the machine's own clock (FINDINGS 54) ---"
|
|
# The strongest form of the claim: the same pixel-exact 120-frame decode out of
|
|
# the same 256 KB ring, with NOTHING outside the machine deciding when a frame
|
|
# may start. The pace gate in src/player/stream.s is byte-for-byte the one
|
|
# FINDINGS 51 measured -- it cannot tell a host-written tick from a machine-
|
|
# written one, which is why this is a test of the clock and not of a new rig.
|
|
DLX_PACE=2 bash tools/bench/pace_run.sh 256 0 > tmp/selfpace_check.log 2>&1 || {
|
|
echo "FAIL: the self-paced pass did not complete."; tail -8 tmp/selfpace_check.log
|
|
exit 1; }
|
|
grep -aE "decoder SELF-PACED|FRAME CLOCK|UNDERRUNS|NO IDLE" tmp/selfpace_check.log
|
|
grep -q "UNDERRUNS: 0/120" tmp/selfpace_check.log || {
|
|
echo "FAIL: the self-paced decoder underran."; exit 1; }
|
|
grep -q "^OK" tmp/selfpace_check.log || {
|
|
echo "FAIL: the self-paced pass was not pixel-exact. The clock changed WHEN"
|
|
echo " frames start; if it changed WHAT they draw, the interrupt is"
|
|
echo " corrupting decoder state."; tail -4 tmp/selfpace_check.log; exit 1; }
|
|
|
|
echo "ALL GREEN"
|