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>
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
"""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")
|
||||
Reference in New Issue
Block a user