#!/usr/bin/env python3
"""retroDE_ps2 — Ch357: verify the RASTER's emitted per-fragment Z (persp_z5) end-to-end.

Replays the captured raster fragment stream {epoch, x, y, persp_z, color} (from tb_top_psmct32_sh3_zsched -> zsched_frags.txt)
through the SAME clamp16 persistent-Z model the RTL ROP (gs_lpddr_zc_emit) implements, and compares the resulting per-pixel
OWNER + reject-vs-paint-order to the software oracle (gs_sh3_z_oracle emits sh3_zsched_zowner.mem / _reject.mem).

If persp_z5 is the authentic screen-linear Z (right value, right pixel), the replay reproduces the oracle: covered=21692,
reject=3971 for the primary [8634,12757,145742]. A wrong Z value or a pipeline-stage misalignment shows up as a reject/owner
mismatch — so this is the decisive check on the new gs_stub RTL, independent of the (separately unit-proven) ROP.

Usage: verify_persp_z.py [frags.txt] [--tag zsched] [--fbpxw 256]
"""
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")

def clamp16(z): z=int(z); return 0xFFFF if z>0xFFFF else (0 if z<0 else z)

def main(argv):
    a=argv[1:]
    frags = a[0] if a and not a[0].startswith("--") else os.path.join(ROOT,"sim","traces","rtl","zsched_frags.txt")
    tag   = a[a.index("--tag")+1] if "--tag" in a else "zsched"
    if not os.path.exists(frags): sys.exit(f"[perspZ] no fragment capture at {frags} (run make tb_top_psmct32_sh3_zsched)")

    # load the oracle owner map: [31]cov [30]reject [27:24]z_owner(+1) [23:8]zq16 [3:0]paint_owner(+1)
    owner_fn=os.path.join(DATA,f"sh3_{tag}_zowner.mem")
    if not os.path.exists(owner_fn): sys.exit(f"[perspZ] no oracle map {owner_fn} (run gs_sh3_z_oracle.py --draw-list ... --emit)")
    orc=[int(l,16)&0xFFFFFFFF for l in open(owner_fn) if l.strip() and not l.startswith("//")]
    NPX=len(orc)
    # infer FB width: prefer param, else from the oracle geometry note isn't stored — require --fbpxw or default 256
    FBPXW = int(a[a.index("--fbpxw")+1]) if "--fbpxw" in a else 256

    # replay the captured fragments (in emit order == dump/paint order across epochs) through persistent clamp16 GEQUAL
    zbuf=[-1]*NPX; z_owner=[-1]*NPX; paint_owner=[-1]*NPX; cov=[0]*NPX
    nfr=0; oob=0
    for ln in open(frags):
        p=ln.split()
        if len(p)<5: continue
        ep=int(p[0]); x=int(p[1]); y=int(p[2]); z=int(p[3])
        o=y*FBPXW+x
        if o<0 or o>=NPX: oob+=1; continue
        nfr+=1; cov[o]=1
        zq=clamp16(z)
        paint_owner[o]=ep
        if zq>=zbuf[o]: zbuf[o]=zq; z_owner[o]=ep     # GEQUAL, persistent across epochs, ZMSK=0

    # compare to the oracle
    covered=sum(cov); reject=sum(1 for o in range(NPX) if cov[o] and z_owner[o]!=paint_owner[o])
    # oracle covered/reject
    orc_cov=sum(1 for w in orc if w>>31); orc_rej=sum(1 for w in orc if (w>>30)&1)
    # per-pixel owner agreement (where both cover)
    owner_mismatch=0; both=0
    for o in range(NPX):
        if cov[o] and (orc[o]>>31):
            both+=1
            orc_owner=((orc[o]>>24)&0xF)-1
            if z_owner[o]!=orc_owner: owner_mismatch+=1

    print(f"[perspZ] replayed {nfr} captured fragments (oob={oob}); FBPXW={FBPXW}, NPX={NPX}")
    print(f"[perspZ] REPLAY : covered={covered}  reject-vs-paint={reject}")
    print(f"[perspZ] ORACLE : covered={orc_cov}  reject={orc_rej}")
    agree = 100.0*(both-owner_mismatch)/max(both,1)
    rej_ratio = reject/max(orc_rej,1)
    print(f"[perspZ] per-pixel z-owner agreement (both-covered {both}): {agree:.2f}%  ({owner_mismatch} mismatch)")
    print(f"[perspZ] reject ratio replay/oracle = {rej_ratio:.3f}  (1.0 = persp_z5 authentic; ~0 or >>1 = broken/misaligned)")
    # tolerate RTL-vs-float edge/coverage + fixed-point-Z noise; catch a GROSS persp_z5 error (wrong stage/value).
    ok = (agree >= 95.0) and (0.80 <= rej_ratio <= 1.20)
    if ok:
        print(f"[perspZ] PASS — the raster's persp_z5 reproduces the clamp16 oracle owner/reject within edge tolerance. "
              f"persp_z5 is the authentic screen-linear Z, correctly aligned to the emit.")
        return 0
    print(f"[perspZ] FAIL — persp_z5 diverges from the oracle (wrong Z value or pipeline-stage misalignment). Numbers above.")
    return 1

if __name__=="__main__":
    raise SystemExit(main(sys.argv))
