ROADMAP K1, the packed player's one open structural item. A frame is a picture
AND a palette, and no run in this tree had pointed a DMA channel at the palette
registers. dmagate.s runs 7-9, gated by dma_run.sh and check.sh:
7. 512 B off the disc into $E82000, bus held -- byte-exact in 256 register
words, read back OUT OF the registers by the 68000;
8. the SAME transfer aimed at RAM -- byte-exact at $2C000, and 256 of 256
palette words still read the poison the CPU wrote, which is what attributes
run 7 to the channel's MAR rather than to the readback path;
9. ONE array-chained start across two kinds of destination -- the palette and
six picture rows at the 1,024 B line stride, 2,048 B byte-exact.
So a packed frame is one channel start: a 193-entry array, palette first, CPU
halted from the first byte to the last. The array is scene-constant, because
the packed layout spends both 256-colour pages and there is no page to flip.
What is left on the CPU per frame in the video path is the channel start and the
READ(10) -- no per-frame PAINT, which is not the same claim as no per-frame CPU.
The destination is POISONED first (62.1). Runs 4-6 wrote into RAM that was zero
and GVRAM that was stale against a record that is mostly pad; "it matches the
disc" was weaker than it read as. The host counts whether the poison actually
discriminates instead of assuming it: 511 of 512, and the gate refuses under 500.
And it opened a hardware item (62.4, ROADMAP B4). MAME maps the palette to
palette_device over memory_array, whose write16 is a plain COMBINE_DATA -- RAM
that honours mem_mask, with no handler that could refuse a byte write. Unlike
GVRAM's 256-colour arm there is nothing here to be wrong about, so the run
bounds the model and not the board. What a real palette register does with a
byte write is unmeasured. A negative costs 0.28% of a frame and nothing else.
29_packed_player.py now also prints the two rows with the per-frame palette
charged -- 55.7% of a frame on the chain, 582 KB/s -- alongside the picture-only
figures the codec comparison is quoted against.
check.sh ALL GREEN before (tmp/check_s30_start.log) and after
(tmp/check_s30_end.log).
Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
666 lines
39 KiB
Bash
Executable File
666 lines
39 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 "--- session 23: the 68000 fills its own ring (FINDINGS 55) ---"
|
|
# ROADMAP P5. Until now the RING was filled by tools/bench/stream.lua: the host
|
|
# held the record index, chose where each record went, wrote the descriptor and
|
|
# advertised it. A player has no host. src/player/ring.i does all of that on the
|
|
# 68000, out of the DLX4 record index in the scene header, and this script keeps
|
|
# only the part that is not the CPU's -- a transport that answers one request at
|
|
# a time at a modelled rate.
|
|
#
|
|
# WHAT IS GATED:
|
|
# * pixel-exact, which is the only test that can see a wrong placement: the
|
|
# block loop reads with a monotonically increasing a0 and no bounds check,
|
|
# so a record placed over one the decoder has not finished corrupts pixels
|
|
# rather than faulting (49.2);
|
|
# * the host AUDITS every placement against its own index and its own list of
|
|
# live records, and refuses the run on the first disagreement;
|
|
# * the wrap policy still produces the SAME 18 wraps the
|
|
# host producer produced in FINDINGS 49.4 -- a third independent
|
|
# implementation of `aligned` landing on the same tiling;
|
|
# * zero underruns at a two-deep request queue, which is the finding: a
|
|
# one-deep queue leaves the channel idle between records and underran 59 of
|
|
# 120 frames on this same container and rate.
|
|
DLX_PACE=2 DLX_RINGOWN=1 DLX_QDEPTH=2 bash tools/bench/pace_run.sh 256 488 \
|
|
> tmp/ringown_check.log 2>&1 || {
|
|
echo "FAIL: the machine-owned ring pass did not complete."
|
|
tail -10 tmp/ringown_check.log; exit 1; }
|
|
grep -aE "MACHINE-OWNED|PREFILL:|CHANNEL IDLE|UNDERRUNS|SEEK SLACK" tmp/ringown_check.log \
|
|
| sed "s/\[STR\] / /"
|
|
grep -q "MISPLACED" tmp/ringown_check.log && {
|
|
echo "FAIL: the 68000 placed a record over one the decoder still owned."
|
|
exit 1; }
|
|
grep -q "UNDERRUNS: 0/120" tmp/ringown_check.log || {
|
|
echo "FAIL: the machine-owned ring underran at a two-deep queue. That is the"
|
|
echo " configuration FINDINGS 55 says keeps the channel busy; if it no"
|
|
echo " longer does, the poll site in src/player/stream.s moved."
|
|
exit 1; }
|
|
grep -q "ring: 18 wraps" tmp/ringown_check.log || {
|
|
echo "FAIL: the machine's own \`aligned\` no longer tiles this container the"
|
|
echo " way FINDINGS 49.4's host producer did (18 wraps). The policy is"
|
|
echo " meant to be the SAME policy in a different place."
|
|
exit 1; }
|
|
grep -q "^OK" tmp/ringown_check.log || {
|
|
echo "FAIL: the machine-owned ring pass was not pixel-exact -- a record was"
|
|
echo " placed or described wrongly."; tail -4 tmp/ringown_check.log
|
|
exit 1; }
|
|
|
|
echo "--- session 23: a seek, and the decode after it (FINDINGS 55) ---"
|
|
# The branch point rehearsed. A second pass over the scene begins with a real
|
|
# seek in src/player/ring.i: the channel is waited quiet, the ring is declared
|
|
# empty, the disc address of record 0 comes out of the index rather than from a
|
|
# walk, and the whole lookahead 51.3 says takes seconds of play to accumulate is
|
|
# thrown away and rebuilt from the prefill. What is gated afterwards is the one
|
|
# thing that can see a wrong seek: the last frame of the SECOND pass has to be
|
|
# pixel-exact, and a SKIP block is a claim about the previous frame, so it is
|
|
# only right if every frame after the seek was.
|
|
DLX_PACE=2 DLX_RINGOWN=1 DLX_QDEPTH=2 DLX_ITER=2 \
|
|
bash tools/bench/pace_run.sh 256 488 > tmp/ringseek_check.log 2>&1 || {
|
|
echo "FAIL: the seek pass did not complete."; tail -10 tmp/ringseek_check.log
|
|
exit 1; }
|
|
grep -aE "SEEK PASS|CHANNEL IDLE|UNDERRUNS" tmp/ringseek_check.log | sed "s/\[STR\] / /"
|
|
grep -q "SEEK PASS 2" tmp/ringseek_check.log || {
|
|
echo "FAIL: no second pass -- the seek never happened, so this gated nothing."
|
|
exit 1; }
|
|
grep -q "UNDERRUNS: 0/120" tmp/ringseek_check.log || {
|
|
echo "FAIL: the pass after the seek underran."; exit 1; }
|
|
grep -q "^OK" tmp/ringseek_check.log || {
|
|
echo "FAIL: the decode after the seek was not pixel-exact."
|
|
tail -4 tmp/ringseek_check.log; exit 1; }
|
|
|
|
echo "--- session 25: the 68000 reads the disc itself (FINDINGS 57) ---"
|
|
# ROADMAP P4, first half. Until now every byte the player consumed was placed in
|
|
# emulated RAM by a host: decode.lua preloaded a container, stream.lua answered a
|
|
# mailbox at a modelled rate. A player has no host. src/player/scsi.i selects a
|
|
# SCSI target on a real MB89352 and issues READ(10) itself.
|
|
#
|
|
# Session 21 recorded this as blocked -- "MAME's x68000 has no MB89352 path" --
|
|
# and that was wrong: `-exp1 cz6bs1` instantiates one, and FINDINGS 32.4 had
|
|
# already read its DMA glue in session 9. What was actually missing was the
|
|
# card's 8 KB boot ROM, which MAME requires to instantiate the device and which
|
|
# the player never executes. scsi_run.sh supplies a blank one on its own rompath.
|
|
#
|
|
# WHAT IS GATED: the register window (60 of 64 addresses -- the two holes ARE the
|
|
# MB89352's missing TMOD and EXBF, and they are what put DREG at $EA0015), and
|
|
# two READ(10)s verified byte-for-byte against the host's copy of the same image,
|
|
# one of them at a NON-ZERO LBA. Nothing here is gated on rate, and nothing here
|
|
# can be: MAME's device models are functional, not transfer-timing accurate.
|
|
# Skipped rather than failed when chdman is absent -- it ships with mame-tools.
|
|
if command -v chdman > /dev/null; then
|
|
bash tools/bench/scsi_run.sh "$DLX" > tmp/scsi_gate.log 2>&1 || {
|
|
echo "FAIL: the 68000 could not read the disc."; tail -14 tmp/scsi_gate.log
|
|
exit 1; }
|
|
grep -aE "ANSWERED|READ\(10\) OK" tmp/scsi_gate.log
|
|
else
|
|
echo " SKIPPED: no chdman (ships with mame-tools) -- cannot build the volume"
|
|
fi
|
|
|
|
echo "--- session 26: the ring is filled off a real SCSI volume (FINDINGS 58) ---"
|
|
# ROADMAP P4b. The stage above shows the 68000 can READ the disc. This shows it
|
|
# can RUN off it: src/player/xfer.i sits behind src/player/ring.i's XF_* mailbox
|
|
# in place of tools/bench/stream.lua's modelled transport, and the same 120
|
|
# frames are decoded out of the same 256 KB ring with NOTHING outside the
|
|
# machine in the transfer path -- no host file, no modelled rate, no synthesised
|
|
# ack.
|
|
#
|
|
# WHAT IS GATED, and it is correctness rather than rate on purpose:
|
|
# * pixel-exact, which is the only test that can see a wrong record: the
|
|
# window in scsi.i decides which of a sector's bytes reach the ring, and a
|
|
# window off by one byte desyncs the bitstream rather than faulting (49.2);
|
|
# * the SAME 18 wraps -- ring.i's placement policy must not be able to tell
|
|
# which transport answered it, and this is the assertion that says it could
|
|
# not. The WRAP COUNT is gated and the mean hole is only reported: DLX5's
|
|
# records are up to 511 B longer than DLX4's, so the hole moved (14.7 KB ->
|
|
# 13.5 KB) while the tiling did not. Gating a number that the container's
|
|
# record lengths move would gate the container, not the policy;
|
|
# * every record accounted for: 120 READ(10)s, and the bytes into the ring
|
|
# EQUAL to the bytes off the disc -- both read out of the container rather
|
|
# than written here. Under DLX4 they differed by 1.34% because a record was
|
|
# not a sector (58.3); DLX5 aligns records to sectors and the covering-sector
|
|
# read disappears, so the gate is now their IDENTITY;
|
|
# * a real mid-stream SEEK with the real transport, in the second pass. This
|
|
# is the one path that could not exist before: ring_seek waits for the
|
|
# channel to go quiet, and with the transport INSIDE the machine the only
|
|
# thing that can retire an outstanding request is that wait loop itself.
|
|
#
|
|
# NOTHING HERE IS GATED ON RATE and nothing here can be. What the run DOES cost
|
|
# is printed by tools/bench/xfer_cost.sh and recorded in FINDINGS 58.2; it is a
|
|
# measurement, and a change in it is a re-derivation there rather than a red
|
|
# light here. Skipped rather than failed when chdman is absent.
|
|
if command -v chdman > /dev/null; then
|
|
DLX_PACE=0 DLX_RINGOWN=1 DLX_QDEPTH=2 DLX_XFER=scsi \
|
|
bash tools/bench/pace_run.sh 256 0 > tmp/p4b_check.log 2>&1 || {
|
|
echo "FAIL: the 68000 could not run the ring off a real SCSI volume."
|
|
tail -12 tmp/p4b_check.log; exit 1; }
|
|
grep -aE "REAL TRANSPORT:|SECTOR OVERHEAD|ring: " tmp/p4b_check.log \
|
|
| sed "s/^ *//;s/^/ /"
|
|
grep -aq "TRANSPORT FAILED" tmp/p4b_check.log && {
|
|
echo "FAIL: a record's READ(10) reported an error."; exit 1; }
|
|
# THE BYTE COUNTS COME OUT OF THE CONTAINER, not out of this file. They were
|
|
# two hardcoded constants fitted to the DLX4 gate container, and session 28's
|
|
# re-encode went red on both of them for the right reason -- the container had
|
|
# changed and the expectation had not. A gate whose expected value is a
|
|
# literal tests the literal.
|
|
EXPECT_B=$(python3 -c "
|
|
import sys; sys.path.insert(0, 'tools/encoder')
|
|
from dlx import DLX
|
|
print(sum(DLX('$DLX').record_lengths()))")
|
|
grep -aq "REAL TRANSPORT: 120 READ(10)s by the 68000, $EXPECT_B B into the ring" \
|
|
tmp/p4b_check.log || {
|
|
echo "FAIL: the 68000 did not fetch all 120 records, or did not fetch"
|
|
echo " $EXPECT_B B of them. A short record is a desync, not a shortfall."
|
|
exit 1; }
|
|
# DLX5 MAKES THESE THE SAME NUMBER, and that identity IS the finding (59.4,
|
|
# and 58.3 option C): a sector-aligned container has no covering-sector read,
|
|
# so the disc moves exactly the records and nothing else. Under DLX4 they
|
|
# differed by 1.34% and both were gated so neither could drift into the other;
|
|
# under DLX5 the gate is that they are EQUAL. If a windowed read ever came
|
|
# back -- a container that was not aligned, or a layout that lost the
|
|
# alignment -- the disc figure would exceed the ring figure and this goes red.
|
|
grep -aq "SECTOR OVERHEAD: $EXPECT_B B off the disc for $EXPECT_B B of record = 0.00%" \
|
|
tmp/p4b_check.log || {
|
|
echo "FAIL: the disc no longer moves EXACTLY the records. On a sector-"
|
|
echo " aligned container (DLX5) there is no covering-sector read at"
|
|
echo " all, so these two counts must be the same $EXPECT_B B. If they"
|
|
echo " differ, either the container lost its alignment or scsi.i is"
|
|
echo " windowing again -- and a DMA channel cannot window (59.4)."
|
|
exit 1; }
|
|
grep -aq "ring: 18 wraps" tmp/p4b_check.log || {
|
|
echo "FAIL: the placement policy tiled this container differently with a"
|
|
echo " real transport behind it than with a modelled one. ring.i is"
|
|
echo " not supposed to be able to tell them apart."; exit 1; }
|
|
grep -aq "^OK" tmp/p4b_check.log || {
|
|
echo "FAIL: the pass off the SCSI volume was not pixel-exact."
|
|
tail -4 tmp/p4b_check.log; exit 1; }
|
|
DLX_PACE=2 DLX_RINGOWN=1 DLX_QDEPTH=2 DLX_ITER=2 DLX_XFER=scsi \
|
|
DLX_SECONDS=240 bash tools/bench/pace_run.sh 256 0 \
|
|
> tmp/p4b_seek_check.log 2>&1 || {
|
|
echo "FAIL: the seek pass off the SCSI volume did not complete."
|
|
tail -12 tmp/p4b_seek_check.log; exit 1; }
|
|
grep -aE "SEEK PASS|IS VACUOUS" tmp/p4b_seek_check.log | sed "s/^ *//;s/^/ /"
|
|
grep -aq "SEEK PASS 2" tmp/p4b_seek_check.log || {
|
|
echo "FAIL: no real seek -- the second pass never threw its ring away, so"
|
|
echo " ring_seek's quiet-wait was never asked to retire an outstanding"
|
|
echo " transfer and this gated nothing."; exit 1; }
|
|
grep -aq "^OK" tmp/p4b_seek_check.log || {
|
|
echo "FAIL: the decode after a seek off the SCSI volume was not pixel-exact."
|
|
tail -4 tmp/p4b_seek_check.log; exit 1; }
|
|
else
|
|
echo " SKIPPED: no chdman (ships with mame-tools) -- cannot build the volume"
|
|
fi
|
|
|
|
echo "--- session 27: the DMAC drives the data phase, and holds the bus (FINDINGS 59) ---"
|
|
# ROADMAP P4a, the last item before M2. The two stages above have the CPU moving
|
|
# every byte itself, at the 87.28 clocks per delivered byte FINDINGS 58.2
|
|
# measured -- 391.8% of a 12 fps frame. This one hands the DATA IN phase to the
|
|
# HD63450 and gates on the thing 57.3 said would be hard to show: that the DMAC,
|
|
# and not the CPU, is driving it.
|
|
#
|
|
# IT IS GATED WITHOUT LOOKING AT $EA0015, and that is the design. With the
|
|
# DMAC's OWN asserted -- which it is at idle here -- MAME cannot distinguish a
|
|
# CPU-driven byte at that address from a DMAC-driven one, so watching it proves
|
|
# nothing. What is gated instead is THE CPU'S OWN PROGRESS:
|
|
# * the same 2,048 B off the disc three ways -- PIO, held, stealing -- all
|
|
# three byte-exact against the host's copy, so the configuration is being
|
|
# compared against a delivery that works and not against nothing;
|
|
# * MTC sampled by the INSTRUCTION AFTER the one that starts the channel: zero
|
|
# in the held configuration (the whole transfer happened between two
|
|
# instructions, because the 68000 did not execute in between) and the full
|
|
# count in the stealing one;
|
|
# * the CPU's own trip count round its wait loop: 1 against hundreds. A
|
|
# counter that CANNOT come out different is 58.3's vacuous "UNDERRUNS: 0/120"
|
|
# again, so the run asserts the contrast and not just the held value;
|
|
# * the channel's own CSR/CER/MTC/MAR, which must say it moved every byte
|
|
# without error;
|
|
# * THE PALETTE REGISTERS AT $E82000 (session 30, ROADMAP K1): the same
|
|
# transfer aimed at the palette, byte-exact into 256 register words read
|
|
# back by the 68000; the SAME transfer aimed 20 KB away leaving the palette
|
|
# as the CPU poisoned it, which is what attributes the first run to the
|
|
# channel's MAR; and ONE array-chained start crossing from the registers
|
|
# into GVRAM, which is the shape of a whole frame -- a palette entry and
|
|
# 192 row entries, started once. What this does NOT settle is the board:
|
|
# MAME models the palette as a generic palette_device over memory_array,
|
|
# whose write16 is a plain COMBINE_DATA, so it cannot tell a register file
|
|
# that takes byte writes from one that does not (FINDINGS 62.4);
|
|
# * and a WINDOWED read through the channel REFUSED. 117 of 120 records start
|
|
# part way into a sector (58.3); a channel writes a contiguous run and cannot
|
|
# drop the bytes in front of one, so it would write the neighbouring records
|
|
# into the ring with no bounds check to catch it (49.2). The refusal is what
|
|
# makes "sector-aligned container" a precondition the transport states.
|
|
#
|
|
# NOT GATED ON RATE, and it cannot be: MAME's DMAC runs on wall-clock attotimes
|
|
# (42.5) and models a held bus by HALTING the CPU rather than by charging it
|
|
# cycles per operand. `W` is untouched. tools/analysis/28_autorequest_cost.py
|
|
# prices what this configuration costs, from the datasheet and an explicit rate.
|
|
# Skipped rather than failed when chdman is absent.
|
|
if command -v chdman > /dev/null; then
|
|
bash tools/bench/dma_run.sh "$DLX" > tmp/dma_gate.log 2>&1 || {
|
|
echo "FAIL: the DMAC did not drive the SCSI data phase."
|
|
tail -16 tmp/dma_gate.log; exit 1; }
|
|
grep -aE "BYTES OK|MTC one instruction|trips round|REFUSED|PALETTE|ONE START" \
|
|
tmp/dma_gate.log \
|
|
| sed 's/^ *//;s/^/ /'
|
|
else
|
|
echo " SKIPPED: no chdman (ships with mame-tools) -- cannot build the volume"
|
|
fi
|
|
|
|
echo "--- session 24: the scene graph, and the gap between branch points (FINDINGS 56) ---"
|
|
# The arcade scene graph is not in this repo and is not redistributable from
|
|
# here. tools/import/scenegraph.py is the ONE file in the tree that knows the
|
|
# outside projects exist; it writes tmp/scenegraph.json in this project's own
|
|
# DLXSCENE1 schema and everything downstream reads only that.
|
|
# What is gated is the IMPORT, not the numbers: 516 sequences and 906 input
|
|
# windows, and the four timing helpers still being the formulas the importer
|
|
# evaluates. Skipped when the checkout is absent.
|
|
DIRKSIMPLE=${DLX_DIRKSIMPLE:-tmp/scenegraph/DirkSimple}
|
|
if [ -f "$DIRKSIMPLE/data/games/lair/game.lua" ]; then
|
|
DLX_DIRKSIMPLE="$DIRKSIMPLE" python3 tools/import/scenegraph.py \
|
|
-o tmp/scenegraph.json > tmp/scenegraph_import.log 2>&1 \
|
|
|| { cat tmp/scenegraph_import.log; exit 1; }
|
|
sed "s/^/ /" tmp/scenegraph_import.log
|
|
grep -q "516 sequences, 906 input windows" tmp/scenegraph_import.log || {
|
|
echo "FAIL: the scene graph did not import to 516/906 -- upstream changed,"
|
|
echo " or the parser silently dropped branches."; exit 1; }
|
|
python3 tools/analysis/25_scene_graph.py --kbps 488 --ring 256 \
|
|
> tmp/scenegraph_check.log 2>&1 \
|
|
|| { tail -20 tmp/scenegraph_check.log; exit 1; }
|
|
grep -aE "^ WORST |^ ZERO-PLAY|^ BRANCH STRUCTURE" tmp/scenegraph_check.log
|
|
else
|
|
echo " SKIPPED: no DirkSimple checkout at $DIRKSIMPLE"
|
|
echo " (git clone --depth 1 https://github.com/icculus/DirkSimple)"
|
|
fi
|
|
|
|
echo "--- session 29: the packed paint, and what it does to the codec (FINDINGS 61) ---"
|
|
# tools/bench/blit.s gained V8/V9/V10 -- the packed full-frame blit, and the two
|
|
# ways a 4x4 BLOCK decoder could survive the packed layout. 47.6.1 had filed the
|
|
# packed paint's `movem` shape as an ASSUMPTION since session 16; this measures
|
|
# it, in the same run as V1/V3/V4 so it is quoted against numbers that have not
|
|
# moved since session 9.
|
|
#
|
|
# WHAT IS GATED IS STRUCTURAL, not numeric, for the reason the load stage gives:
|
|
# MAME samples these on a 1/56.69 s clock and no cost model in the tree depends
|
|
# on their exact value. What DOES depend on them is the ORDER, and the order is
|
|
# the whole of FINDINGS 61:
|
|
# V8 < V1 packing halves the full-frame literal paint
|
|
# V9 > V4 packing makes a BLOCK decoder DEARER, not cheaper
|
|
# V10 < V4 unless the blocks are paired, which costs SKIPs instead
|
|
# A tree where any of those flipped has a different answer to 44.7 and should
|
|
# say so out loud rather than let 29_packed_player.py narrate the old one.
|
|
python3 tools/bench/prep_frame.py tmp/fr_00020 tmp/frame256.bin 0 --reserve-black
|
|
rm -f tmp/blit_v8.log
|
|
( cd tmp && SDL_VIDEODRIVER=dummy stdbuf -oL timeout -k 5 300 mame x68000 \
|
|
-bios ipl10 -ramsize 2M -video soft -window -sound none -nothrottle -plugins \
|
|
-autoboot_script ../tools/bench/blit.lua -seconds_to_run 120 \
|
|
> blit_v8.log 2>&1 )
|
|
grep -aq "summary (instruction cycles only" tmp/blit_v8.log || {
|
|
echo "FAIL: the blit timing run produced no summary -- it did not finish."
|
|
tail -8 tmp/blit_v8.log; exit 1; }
|
|
python3 - <<'EOF' || exit 1
|
|
import re, sys
|
|
v = {}
|
|
for line in open("tmp/blit_v8.log", errors="replace"):
|
|
m = re.search(r"V(\d+)\s+(\d+) cyc", line)
|
|
if m: v[int(m.group(1))] = int(m.group(2))
|
|
need = (1, 2, 3, 4, 8, 9, 10)
|
|
missing = [n for n in need if n not in v]
|
|
if missing: sys.exit(f"FAIL: blit.lua reported no V{missing} -- run incomplete.")
|
|
for a, op, b, why in ((8, "<", 1, "packing did not halve the literal paint"),
|
|
(9, ">", 4, "packed BLOCK order came out CHEAPER than "
|
|
"unpacked -- 61.3's conclusion is inverted"),
|
|
(10, "<", 4, "pairing the blocks did not buy back the "
|
|
"movem shape")):
|
|
ok = v[a] < v[b] if op == "<" else v[a] > v[b]
|
|
if not ok:
|
|
sys.exit(f"FAIL: V{a} {v[a]:,} is not {op} V{b} {v[b]:,} -- {why}.")
|
|
print(f" V1 {v[1]:,} / V8 PACKED {v[8]:,} = {100*v[8]/v[1]:.0f}% -- "
|
|
f"and V3, the unpacked WRITE-ONLY floor, is {v[3]:,}")
|
|
print(f" V4 {v[4]:,} / V9 packed-block {v[9]:,} = {100*v[9]/v[4]:.0f}% -- "
|
|
f"packing costs a BLOCK decoder {100*v[9]/v[4]-100:.0f}%")
|
|
print(f" V10 paired blocks {v[10]:,} = {100*v[10]/v[4]:.0f}% of V4, and pairing "
|
|
f"is paid for in SKIPs")
|
|
EOF
|
|
python3 tools/analysis/29_packed_player.py "$DLX" > tmp/packed_player.log 2>&1 \
|
|
|| { tail -20 tmp/packed_player.log; exit 1; }
|
|
grep -aE "SKIP block PAIRS|free / DMAC->GVRAM / PACKED|^ CODEC, gate" \
|
|
tmp/packed_player.log
|
|
|
|
echo "ALL GREEN"
|