#!/usr/bin/env python3 """retroDE_ps2 — Ch341 Brick 2: faithful texture downscale + boot-fixture generation. Reads the LOCAL extracted cube texture (256x256 PSMCT32, from gs_texture.py --extract), box-downsamples it to 64x64 PSMCT32 (a declared, reported linear transform — NOT a GS-feature approximation; the same class as the Ch340 viewport fit), and emits the 64x64 texels as a $readmemh .mem the boot setup payload uploads to a VRAM-resident TBP. Also runs Codex's gate: hash the dump's N texture uploads and report whether they are byte-identical (so one preload suffices). Only aggregate facts/hashes are committable; the texel .mem stays LOCAL (derived from the dump). Declared transform: source 256x256 -> 64x64 (factor 4) => translator must scale UVs by /4. Usage: gs_bake_texture.py [--blob extracted/tex0_blob.bin] [--out outdir] [--report r.txt] """ import sys, os, struct, hashlib sys.path.insert(0, os.path.dirname(__file__)) import gs_parse, gs_texture SRC=256; DST=64; FACTOR=SRC//DST # 4 def upload_identity_gate(path): """Hash every IMAGE upload payload; report if all byte-identical.""" d = gs_parse.read_dump_bytes(path) h, events = gs_parse.parse_dump(path) hashes=[] for e in events: if e.kind=="IMAGE": blob = d[e.byte_off:e.byte_off+e.info.get("bytes",0)] hashes.append(hashlib.sha256(blob).hexdigest()) return hashes def downsample_psmct32(blob): """256x256 ABGR words -> 64x64 ABGR words, box filter (average each 4x4 source block per channel).""" assert len(blob) == SRC*SRC*4, f"expected {SRC*SRC*4} bytes, got {len(blob)}" src = struct.unpack(f"<{SRC*SRC}I", blob) out = [] for oy in range(DST): for ox in range(DST): ar=ag=ab=aa=0 for dy in range(FACTOR): for dx in range(FACTOR): w = src[(oy*FACTOR+dy)*SRC + (ox*FACTOR+dx)] ar += w & 0xFF; ag += (w>>8)&0xFF; ab += (w>>16)&0xFF; aa += (w>>24)&0xFF n = FACTOR*FACTOR out.append((ar//n) | ((ag//n)<<8) | ((ab//n)<<16) | ((aa//n)<<24)) return out # 64*64 ABGR words def main(argv): if len(argv)<2: print(__doc__); return 2 path=argv[1] def opt(n,d=None): return argv[argv.index(n)+1] if n in argv else d blobp = opt("--blob", os.path.join(os.path.dirname(path),"extracted","tex0_blob.bin")) outdir = opt("--out", os.path.join(os.path.dirname(path),"extracted")) R=[f"# Ch341 Brick 2 texture bake (source {os.path.basename(path)}; aggregate facts/hashes only)"] # --- gate: are the uploads byte-identical? (auditable: list every distinct hash + its count) --- hh = upload_identity_gate(path) from collections import Counter cnt = Counter(hh) R.append(f"texture uploads: {len(hh)} distinct payloads: {len(cnt)} " + ("ALL BYTE-IDENTICAL -> one preload suffices" if len(cnt)==1 else "DIFFER -> translator must not let a subsegment cross a re-upload to its TBP")) for i,(hsh,c) in enumerate(sorted(cnt.items(), key=lambda x:-x[1])): R.append(f" upload payload #{i}: sha256[0..15]={hsh[:16]} count={c}") if not os.path.exists(blobp): R.append(f"!! no extracted blob at {blobp} (run gs_texture.py --extract first)") print("\n".join(R)); return 1 blob = open(blobp,"rb").read() src_hash = hashlib.sha256(blob).hexdigest() out = downsample_psmct32(blob) out_bytes = struct.pack(f"<{len(out)}I", *out) out_hash = hashlib.sha256(out_bytes).hexdigest() R.append("") R.append(f"downsample: {SRC}x{SRC} PSMCT32 -> {DST}x{DST} PSMCT32 (box filter /{FACTOR}; UV scale /{FACTOR})") R.append(f" src sha256[0..15]={src_hash[:16]} dst sha256[0..15]={out_hash[:16]} dst_bytes={len(out_bytes)} (= {DST}*{DST}*4)") os.makedirs(outdir, exist_ok=True) # boot IMAGE upload packs 4 ABGR words / 128-bit qword -> $readmemh as 64-bit? bake.py packs the # IMAGE payload as 64-bit words (lane pairs). Emit one 32-bit ABGR texel per line (the bake helper # will pack); also emit a raw .bin for reuse. LOCAL only. memp = os.path.join(outdir, "cube_tex_64.mem") with open(memp,"w") as f: for w in out: f.write(f"{w & 0xFFFFFFFF:08x}\n") binp = os.path.join(outdir, "cube_tex_64.bin") open(binp,"wb").write(out_bytes) R.append(f" wrote LOCAL fixture: {os.path.basename(memp)} ({len(out)} texels) + {os.path.basename(binp)}") report="\n".join(R)+"\n" print(report) if opt("--report"): open(opt("--report"),"w").write(report); print(f"[wrote report -> {opt('--report')}]") return 0 if __name__ == "__main__": sys.exit(main(sys.argv))