These changes were made and verified earlier this session (the all-cyan bug
fix is already recorded as SOLVED in project memory) but never got committed --
render_final.py has referenced dpl_sampler as a module in its docstring since
ac4142e7, yet dpl_sampler.py itself was never added, leaving the repo unable
to run the M4b/M4c/M4d renderer scripts that all import it.
texstore.py: fix the texel word format. SVT texels are [pad,B,G,R] (ground
truth: dpl3-revive patha/vrboard.py do_texels + stage_assets.py); reading RGB
as bytes (0,1,2)=(pad,B,G) zeroed red, producing the all-cyan render. Correct
read is bytes (3,2,1).
dpl_sampler.py: the authentic IG-board texture-value model (wrap repeat/clamp,
near-black CUT keying) factored into its own tested module, distilled from the
shipped libDPL headers -- see IG-SHADING-MODEL.md (also added, referenced by
render_final.py's docstring but likewise never committed).
igc_exec.py: OP48/OP49 candidate opcodes (pixel global-X/Y seed into mem) added
to the CPU golden-model executor alongside the existing SCMEMA seed handling.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
87 lines
3.9 KiB
Python
87 lines
3.9 KiB
Python
"""M5 integrate: battle frame with the VERIFIED perspective divide (correct
|
|
texel scale/tiling) + per-quad texid -> texture (best-effort binding, since the
|
|
exact texid->handle needs the board texture-RAM model). Real colours from real
|
|
textures, perspective-correct.
|
|
|
|
Textures decode via the SVT [pad,B,G,R] format (texstore -> RGB); the board
|
|
texture-value model (wrap + near-black cutout) is applied through dpl_sampler,
|
|
distilled from libDPL dpl_TEX_VALUE + the real renderer. See IG-SHADING-MODEL.md."""
|
|
import pickle, struct, os, sys
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
sys.path.insert(0, r'C:\VWE\TeslaRel410\dpl3-revive\patha')
|
|
import numpy as np
|
|
from PIL import Image
|
|
from texstore import build_texstore
|
|
from dpl_sampler import mode_to_texflags, wrap_index, composite
|
|
HERE=os.path.dirname(os.path.abspath(__file__))
|
|
|
|
data=open(r'C:/VWE/TeslaRel410/emulator/render-bridge/captures/netdeath-20260708.fifodump','rb').read()
|
|
recs=[]; off=0
|
|
while True:
|
|
i=data.find(b'VPXM',off)
|
|
if i<0: break
|
|
ln=struct.unpack_from('<I',data,i+4)[0]; body=data[i+8:i+8+ln]; off=i+8+ln
|
|
if len(body)>=4:
|
|
a=struct.unpack_from('<I',body,0)[0]
|
|
if a<0x100: recs.append((a,body[4:]))
|
|
store=build_texstore(recs)
|
|
texlist=list(store.items()) # [(handle,(u,v,mode,arr)), ...]
|
|
print("%d textures"%len(texlist))
|
|
|
|
d=pickle.load(open('battle_prog.pkl','rb')); prog=d['prog']
|
|
def f32(w): return struct.unpack('<f',struct.pack('<I',w&0xffffffff))[0]
|
|
def rd(a): return prog.get(a,0)
|
|
setups=[a for a in sorted(prog) if rd(a)==0x100]
|
|
EDGE={0x42,0x0d,0x2c}
|
|
W,H=832,512
|
|
yy,xx=np.mgrid[0:H,0:W].astype(np.float64)
|
|
zbuf=np.full((H,W),-1e30)
|
|
img=np.zeros((H,W,3),np.uint8)
|
|
drawn=0
|
|
for a in setups:
|
|
p=a+4; edges=[]
|
|
while ((rd(p)>>8)&0xff) in EDGE and len(edges)<6:
|
|
edges.append((f32(rd(p+4)),f32(rd(p+8)),f32(rd(p+12)))); p+=16
|
|
if len(edges)<3: continue
|
|
ep=np.ones((H,W),bool); en=np.ones((H,W),bool)
|
|
for A,B,C in edges:
|
|
v=A*xx+B*yy+C; ep&=v>=0; en&=v<0
|
|
inside=ep|en
|
|
if not inside.any(): continue
|
|
zp=tzp=up=vp=None; tid=None; q=p
|
|
for _ in range(70):
|
|
w=rd(q); op=(w>>8)&0xff; ad=w&0xff
|
|
if op==0x21 and zp is None: zp=(f32(rd(q+4)),f32(rd(q+8)),f32(rd(q+12)))
|
|
if op==0x43 and ad==32 and tzp is None: tzp=(f32(rd(q+4)),f32(rd(q+8)),f32(rd(q+12)))
|
|
if op==0x43 and ad==58 and up is None: up=(f32(rd(q+4)),f32(rd(q+8)),f32(rd(q+12)))
|
|
if op==0x43 and ad==78 and vp is None: vp=(f32(rd(q+4)),f32(rd(q+8)),f32(rd(q+12)))
|
|
if op==0xf7 and ad in (141,142) and tid is None: tid=(rd(q+4)>>2)&0x3f
|
|
q+=4
|
|
if zp is None: continue
|
|
z=zp[0]*xx+zp[1]*yy+zp[2]
|
|
inside&=z>zbuf
|
|
if not inside.any(): continue
|
|
# z is claimed only on pixels the poly actually OWNS (deferred past any cutout)
|
|
if up and vp and tzp:
|
|
# best-effort texid -> texture (exact binding = board tex-RAM model, pending)
|
|
h,(tu,tv,mode,arr)=texlist[(tid or 0)%len(texlist)]
|
|
wrap_u,wrap_v,cut_mode=mode_to_texflags(mode)
|
|
tzv=tzp[0]*xx+tzp[1]*yy+tzp[2]
|
|
tzv=np.where(np.abs(tzv)<1.0, np.sign(tzv)+ (tzv==0), tzv)
|
|
# VERIFIED divide: texel8 = (texu/texz)*2048 (3 int + 8 texel bits), tiled to tex size
|
|
ucoord=((up[0]*xx+up[1]*yy+up[2])/tzv*2048).astype(np.int64)
|
|
vcoord=((vp[0]*xx+vp[1]*yy+vp[2])/tzv*2048).astype(np.int64)
|
|
ui=wrap_index((ucoord*tu)>>8, tu, wrap_u) # 8-bit texel -> tex-size index
|
|
vi=wrap_index((vcoord*tv)>>8, tv, wrap_v)
|
|
samp=arr[vi,ui] # HxWx3 RGB (SVT xbgr -> RGB in texstore)
|
|
# board texture-value model: opaque, or CUT (near-black keying), see dpl_sampler
|
|
drawmask=composite(img, inside, samp, cut_mode)
|
|
else:
|
|
img[inside]=(60,60,70)
|
|
drawmask=inside
|
|
zbuf[drawmask]=z[drawmask]
|
|
drawn+=1
|
|
print("drawn %d quads"%drawn)
|
|
Image.fromarray(img,'RGB').save(os.path.join(HERE,'battle_final.png'))
|
|
print("wrote battle_final.png")
|