Snapshot: fog implementation + fidelity tooling baseline (pre bilinear-clamp fix)

Per-vertex GS fog end-to-end (gs_stub emit incl. persp_emit5, gs_prim_list_feeder
XYZ2->XYZF2 on PRIM.FGE, gs_make_sh3_scheduler_fixture.py F/FGE packing), new fog
TBs, fidelity attribution tooling. Functional baseline before removing the dead
bilinear lerp8 clamps (Codex: 161-node comb loop -> -0.042ns setup fail).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-20 19:56:46 -04:00
parent ec82764bef
commit ba74bbd5aa
476 changed files with 696247 additions and 130119 deletions
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""retroDE_ps2 — Ch356: composition == isolated RTL bit-for-bit check.
Renders each epoch ALONE (tb +ONLY=<k> +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 <fb_ALL.hex> <fb_0.hex> <fb_1.hex> ...
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 <fb_ALL.hex> <fb_0.hex> <fb_1.hex> ...")
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))