#!/usr/bin/env python3
"""retroDE_ps2 — Ch347 authentic asset extractor: real Silent Hill 3 PSMT8 texture + real CLUT.

Codex target A: "authentic SH3 palettized texture + real palette rendered through chosen sprite geometry"
— NOT a faithful SH3 draw. We pick a CLEAN candidate whose texture is a NATIVE PSMT8 upload (linear payload,
no GS-memory model needed) and whose CLUT is a clean 256-entry PSMCT32 upload, both validated by rendering to
a coherent SH3 surface (see captures/gs/silenthill3/extracted/*.png).

Default candidate (dump 224139): PSMT8 tex tbp=13824 (128x128, native upload, 99 distinct indices) + CLUT
cbp=14282 (16x16=256 PSMCT32, read LINEARLY — csm=0 but linear order renders coherent; CSM1 bit-swap scrambles).

Primary render = DECAL (opaque): the authentic CLUT RGB is kept BYTE-FOR-BYTE; alpha (SH3's real ~0x04) is
IGNORED by the render mode, never rewritten (Codex guardrail). Emits the index texture, the CLUT, and a
software DECAL reference FB for the pre-fit pixel-diff.

Outputs (LOCAL, dump-derived -> gitignored) into sim/data/top_psmct32_raster_demo/:
  sh3_tex_idx.mem   128x128 PSMT8 indices, 4 per 32-bit word (low->high byte), $readmemh
  sh3_clut.mem      256 PSMCT32 ABGR CLUT entries (one per line) — authentic RGB, authentic alpha
  sh3_ref.mem       software DECAL reference FB 128x128 PSMCT32 (CLUT[idx] RGB, opaque) for pixel-diff
  sh3_authentic.png visual confirmation (DECAL)
"""
import sys, os, glob, struct
HERE=os.path.dirname(os.path.abspath(__file__)); ROOT=os.path.normpath(os.path.join(HERE,".."))
sys.path.insert(0, os.path.join(ROOT,"tools")); import gs_texture_residency as R
DATA=os.path.join(ROOT,"sim","data","top_psmct32_raster_demo")
EXTR=os.path.join(ROOT,"captures","gs","silenthill3","extracted")

def main(argv):
    dump = argv[1] if len(argv)>1 else None
    if not dump:
        cands = glob.glob(os.path.join(ROOT,"captures","gs","silenthill3","*224139*.gs.zst"))
        if not cands: sys.exit("no SH3 dump found; pass the .gs.zst path")
        dump = cands[0]
    TBP = int(argv[2]) if len(argv)>2 else 13824     # native PSMT8 texture base
    CBP = int(argv[3]) if len(argv)>3 else 14282     # CLUT base
    W = H = 128

    d,h,events,uploads,runs,vram = R.collect(dump,0)
    # texture: latest NATIVE PSMT8 upload to TBP (linear row-major payload)
    tu = [u for u in uploads if u["dbp"]==TBP and u["dpsm"]==0x13]
    if not tu: sys.exit(f"no native PSMT8 (dpsm=0x13) upload to tbp={TBP} — not a clean candidate")
    tup = max(tu, key=lambda u:u["idx"]); tex = d[tup["blob_range"][0]:tup["blob_range"][1]]
    if len(tex) < W*H: sys.exit(f"texture payload {len(tex)}B < {W*H}")
    idx = [tex[y*W+x] for y in range(H) for x in range(W)]
    # CLUT: latest PSMCT32 256-entry upload to CBP, read LINEARLY
    cu = [u for u in uploads if u["dbp"]==CBP and u["dpsm"]==0x00]
    if not cu: sys.exit(f"no PSMCT32 CLUT upload to cbp={CBP}")
    cup = max(cu, key=lambda u:u["idx"]); cb = d[cup["blob_range"][0]:cup["blob_range"][1]]
    pal = [int.from_bytes(cb[i*4:i*4+4],"little") for i in range(256)]

    # software DECAL reference = EXACTLY what the hardware DECAL emit stores: the texel = CLUT[idx], byte-for-
    # byte authentic (RGB AND the real ~0x04 alpha — nothing rewritten). The pre-fit TB pixel-diffs RGB (the
    # authentic-art claim); the A byte is preserved here but not the focus. The PNG forces opaque for display.
    ref = [pal[i] for i in idx]

    os.makedirs(DATA, exist_ok=True); os.makedirs(EXTR, exist_ok=True)
    # PSMT8 indices packed 4/word (byte0=lowest x), $readmemh as 32-bit
    with open(os.path.join(DATA,"sh3_tex_idx.mem"),"w") as f:
        f.write(f"// Ch347 LOCAL authentic SH3 PSMT8 indices {W}x{H} (native upload tbp={TBP}). gitignored.\n")
        for w in range(0, W*H, 4):
            word = idx[w] | (idx[w+1]<<8) | (idx[w+2]<<16) | (idx[w+3]<<24)
            f.write(f"{word:08x}\n")
    with open(os.path.join(DATA,"sh3_clut.mem"),"w") as f:
        f.write(f"// Ch347 LOCAL authentic SH3 CLUT 256xPSMCT32 (cbp={CBP}, linear). RGB+alpha authentic. gitignored.\n")
        for p in pal: f.write(f"{p & 0xFFFFFFFF:08x}\n")
    with open(os.path.join(DATA,"sh3_ref.mem"),"w") as f:
        f.write(f"// Ch347 LOCAL SW DECAL reference FB {W}x{H} (CLUT[idx] RGB, opaque display). gitignored.\n")
        for p in ref: f.write(f"{p & 0xFFFFFFFF:08x}\n")
    try:
        from PIL import Image
        img=Image.new('RGBA',(W,H)); img.putdata([(p&0xFF,(p>>8)&0xFF,(p>>16)&0xFF,0xFF) for p in ref])
        img.save(os.path.join(EXTR,"sh3_authentic.png"))
    except Exception as e:
        print("(PIL unavailable, skipped PNG:", e, ")")

    # --- Ch347 (ii): a DETERMINISTIC 64x64 authentic CROP (Codex first-silicon target). ---
    # The crop is a DIRECT SUBSET of the extracted indices (no resample/transform); the CLUT is unchanged.
    # Deterministic rule: the 64x64 window (stride 8) with the MOST distinct indices, tie-break smallest (cy,cx)
    # — guarantees real content + a reproducible, reported origin.
    CW = CH = 64
    best = (-1, 0, 0)
    for cy in range(0, H-CH+1, 8):
        for cx in range(0, W-CW+1, 8):
            s = set(idx[(cy+ly)*W + (cx+lx)] for ly in range(CH) for lx in range(CW))
            if len(s) > best[0]: best = (len(s), cx, cy)
    _, CX, CY = best
    cidx = [idx[(CY+ly)*W + (CX+lx)] for ly in range(CH) for lx in range(CW)]
    cref = [pal[i] for i in cidx]
    with open(os.path.join(DATA,"sh3_tex_idx64.mem"),"w") as f:
        f.write(f"// Ch347 LOCAL authentic SH3 PSMT8 64x64 CROP @({CX},{CY}) of tbp={TBP} 128x128 (direct subset). gitignored.\n")
        for w in range(0, CW*CH, 4):
            f.write(f"{cidx[w] | (cidx[w+1]<<8) | (cidx[w+2]<<16) | (cidx[w+3]<<24):08x}\n")
    with open(os.path.join(DATA,"sh3_ref64.mem"),"w") as f:
        f.write(f"// Ch347 LOCAL SW DECAL reference 64x64 CROP @({CX},{CY}) (CLUT[idx] RGB). gitignored.\n")
        for p in cref: f.write(f"{p & 0xFFFFFFFF:08x}\n")
    try:
        from PIL import Image
        ci=Image.new('RGBA',(CW,CH)); ci.putdata([(p&0xFF,(p>>8)&0xFF,(p>>16)&0xFF,0xFF) for p in cref])
        ci.save(os.path.join(EXTR,"sh3_authentic64.png"))
    except Exception: pass

    print(f"[Ch347] SH3 authentic asset: tex tbp={TBP} {W}x{H} ({len(set(idx))} distinct indices), CLUT cbp={CBP} "
          f"({len(set(pal))} colors). DECAL reference emitted.")
    print(f"[Ch347] 64x64 CROP @({CX},{CY}) [deterministic max-distinct window]: {len(set(cidx))} distinct indices, "
          f"{len(set(cref))} colors -> sh3_tex_idx64.mem, sh3_ref64.mem")
    print(f"[Ch347] -> {DATA}/sh3_tex_idx*.mem, sh3_clut.mem, sh3_ref*.mem (+ {EXTR}/sh3_authentic*.png) — all LOCAL")

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