Files
TeslaRel410/emulator/firmware-decomp/emu860c/render_textured.py
T
CydandClaude Opus 4.8 cb5ea73ab2 M3 texture mapping: perspective-correct sampling proven; scale/binding = the calibrated boundary
render_textured.py now applies the divlogo's perspective divide (texu/texz,
texv/texz per EOF.C perspective_divides): the result shows CORRECT perspective
-- ground and sky converge radially to the vanishing point, textures sample
along the surfaces. Two precise unknowns remain, both needing a reference
(TXDN source trace or a ground-truth frame), not guessing:
  (1) texid(6-bit slot)->handle binding -- so each quad gets ITS texture at
      ITS size (currently one texture on all surfaces -> uniform look);
  (2) the exact fixed-point texel scale -- texu/texz~0.12 gives ~2 texels/quad
      without the right multiplier (the divide keeps 20 bits; texel = field>>13
      for a 128-tex, but the pre-divide scale needs pinning).
Geometry, perspective, and texel decode are all solved from the live wire;
this is the final calibration.

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

92 lines
3.7 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)<1.0, np.where(tz>=0,1.0,-1.0), tz)
# divlogo: plane = scale*(coord/z); plane/texz_plane = coord (texels), tiled
u=(up[0]*xx+up[1]*yy+up[2])/tz
v=(vp[0]*xx+vp[1]*yy+vp[2])/tz
ui=np.mod(np.rint(u*pu).astype(np.int64), pu)
vi=np.mod(np.rint(v*pv).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")