#!/usr/bin/env python3 """retroDE_ps2 — Ch356: composition == isolated RTL bit-for-bit check. Renders each epoch ALONE (tb +ONLY= +FBDUMP) over a precleared-black FB, then composites the N isolation dumps in paint order (epoch 0, 1, ..., N-1; DECAL/overwrite) and asserts the result equals the joint ALL-mode render BIT-FOR-BIT. This is Codex's Ch356 accumulation-correctness proof: the scheduler drawing the epochs together must produce exactly the same framebuffer as compositing the individually-rendered epochs (no cache bleed, no stale pixels, correct rebind). Composition operator (matches the RTL: precleared black bg, each epoch overwrites where it draws): compose[px] = the LAST epoch (highest k) that DREW px (isolation dump non-zero); else black. Note: an epoch is DECAL/opaque, so its written value replaces whatever is underneath. A genuine opaque-BLACK texel (dump==0 where the epoch drew) is indistinguishable from unwritten — but the joint ALL render is likewise black there (black-over-anything = black in this content), so nonzero-wins composition still matches ALL bit-for-bit. Do NOT use reference coverage to pick the owner: the RTL edge coverage differs slightly from the float reference, and a reference-covered-but-RTL-unwritten pixel must fall through to the epoch that ACTUALLY drew it. Usage: compose_sched.py ... Each *.hex file = W*H lines of 8-hex-digit PSMCT32 words (row-major), as emitted by +FBDUMP. """ import sys, os def load(fn): out=[] with open(fn) as f: for ln in f: s=ln.strip() if not s or s.startswith("//"): continue out.append(int(s,16)&0xFFFFFFFF) return out def main(argv): a=argv[1:] W=384 if "--width" in a: i=a.index("--width"); W=int(a[i+1]); del a[i:i+2] if len(a)<3: sys.exit("usage: compose_sched.py ...") all_fb=load(a[0]); eps=[load(x) for x in a[1:]]; N=len(eps) npx=len(all_fb) for k,e in enumerate(eps): if len(e)!=npx: sys.exit(f"[compose] epoch {k} dump {len(e)} px != ALL {npx}") comp=[0]*npx for px in range(npx): for k in range(N-1,-1,-1): if eps[k][px]!=0: comp[px]=eps[k][px]; break # last epoch that actually DREW (DECAL nonzero-wins) mism=[px for px in range(npx) if comp[px]!=all_fb[px]] black_all=sum(1 for px in mism if all_fb[px]==0) black_cmp=sum(1 for px in mism if comp[px]==0) print(f"[compose] {N} epochs, {npx} px. composition vs ALL: {npx-len(mism)} exact, {len(mism)} mismatch " f"({100.0*(npx-len(mism))/npx:.4f}% exact)") if mism: print(f"[compose] of the {len(mism)} mismatches: ALL==black:{black_all} compose==black:{black_cmp}") for px in mism[:12]: print(f"[compose] px {px} (x={px%W},y={px//W}): compose={comp[px]:08x} ALL={all_fb[px]:08x}") if not mism: print("[compose] PASS: joint ALL render == composited isolation dumps BIT-FOR-BIT (accumulation exact)") return 0 print("[compose] FAIL: composition differs from ALL") return 1 if __name__=="__main__": raise SystemExit(main(sys.argv))