Files
retroDE_ps2/tools/diagnose_zsched_frag_z.py
thejayman77 ba74bbd5aa 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>
2026-07-20 19:56:46 -04:00

206 lines
7.7 KiB
Python

#!/usr/bin/env python3
"""Compare captured RTL Z fragments against the fixture's software Z planes.
This is intentionally per-fragment, not per-final-frame. It reconstructs the
same clipped triangles used by the ZSCHED feeder fixture, then asks whether each
captured RTL fragment's Z is close to the expected screen-linear interpolated Z
at that pixel.
Usage:
tools/diagnose_zsched_frag_z.py [sim/traces/rtl/zsched_frags.txt]
"""
import os
import sys
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")
sys.path.insert(0, HERE)
import gs_make_sh3_multidraw_fixture as MD
def load_epochs(tag="zsched"):
path = os.path.join(DATA, f"sh3_{tag}_epochs.txt")
out = []
with open(path) as f:
for ln in f:
s = ln.strip()
if not s or s.startswith("#") or s.startswith("META"):
continue
p = s.split()
out.append(int(p[1]))
return out
def find_dump():
import glob
c = glob.glob(os.path.join(ROOT, "captures", "gs", "silenthill3", "*224139*.gs.zst"))
if not c:
raise SystemExit("[fragz] no SH3 224139 dump found")
return c[0]
def edge(ax, ay, bx, by, px, py):
return (px - ax) * (by - ay) - (py - ay) * (bx - ax)
def clip_rect_z(tri, w, h):
def lerp(p1, p2, al):
return {k: (p1[k] + al * (p2[k] - p1[k])) for k in ("x", "y", "z", "s", "t", "q")}
def clip_edge(poly, inside, isect):
out = []
n = len(poly)
for i in range(n):
a = poly[i]
b = poly[(i + 1) % n]
ina = inside(a)
inb = inside(b)
if ina:
out.append(a)
if ina != inb:
out.append(isect(a, b))
return out
poly = [dict(x=v["x"], y=v["y"], z=v.get("z", 0.0), s=v["s"], t=v["t"], q=v["q"]) for v in tri]
poly = clip_edge(poly, lambda p: p["x"] >= 0.0, lambda a, b: lerp(a, b, (0.0 - a["x"]) / (b["x"] - a["x"])))
if not poly:
return []
poly = clip_edge(poly, lambda p: p["x"] <= w, lambda a, b: lerp(a, b, (w - a["x"]) / (b["x"] - a["x"])))
if not poly:
return []
poly = clip_edge(poly, lambda p: p["y"] >= 0.0, lambda a, b: lerp(a, b, (0.0 - a["y"]) / (b["y"] - a["y"])))
if not poly:
return []
poly = clip_edge(poly, lambda p: p["y"] <= h, lambda a, b: lerp(a, b, (h - a["y"]) / (b["y"] - a["y"])))
if len(poly) < 3:
return []
return [(poly[0], poly[k], poly[k + 1]) for k in range(1, len(poly) - 1)]
def tri_z_at(tri, x, y):
v0, v1, v2 = tri
ar = edge(v0["x"], v0["y"], v1["x"], v1["y"], v2["x"], v2["y"])
if abs(ar) < 1e-9:
return None
inv = 1.0 / ar
w0 = edge(v1["x"], v1["y"], v2["x"], v2["y"], x, y) * inv
w1 = edge(v2["x"], v2["y"], v0["x"], v0["y"], x, y) * inv
w2 = 1.0 - w0 - w1
if w0 < -0.001 or w1 < -0.001 or w2 < -0.001:
return None
return w0 * v0["z"] + w1 * v1["z"] + w2 * v2["z"]
def build_epoch_tris(draw_idxs, fbw, fbh):
got, _ = MD.load_draws(find_dump(), draw_idxs)
eps = [got[i] for i in draw_idxs]
ox = int(min(min(v["x"] for v in e["verts"]) for e in eps))
oy = int(min(min(v["y"] for v in e["verts"]) for e in eps))
out = []
for e in eps:
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"]]
raw = [(fv[i - 2], fv[i - 1], fv[i]) for i in range(2, len(fv))]
tris = []
for tri in raw:
tris.extend(clip_rect_z(tri, fbw, fbh))
out.append(tris)
return out
def best_expected(tris, x, y, sample_center):
sx = x + 0.5 if sample_center else float(x)
sy = y + 0.5 if sample_center else float(y)
vals = []
for tri in tris:
z = tri_z_at(tri, sx, sy)
if z is not None:
vals.append(z)
return vals
def main(argv):
frags = argv[1] if len(argv) > 1 else os.path.join(ROOT, "sim", "traces", "rtl", "zsched_frags.txt")
draw_idxs = load_epochs("zsched")
fbw, fbh = 256, 210
tris_by_ep = build_epoch_tris(draw_idxs, fbw, fbh)
stats = {
"n": 0, "nocov_center": 0, "nocov_corner": 0,
"center_le0": 0, "center_le1": 0, "center_le16": 0, "center_le256": 0,
"corner_le0": 0, "corner_le1": 0, "corner_le16": 0, "corner_le256": 0,
}
abs_sum_center = 0.0
abs_sum_corner = 0.0
ratios = []
examples = []
with open(frags) as f:
for ln in f:
p = ln.split()
if len(p) < 5:
continue
ep, x, y, rz = int(p[0]), int(p[1]), int(p[2]), int(p[3])
stats["n"] += 1
cvals = best_expected(tris_by_ep[ep], x, y, True)
kvals = best_expected(tris_by_ep[ep], x, y, False)
if not cvals:
stats["nocov_center"] += 1
cdiff = None
else:
cz = min(cvals, key=lambda z: abs(rz - z))
cdiff = abs(rz - cz)
abs_sum_center += cdiff
if cz:
ratios.append(rz / cz)
if cdiff <= 0.5:
stats["center_le0"] += 1
if cdiff <= 1:
stats["center_le1"] += 1
if cdiff <= 16:
stats["center_le16"] += 1
if cdiff <= 256:
stats["center_le256"] += 1
if not kvals:
stats["nocov_corner"] += 1
kdiff = None
else:
kz = min(kvals, key=lambda z: abs(rz - z))
kdiff = abs(rz - kz)
abs_sum_corner += kdiff
if kdiff <= 0.5:
stats["corner_le0"] += 1
if kdiff <= 1:
stats["corner_le1"] += 1
if kdiff <= 16:
stats["corner_le16"] += 1
if kdiff <= 256:
stats["corner_le256"] += 1
if len(examples) < 16 and ((cdiff is None or cdiff > 256) and (kdiff is None or kdiff > 256)):
examples.append((ep, x, y, rz, cdiff, kdiff, cvals[:3], kvals[:3]))
n = stats["n"]
def pct(k):
return 100.0 * stats[k] / n if n else 0.0
print(f"[fragz] fragments={n} draw_idxs={draw_idxs}")
print(f"[fragz] center coverage misses={stats['nocov_center']} corner coverage misses={stats['nocov_corner']}")
print(f"[fragz] center absdiff: <=0.5 {stats['center_le0']} ({pct('center_le0'):.2f}%) <=1 {stats['center_le1']} ({pct('center_le1'):.2f}%) <=16 {stats['center_le16']} ({pct('center_le16'):.2f}%) <=256 {stats['center_le256']} ({pct('center_le256'):.2f}%)")
print(f"[fragz] corner absdiff: <=0.5 {stats['corner_le0']} ({pct('corner_le0'):.2f}%) <=1 {stats['corner_le1']} ({pct('corner_le1'):.2f}%) <=16 {stats['corner_le16']} ({pct('corner_le16'):.2f}%) <=256 {stats['corner_le256']} ({pct('corner_le256'):.2f}%)")
if n - stats["nocov_center"] > 0:
print(f"[fragz] mean center absdiff={abs_sum_center / (n - stats['nocov_center']):.2f}")
if n - stats["nocov_corner"] > 0:
print(f"[fragz] mean corner absdiff={abs_sum_corner / (n - stats['nocov_corner']):.2f}")
if ratios:
ratios.sort()
mid = ratios[len(ratios) // 2]
print(f"[fragz] raw/expected center ratio: p10={ratios[len(ratios)//10]:.4f} median={mid:.4f} p90={ratios[(len(ratios)*9)//10]:.4f}")
for ep, x, y, rz, cdiff, kdiff, cvals, kvals in examples:
print(f"[fragz] bad ep{ep} ({x},{y}) rtl_z={rz} center_diff={cdiff} corner_diff={kdiff} center_z={[round(v,1) for v in cvals]} corner_z={[round(v,1) for v in kvals]}")
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv))