Files
Dragon-s-Lair-X68k/tools/bench/verify_packed.py
T
prosolis 6f698ca226 Put the player on a real volume, and find the write window is the frame
ROADMAP K3. src/player/packed.s (2,898 B) brings up its own display, builds
its own 193-entry DMA chain, keeps its own frame clock off V-DISP and fetches
every record itself with READ(10) off a CZ-6BS1. The rig writes no picture
byte, no palette entry and no CRTC register.

120 of 120 frames pixel-exact, every one compared, in both palette orders --
the gate had to grow to do it, because a packed frame is a LITERAL and the
codec's recursion was what made one comparison audit 120.

And the write window turns out to be the frame. A packed write requires R20
bit 11, buffer mode blanks the layer, and a DMAC-direct player holds the
window open for the whole data phase, so

    dark fraction of a slot = record bytes / (DATA-PHASE rate x slot)

which is 1.0 at the container's own 582.0 KB/s: every frame delivered, on
time, pixel-exact, and none of them displayed. The rate in that expression is
the BURST rate, a third hardware number B1 has no test for. It reverses 61.5's
ranking -- a packed player that DMAs to RAM and paints with the measured 27.3%
blit is on screen 72.7% of every slot at any rate, and the two are equal only
at 2,131 KB/s = 3.7x the wire.

And a held channel costs the frame clock half its ticks without the clock
being able to tell: 487 of 1,038 V-DISP edges lost, zero late frames reported,
the player believing 12 fps while the screen ran at 6.37.

FINDINGS 64. ROADMAP K4 opened and fenced behind B2.
check.sh ALL GREEN before and after.

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
2026-08-25 09:10:48 -07:00

115 lines
5.3 KiB
Python

#!/usr/bin/env python3
"""Is EVERY frame the packed player put on screen pixel-exact? ROADMAP K3.
python3 tools/bench/verify_packed.py <in.dlxp> [--snap tmp/snap_packed]
[--map tmp/packed_snaps.csv]
[--min-frames N]
WHY THIS CHECKS ALL OF THEM AND tools/bench/verify_decode.py CHECKS ONE. The
codec is temporally recursive: a SKIP block is a claim that the previous frame is
still in GVRAM, so the last frame of a sequential run is only correct if every
frame before it was, and one comparison audits 120. A packed frame is a
LITERAL -- 192 rows of picture and a whole new palette, written over whatever
was there. Frame 119 being right says nothing at all about frame 60. The
simplification that deleted the ring, the codebooks and the decoder also deleted
the gate's free lunch, and this is the bill.
WHAT IS COMPARED. MAME's own screen, through MAME's own video code: the
snapshot is what the display produced out of GVRAM and the palette REGISTERS.
Nothing here re-implements the packed interleave -- that is deliberate and it is
the same rule tools/bench/gvpack/verify_dlxp.py was built on, because a
container round-trips against its own inverse whether or not its byte order is
the one the hardware wants. The reference is dlxp.render(i), which is the
palette in the record applied to the indices in the record.
THE LETTERBOX IS CHECKED TOO, and it is not padding. The picture is 192 rows of
a 256-row screen; the other 64 rows are STATIC SETUP the 68000 wrote once at
scene start (packed.s pg_static) and the channel never touches again. If they
were wrong -- or if they decayed as the per-frame palette moved under them --
the picture would still be pixel-exact and the screen would not be. Index 255
is black in every frame's palette by construction (vq.frame_palette), so this
also gates that reservation across all 120 records.
"""
import argparse, csv, os, sys
sys.path.insert(0, "tools/encoder")
import numpy as np
from PIL import Image
from dlxp import DLXP
ap = argparse.ArgumentParser()
ap.add_argument("container")
ap.add_argument("--snap", default="tmp/snap_packed")
ap.add_argument("--map", default="tmp/packed_snaps.csv")
ap.add_argument("--min-frames", type=int, default=1,
help="fail if fewer than this many frames were sampled -- a "
"run that displayed nothing must not pass as a run with "
"no mismatches in it")
a = ap.parse_args()
d = DLXP(a.container)
if not d.has_palette:
# A --no-palette container leaves the palette registers holding whatever the
# scene setup put there, and this rig's player writes none -- so there is no
# reference for what the screen should show. Say so rather than compare
# against an assumption.
sys.exit(f"{a.container} carries no palette; this gate has no reference "
f"for what the display should have produced.")
with open(a.map) as fh:
pairs = [(r["snapshot"], int(r["frame"])) for r in csv.DictReader(fh)]
if len(pairs) < a.min_frames:
print(f"FAIL 0. only {len(pairs)} frames were sampled, --min-frames is "
f"{a.min_frames}. A player whose write window never closed displays "
f"nothing, and an empty comparison is not a pass.")
sys.exit(1)
SCRH, SCRW = 256, 256
YOFF = (SCRH - d.H) // 2
fails, checked = [], 0
for name, fr in pairs:
path = f"{a.snap}/x68000/{name}.png"
if not os.path.exists(path):
fails.append(f"snapshot {name} (frame {fr}) is missing from {a.snap}")
continue
s = np.asarray(Image.open(path).convert("RGB")).astype(int)
if s.shape[:2] != (2 * SCRH, SCRW):
fails.append(f"frame {fr}: geometry {s.shape[1]}x{s.shape[0]}, "
f"expected {SCRW}x{2*SCRH}")
continue
if not all(np.array_equal(s[i], s[i + 1]) for i in range(1, s.shape[0] - 1, 2)):
fails.append(f"frame {fr}: double-scan pairing (1,2),(3,4),... broken")
continue
g = s[0::2]
pal = d.palette_rgb(fr)
exp = np.empty((SCRH, SCRW, 3), int)
exp[:] = pal[255] # the letterbox, and the reservation
exp[YOFF:YOFF + d.H] = d.render(fr)
checked += 1
if np.array_equal(g, exp):
continue
bad = (g != exp).any(2)
by, bx = np.where(bad)
inpic = ((by >= YOFF) & (by < YOFF + d.H)).sum()
fails.append(f"frame {fr} (snapshot {name}): {bad.sum()} px differ "
f"({inpic} in the picture, {bad.sum()-inpic} in the "
f"letterbox), first at y={by[0]} x={bx[0]}, maxdiff "
f"{abs(g-exp).max()}")
for f in fails[:12]:
print("FAIL " + f)
if len(fails) > 12:
print(f"FAIL ... and {len(fails)-12} more")
if fails:
print(f" {checked-len([f for f in fails])} of {len(pairs)} sampled "
f"frames compared clean")
sys.exit(1)
lo, hi = min(f for _, f in pairs), max(f for _, f in pairs)
print(f"OK {checked} frames of {a.container} pixel-exact on the emulated "
f"68000, frames {lo}..{hi} of {d.nframes}")
print(f" every one of them a LITERAL: no decoder, no codebook, no ring. "
f"Screen {SCRW}x{SCRH}, picture {d.W}x{d.H} at y={YOFF}, letterbox on "
f"the reserved index 255.")
print(f" palette {'LAST' if d.palette_last else 'FIRST'} in the record, "
f"{d.pal_bytes} B, compared as the DISPLAY renders it (GRB555+I out of "
f"the palette registers)")