#!/usr/bin/env python3
"""retroDE_ps2 — Ch347 authentic SH3 board fixture: bootlet GIF payload that uploads the real SH3 PSMT8
texture + real CLUT and draws it as a DECAL sprite. Built directly on the Ch296 PSMT8+CLUT board template
(bake.build_clut8_demo_payload), scaled to 128x128 + a 256-entry CLUT.

Mirrors gs_make_cube_fixture.py: imports bake.py helpers, reads the dump-derived assets emitted by
gs_extract_sh3_clut.py (sh3_tex_idx.mem + sh3_clut.mem), and writes the LOCAL/gitignored board fixtures:
  payload_sh3_clut.mem   GIF payload: BITBLT CLUT (256) + BITBLT PSMT8 tex (128x128) + TEX0(PSMT8,CLD=1,CSM2
                         linear) + one 128x128 DECAL SPRITE
  bios_sh3_clut.mem      one-shot bootlet, QWC = payload qword count, DISPLAY1 = 128x128

DECAL/opaque (TFX=1) so the authentic art is visible; the real CLUT alpha (~0x04) is kept BYTE-FOR-BYTE in
the palette, just ignored by the render mode (Codex guardrail). CLUT declared CSM2/linear (the order
validated against the authentic render in tools/gs_extract_sh3_clut.py).
"""
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")
sys.path.insert(0, DATA); import bake  # reuse giftag/aplusd/*_pack/bootlet helpers

# Ch347 (ii): the 64x64 authentic CROP — fits the PROVEN 64x64 bram CLUT config (no new 128 KiB/scanout
# integration). Full 128x128 is the follow-up rung (i). Swap sh3_tex_idx64->sh3_tex_idx + W/H/FBW for (i).
W = H = 64
FBW   = 1                 # 64 px / 64
TBP0  = 64                # texture base byte = 64*256 = 0x4000 (just past the 64x64 PSMCT32 FB = 16 KiB)
TBW   = 1                 # 64 texels/row stride
CBP   = 80                # CLUT base byte = 80*256 = 0x5000 (past the 4 KiB texture)
RAM_QWORDS = 512          # 8 KiB EE RAM payload; SH3 64x64 payload ~340 qw fits

def read_mem(name, n):
    vals=[]
    for ln in open(os.path.join(DATA,name)):
        ln=ln.strip()
        if ln and not ln.startswith("//"): vals.append(int(ln,16))
    if len(vals) < n: sys.exit(f"{name}: {len(vals)} < {n} (run tools/gs_extract_sh3_clut.py first)")
    return vals

tex_words = read_mem("sh3_tex_idx64.mem", W*H//4)  # 1024 packed words (4 indices each) — the 64x64 crop
clut      = read_mem("sh3_clut.mem", 256)          # 256 PSMCT32 entries

# TEX0_1: PSMT8 128x128 (TW=TH=7), TFX=DECAL, + CLUT side (CBP, CPSM=PSMCT32, CSM2 linear, CLD=1 -> load)
def tex0_sh3():
    v = bake.tex0_pack(TBP0, TBW, psm=0x13, tw=6, th=6, tfx=1)   # 64x64 (TW=TH=6)
    v |= (CBP & 0x3FFF) << 37
    v |= (0   & 0xF)    << 51    # CPSM = PSMCT32
    v |= (1   & 0x1)    << 55    # CSM  = CSM2 (linear)
    v |= (0   & 0x1F)   << 56    # CSA  = 0
    v |= (1   & 0x7)    << 61    # CLD  = 1 (fire VRAM->CLUT load on commit)
    return v

def build_payload():
    qw=[]
    # U1: BITBLT the CLUT (256 PSMCT32 entries, 16x16) -> VRAM[CBP*256]
    qw.append(bake.giftag(1,0,0,4,0x0000_0000_0000_EEEE))
    qw.append(bake.aplusd(bake.R_BITBLTBUF, bake.bitbltbuf_pack(CBP, 1, 0)))   # DPSM=PSMCT32
    qw.append(bake.aplusd(bake.R_TRXPOS,    bake.trxpos_pack(0,0)))
    qw.append(bake.aplusd(bake.R_TRXREG,    bake.trxreg_pack(256,1)))          # 256 entries as ONE CONTIGUOUS
    #   row — PSMCT32 row_stride = DBW*256, so a 16x16 upload would scatter entries (16/256-B row) while
    #   clut_loader reads them contiguously; 256x1 keeps them packed (the Ch296 8-entry demo used 8x1).
    qw.append(bake.aplusd(bake.R_TRXDIR,    bake.trxdir_pack(0)))
    qw.append(bake.giftag(256//4,0,2,0,0))                                     # IMAGE: 4 entries/qword
    for q in range(256//4):
        word=0
        for lane in range(4): word |= (clut[q*4+lane] & 0xFFFFFFFF) << (32*lane)
        qw.append(word)
    # U2: upload the PSMT8 index texture as a PSMCT32 transfer (full 32-bit WORD writes — NOT PSMT8 byte
    # writes, which RMW the 32-bit VRAM word and corrupt one word on 4 consecutive same-word byte-writes).
    # The bytes land identically (TBP*256 + w*4); the TEX0 still READS them as PSMT8. This is exactly the
    # "upload as PSMCT32, bind as PSMT8" trick SH3 itself uses. Upload as (W*H/4)x1 = 1024x1 PSMCT32.
    qw.append(bake.giftag(1,0,0,4,0x0000_0000_0000_EEEE))
    qw.append(bake.aplusd(bake.R_BITBLTBUF, bake.bitbltbuf_pack(TBP0, 16, 0x00)))   # DPSM=PSMCT32, DBW=16 (1 row)
    qw.append(bake.aplusd(bake.R_TRXPOS,    bake.trxpos_pack(0,0)))
    qw.append(bake.aplusd(bake.R_TRXREG,    bake.trxreg_pack(W*H//4, 1)))           # 1024 PSMCT32 words x 1 row
    qw.append(bake.aplusd(bake.R_TRXDIR,    bake.trxdir_pack(0)))
    qw.append(bake.giftag(W*H//16,0,2,0,0))    # 256 qwords, 4 PSMCT32 words each (= the packed indices)
    for q in range(W*H//16):
        word=0
        for k in range(4): word |= (tex_words[q*4+k] & 0xFFFFFFFF) << (32*k)
        qw.append(word)
    # U3: PRIM(SPRITE+TME) + FRAME + TEX0(PSMT8+CLUT load) + RGBAQ + UV0/XYZ2_0
    frame_val = bake.frame_1_psmct32(FBW)
    qw.append(bake.giftag(1,0,0,6,0x0000_0000_00EE_EEEE))
    qw.append(bake.aplusd(bake.R_PRIM,    bake.prim_sprite_tme()))
    qw.append(bake.aplusd(bake.R_FRAME_1, frame_val))
    qw.append(bake.aplusd(bake.R_TEX0_1,  tex0_sh3()))
    qw.append(bake.aplusd(bake.R_RGBAQ,   bake.rgbaq_data(0x80,0x80,0x80)))
    qw.append(bake.aplusd(bake.R_UV,      bake.uv_data(0,0)))
    qw.append(bake.aplusd(bake.R_XYZ2,    bake.xyz2_data(0,0)))
    # U4: UV1/XYZ2_1 closing the 128x128 textured sprite. EOP.
    qw.append(bake.giftag(1,1,0,2,0x0000_0000_0000_00EE))
    qw.append(bake.aplusd(bake.R_UV,   bake.uv_data(W-1,H-1)))
    qw.append(bake.aplusd(bake.R_XYZ2, bake.xyz2_data(W-1,H-1)))
    return qw

payload = build_payload()
qwc = len(payload)
if 16 + qwc > RAM_QWORDS: sys.exit(f"payload {qwc} qw + 16 > {RAM_QWORDS} (bump RAM_QWORDS / RAM_SIZE_BYTES)")

# DISPLAY1 hi = (DH<<12)|DW for 64x64 (magh=magv=1): DW=DH=63
disp_hi = (63 << 12) | 63
with open(os.path.join(DATA,"payload_sh3_clut.mem"),"w") as f:
    f.write(f"// Ch347 LOCAL authentic SH3 64x64-CROP PSMT8+CLUT board payload (tex @ TBP={TBP0}, CLUT @ CBP={CBP}).\n")
    f.write(f"// SH3-derived -> gitignored. qw 0..15 zero; qw 16.. = GIF (BITBLT CLUT + tex + DECAL sprite). QWC={qwc}.\n")
    for _ in range(16): f.write(f"{0:032x}\n")
    for w in payload: f.write(f"{w & ((1<<128)-1):032x}\n")
    for _ in range(RAM_QWORDS-16-qwc): f.write(f"{0:032x}\n")
bake.write_bios_mem(
    "bios_sh3_clut.mem",
    bake.build_textured_demo_bootlet_disp(qwc, disp_hi, FBW),
    f"Ch347 LOCAL authentic SH3 64x64-crop PSMT8+CLUT setup bootlet (QWC={qwc}, DISPLAY1={W}x{H}). SH3-derived -> gitignored.")
print(f"[Ch347] payload_sh3_clut.mem: {qwc} qw (BITBLT 256-CLUT + {W}x{H} PSMT8 tex + DECAL sprite, 8 KiB RAM)")
print(f"[Ch347] bios_sh3_clut.mem   : bootlet QWC={qwc}, DISPLAY1={W}x{H} (disp_hi=0x{disp_hi:x}), FBW={FBW}")
print(f"[Ch347] VRAM layout: FB {W}x{H} PSMCT32 @0 ({W*H*4//1024}KiB) + PSMT8 tex @TBP={TBP0} ({W*H//1024}KiB) + CLUT @CBP={CBP} (1KiB) -> fits the proven 64x64 bram config")
