Files
Dragon-s-Lair-X68k/tools/analysis/01_naive_BROKEN.py
T
reala-misaki 65112b9305 Session 1: hardware research, content measurement, codec decision, MAME harness
Verified GVRAM is one word-access per pixel in ALL color modes; chose 256-color
256x192 with movem.l bursts (page 1 sacrificed as double-buffer).

Measured 8 scenes from the Blu-ray source: blit costs under 8% of the 12fps
cycle budget, so I/O is the bottleneck, not CPU. Naive delta+RLE reaches only
3.2:1 (365 KB/s, 470MB) -> decision to use 4x4 vector quantization (~30 KB/s).

"Shot on twos" assumption failed: the transfer has zero duplicate frames, so
12fps requires explicit decimation.

Documents three false measurement results and their root causes (per-frame
Floyd-Steinberg dithering, temporal denoise, exact-match dedupe on noisy source).

MAME Lua injection harness works and is reusable for cycle-cost measurement;
the IOCS _B_READ disk benchmark is blocked returning -1.

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
2026-08-23 11:23:49 -07:00

93 lines
3.7 KiB
Python

import numpy as np, glob
from PIL import Image
files = sorted(glob.glob("an/f*.png"))
rgb = [np.asarray(Image.open(f).convert("RGB")) for f in files]
N = len(rgb)
print(f"frames={N} size={rgb[0].shape}")
# --- 1. duplicate / twos detection on the SOURCE ---
exact = 0; near = 0; diffs = []
for i in range(1, N):
d = np.abs(rgb[i].astype(np.int16) - rgb[i-1].astype(np.int16))
m = d.mean()
diffs.append(m)
if m == 0: exact += 1
elif m < 1.0: near += 1
print(f"\n--- source frame-to-frame (24fps) ---")
print(f"exact duplicates : {exact}/{N-1} ({100*exact/(N-1):.1f}%)")
print(f"near-dup (<1.0) : {near}/{N-1} ({100*near/(N-1):.1f}%)")
print(f"mean abs diff : {np.mean(diffs):.2f}")
# --- 2. build a per-scene 256-color palette from the whole clip ---
sample = np.concatenate([rgb[i].reshape(-1,3) for i in range(0,N,4)])
pal_img = Image.fromarray(sample.reshape(-1,1,3).astype(np.uint8))
pal = pal_img.quantize(colors=256, method=Image.MEDIANCUT, dither=Image.NONE)
palette = pal.getpalette()[:768]
ref = Image.new("P", (1,1)); ref.putpalette(palette)
idx = []
for a in rgb:
q = Image.fromarray(a).quantize(palette=ref, dither=Image.FLOYDSTEINBERG)
idx.append(np.asarray(q, dtype=np.uint8))
# quantization error
err = np.mean([np.abs(np.asarray(Image.fromarray(idx[i]).convert("P")) ) for i in range(0)]) if False else None
# --- 3. drop duplicate frames -> unique frame stream ---
keep = [0]
for i in range(1, N):
if not np.array_equal(idx[i], idx[keep[-1]]):
keep.append(i)
print(f"\n--- after 256-color quantize + dedupe ---")
print(f"unique frames : {len(keep)}/{N} -> effective {len(keep)/12.0:.1f} fps")
# --- 4. delta sparsity between consecutive UNIQUE frames ---
changed_pct = []
for j in range(1, len(keep)):
a, b = idx[keep[j-1]], idx[keep[j]]
changed_pct.append(100.0*np.count_nonzero(a != b)/a.size)
print(f"pixels changed : mean {np.mean(changed_pct):.1f}% median {np.median(changed_pct):.1f}% p90 {np.percentile(changed_pct,90):.1f}% max {np.max(changed_pct):.1f}%")
# --- 5. estimate compressed size: row-span delta + RLE within span ---
def encode_size(a, b, gap=4):
total = 0
H, W = a.shape
for y in range(H):
ra, rb = a[y], b[y]
diff = np.nonzero(ra != rb)[0]
if len(diff) == 0: continue
# merge runs separated by < gap
spans = []; s = diff[0]; p = diff[0]
for x in diff[1:]:
if x - p > gap: spans.append((s,p)); s = x
p = x
spans.append((s,p))
total += 2 # row header: y + span count
for (s0,e0) in spans:
seg = rb[s0:e0+1]
total += 2 # x start + length
# RLE within segment
i2 = 0; cost = 0
while i2 < len(seg):
run = 1
while i2+run < len(seg) and seg[i2+run] == seg[i2] and run < 127: run += 1
cost += 2 if run >= 3 else run
i2 += run
total += cost
return total
sizes = [encode_size(idx[keep[j-1]], idx[keep[j]]) for j in range(1, len(keep))]
print(f"\n--- codec estimate (row-span delta + RLE) ---")
print(f"delta frame bytes: mean {np.mean(sizes):.0f} median {np.median(sizes):.0f} p90 {np.percentile(sizes,90):.0f} max {np.max(sizes):.0f}")
raw = 256*192
print(f"vs raw {raw} B/frame -> mean ratio {raw/np.mean(sizes):.1f}:1")
fps_eff = len(keep)/12.0
byterate = np.mean(sizes)*fps_eff
print(f"\nstream rate : {byterate/1024:.1f} KB/s")
print(f"22 min extrapol. : {byterate*22*60/1048576:.0f} MB video")
# cycle cost: ~1 word write per changed pixel, movem amortized
mean_changed_px = np.mean(changed_pct)/100*raw
print(f"mean changed px : {mean_changed_px:.0f} -> blit ~{mean_changed_px*6.5/1000:.0f}k cycles (budget 833k)")