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
This commit is contained in:
reala-misaki
2026-08-23 11:23:49 -07:00
commit 65112b9305
19 changed files with 842 additions and 0 deletions
+92
View File
@@ -0,0 +1,92 @@
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)")
+61
View File
@@ -0,0 +1,61 @@
import numpy as np, glob
from PIL import Image
files = sorted(glob.glob("an2/f*.png"))
imgs = [Image.open(f) for f in files]
print("PIL mode:", imgs[0].mode)
idx = [np.asarray(im.convert("P") if im.mode!="P" else im, dtype=np.uint8) for im in imgs]
N = len(idx); H,W = idx[0].shape; raw = H*W
print(f"frames={N} size={H}x{W} raw={raw} B/frame")
# --- per-pair change stats on the QUANTIZED stream ---
pct = np.array([100.0*np.count_nonzero(idx[i]!=idx[i-1])/raw for i in range(1,N)])
print(f"\n--- consecutive change % (24fps, quantized) ---")
print(f"mean {pct.mean():.1f} median {np.median(pct):.1f} p10 {np.percentile(pct,10):.1f} p90 {np.percentile(pct,90):.1f} max {pct.max():.1f}")
print(f"pairs under 2% changed: {np.count_nonzero(pct<2.0)}/{len(pct)} ({100*np.count_nonzero(pct<2.0)/len(pct):.0f}%)")
# --- threshold dedupe (twos detection) ---
THRESH = 2.0
keep=[0]
for i in range(1,N):
if 100.0*np.count_nonzero(idx[i]!=idx[keep[-1]])/raw >= THRESH:
keep.append(i)
print(f"\n--- dedupe @ {THRESH}% ---")
print(f"unique frames: {len(keep)}/{N} -> effective {len(keep)/12.0:.1f} fps")
cp = np.array([100.0*np.count_nonzero(idx[keep[j]]!=idx[keep[j-1]])/raw for j in range(1,len(keep))])
print(f"unique-pair change %: mean {cp.mean():.1f} median {np.median(cp):.1f} p90 {np.percentile(cp,90):.1f} max {cp.max():.1f}")
def encode_size(a,b,gap=4):
total=0
for y in range(a.shape[0]):
ra,rb=a[y],b[y]
d=np.nonzero(ra!=rb)[0]
if len(d)==0: continue
spans=[]; s=d[0]; p=d[0]
for x in d[1:]:
if x-p>gap: spans.append((s,p)); s=x
p=x
spans.append((s,p))
total+=2
for s0,e0 in spans:
seg=rb[s0:e0+1]; total+=2
i2=0
while i2<len(seg):
r=1
while i2+r<len(seg) and seg[i2+r]==seg[i2] and r<127: r+=1
total += 2 if r>=3 else r
i2+=r
return total
sz=np.array([encode_size(idx[keep[j-1]],idx[keep[j]]) for j in range(1,len(keep))])
fps_eff=len(keep)/12.0
rate=sz.mean()*fps_eff
print(f"\n--- codec estimate ---")
print(f"delta bytes: mean {sz.mean():.0f} median {np.median(sz):.0f} p90 {np.percentile(sz,90):.0f} max {sz.max():.0f}")
print(f"ratio vs raw: {raw/sz.mean():.1f}:1")
print(f"stream rate : {rate/1024:.1f} KB/s")
print(f"22min video : {rate*22*60/1048576:.0f} MB")
px=cp.mean()/100*raw
print(f"blit cost : ~{px*6.5/1000:.0f}k cycles/frame (budget 833k)")
print(f"p90 blit : ~{np.percentile(cp,90)/100*raw*6.5/1000:.0f}k cycles")
+11
View File
@@ -0,0 +1,11 @@
import numpy as np, glob, sys
from PIL import Image
d=sys.argv[1]
f=sorted(glob.glob(f"{d}/f*.png"))
a=[np.asarray(Image.open(x).convert("RGB"),dtype=np.int16) for x in f]
# noise-tolerant per-pair change: % of pixels differing by more than 8 levels
p=np.array([100.0*np.count_nonzero(np.abs(a[i]-a[i-1]).max(axis=2)>8)/(a[0].shape[0]*a[0].shape[1]) for i in range(1,len(a))])
print(f" pairs={len(p)} mean={p.mean():.2f}% median={np.median(p):.2f}% p90={np.percentile(p,90):.2f}% max={p.max():.2f}%")
ev,od=p[0::2],p[1::2]
print(f" even-idx pairs mean={ev.mean():.2f}% odd-idx pairs mean={od.mean():.2f}% ratio={max(ev.mean(),od.mean())/max(min(ev.mean(),od.mean()),1e-9):.1f}x")
print(f" first 16 pairs: {np.round(p[:16],2)}")
+54
View File
@@ -0,0 +1,54 @@
import numpy as np, glob, os
from PIL import Image
def enc(a,b,gap=4):
t=0
for y in range(a.shape[0]):
ra,rb=a[y],b[y]; d=np.nonzero(ra!=rb)[0]
if not len(d): continue
sp=[]; s=d[0]; p=d[0]
for x in d[1:]:
if x-p>gap: sp.append((s,p)); s=x
p=x
sp.append((s,p)); t+=2
for s0,e0 in sp:
seg=rb[s0:e0+1]; t+=2; i=0
while i<len(seg):
r=1
while i+r<len(seg) and seg[i+r]==seg[i] and r<127: r+=1
t += 2 if r>=3 else r; i+=r
return t
RAW=256*192; rows=[]
for d in sorted(glob.glob("samp/*")):
s=os.path.basename(d)
fs=sorted(glob.glob(f"{d}/f*.png"))
if len(fs)<10: continue
rgb=[np.asarray(Image.open(x).convert("RGB")) for x in fs]
# one shared palette per scene, no dither (cel art is flat)
samp=np.concatenate([r.reshape(-1,3) for r in rgb[::3]])
ref=Image.fromarray(samp.reshape(-1,1,3)).quantize(colors=256,method=Image.MEDIANCUT,dither=Image.NONE)
idx=[np.asarray(Image.fromarray(r).quantize(palette=ref,dither=Image.NONE),dtype=np.uint8) for r in rgb]
N=len(idx)
# dedupe @0.3%
keep=[0]
for i in range(1,N):
if 100.0*np.count_nonzero(idx[i]!=idx[keep[-1]])/RAW >= 0.3: keep.append(i)
fps=len(keep)/5.0
cp=np.array([100.0*np.count_nonzero(idx[keep[j]]!=idx[keep[j-1]])/RAW for j in range(1,len(keep))])
sz=np.array([enc(idx[keep[j-1]],idx[keep[j]]) for j in range(1,len(keep))])
rows.append((s,N,len(keep),fps,cp.mean(),np.percentile(cp,90),sz.mean(),np.percentile(sz,90),sz.mean()*fps))
print(f"{s}: src={N} uniq={len(keep)} fps={fps:4.1f} chg={cp.mean():5.1f}%/p90 {np.percentile(cp,90):5.1f}% delta={sz.mean():6.0f}B/p90 {np.percentile(sz,90):6.0f} rate={sz.mean()*fps/1024:6.1f}KB/s")
r=np.array([x[3:] for x in rows],dtype=float)
print("\n=== AGGREGATE (8 scenes) ===")
print(f"effective fps after dedupe : {r[:,0].mean():.1f}")
print(f"pixels changed / frame : mean {r[:,1].mean():.1f}% p90 {r[:,2].mean():.1f}%")
print(f"delta frame size : mean {r[:,3].mean():.0f} B p90 {r[:,4].mean():.0f} B")
print(f"compression vs raw : {RAW/r[:,3].mean():.1f}:1")
rate=r[:,5].mean()
print(f"stream rate : {rate/1024:.0f} KB/s")
print(f"22 min of video : {rate*22*60/1048576:.0f} MB")
px=r[:,1].mean()/100*RAW
print(f"blit cost mean : ~{px*6.5/1000:.0f}k cycles (budget 833k @12fps)")
print(f"blit cost p90 : ~{r[:,2].mean()/100*RAW*6.5/1000:.0f}k cycles")