#!/usr/bin/env python3 """Compare a MAME VRAM dump with the logical frame the movie should show.""" from __future__ import annotations import argparse import sys from pathlib import Path import numpy as np ROOT = Path(__file__).resolve().parents[1] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from pipeline.movie import read_movie from pipeline.tilepack import apply_record VRAM_STRIDE = 256 def vram_to_logical(data: bytes, width: int = 640, height: int = 512, scale: int = 2) -> np.ndarray: """Unpack a 1-bpp VRAM dump and majority-downsample scaleƗscale blocks.""" screen_w = width * scale screen_h = height * scale need = VRAM_STRIDE * screen_h if len(data) < need: raise ValueError("VRAM dump is %d bytes, need at least %d" % (len(data), need)) rows = np.frombuffer(data[:need], dtype=np.uint8).reshape(screen_h, VRAM_STRIDE) bits = np.unpackbits(rows, axis=1, bitorder="little")[:, :screen_w] blocks = bits.astype(np.uint16).reshape(height, scale, width, scale) # The driver's palette: bit 1 = white, bit 0 = black. return blocks.mean(axis=(1, 3)) < 0.5 def expected_frame(movie_path: str, frame: int) -> np.ndarray: header, records = read_movie(movie_path) if frame < 0 or frame >= len(records): raise ValueError("frame %d outside 0..%d" % (frame, len(records) - 1)) canvas = np.zeros((header.height, header.width), dtype=bool) for record in records[:frame + 1]: apply_record(canvas, record, tile=header.tile) return canvas def main(argv=None) -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--vram", required=True) parser.add_argument("--movie", required=True) parser.add_argument("--frame", type=int, default=0) parser.add_argument("--scale", type=int, default=2) args = parser.parse_args(argv) data = Path(args.vram).read_bytes() header, _ = read_movie(args.movie) actual = vram_to_logical(data, header.width, header.height, args.scale) expected = expected_frame(args.movie, args.frame) if np.array_equal(actual, expected): print("EXACT frame %d" % args.frame) return 0 bad = int(np.count_nonzero(actual != expected)) ys, xs = np.where(actual != expected) print("MISMATCH frame %d: %d logical pixels differ" % (args.frame, bad)) for y, x in list(zip(ys, xs))[:16]: print(" (%d,%d) expected=%d actual=%d" % (x, y, expected[y, x], actual[y, x])) return 1 if __name__ == "__main__": raise SystemExit(main())