#!/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")