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:
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env python3
|
||||
"""retroDE_ps2 — Ch357 (re-scoped): PSMZ16S-accurate persistent-Z software ORACLE at the 384x381 scheduler geometry.
|
||||
|
||||
Codex pre-RTL gate: build a PSMZ16S-accurate oracle (authentic per-vertex Z interpolation, quantization/storage, GEQUAL,
|
||||
ZTE, ZMSK), replace constant-Z flattening with authentic XYZ2 Z, and produce an expected reject/owner map quantifying how
|
||||
the Z-tested framebuffer must differ from paint order.
|
||||
|
||||
MODEL (empirically grounded — see gs_sh3_z_census.py):
|
||||
* geometry: the EXISTING Ch356 scheduler union FB, 384x381 (OX,OY union origin), draws translated into it.
|
||||
* Z is interpolated LINEARLY in screen space (GS Z is screen-linear, NOT perspective-corrected).
|
||||
* PSMZ16S quantization: Zq = (24-bit vertex Z) >> 8 -> the high 16 bits (the whole scene uses the full 24-bit range;
|
||||
>>8 maps it onto 16 bits coherently; & 0xFFFF would wrap a surface). The signed "S" bias is order-preserving, so the
|
||||
GEQUAL reject/owner outcome is invariant to it.
|
||||
* shared PERSISTENT 16-bit Z buffer across ALL 3 epochs (cleared once to 0 = farthest). Per fragment:
|
||||
ZTE=1: pass iff Zq >= zbuf[px] (GEQUAL, larger Z = nearer on PS2).
|
||||
on pass: write owner=epoch; ZMSK=0 -> zbuf[px]=Zq.
|
||||
* paint-order (what we ship today) = same coverage but ALWAYS overwrite (no Z test).
|
||||
|
||||
Outputs (to sim/data/top_psmct32_raster_demo/, LOCAL/gitignored):
|
||||
* sh3_zsched_zowner.mem — per pixel: [31]cov [30]rejected-vs-paint [26:24]z_owner_epoch [23:8]zq16 [ ... ]
|
||||
* sh3_zsched_reject.mem — per pixel: paint_owner vs z_owner diff mask (the far-late rejects)
|
||||
* a quantified report: covered px, pixels where Z rejects the paint-order winner, per-epoch owned counts (paint vs Z).
|
||||
|
||||
Usage: gs_sh3_z_oracle.py [dump.gs.zst] [--draw-list i0,i1,i2] [--emit]
|
||||
"""
|
||||
import sys, os
|
||||
HERE=os.path.dirname(os.path.abspath(__file__)); ROOT=os.path.normpath(os.path.join(HERE,".."))
|
||||
DATA=os.path.join(ROOT,"sim","data","top_psmct32_raster_demo")
|
||||
import gs_sh3_recon as RC
|
||||
import gs_make_sh3_multidraw_fixture as MD # load_draws (verts incl z) + edge
|
||||
|
||||
DEFAULT_DRAWS=[11671, 19562, 89761]
|
||||
|
||||
# PSMZ16S 24/32-bit -> 16-bit reduction. NOT yet authoritatively pinned (Codex precondition): candidate hardware models
|
||||
# give DIFFERENT reject maps, so the oracle is parameterized until ground-truth (PCSX2 dump replay) decides.
|
||||
# shift8 : Zq = Z >> 8 (high 16 of the scene's 24-bit range; coherent, no wrap)
|
||||
# mask16 : Zq = Z & 0xFFFF (low 16 — what PCSX2 WritePixel16 stores; WRAPS a 24-bit surface)
|
||||
# clamp16: Zq = min(Z, 0xFFFF) (near draws collapse to 0xFFFF)
|
||||
def zquant(z, model):
|
||||
z=int(z)
|
||||
if model=="mask16": return z & 0xFFFF
|
||||
if model=="clamp16": return 0xFFFF if z>0xFFFF else (0 if z<0 else z)
|
||||
q = z >> 8 # shift8 (default)
|
||||
return 0xFFFF if q>0xFFFF else (0 if q<0 else q)
|
||||
|
||||
def bbox(dr):
|
||||
xs=[v["x"] for v in dr["verts"]]; ys=[v["y"] for v in dr["verts"]]; return (min(xs),min(ys),max(xs),max(ys))
|
||||
|
||||
def main(argv):
|
||||
a=argv[1:]; dump=a[0] if a and not a[0].startswith("--") else None
|
||||
idxs=[int(x) for x in a[a.index("--draw-list")+1].split(",")] if "--draw-list" in a else list(DEFAULT_DRAWS)
|
||||
ZMODEL = a[a.index("--zmodel")+1] if "--zmodel" in a else "clamp16"
|
||||
# KNOWN VECTORS pinned against the PCSX2 SOFTWARE RASTER path (authoritative; NOT the bare memory write):
|
||||
# GSLocalMemory.cpp:199 PSMZ16S -> PSM_FMT_16
|
||||
# GSRendererSW.cpp:1439 z_max = 0xffffffff >> (fmt*8) = 0xFFFF
|
||||
# GSDrawScanline…:1129 source_z = CLAMP(interpolated_z, z_max) ("Clamp Z to ZPSM_FMT_MAX")
|
||||
# GSDrawScanline…:1157 dest_z = stored_z & 0xFFFF ; pass = source_z >= dest_z (GEQUAL)
|
||||
# => the model is CLAMP16 (min with 0xFFFF), NOT mask16. WritePixel16((u16)c) is only the final store, after the clamp.
|
||||
# The "S" in PSMZ16S = swizzled STORAGE layout, not signed depth (Codex) — address swizzle only, not the value reduction.
|
||||
for zin,exp in ((0x00FED407,0xFFFF),(0x00015534,0xFFFF),(0x0000B2C2,0xB2C2),(0xFFFFFFFF,0xFFFF),(0x00008000,0x8000),(0,0)):
|
||||
assert zquant(zin,"clamp16")==exp, f"clamp16 vector fail 0x{zin:x}->0x{zquant(zin,'clamp16'):x} exp 0x{exp:x}"
|
||||
print("[Zoracle] PSMZ16S known-vector self-test PASS (clamp16 == PCSX2 SW raster source-clamp to 0xFFFF)")
|
||||
if dump is None:
|
||||
import glob; c=glob.glob(os.path.join(ROOT,"captures","gs","silenthill3","*224139*.gs.zst"))
|
||||
if not c: sys.exit("no SH3 dump found; pass the .gs.zst path")
|
||||
dump=c[0]
|
||||
print(f"[Zoracle] dump={os.path.basename(dump)} epochs={idxs} (PSMZ16S zmodel={ZMODEL}, GEQUAL, persistent)")
|
||||
|
||||
got,_=MD.load_draws(dump, idxs)
|
||||
for i in idxs:
|
||||
if i not in got: sys.exit(f"[Zoracle] FAIL: idx{i} not found as a textured draw")
|
||||
eps=[got[i] for i in idxs]
|
||||
|
||||
# depth state gate: all real GEQUAL, ZMSK=0, shared ZBP (the census precondition)
|
||||
for e in eps:
|
||||
t=e["state"]["test"]; z=e["state"]["zbuf"]
|
||||
zte=(t>>16)&1; ztst=(t>>17)&3; zmsk=(z>>32)&1
|
||||
if not (zte==1 and ztst==2 and zmsk==0):
|
||||
sys.exit(f"[Zoracle] FAIL: idx{e['first_idx']} not real GEQUAL depth (zte={zte} ztst={ztst} zmsk={zmsk})")
|
||||
zbps={ (e['state']['zbuf']>>0)&0x1FF for e in eps }
|
||||
print(f"[Zoracle] all epochs GEQUAL/ZMSK=0; shared ZBP={zbps} (single Z buffer)")
|
||||
|
||||
# union geometry = the EXISTING Ch356 scheduler FB (384x381)
|
||||
OX=int(min(bbox(e)[0] for e in eps)); OY=int(min(bbox(e)[1] for e in eps))
|
||||
UX=max(bbox(e)[2] for e in eps); UY=max(bbox(e)[3] for e in eps)
|
||||
W=int(UX)-OX+1; H=int(UY)-OY+1; FBW=(W+63)//64; FBPXW=FBW*64
|
||||
print(f"[Zoracle] union origin=({OX},{OY}) -> FB {FBPXW}x{H} (FBW={FBW}) [existing Ch356 scheduler geometry]")
|
||||
NPX=FBPXW*H; edge=MD.edge
|
||||
|
||||
# per-epoch texture idx + palette (authentic), for color/owner readout
|
||||
TEX=512*512; epdata=[]
|
||||
for e in eps:
|
||||
t0=e["state"]["tex0"]; mem,*_=RC.build_localmem_to(dump, e["first_idx"])
|
||||
idx=mem.read_psmt8(t0['tbp'], t0['tbw'], 512, 512)
|
||||
pal=RC.read_clut32(mem, t0['cbp'], order="grid")
|
||||
epdata.append(dict(idx=idx, pal=pal))
|
||||
|
||||
def tris(e):
|
||||
fv=[dict(x=v["x"]-OX,y=v["y"]-OY,z=v["z"],s=v["s"],t=v["t"],q=v["q"]) for v in e["verts"]]
|
||||
return [(fv[i-2],fv[i-1],fv[i]) for i in range(2,len(fv))]
|
||||
|
||||
# buffers
|
||||
zbuf=[-1]*NPX # stored Zq (16-bit); -1 = uninitialised (cleared "farther than any" => first frag passes)
|
||||
z_owner=[-1]*NPX; z_zq=[0]*NPX # Z-tested owner epoch + its Zq
|
||||
paint_owner=[-1]*NPX # paint-order owner (last covering, no Z)
|
||||
cov=[0]*NPX
|
||||
|
||||
for k,e in enumerate(eps):
|
||||
for (v0,v1,v2) in tris(e):
|
||||
ar=edge(v0["x"],v0["y"],v1["x"],v1["y"],v2["x"],v2["y"])
|
||||
if abs(ar)<1e-9: continue
|
||||
inv=1.0/ar
|
||||
minx=max(0,int(min(v0["x"],v1["x"],v2["x"]))); maxx=min(FBPXW-1,int(max(v0["x"],v1["x"],v2["x"]))+1)
|
||||
miny=max(0,int(min(v0["y"],v1["y"],v2["y"]))); maxy=min(H-1,int(max(v0["y"],v1["y"],v2["y"]))+1)
|
||||
for py in range(miny,maxy+1):
|
||||
for px in range(minx,maxx+1):
|
||||
cx,cy=px+0.5,py+0.5
|
||||
w0=edge(v1["x"],v1["y"],v2["x"],v2["y"],cx,cy)*inv
|
||||
w1=edge(v2["x"],v2["y"],v0["x"],v0["y"],cx,cy)*inv
|
||||
w2=1.0-w0-w1
|
||||
if w0<-0.001 or w1<-0.001 or w2<-0.001: continue
|
||||
o=py*FBPXW+px; cov[o]=1
|
||||
zf=w0*v0["z"]+w1*v1["z"]+w2*v2["z"] # screen-linear Z interp (24-bit)
|
||||
zq=zquant(zf, ZMODEL) # PSMZ16S reduction (model-parameterized, see zquant)
|
||||
paint_owner[o]=k # paint-order always overwrites
|
||||
if zq >= zbuf[o]: # GEQUAL (>= handles uninit -1 too)
|
||||
zbuf[o]=zq; z_owner[o]=k; z_zq[o]=zq # ZTE pass + ZMSK=0 write
|
||||
|
||||
covered=sum(cov)
|
||||
# reject vs paint-order: covered pixels where the Z-tested owner != the paint-order owner (far-late fragment rejected)
|
||||
reject=[1 if (cov[o] and z_owner[o]!=paint_owner[o]) else 0 for o in range(NPX)]
|
||||
nreject=sum(reject)
|
||||
paint_ct=[0]*len(eps); z_ct=[0]*len(eps)
|
||||
for o in range(NPX):
|
||||
if cov[o]:
|
||||
if paint_owner[o]>=0: paint_ct[paint_owner[o]]+=1
|
||||
if z_owner[o]>=0: z_ct[z_owner[o]]+=1
|
||||
|
||||
print(f"\n[Zoracle] covered px={covered}")
|
||||
print(f"[Zoracle] per-epoch OWNED pixels (final, after all 3 epochs):")
|
||||
for k,e in enumerate(eps):
|
||||
print(f" epoch {k} idx{e['first_idx']:>6}: paint-order={paint_ct[k]:>7} Z-tested={z_ct[k]:>7} (delta {z_ct[k]-paint_ct[k]:+d})")
|
||||
print(f"[Zoracle] pixels where Z REJECTS the paint-order winner (final FB MUST differ here): {nreject} "
|
||||
f"({100.0*nreject/max(covered,1):.2f}% of covered)")
|
||||
if nreject==0:
|
||||
print("[Zoracle] (no difference — paint order would already match; Z not needed. Unexpected for this scene.)")
|
||||
else:
|
||||
print(f"[Zoracle] => the far-late draw is depth-rejected in the overlaps; paint-order output demonstrably differs.")
|
||||
|
||||
if "--emit" in a:
|
||||
def wmem(name, words, banner):
|
||||
with open(os.path.join(DATA,name),"w") as f:
|
||||
f.write(f"// {banner}\n")
|
||||
for x in words: f.write(f"{x&0xFFFFFFFF:08x}\n")
|
||||
# z-owner map: [31]cov [30]reject-vs-paint [27:24]z_owner(+1,0=none) [23:8]zq16 [3:0]paint_owner(+1)
|
||||
owner=[]
|
||||
for o in range(NPX):
|
||||
zo=(z_owner[o]+1)&0xF; po=(paint_owner[o]+1)&0xF
|
||||
owner.append((cov[o]<<31)|(reject[o]<<30)|(zo<<24)|((z_zq[o]&0xFFFF)<<8)|po)
|
||||
wmem("sh3_zsched_zowner.mem", owner, "Ch357 LOCAL persistent-Z oracle: cov|reject|z_owner|zq16|paint_owner. gitignored.")
|
||||
wmem("sh3_zsched_reject.mem", reject, "Ch357 LOCAL per-pixel reject-vs-paint mask (far-late Z rejects). gitignored.")
|
||||
print(f"[Zoracle] emitted sh3_zsched_zowner.mem + sh3_zsched_reject.mem -> {DATA}")
|
||||
return 0
|
||||
|
||||
if __name__=="__main__":
|
||||
raise SystemExit(main(sys.argv))
|
||||
Reference in New Issue
Block a user