Battle geometry, rendered honestly: coherent arena floor + horizon + sky
Fixed the readout: the effect packets are 3-4 edge TEXTURED quads (0x2c/0x42/ 0x0d edges + z + texz/texu/texv planes -- NO Gouraud colour, confirming the scene is textured). Two bugs in the first pass: (1) 0x2c edges were treated as 'accept both sides', turning every quad into a full-plane fill; (2) only 2-edge packets were caught, dropping ~3/4 of the geometry. render_battle.py applies the render_wide30 polygon-winding rule (inside = all-edges>=0 OR all<0) -> 78 polygons of 141 setups. Result (flat-per-polygon + depth): a geometrically coherent first-person scene -- sky at far depth, a ground plane ramping smoothly from horizon to foreground, a clean horizon line, upright structures. The perspective is consistent; this is real scene geometry, not the muddy u/v wedges of the first attempt. Surfaces still need texels (TXDN/M3) for their skin. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
"""Dump one mid-scene battle draw's effect program for honest inspection."""
|
||||
import sys, os, struct, pickle
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HERE); sys.path.insert(0, os.path.dirname(HERE))
|
||||
sys.path.insert(0, r'C:\VWE\TeslaRel410\dpl3-revive\patha')
|
||||
import emu860, emu_main, emu860c
|
||||
from driver import boot, CpuShim
|
||||
from vrboard import A
|
||||
ANAME = {int(a): a.name for a in A}
|
||||
emu860.Mem.log = lambda self, *a, **k: None
|
||||
TARGET = int(sys.argv[2]) if len(sys.argv) > 2 else 6234
|
||||
|
||||
data = open(sys.argv[1], '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:]))
|
||||
r=boot(fw='vrend410', queue=recs); shim=CpuShim()
|
||||
RECV=emu_main.MAPS['vrend410']['receive']
|
||||
while r.qi < TARGET:
|
||||
reason,_=emu860c.run(500_000_000)
|
||||
if reason==3: continue
|
||||
pc=emu860c.getstate()['pc']; h=r.hooks.get(pc)
|
||||
if h is None: break
|
||||
if pc==RECV and r.qi<len(r.queue) and ANAME.get(r.queue[r.qi][0])=='draw_scene' and r.qi>=TARGET-1:
|
||||
# run this draw fully, capturing the program region after
|
||||
h(shim)
|
||||
while True:
|
||||
reason,_=emu860c.run(500_000_000)
|
||||
if reason==3: continue
|
||||
if emu860c.getstate()['pc']==RECV: break
|
||||
hh=r.hooks.get(emu860c.getstate()['pc'])
|
||||
if hh: hh(shim)
|
||||
else: break
|
||||
break
|
||||
if h: h(shim)
|
||||
prog={a:emu860c.r32(a) for a in range(0x08158000,0x08170000,4) if emu860c.r32(a)}
|
||||
pickle.dump({'prog':prog,'qi':r.qi}, open(os.path.join(HERE,'battle_prog.pkl'),'wb'))
|
||||
print("dumped %d prog cells at cmd %d"%(len(prog), r.qi))
|
||||
@@ -0,0 +1,90 @@
|
||||
"""Honest battle geometry: 0x2c/0x42/0x0d edges as proper half-planes with
|
||||
polygon-level winding (inside = all>=0 OR all<0), real z-buffer. The scene is
|
||||
TEXTURED (texz/texu/texv planes, no Gouraud colour), so with no TXDN texels we
|
||||
show STRUCTURE: flat distinct colour per polygon + depth + u/v. numpy, offline."""
|
||||
import pickle, struct, os
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
d = pickle.load(open(os.path.join(HERE, '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)
|
||||
W, H = 832, 512
|
||||
setups = [a for a in sorted(prog) if rd(a) == 0x100]
|
||||
EDGE = {0x42, 0x0d, 0x2c}
|
||||
|
||||
yy, xx = np.mgrid[0:H, 0:W].astype(np.float64)
|
||||
zbuf = np.full((H, W), -1e30)
|
||||
flat = np.zeros((H, W, 3), np.uint8)
|
||||
uv = np.zeros((H, W, 3))
|
||||
PAL = [(200,80,80),(80,200,120),(90,140,220),(220,200,90),(200,120,220),
|
||||
(120,220,220),(240,150,80),(150,120,230),(90,210,160),(230,110,150)]
|
||||
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
|
||||
vsum_pos = np.ones((H, W), bool)
|
||||
vsum_neg = np.ones((H, W), bool)
|
||||
for A, B, C in edges:
|
||||
v = A * xx + B * yy + C
|
||||
vsum_pos &= v >= 0
|
||||
vsum_neg &= v < 0
|
||||
en = vsum_pos | vsum_neg
|
||||
if not en.any():
|
||||
continue
|
||||
# z = the op-0x21 within the next ~10 words
|
||||
zp = None; zq = p
|
||||
for _ in range(12):
|
||||
if ((rd(zq) >> 8) & 0xff) == 0x21:
|
||||
zp = (f32(rd(zq+4)), f32(rd(zq+8)), f32(rd(zq+12))); break
|
||||
zq += 4
|
||||
if zp is None:
|
||||
continue
|
||||
z = zp[0] * xx + zp[1] * yy + zp[2]
|
||||
en &= z > zbuf
|
||||
if not en.any():
|
||||
continue
|
||||
zbuf[en] = z[en]
|
||||
flat[en] = PAL[drawn % len(PAL)]
|
||||
# texu/texv planes (addr 58 / 78 via op 0x43) for the uv view
|
||||
up = vp = None; q = zq
|
||||
for _ in range(50):
|
||||
w = rd(q)
|
||||
if ((w >> 8) & 0xff) == 0x43 and (w & 0xff) == 58 and up is None:
|
||||
up = (f32(rd(q+4)), f32(rd(q+8)), f32(rd(q+12)))
|
||||
if ((w >> 8) & 0xff) == 0x43 and (w & 0xff) == 78 and vp is None:
|
||||
vp = (f32(rd(q+4)), f32(rd(q+8)), f32(rd(q+12)))
|
||||
if up and vp: break
|
||||
q += 4
|
||||
if up and vp:
|
||||
u = up[0]*xx + up[1]*yy + up[2]
|
||||
v = vp[0]*xx + vp[1]*yy + vp[2]
|
||||
uv[..., 0][en] = u[en]; uv[..., 1][en] = v[en]
|
||||
drawn += 1
|
||||
|
||||
print("drawn %d polygons of %d setups; covered %d px" %
|
||||
(drawn, len(setups), int((zbuf > -1e29).sum())))
|
||||
Image.fromarray(flat, 'RGB').save(os.path.join(HERE, 'battle_flat.png'))
|
||||
zv = zbuf.copy(); m = zv > -1e29
|
||||
zi = np.zeros((H, W), np.uint8)
|
||||
if m.any():
|
||||
zn = (zv - zv[m].min()) / max(1e-9, np.ptp(zv[m]))
|
||||
zi[m] = (40 + 215*zn[m]).astype(np.uint8)
|
||||
Image.fromarray(zi, 'L').save(os.path.join(HERE, 'battle_depth.png'))
|
||||
# uv normalized globally (spatially coherent, unlike per-prim)
|
||||
um, vm = uv[..., 0], uv[..., 1]
|
||||
rgb = np.zeros((H, W, 3), np.uint8)
|
||||
if m.any():
|
||||
un = (um - um[m].min())/max(1e-9, np.ptp(um[m]))
|
||||
vn = (vm - vm[m].min())/max(1e-9, np.ptp(vm[m]))
|
||||
rgb[..., 0][m] = (40 + 200*un[m]).astype(np.uint8)
|
||||
rgb[..., 1][m] = (40 + 200*vn[m]).astype(np.uint8)
|
||||
rgb[..., 2][m] = 120
|
||||
Image.fromarray(rgb, 'RGB').save(os.path.join(HERE, 'battle_uv.png'))
|
||||
print("wrote battle_flat.png + battle_depth.png + battle_uv.png")
|
||||
Reference in New Issue
Block a user