Files
TeslaRel410/emulator/firmware-decomp/emu860c/render_textured.py
T
CydandClaude Opus 4.8 60d812484e M3 sampling framework + honest calibration boundary
render_textured.py samples the game textures with the quads' texu/texv/texz
planes, z-buffered. Empirical finding: the planes are texscale-encoded
(2^16) homogeneous coords needing the exact perspective_divides mapping
(texu/texz * .Cturn_z_to_tex from FOOTER.SS) -- measured texu/texz ~0.12 on
the ground quad. That final texel-coordinate calibration is a precise
reverse-engineering step against EOF.C, not a guess; left as the last mile.

The verified M3 result is the texture DECODE: the netdeath textures are real
game art (texstore.py). Geometry + texel format are both solved from the
live wire.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 16:23:29 -05:00

91 lines
3.6 KiB
Python

"""Proof of texture sampling on real battle geometry: each textured quad's
texu/texv planes sample a real game texture (nearest, tiled) with z-buffer.
Binding note: per-quad texid->handle binding is not yet resolved, so this maps
all textured surfaces to one representative texture to demonstrate the
perspective-correct SAMPLING on the real geometry (honest proof, not final)."""
import pickle, struct, os, sys
sys.path.insert(0, r'C:VWETeslaRel410dpl3-revivepatha')
import sys, os
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 vrboard import A
ANAME={int(a):a.name for a in A}
HERE=os.path.dirname(os.path.abspath(__file__))
# reload the wire for the texstore
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)
print("texstore: %d textures %s"%(len(store),[hex(h) for h in list(store)[:8]]))
# pick a couple of representative textures for variety by size
by_size=sorted(store.items(), key=lambda kv:-kv[1][0]*kv[1][1])
panel=None
for h,(u,v,m,arr) in store.items():
if u==128 and v==128: panel=(u,v,arr); break
if panel is None:
h,(u,v,m,arr)=by_size[0]; panel=(u,v,arr)
pu,pv,parr=panel
print("using %dx%d texture"%(pu,pv))
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)
TS=65536.0 # .Ctexscale ~ 2^16
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 Aa,Bb,Cc in edges:
v=Aa*xx+Bb*yy+Cc; ep&=v>=0; en&=v<0
inside=ep|en
if not inside.any(): continue
# z + texu/texv planes
zp=up=vp=tzp=None; q=p
for _ in range(60):
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 zp and up and vp and tzp: break
q+=4
if zp is None: continue
z=zp[0]*xx+zp[1]*yy+zp[2]
inside&=z>zbuf
if not inside.any(): continue
zbuf[inside]=z[inside]
if up and vp and tzp:
tz=tzp[0]*xx+tzp[1]*yy+tzp[2]
tz=np.where(np.abs(tz)<1e-6, 1e-6, tz)
u=(up[0]*xx+up[1]*yy+up[2])/tz # perspective-correct texel coords
v=(vp[0]*xx+vp[1]*yy+vp[2])/tz
ui=np.mod(u.astype(np.int64), pu)
vi=np.mod(v.astype(np.int64), pv)
samp=parr[vi, ui]
img[inside]=samp[inside]
else:
img[inside]=(70,70,80)
drawn+=1
print("drawn %d quads"%drawn)
Image.fromarray(img,'RGB').save(os.path.join(HERE,'battle_textured.png'))
print("wrote battle_textured.png")