Renderer: vendor live-bridge scripts; GPU backend unblocked (60fps)
- emulator/render-bridge/: the pod->Dave's-renderer live bridge (rescued from session scratchpad): live_bridge.py (first-person cam from the 0x1f pose, arrow-key eye-height trim), fp/chase offline renders, fifobridge, diagnostics, gauge_arena[_sound].conf, and launch_pod.ps1 (one-command pod+bridge restart: pentapus VPX_EXPLODE + AWE32 sound + GL bridge). - _backend.py: renderer selection -- vrview_gl.GLRenderer (moderngl) by default, VRVIEW_SOFT=1 for the software reference; backend-agnostic frame save via last_frame. - GPU backend runs on side-by-side CPython 3.13 (moderngl/glcontext cp313 wheels; no MSVC needed): 63fps headless / 60.7 windowed vs 21fps software on the same scene, and the GL path also draws the vertex-alpha cloud dome. - vrview.py: VRVIEW_GAMMA env selects DAC gamma (1.25 live-renderer legacy vs 1.7 per the original GAMMA.C) for A/B against the real pod. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -430,6 +430,10 @@ class Renderer:
|
|||||||
self._last_ms = 0.0
|
self._last_ms = 0.0
|
||||||
self._psys = {} # SPECIALFX particle pools by code
|
self._psys = {} # SPECIALFX particle pools by code
|
||||||
self.light = np.array([0.3, 0.8, 0.5]); self.light /= np.linalg.norm(self.light)
|
self.light = np.array([0.3, 0.8, 0.5]); self.light /= np.linalg.norm(self.light)
|
||||||
|
# Division 10-bit-DAC output gamma. GAMMA.C (the original source) builds
|
||||||
|
# out = (i/255)^(1/1.7); this live renderer historically used 1.25.
|
||||||
|
# VRVIEW_GAMMA selects it so the two can be compared against the real pod.
|
||||||
|
self.gamma = float(os.environ.get('VRVIEW_GAMMA', '1.25'))
|
||||||
|
|
||||||
def dcs_matrix(self, board, h):
|
def dcs_matrix(self, board, h):
|
||||||
m = None
|
m = None
|
||||||
@@ -752,8 +756,9 @@ class Renderer:
|
|||||||
self.skip / 60.0)
|
self.skip / 60.0)
|
||||||
|
|
||||||
arr = np.clip(img, 0, 255).astype(np.uint8)
|
arr = np.clip(img, 0, 255).astype(np.uint8)
|
||||||
# Division DAC gamma (out = in^(1/1.25)) -- match render_preview.py
|
# Division DAC output gamma (VRVIEW_GAMMA; GAMMA.C = 1.7, live-renderer
|
||||||
arr = (np.power(arr / 255.0, 1 / 1.25) * 255).astype(np.uint8)
|
# legacy = 1.25)
|
||||||
|
arr = (np.power(arr / 255.0, 1.0 / self.gamma) * 255).astype(np.uint8)
|
||||||
surf = pg.surfarray.make_surface(np.transpose(arr, (1, 0, 2)))
|
surf = pg.surfarray.make_surface(np.transpose(arr, (1, 0, 2)))
|
||||||
self.screen.blit(surf, (0, 0))
|
self.screen.blit(surf, (0, 0))
|
||||||
pg.display.flip()
|
pg.display.flip()
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
"""Renderer backend selection for the render-bridge scripts.
|
||||||
|
|
||||||
|
Default = the dpl3-revive GPU backend (vrview_gl.GLRenderer, moderngl);
|
||||||
|
VRVIEW_SOFT=1 forces the software rasterizer (vrview.Renderer), which stays
|
||||||
|
the debugging reference. Both share SceneCache/cam_matrix/draw/last_frame,
|
||||||
|
so scripts only swap the class.
|
||||||
|
|
||||||
|
Run under the Windows CPython with the wheels installed (py -3.13):
|
||||||
|
numpy pygame-ce moderngl pillow
|
||||||
|
(the MSYS2 python has no pip; 3.14 has no moderngl/glcontext wheel yet).
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||||
|
'..', '..', 'dpl3-revive', 'patha'))
|
||||||
|
|
||||||
|
|
||||||
|
def pick_renderer():
|
||||||
|
"""Return (RendererClass, backend_name)."""
|
||||||
|
if os.environ.get('VRVIEW_SOFT') == '1':
|
||||||
|
from vrview import Renderer
|
||||||
|
return Renderer, 'software'
|
||||||
|
try:
|
||||||
|
from vrview_gl import GLRenderer
|
||||||
|
return GLRenderer, 'GL'
|
||||||
|
except Exception as e:
|
||||||
|
print(f"GL backend unavailable ({e}); using software rasterizer",
|
||||||
|
file=sys.stderr)
|
||||||
|
from vrview import Renderer
|
||||||
|
return Renderer, 'software'
|
||||||
|
|
||||||
|
|
||||||
|
def save_frame(r, out):
|
||||||
|
"""Save the last rendered frame. Works headless on both backends
|
||||||
|
(GL has no screen surface -- read the FBO via last_frame)."""
|
||||||
|
arr = r.last_frame
|
||||||
|
if arr is None:
|
||||||
|
raise RuntimeError('no frame rendered yet')
|
||||||
|
from PIL import Image
|
||||||
|
Image.fromarray(arr).save(out)
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import os, sys, struct
|
||||||
|
sys.path.insert(0, r'C:\VWE\TeslaRel410\dpl3-revive\patha')
|
||||||
|
os.environ.setdefault('SDL_VIDEODRIVER', 'dummy')
|
||||||
|
import numpy as np
|
||||||
|
from vrboard import VirtualBoard, Msg, A
|
||||||
|
from vrview import Renderer
|
||||||
|
|
||||||
|
data = open(sys.argv[1], 'rb').read()
|
||||||
|
board = VirtualBoard()
|
||||||
|
off = 0
|
||||||
|
while off + 8 <= len(data):
|
||||||
|
if data[off:off + 4] != b'VPXM':
|
||||||
|
off += 1; continue
|
||||||
|
ln = struct.unpack_from('<I', data, off + 4)[0]
|
||||||
|
body = data[off + 8:off + 8 + ln]; off += 8 + ln
|
||||||
|
if len(body) >= 4:
|
||||||
|
a = struct.unpack_from('<I', body, 0)[0]
|
||||||
|
if a < 0x100:
|
||||||
|
try: board.handle(Msg(False, 0xff, a, body[4:]))
|
||||||
|
except Exception: pass
|
||||||
|
|
||||||
|
r = Renderer(w=832, h=512)
|
||||||
|
c = r.cache; c.maybe_rebuild(board)
|
||||||
|
print('munga', board.munga, 'view', hex(c.view) if c.view else None,
|
||||||
|
'cam_chain', [hex(h) for h in c.cam_chain])
|
||||||
|
np.set_printoptions(suppress=True)
|
||||||
|
try:
|
||||||
|
cam = r.cam_matrix(board)
|
||||||
|
print('DAVE cam eye', cam[3, :3].round(1),
|
||||||
|
'back(+z)row', cam[2, :3].round(3), '(camera looks -back)')
|
||||||
|
except Exception as e:
|
||||||
|
print('DAVE cam err', e)
|
||||||
|
|
||||||
|
# geometry world centroid + bounds
|
||||||
|
pts = []
|
||||||
|
for inst in c.instances:
|
||||||
|
M = r.chain_matrix(board, inst['chain'])
|
||||||
|
if np.all(np.isfinite(M[3, :3])): pts.append(M[3, :3])
|
||||||
|
pts = np.array(pts)
|
||||||
|
print('GEOM centroid', pts.mean(0).round(1),
|
||||||
|
'min', pts.min(0).round(1), 'max', pts.max(0).round(1))
|
||||||
|
|
||||||
|
# the animated vehicle poses (0x1f) -- the player camera should be one of these
|
||||||
|
print('anim_abs vehicles:', len(board.anim_abs))
|
||||||
|
for h, f in list(board.anim_abs.items()):
|
||||||
|
R = np.array(f[:9]).reshape(3, 3); t = np.array(f[9:12])
|
||||||
|
print(' veh', hex(h), 'pos', t.round(1),
|
||||||
|
'Zrow', R[2].round(3), 'is_cam_chain', h in c.cam_chain)
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import os, sys, struct
|
||||||
|
os.environ['VRVIEW_CHASE'] = sys.argv[3] if len(sys.argv) > 3 else '2'
|
||||||
|
os.environ.setdefault('SDL_VIDEODRIVER', 'dummy')
|
||||||
|
from _backend import pick_renderer, save_frame
|
||||||
|
from vrboard import VirtualBoard, Msg, A
|
||||||
|
Renderer, backend = pick_renderer()
|
||||||
|
data = open(sys.argv[1], 'rb').read()
|
||||||
|
board = VirtualBoard()
|
||||||
|
off = 0
|
||||||
|
while off + 8 <= len(data):
|
||||||
|
if data[off:off + 4] != b'VPXM':
|
||||||
|
off += 1; continue
|
||||||
|
ln = struct.unpack_from('<I', data, off + 4)[0]
|
||||||
|
body = data[off + 8:off + 8 + ln]; off += 8 + ln
|
||||||
|
if len(body) >= 4:
|
||||||
|
a = struct.unpack_from('<I', body, 0)[0]
|
||||||
|
if a < 0x100:
|
||||||
|
try: board.handle(Msg(False, 0xff, a, body[4:]))
|
||||||
|
except Exception: pass
|
||||||
|
r = Renderer(w=832, h=512)
|
||||||
|
r.cache.maybe_rebuild(board)
|
||||||
|
c = r.cache
|
||||||
|
# how many geometries got a texture bound?
|
||||||
|
bound = sum(1 for inst in c.instances for g in inst['geoms'] if g in c.geom_tex)
|
||||||
|
tot = sum(len(inst['geoms']) for inst in c.instances)
|
||||||
|
print(f"instances={len(c.instances)} geoms_total={tot} geoms_textured={bound} "
|
||||||
|
f"geom_tex={len(c.geom_tex)} geom_mtl={len(c.geom_mtl)} "
|
||||||
|
f"ambient={c.ambient.round(2).tolist()} dirlights={len(c.dirlights)}")
|
||||||
|
r.draw(board)
|
||||||
|
save_frame(r, sys.argv[2])
|
||||||
|
print("wrote", sys.argv[2], f"[{backend}]")
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import os,sys
|
||||||
|
sys.path.insert(0, r"C:\VWE\TeslaRel410\dpl3-revive\patha")
|
||||||
|
os.environ.setdefault("SDL_VIDEODRIVER","dummy")
|
||||||
|
from vrboard import VirtualBoard, Assembler
|
||||||
|
from decode_anim import find_framed_start
|
||||||
|
data=open("bt8.raw.bin","rb").read()
|
||||||
|
b=VirtualBoard(); a=Assembler(); a.feed(data[find_framed_start(data):])
|
||||||
|
for m in a:
|
||||||
|
try: b.handle(m)
|
||||||
|
except Exception: pass
|
||||||
|
if b.frames>=600: break
|
||||||
|
print("frames",b.frames,"munga",getattr(b,"munga",None))
|
||||||
|
for attr in ("anim","anim_abs"):
|
||||||
|
v=getattr(b,attr,None)
|
||||||
|
print(attr, type(v).__name__, (len(v) if v is not None else None), (list(v)[:6] if v else None))
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Phase 1 core: feed OUR emulator's VPX_FIFODUMP (VPXM records = our game's
|
||||||
|
live VelociRender wire) into the friend's virtual board + renderer. Proves our
|
||||||
|
wire drives their renderer with no rebuild. Offline here; live = tail the file.
|
||||||
|
|
||||||
|
py fifobridge.py <file.fifodump> <out.png> [distmul]
|
||||||
|
"""
|
||||||
|
import os, sys, struct
|
||||||
|
sys.path.insert(0, r'C:\VWE\TeslaRel410\dpl3-revive\patha')
|
||||||
|
os.environ.setdefault('SDL_VIDEODRIVER', 'dummy')
|
||||||
|
import numpy as np
|
||||||
|
from vrboard import VirtualBoard, Msg, A
|
||||||
|
from vrview import Renderer
|
||||||
|
|
||||||
|
cap = sys.argv[1]
|
||||||
|
out = sys.argv[2] if len(sys.argv) > 2 else 'fifobridge.png'
|
||||||
|
distmul = float(sys.argv[3]) if len(sys.argv) > 3 else 1.4
|
||||||
|
|
||||||
|
VALID = set(int(a) for a in A)
|
||||||
|
|
||||||
|
def records(data):
|
||||||
|
off = 0
|
||||||
|
while off + 8 <= len(data):
|
||||||
|
if data[off:off+4] != b'VPXM':
|
||||||
|
off += 1; continue
|
||||||
|
ln = struct.unpack_from('<I', data, off+4)[0]
|
||||||
|
yield data[off+8:off+8+ln]
|
||||||
|
off += 8 + ln
|
||||||
|
|
||||||
|
data = open(cap, 'rb').read()
|
||||||
|
board = VirtualBoard()
|
||||||
|
nrec = nfed = nskip = nerr = 0
|
||||||
|
for body in records(data):
|
||||||
|
nrec += 1
|
||||||
|
if len(body) < 4:
|
||||||
|
continue
|
||||||
|
action = struct.unpack_from('<I', body, 0)[0]
|
||||||
|
if action >= 0x100: # init-arg string / non-message bursts
|
||||||
|
nskip += 1; continue # (real actions are all < 0x100, incl.
|
||||||
|
# the unnamed game ones: 0x1f, 0x2d...)
|
||||||
|
try:
|
||||||
|
board.handle(Msg(False, 0xff, action, body[4:]))
|
||||||
|
nfed += 1
|
||||||
|
except Exception:
|
||||||
|
nerr += 1
|
||||||
|
print(f'records={nrec} fed={nfed} skipped={nskip} errs={nerr}')
|
||||||
|
print(f'nodes={len(board.nodes)} geom={len(board.geom)} tex={len(board.tex)} '
|
||||||
|
f'anim_abs={len(getattr(board,"anim_abs",{}))} munga={getattr(board,"munga",None)}')
|
||||||
|
|
||||||
|
r = Renderer(w=832, h=512)
|
||||||
|
r.cache.maybe_rebuild(board)
|
||||||
|
c = r.cache
|
||||||
|
print(f'instances={len(c.instances)}')
|
||||||
|
|
||||||
|
# synthetic aerial camera framing all decoded geometry + lift far clip
|
||||||
|
mins = np.array([np.inf]*3); maxs = np.array([-np.inf]*3); nv = 0
|
||||||
|
for inst in c.instances:
|
||||||
|
M = r.chain_matrix(board, inst['chain'])
|
||||||
|
for gh in inst['geoms']:
|
||||||
|
mesh = c._mesh(board, gh)
|
||||||
|
if not mesh or 'pos' not in mesh or len(mesh['pos']) == 0:
|
||||||
|
continue
|
||||||
|
pw = mesh['pos'] @ M[:3, :3] + M[3, :3]
|
||||||
|
mins = np.minimum(mins, pw.min(0)); maxs = np.maximum(maxs, pw.max(0)); nv += len(pw)
|
||||||
|
if not np.isfinite(mins).all():
|
||||||
|
print('NO renderable geometry'); sys.exit(1)
|
||||||
|
center = (mins+maxs)/2; radius = float(np.linalg.norm(maxs-mins)/2) or 400.0
|
||||||
|
print(f'bounds min={mins.round(1)} max={maxs.round(1)} center={center.round(1)} radius={radius:.1f} verts={nv}')
|
||||||
|
|
||||||
|
eye = center + np.array([radius*0.5, -radius*0.8, radius*distmul])
|
||||||
|
fwd = center - eye; fwd /= np.linalg.norm(fwd); back = -fwd
|
||||||
|
right = np.cross([0.0,-1.0,0.0], back); right /= np.linalg.norm(right)
|
||||||
|
up = np.cross(back, right)
|
||||||
|
M = np.eye(4); M[0,:3],M[1,:3],M[2,:3],M[3,:3] = right, up, back, eye
|
||||||
|
r.cam_matrix = lambda _b, _M=M: _M
|
||||||
|
if c.view is not None and c.view in board.nodes:
|
||||||
|
vb = bytearray(board.nodes[c.view].get('body') or b'')
|
||||||
|
if len(vb) >= 56:
|
||||||
|
struct.pack_into('<f', vb, 52, 1.0e6); board.nodes[c.view]['body'] = bytes(vb)
|
||||||
|
r.draw(board)
|
||||||
|
r.pygame.image.save(r.screen, out)
|
||||||
|
print('wrote', out)
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
"""First-person cockpit camera from the player vehicle's 0x1f pose.
|
||||||
|
py fp_render.py <fifodump> <out.png> [upoff] [worldup_y] [fwd_sign]"""
|
||||||
|
import os, sys, struct
|
||||||
|
os.environ.setdefault('SDL_VIDEODRIVER', 'dummy')
|
||||||
|
import numpy as np
|
||||||
|
from _backend import pick_renderer, save_frame
|
||||||
|
from vrboard import VirtualBoard, Msg, A
|
||||||
|
Renderer, backend = pick_renderer()
|
||||||
|
|
||||||
|
data = open(sys.argv[1], 'rb').read()
|
||||||
|
out = sys.argv[2]
|
||||||
|
upoff = float(sys.argv[3]) if len(sys.argv) > 3 else 8.0
|
||||||
|
wuy = float(sys.argv[4]) if len(sys.argv) > 4 else 1.0 # worldup y sign
|
||||||
|
fsign = float(sys.argv[5]) if len(sys.argv) > 5 else 1.0 # nose sign
|
||||||
|
board = VirtualBoard()
|
||||||
|
off = 0
|
||||||
|
while off + 8 <= len(data):
|
||||||
|
if data[off:off + 4] != b'VPXM':
|
||||||
|
off += 1; continue
|
||||||
|
ln = struct.unpack_from('<I', data, off + 4)[0]
|
||||||
|
body = data[off + 8:off + 8 + ln]; off += 8 + ln
|
||||||
|
if len(body) >= 4:
|
||||||
|
a = struct.unpack_from('<I', body, 0)[0]
|
||||||
|
if a < 0x100:
|
||||||
|
try: board.handle(Msg(False, 0xff, a, body[4:]))
|
||||||
|
except Exception: pass
|
||||||
|
|
||||||
|
r = Renderer(w=832, h=512)
|
||||||
|
c = r.cache; c.maybe_rebuild(board)
|
||||||
|
anim = board.anim_abs
|
||||||
|
chain = c.cam_chain
|
||||||
|
h = None
|
||||||
|
if chain and chain[-1] in anim:
|
||||||
|
h = chain[-1]
|
||||||
|
elif anim:
|
||||||
|
h = max(anim, key=lambda k: float(np.abs(np.array(anim[k][9:12])).sum()))
|
||||||
|
if h is None:
|
||||||
|
print("no animated vehicle"); sys.exit(1)
|
||||||
|
f = anim[h]
|
||||||
|
R = np.array(f[:9]).reshape(3, 3); t = np.array(f[9:12])
|
||||||
|
fwd = fsign * R[2] # vehicle nose (+Z), sign-tunable
|
||||||
|
fwd = fwd / max(np.linalg.norm(fwd), 1e-6)
|
||||||
|
worldup = np.array([0.0, wuy, 0.0])
|
||||||
|
eye = t + worldup * upoff # up out of the cockpit
|
||||||
|
back = -fwd
|
||||||
|
right = np.cross(worldup, back); right /= max(np.linalg.norm(right), 1e-6)
|
||||||
|
up = np.cross(back, right)
|
||||||
|
M = np.eye(4)
|
||||||
|
M[0, :3], M[1, :3], M[2, :3], M[3, :3] = right, up, back, eye
|
||||||
|
r.cam_matrix = lambda _b, _M=M: _M
|
||||||
|
r.draw(board)
|
||||||
|
save_frame(r, out)
|
||||||
|
print(f"wrote {out} [{backend}] veh={hex(h)} eye={eye.round(1)} fwd={fwd.round(2)}")
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
[sdl]
|
||||||
|
output=opengl
|
||||||
|
priority=higher,higher
|
||||||
|
[dosbox]
|
||||||
|
memsize=32
|
||||||
|
machine=svga_s3
|
||||||
|
[cpu]
|
||||||
|
core=dynamic
|
||||||
|
cputype=pentium
|
||||||
|
cycles=max
|
||||||
|
[serial]
|
||||||
|
serial1=directserial realport:COM1 rxpollus:100 rxburst:16
|
||||||
|
serial2=disabled
|
||||||
|
[autoexec]
|
||||||
|
mount c "C:\VWE\TeslaRel410\ALPHA_1"
|
||||||
|
c:
|
||||||
|
cd \REL410\BT
|
||||||
|
set VIDEOFORMAT=svga
|
||||||
|
set BLASTER=A220 I5 D1 H5 P330 T6
|
||||||
|
set TEMP=c:\
|
||||||
|
set HEAPSIZE=15000000
|
||||||
|
set L4GAUGE=640x480x16
|
||||||
|
call setenv.bat r s n g
|
||||||
|
32rtm.exe -x
|
||||||
|
btl4opt.exe -egg testarn.egg
|
||||||
|
32rtm.exe -u
|
||||||
|
echo ALPHA1-RUN-DONE
|
||||||
|
pause
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
[sdl]
|
||||||
|
output=opengl
|
||||||
|
# higher,higher not highest: HIGH_PRIORITY_CLASS starved the host desktop;
|
||||||
|
# with the retry patches a rare dropout self-recovers (see gauge_rio.conf).
|
||||||
|
priority=higher,higher
|
||||||
|
[dosbox]
|
||||||
|
memsize=32
|
||||||
|
machine=svga_s3
|
||||||
|
[cpu]
|
||||||
|
core=dynamic
|
||||||
|
cputype=pentium
|
||||||
|
cycles=max
|
||||||
|
[sblaster]
|
||||||
|
sbtype=sb16
|
||||||
|
sbbase=220
|
||||||
|
irq=5
|
||||||
|
dma=1
|
||||||
|
hdma=5
|
||||||
|
[mixer]
|
||||||
|
# match the EMU8000s' native rate (no resample) and buffer ~60ms so brief
|
||||||
|
# emulation-thread stalls (RIO retry recovery) don't audibly chop
|
||||||
|
rate=44100
|
||||||
|
blocksize=1024
|
||||||
|
prebuffer=60
|
||||||
|
[serial]
|
||||||
|
# RIO on COM1 with the low-latency options (rxpollus/rxburst) so the board's
|
||||||
|
# few-ms ACK deadline is met; plasma COM2 later.
|
||||||
|
serial1=directserial realport:COM1 rxpollus:100 rxburst:16
|
||||||
|
serial2=disabled
|
||||||
|
[autoexec]
|
||||||
|
mount c "C:\VWE\TeslaRel410\ALPHA_1"
|
||||||
|
c:
|
||||||
|
cd \REL410\BT
|
||||||
|
set VIDEOFORMAT=svga
|
||||||
|
rem production pod card init (PARAMETR.BAT:181-186): DIAGNOSE + AWEUTIL per
|
||||||
|
rem card -- AWEUTIL /S does the EMU8000 bring-up and DRAM detect the HMI SOS
|
||||||
|
rem driver relies on; skipping it left the cards uninitialized (silent).
|
||||||
|
rem aweutil /s SKIPPED for now: it verifies the AWE32 GM ROM, which the
|
||||||
|
rem emulated cards lack (hangs in a retry loop) -- restore once the ROM is
|
||||||
|
rem dumped from a real card. diagnose /s kept (passes, sets mixer config).
|
||||||
|
set BLASTER=A220 I5 D1 H5 P330 T6
|
||||||
|
c:\sb16\diagnose /s
|
||||||
|
set BLASTER=A240 I7 D3 H6 P300 T6
|
||||||
|
c:\sb16\diagnose /s
|
||||||
|
set BLASTER=A220 I5 D1 H5 P330 T6
|
||||||
|
set TEMP=c:\
|
||||||
|
rem arena1 city mission (TESTARN.EGG: map=arena1, time=day) with the RIO
|
||||||
|
rem attached; stdout redirected so mission-load progress survives kills.
|
||||||
|
set HEAPSIZE=15000000
|
||||||
|
set L4GAUGE=640x480x16
|
||||||
|
call setenv.bat r s s g
|
||||||
|
32rtm.exe -x
|
||||||
|
btl4opt.exe -egg testarn.egg
|
||||||
|
32rtm.exe -u
|
||||||
|
echo ALPHA1-RUN-DONE
|
||||||
|
pause
|
||||||
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import os, sys, struct
|
||||||
|
sys.path.insert(0, r'C:\VWE\TeslaRel410\dpl3-revive\patha')
|
||||||
|
os.environ.setdefault('SDL_VIDEODRIVER', 'dummy')
|
||||||
|
import numpy as np
|
||||||
|
from vrboard import VirtualBoard, Msg, A
|
||||||
|
from vrview import Renderer
|
||||||
|
|
||||||
|
data = open(sys.argv[1], 'rb').read()
|
||||||
|
board = VirtualBoard()
|
||||||
|
off = 0
|
||||||
|
while off + 8 <= len(data):
|
||||||
|
if data[off:off + 4] != b'VPXM':
|
||||||
|
off += 1; continue
|
||||||
|
ln = struct.unpack_from('<I', data, off + 4)[0]
|
||||||
|
body = data[off + 8:off + 8 + ln]; off += 8 + ln
|
||||||
|
if len(body) >= 4:
|
||||||
|
a = struct.unpack_from('<I', body, 0)[0]
|
||||||
|
if a < 0x100:
|
||||||
|
try: board.handle(Msg(False, 0xff, a, body[4:]))
|
||||||
|
except Exception: pass
|
||||||
|
|
||||||
|
r = Renderer(w=832, h=512); c = r.cache; c.maybe_rebuild(board)
|
||||||
|
anim = board.anim_abs; chain = c.cam_chain
|
||||||
|
h = chain[-1] if (chain and chain[-1] in anim) else \
|
||||||
|
max(anim, key=lambda k: float(np.abs(np.array(anim[k][9:12])).sum()))
|
||||||
|
mech = np.array(anim[h][9:12])
|
||||||
|
print('MECH pos', mech.round(1))
|
||||||
|
if c.view and c.view in board.nodes:
|
||||||
|
vb = board.nodes[c.view]['body'] or b''
|
||||||
|
if len(vb) >= 96:
|
||||||
|
f = struct.unpack_from('<24f', vb, 0)
|
||||||
|
print('VIEW hither(near)=%.1f yon(far)=%.1f zeye=%.2f imageplane x[%.2f,%.2f] y[%.2f,%.2f]'
|
||||||
|
% (f[12], f[13], f[9], f[5], f[7], f[6], f[8]))
|
||||||
|
|
||||||
|
allpos = []
|
||||||
|
for inst in c.instances:
|
||||||
|
M = r.chain_matrix(board, inst['chain'])
|
||||||
|
for g in inst['geoms']:
|
||||||
|
mesh = c._mesh(board, g)
|
||||||
|
if not mesh or 'pos' not in mesh or len(mesh['pos']) == 0:
|
||||||
|
continue
|
||||||
|
pw = mesh['pos'] @ M[:3, :3] + M[3, :3]
|
||||||
|
allpos.append(pw)
|
||||||
|
allpos = np.concatenate(allpos)
|
||||||
|
print('ALL verts: %d Y[min=%.1f max=%.1f median=%.1f]'
|
||||||
|
% (len(allpos), allpos[:, 1].min(), allpos[:, 1].max(), np.median(allpos[:, 1])))
|
||||||
|
|
||||||
|
# geometry near the mech in the XZ plane (would be the ground under/around it)
|
||||||
|
d = np.sqrt((allpos[:, 0] - mech[0])**2 + (allpos[:, 2] - mech[2])**2)
|
||||||
|
for rad in (100, 300, 800, 2000):
|
||||||
|
near = allpos[d < rad]
|
||||||
|
if len(near):
|
||||||
|
print(' within %4d of mech: %6d verts Y[%.1f..%.1f] med %.1f'
|
||||||
|
% (rad, len(near), near[:, 1].min(), near[:, 1].max(), np.median(near[:, 1])))
|
||||||
|
else:
|
||||||
|
print(' within %4d of mech: 0 verts (NO geometry here)' % rad)
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# Launch the full pod experience: DOSBox-X pod (pentapus 7-display explode
|
||||||
|
# mode + dual-AWE32 sound) + Dave's GL renderer bridged to the live wire.
|
||||||
|
#
|
||||||
|
# .\launch_pod.ps1 # restart pod + bridge (stops previous run)
|
||||||
|
# .\launch_pod.ps1 -NoSound # skip AWE32 (saves the ~4 min SBK upload)
|
||||||
|
# .\launch_pod.ps1 -NoBridge # pod only
|
||||||
|
# .\launch_pod.ps1 -Conf <path> # different mission/conf
|
||||||
|
#
|
||||||
|
# Pentapus mode (VPX_EXPLODE=1): all 7 cockpit displays as desktop windows --
|
||||||
|
# 5 mono MFDs (green phosphor), portrait radar, Division main -- needs a wide
|
||||||
|
# desktop (~2830px). Per-window override: VPX_WIN<g>/VPX_MAIN = "x,y[,w,h]".
|
||||||
|
# Eye height for the bridge camera: FP_UPOFF env (see live_bridge.py).
|
||||||
|
param(
|
||||||
|
[string]$Conf = "$PSScriptRoot\gauge_arena_sound.conf",
|
||||||
|
[string]$Work = "$env:LOCALAPPDATA\Temp\vwe-pod",
|
||||||
|
[switch]$NoBridge,
|
||||||
|
[switch]$NoSound
|
||||||
|
)
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
New-Item -ItemType Directory -Force $Work | Out-Null
|
||||||
|
|
||||||
|
# a restart means the previous run dies first (COM1 + the fifodump are exclusive)
|
||||||
|
foreach ($pidfile in "$Work\pod_pid.txt", "$Work\bridge_pid.txt") {
|
||||||
|
if (Test-Path $pidfile) {
|
||||||
|
$old = Get-Process -Id (Get-Content $pidfile) -ErrorAction SilentlyContinue
|
||||||
|
if ($old) { Write-Host "stopping previous $([IO.Path]::GetFileNameWithoutExtension($pidfile)) ($($old.Id))"; $old | Stop-Process -Force }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# the bridge pid file records the py.exe wrapper; its python child holds the
|
||||||
|
# fifodump open (blocks the Remove-Item below) -- sweep it by command line
|
||||||
|
Get-CimInstance Win32_Process -Filter "Name like 'py%'" |
|
||||||
|
Where-Object { $_.CommandLine -match 'live_bridge' } |
|
||||||
|
ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
|
||||||
|
# fresh wire artifacts each run (the RIO tap APPENDS across runs otherwise --
|
||||||
|
# stale init sequences from the previous run poison diagnosis)
|
||||||
|
Remove-Item "$Work\live.fifodump", "$Work\riotap.txt" -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
$env:VPXLOG = "$Work\vpxresp.txt"
|
||||||
|
$env:VPX_RESPOND = '1'
|
||||||
|
$env:VPX_RENDER = '1'
|
||||||
|
$env:VPX_EXPLODE = '1' # pentapus: 7 cockpit displays
|
||||||
|
$env:VPX_DUMPDIR = $Work
|
||||||
|
$env:VPX_FIFODUMP = "$Work\live.fifodump"
|
||||||
|
$env:RIO_TAP = "$Work\riotap.txt"
|
||||||
|
if ($NoSound) {
|
||||||
|
Remove-Item Env:VWE_AWE32, Env:VWE_AWE_ROM -ErrorAction SilentlyContinue
|
||||||
|
} else {
|
||||||
|
$env:VWE_AWE32 = '1'
|
||||||
|
$env:VWE_AWE_ROM = (Resolve-Path "$PSScriptRoot\..\roms\awe32.raw").Path
|
||||||
|
}
|
||||||
|
|
||||||
|
$dosbox = (Resolve-Path "$PSScriptRoot\..\src\src\dosbox-x.exe").Path
|
||||||
|
$p = Start-Process $dosbox -ArgumentList '-conf', $Conf `
|
||||||
|
-RedirectStandardError "$Work\pod_err.txt" `
|
||||||
|
-RedirectStandardOutput "$Work\pod_out.txt" -PassThru
|
||||||
|
$p.Id | Set-Content "$Work\pod_pid.txt"
|
||||||
|
Write-Host "pod PID $($p.Id) conf=$([IO.Path]::GetFileName($Conf)) work=$Work"
|
||||||
|
if (-not $NoSound) { Write-Host "sound ON: boot adds ~4 min for the SoundFont upload" }
|
||||||
|
|
||||||
|
if (-not $NoBridge) {
|
||||||
|
# live_bridge waits for the fifodump itself; start it right away
|
||||||
|
$b = Start-Process py -ArgumentList '-3.13', "$PSScriptRoot\live_bridge.py", "$Work\live.fifodump" `
|
||||||
|
-RedirectStandardError "$Work\bridge_err.txt" `
|
||||||
|
-RedirectStandardOutput "$Work\bridge_out.txt" -PassThru
|
||||||
|
$b.Id | Set-Content "$Work\bridge_pid.txt"
|
||||||
|
Write-Host "bridge PID $($b.Id) [GL] (arrows in its window tune eye height)"
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Wire Dave's dpl3-revive renderer into our LIVE running pod.
|
||||||
|
|
||||||
|
Tails our device's VPX_FIFODUMP (VPXM records = the game's live VelociRender
|
||||||
|
wire), feeds each message into Dave's VirtualBoard, and renders every
|
||||||
|
draw_scene with his vrview software rasterizer in a real window -- using the
|
||||||
|
game's own camera (the player's RIO input drives it).
|
||||||
|
|
||||||
|
py live_bridge.py <live.fifodump>
|
||||||
|
"""
|
||||||
|
import os, sys, struct, time
|
||||||
|
import numpy as np
|
||||||
|
from _backend import pick_renderer
|
||||||
|
from vrboard import VirtualBoard, Msg, A
|
||||||
|
Renderer, backend = pick_renderer()
|
||||||
|
|
||||||
|
path = sys.argv[1]
|
||||||
|
board = VirtualBoard()
|
||||||
|
board.munga = False
|
||||||
|
r = Renderer(w=832, h=512,
|
||||||
|
title=f"dpl3-revive renderer (Dave) -- LIVE from our pod [{backend}]")
|
||||||
|
r.fps = int(os.environ.get('BRIDGE_FPS', '60' if backend == 'GL' else '30'))
|
||||||
|
# eye height above the mech origin (cockpit). Mutable: UP/DOWN arrows = +-5,
|
||||||
|
# LEFT/RIGHT = +-1, live (the render window must be focused). 35 was tuned
|
||||||
|
# before the ground rendered and is way too high against real terrain.
|
||||||
|
UPOFF = [float(os.environ.get('FP_UPOFF', '12'))]
|
||||||
|
|
||||||
|
def fp_cam(board, cache):
|
||||||
|
"""First-person cockpit camera from the player vehicle's 0x1f pose: eye at
|
||||||
|
the mech (+up), looking along its nose (+Z row), y-up world. Beats Dave's
|
||||||
|
11-DCS cam chain (which renders sky through our wire's convention)."""
|
||||||
|
anim = board.anim_abs
|
||||||
|
chain = cache.cam_chain
|
||||||
|
h = None
|
||||||
|
if chain and chain[-1] in anim:
|
||||||
|
h = chain[-1]
|
||||||
|
elif anim:
|
||||||
|
h = max(anim, key=lambda k: float(np.abs(np.array(anim[k][9:12])).sum()))
|
||||||
|
if h is None:
|
||||||
|
return None
|
||||||
|
f = anim[h]
|
||||||
|
R = np.array(f[:9]).reshape(3, 3)
|
||||||
|
t = np.array(f[9:12])
|
||||||
|
# look along the mech's travel direction. The +Z row rendered BACKWARD
|
||||||
|
# (user: "geometry moving away as I drive"), so forward = -Z row.
|
||||||
|
# FP_FWD_SIGN flips it back if needed.
|
||||||
|
fwd = float(os.environ.get('FP_FWD_SIGN', '-1')) * R[2]
|
||||||
|
n = np.linalg.norm(fwd)
|
||||||
|
if n < 1e-6:
|
||||||
|
return None
|
||||||
|
fwd = fwd / n
|
||||||
|
worldup = np.array([0.0, 1.0, 0.0])
|
||||||
|
eye = t + worldup * UPOFF[0]
|
||||||
|
back = -fwd
|
||||||
|
right = np.cross(worldup, back)
|
||||||
|
rn = np.linalg.norm(right)
|
||||||
|
if rn < 1e-6:
|
||||||
|
return None
|
||||||
|
right /= rn
|
||||||
|
up = np.cross(back, right)
|
||||||
|
# FP_RIGHT_SIGN=-1 mirrors the image X (verified: exact fliplr). Tried as a
|
||||||
|
# yaw fix 2026-07-06 but the inverted-yaw report was about the NATIVE C++
|
||||||
|
# render, not this bridge -- the mirror just flipped the arena layout and
|
||||||
|
# made the bridge yaw wrong too. Keep +1 (the validated look).
|
||||||
|
right *= float(os.environ.get('FP_RIGHT_SIGN', '1'))
|
||||||
|
M = np.eye(4)
|
||||||
|
M[0, :3], M[1, :3], M[2, :3], M[3, :3] = right, up, back, eye
|
||||||
|
return M
|
||||||
|
|
||||||
|
def render(board):
|
||||||
|
try:
|
||||||
|
pg = r.pygame
|
||||||
|
for ev in pg.event.get(): # drain before r.draw; tune eye height
|
||||||
|
if ev.type == pg.QUIT:
|
||||||
|
raise KeyboardInterrupt
|
||||||
|
if ev.type == pg.KEYDOWN:
|
||||||
|
step = {pg.K_UP: 5, pg.K_DOWN: -5,
|
||||||
|
pg.K_RIGHT: 1, pg.K_LEFT: -1}.get(ev.key)
|
||||||
|
if step:
|
||||||
|
UPOFF[0] = max(0.0, UPOFF[0] + step)
|
||||||
|
print(f"eye height = {UPOFF[0]:.0f}", flush=True)
|
||||||
|
r.cache.maybe_rebuild(board)
|
||||||
|
M = fp_cam(board, r.cache)
|
||||||
|
if M is not None:
|
||||||
|
r.cam_matrix = lambda _b, _M=M: _M
|
||||||
|
r.draw(board)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
raise
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print(f"waiting for {path} ...")
|
||||||
|
while not os.path.exists(path):
|
||||||
|
time.sleep(0.2)
|
||||||
|
f = open(path, 'rb')
|
||||||
|
print("tailing live wire -> Dave's renderer; drive the pod")
|
||||||
|
|
||||||
|
pending = b''
|
||||||
|
frames = 0
|
||||||
|
last_report = time.time()
|
||||||
|
while True:
|
||||||
|
chunk = f.read(1 << 20)
|
||||||
|
if chunk:
|
||||||
|
pending += chunk
|
||||||
|
off = 0
|
||||||
|
n = len(pending)
|
||||||
|
while n - off >= 8:
|
||||||
|
if pending[off:off + 4] != b'VPXM':
|
||||||
|
off += 1
|
||||||
|
continue
|
||||||
|
ln = struct.unpack_from('<I', pending, off + 4)[0]
|
||||||
|
if n - off < 8 + ln:
|
||||||
|
break # incomplete record: wait for more
|
||||||
|
body = pending[off + 8:off + 8 + ln]
|
||||||
|
off += 8 + ln
|
||||||
|
if len(body) >= 4:
|
||||||
|
action = struct.unpack_from('<I', body, 0)[0]
|
||||||
|
if action < 0x100:
|
||||||
|
try:
|
||||||
|
board.handle(Msg(False, 0xff, action, body[4:]))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if action == 9: # draw_scene -> present a frame
|
||||||
|
render(board)
|
||||||
|
frames += 1
|
||||||
|
pending = pending[off:]
|
||||||
|
if not chunk:
|
||||||
|
try: r.pump()
|
||||||
|
except KeyboardInterrupt: break
|
||||||
|
except Exception: pass
|
||||||
|
time.sleep(0.02)
|
||||||
|
if time.time() - last_report > 4:
|
||||||
|
last_report = time.time()
|
||||||
|
print(f"frames={frames} nodes={len(board.nodes)} "
|
||||||
|
f"uploads={len(board.uploads)} tex={len(board.tex)} "
|
||||||
|
f"munga={board.munga} anim_abs={len(board.anim_abs)}")
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Show OUR BTL4OPT arena (decoded by the friend's board from a captured wire
|
||||||
|
stream) in a real on-screen window, orbiting a synthetic camera around it.
|
||||||
|
Decode once, then orbit forever; close the window to stop."""
|
||||||
|
import os, sys, struct, math
|
||||||
|
sys.path.insert(0, r'C:\VWE\TeslaRel410\dpl3-revive\patha')
|
||||||
|
# real window (no SDL dummy)
|
||||||
|
import numpy as np
|
||||||
|
from vrboard import VirtualBoard, Assembler
|
||||||
|
from decode_anim import find_framed_start
|
||||||
|
from vrview import Renderer
|
||||||
|
|
||||||
|
cap = sys.argv[1] if len(sys.argv) > 1 else r'C:\VWE\TeslaRel410\dpl3-revive\patha\bt8.raw.bin'
|
||||||
|
upto = int(sys.argv[2]) if len(sys.argv) > 2 else 800
|
||||||
|
W, H = 720, 450
|
||||||
|
|
||||||
|
data = open(cap, 'rb').read()
|
||||||
|
board = VirtualBoard()
|
||||||
|
asm = Assembler(); asm.feed(data[find_framed_start(data):])
|
||||||
|
for m in asm:
|
||||||
|
try: board.handle(m)
|
||||||
|
except Exception: pass
|
||||||
|
if board.frames >= upto: break
|
||||||
|
print(f'decoded: nodes={len(board.nodes)} frames={board.frames}')
|
||||||
|
|
||||||
|
W, H = 560, 350
|
||||||
|
r = Renderer(w=W, h=H, title='VWE pod - Division render (dpl3-revive) - our BTL4OPT arena')
|
||||||
|
r.cache.maybe_rebuild(board)
|
||||||
|
c = r.cache
|
||||||
|
|
||||||
|
# aim at the mechs (anim_abs vehicle positions); ignore ones still at origin
|
||||||
|
mechs = np.array([np.array(f[9:12]) for f in board.anim_abs.values()])
|
||||||
|
real = mechs[np.abs(mechs).sum(1) > 1.0]
|
||||||
|
center = real.mean(0) if len(real) else np.zeros(3)
|
||||||
|
radius = 480.0 # close orbit around the mech/dome cluster
|
||||||
|
print(f'mechs={mechs.round(0).tolist()} target={center.round(1)} radius={radius}')
|
||||||
|
|
||||||
|
# lift the far clip so the whole arena renders (yon = view float idx 13, off 52)
|
||||||
|
if c.view is not None and c.view in board.nodes:
|
||||||
|
vb = bytearray(board.nodes[c.view].get('body') or b'')
|
||||||
|
if len(vb) >= 56:
|
||||||
|
struct.pack_into('<f', vb, 52, 1.0e6)
|
||||||
|
board.nodes[c.view]['body'] = bytes(vb)
|
||||||
|
|
||||||
|
def cam_at(angle_deg):
|
||||||
|
a = math.radians(angle_deg)
|
||||||
|
dist = radius * 2.2
|
||||||
|
# y-down world: negative y = up, so raise the eye with -height
|
||||||
|
eye = center + np.array([math.sin(a) * dist, -radius * 0.7, math.cos(a) * dist])
|
||||||
|
fwd = center - eye; fwd /= max(np.linalg.norm(fwd), 1e-6)
|
||||||
|
back = -fwd
|
||||||
|
right = np.cross([0.0, -1.0, 0.0], back); right /= max(np.linalg.norm(right), 1e-6)
|
||||||
|
up = np.cross(back, right)
|
||||||
|
M = np.eye(4)
|
||||||
|
M[0, :3], M[1, :3], M[2, :3], M[3, :3] = right, up, back, eye
|
||||||
|
return M
|
||||||
|
|
||||||
|
r.fps = 60
|
||||||
|
ang = 0.0
|
||||||
|
print('orbiting; close the window to stop')
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
M = cam_at(ang)
|
||||||
|
r.cam_matrix = lambda _b, _M=M: _M
|
||||||
|
r.draw(board) # renders + paces
|
||||||
|
ang = (ang + 2.0) % 360.0
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print('window closed')
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Phase 0 hero shot: replay a BTL4OPT capture, aim a synthetic camera at the
|
||||||
|
decoded animated mechs (anim_abs), lift the far-clip so the whole arena shows."""
|
||||||
|
import os, sys, struct
|
||||||
|
sys.path.insert(0, r'C:\VWE\TeslaRel410\dpl3-revive\patha')
|
||||||
|
os.environ.setdefault('SDL_VIDEODRIVER', 'dummy')
|
||||||
|
import numpy as np
|
||||||
|
from vrboard import VirtualBoard, Assembler
|
||||||
|
from decode_anim import find_framed_start
|
||||||
|
from vrview import Renderer
|
||||||
|
|
||||||
|
cap = sys.argv[1]
|
||||||
|
upto = int(sys.argv[2])
|
||||||
|
out = sys.argv[3]
|
||||||
|
dist = float(sys.argv[4]) if len(sys.argv) > 4 else 900.0
|
||||||
|
high = float(sys.argv[5]) if len(sys.argv) > 5 else 350.0
|
||||||
|
|
||||||
|
data = open(cap, 'rb').read()
|
||||||
|
board = VirtualBoard()
|
||||||
|
asm = Assembler(); asm.feed(data[find_framed_start(data):])
|
||||||
|
for m in asm:
|
||||||
|
try: board.handle(m)
|
||||||
|
except Exception: pass
|
||||||
|
if board.frames >= upto: break
|
||||||
|
|
||||||
|
r = Renderer(w=832, h=512)
|
||||||
|
r.cache.maybe_rebuild(board)
|
||||||
|
c = r.cache
|
||||||
|
|
||||||
|
# aim at the centroid of the animated vehicles (the mechs)
|
||||||
|
pts = np.array([np.array(f[9:12]) for f in board.anim_abs.values()])
|
||||||
|
target = pts.mean(0)
|
||||||
|
print('mech positions:'); print(pts.round(1))
|
||||||
|
print('target', target.round(1))
|
||||||
|
|
||||||
|
# camera: pulled back (+z), raised (-y is up in this y-down world)
|
||||||
|
eye = target + np.array([dist*0.4, -high, dist])
|
||||||
|
fwd = target - eye; fwd /= np.linalg.norm(fwd)
|
||||||
|
back = -fwd
|
||||||
|
worldup = np.array([0.0, -1.0, 0.0])
|
||||||
|
right = np.cross(worldup, back); right /= np.linalg.norm(right)
|
||||||
|
up = np.cross(back, right)
|
||||||
|
M = np.eye(4)
|
||||||
|
M[0,:3], M[1,:3], M[2,:3], M[3,:3] = right, up, back, eye
|
||||||
|
r.cam_matrix = lambda _b, _M=M: _M
|
||||||
|
|
||||||
|
# lift the far clip (yon = view-body float idx 13 -> byte offset 52) so the
|
||||||
|
# whole arena renders instead of being culled at the fog wall
|
||||||
|
if c.view is not None and c.view in board.nodes:
|
||||||
|
vb = bytearray(board.nodes[c.view].get('body') or b'')
|
||||||
|
if len(vb) >= 56:
|
||||||
|
struct.pack_into('<f', vb, 52, 1.0e6) # yon
|
||||||
|
board.nodes[c.view]['body'] = bytes(vb)
|
||||||
|
print('patched yon -> 1e6')
|
||||||
|
|
||||||
|
r.draw(board)
|
||||||
|
r.pygame.image.save(r.screen, out)
|
||||||
|
print('wrote', out)
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Phase 0: replay a captured BTL4OPT VelociRender stream through the friend's
|
||||||
|
virtual board and render the decoded world from a synthetic aerial camera
|
||||||
|
(the game's own camera path isn't decoded yet -> singular matrix). Proves their
|
||||||
|
renderer reconstructs OUR game's geometry+textures from the live wire.
|
||||||
|
|
||||||
|
py phase0_render.py <capture.raw.bin> <frames> <out.png> [distmul] [ymul]
|
||||||
|
"""
|
||||||
|
import os, sys
|
||||||
|
PATHA = r'C:\VWE\TeslaRel410\dpl3-revive\patha'
|
||||||
|
sys.path.insert(0, PATHA)
|
||||||
|
os.environ.setdefault('SDL_VIDEODRIVER', 'dummy')
|
||||||
|
import numpy as np
|
||||||
|
from vrboard import VirtualBoard, Assembler
|
||||||
|
from decode_anim import find_framed_start
|
||||||
|
from vrview import Renderer
|
||||||
|
|
||||||
|
cap = sys.argv[1]
|
||||||
|
upto = int(sys.argv[2]) if len(sys.argv) > 2 else 400
|
||||||
|
out = sys.argv[3]
|
||||||
|
distmul = float(sys.argv[4]) if len(sys.argv) > 4 else 1.6
|
||||||
|
ymul = float(sys.argv[5]) if len(sys.argv) > 5 else 1.2 # +up (y-down world -> negate below)
|
||||||
|
|
||||||
|
data = open(cap, 'rb').read()
|
||||||
|
board = VirtualBoard()
|
||||||
|
asm = Assembler(); asm.feed(data[find_framed_start(data):])
|
||||||
|
n = 0
|
||||||
|
for m in asm:
|
||||||
|
try:
|
||||||
|
board.handle(m)
|
||||||
|
except Exception as e:
|
||||||
|
n += 1
|
||||||
|
if board.frames >= upto:
|
||||||
|
break
|
||||||
|
print(f'replayed frame={board.frames} nodes={len(board.nodes)} '
|
||||||
|
f'geom={len(board.geom)} tex={len(board.tex)} handler_errs={n}')
|
||||||
|
|
||||||
|
r = Renderer(w=832, h=512)
|
||||||
|
r.cache.maybe_rebuild(board)
|
||||||
|
c = r.cache
|
||||||
|
print(f'instances={len(c.instances)}')
|
||||||
|
|
||||||
|
# world-space bounds over every renderable instance triangle
|
||||||
|
mins = np.array([np.inf]*3); maxs = np.array([-np.inf]*3)
|
||||||
|
nverts = 0
|
||||||
|
for inst in c.instances:
|
||||||
|
M = r.chain_matrix(board, inst['chain'])
|
||||||
|
for gh in inst['geoms']:
|
||||||
|
mesh = c._mesh(board, gh)
|
||||||
|
if not mesh or 'pos' not in mesh or len(mesh['pos']) == 0:
|
||||||
|
continue
|
||||||
|
pw = mesh['pos'] @ M[:3, :3] + M[3, :3]
|
||||||
|
mins = np.minimum(mins, pw.min(0)); maxs = np.maximum(maxs, pw.max(0))
|
||||||
|
nverts += len(pw)
|
||||||
|
if not np.isfinite(mins).all():
|
||||||
|
print('NO renderable geometry'); sys.exit(1)
|
||||||
|
center = (mins + maxs) / 2.0
|
||||||
|
size = maxs - mins
|
||||||
|
radius = float(np.linalg.norm(size) / 2.0) or 100.0
|
||||||
|
print(f'world bounds min={mins.round(1)} max={maxs.round(1)}')
|
||||||
|
print(f'center={center.round(1)} size={size.round(1)} radius={radius:.1f} verts={nverts}')
|
||||||
|
|
||||||
|
# synthetic aerial look-at. DPL world is y-DOWN, so "up above" = -y.
|
||||||
|
eye = center + np.array([radius*0.6, -radius*ymul, radius*distmul])
|
||||||
|
fwd = center - eye; fwd /= max(np.linalg.norm(fwd), 1e-6)
|
||||||
|
back = -fwd
|
||||||
|
worldup = np.array([0.0, -1.0, 0.0]) # y-down
|
||||||
|
right = np.cross(worldup, back); right /= max(np.linalg.norm(right), 1e-6)
|
||||||
|
up = np.cross(back, right)
|
||||||
|
M = np.eye(4)
|
||||||
|
M[0, :3], M[1, :3], M[2, :3], M[3, :3] = right, up, back, eye
|
||||||
|
|
||||||
|
# widen the far clip so the aerial distance doesn't cull the whole scene
|
||||||
|
r.cam_matrix = lambda _b, _M=M: _M
|
||||||
|
if c.view is not None and c.view in board.nodes:
|
||||||
|
pass # projection still read from the view body inside draw()
|
||||||
|
|
||||||
|
r.draw(board)
|
||||||
|
r.pygame.image.save(r.screen, out)
|
||||||
|
print('wrote', out)
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Play a captured BTL4OPT VelociRender stream back through the friend's board
|
||||||
|
+ software renderer in a REAL on-screen window (chase cam follows a mech).
|
||||||
|
Loops forever; close the window to stop.
|
||||||
|
|
||||||
|
py playback.py [capture.raw.bin] [WxH] [chase]
|
||||||
|
"""
|
||||||
|
import os, sys, time
|
||||||
|
sys.path.insert(0, r'C:\VWE\TeslaRel410\dpl3-revive\patha')
|
||||||
|
# real window: do NOT set SDL_VIDEODRIVER=dummy
|
||||||
|
cap = sys.argv[1] if len(sys.argv) > 1 else r'C:\VWE\TeslaRel410\dpl3-revive\patha\bt8.raw.bin'
|
||||||
|
size = sys.argv[2] if len(sys.argv) > 2 else '640x400'
|
||||||
|
os.environ['VRVIEW_CHASE'] = sys.argv[3] if len(sys.argv) > 3 else '2'
|
||||||
|
W, H = (int(x) for x in size.lower().split('x'))
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from vrboard import VirtualBoard, Assembler, A
|
||||||
|
from decode_anim import find_framed_start
|
||||||
|
from vrview import Renderer
|
||||||
|
|
||||||
|
data = open(cap, 'rb').read()
|
||||||
|
start = find_framed_start(data)
|
||||||
|
r = Renderer(w=W, h=H,
|
||||||
|
title='VWE pod - Division render (dpl3-revive board) - our BTL4OPT arena')
|
||||||
|
r.fps = 30
|
||||||
|
|
||||||
|
print(f'playing {cap} ({len(data)} bytes) in a {W}x{H} window; close it to stop')
|
||||||
|
try:
|
||||||
|
while True: # loop the capture
|
||||||
|
board = VirtualBoard(); board._view = r
|
||||||
|
asm = Assembler(); asm.feed(data[start:])
|
||||||
|
drew = 0
|
||||||
|
for m in asm:
|
||||||
|
try:
|
||||||
|
board.handle(m)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if (not m.iserver) and m.action == A.draw_scene:
|
||||||
|
r.draw(board) # renders (chase cam) + paces to fps
|
||||||
|
drew += 1
|
||||||
|
print(f' looped: {drew} frames drawn, restarting')
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print('window closed')
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import os, sys, struct
|
||||||
|
sys.path.insert(0, r'C:\VWE\TeslaRel410\dpl3-revive\patha')
|
||||||
|
os.environ.setdefault('SDL_VIDEODRIVER', 'dummy')
|
||||||
|
import numpy as np
|
||||||
|
from vrboard import VirtualBoard, Msg, A
|
||||||
|
from vrview import Renderer
|
||||||
|
|
||||||
|
data = open(sys.argv[1], 'rb').read()
|
||||||
|
board = VirtualBoard()
|
||||||
|
off = 0
|
||||||
|
while off + 8 <= len(data):
|
||||||
|
if data[off:off + 4] != b'VPXM':
|
||||||
|
off += 1; continue
|
||||||
|
ln = struct.unpack_from('<I', data, off + 4)[0]
|
||||||
|
body = data[off + 8:off + 8 + ln]; off += 8 + ln
|
||||||
|
if len(body) >= 4:
|
||||||
|
a = struct.unpack_from('<I', body, 0)[0]
|
||||||
|
if a < 0x100:
|
||||||
|
try: board.handle(Msg(False, 0xff, a, body[4:]))
|
||||||
|
except Exception: pass
|
||||||
|
|
||||||
|
r = Renderer(w=832, h=512); c = r.cache; c.maybe_rebuild(board)
|
||||||
|
anim = board.anim_abs; chain = c.cam_chain
|
||||||
|
h = chain[-1] if (chain and chain[-1] in anim) else \
|
||||||
|
max(anim, key=lambda k: float(np.abs(np.array(anim[k][9:12])).sum()))
|
||||||
|
mech = np.array(anim[h][9:12])
|
||||||
|
updir = float(sys.argv[3]) if len(sys.argv) > 3 else 1.0 # +1 or -1 for up axis
|
||||||
|
dist = float(sys.argv[4]) if len(sys.argv) > 4 else 900.0
|
||||||
|
# camera high above the mech, looking straight down; 'north' = +z as screen up
|
||||||
|
eye = mech + np.array([0.0, updir * dist, 0.0])
|
||||||
|
back = np.array([0.0, updir, 0.0]) # look -back = down
|
||||||
|
worldref = np.array([0.0, 0.0, 1.0])
|
||||||
|
right = np.cross(worldref, back); right /= max(np.linalg.norm(right), 1e-6)
|
||||||
|
up = np.cross(back, right)
|
||||||
|
M = np.eye(4); M[0, :3], M[1, :3], M[2, :3], M[3, :3] = right, up, back, eye
|
||||||
|
r.cam_matrix = lambda _b, _M=M: _M
|
||||||
|
# widen far clip so the whole area shows
|
||||||
|
if c.view and c.view in board.nodes:
|
||||||
|
vb = bytearray(board.nodes[c.view].get('body') or b'')
|
||||||
|
if len(vb) >= 56:
|
||||||
|
struct.pack_into('<f', vb, 52, 1.0e6)
|
||||||
|
board.nodes[c.view]['body'] = bytes(vb)
|
||||||
|
r.draw(board)
|
||||||
|
r.pygame.image.save(r.screen, sys.argv[2])
|
||||||
|
print(f"wrote {sys.argv[2]} mech={mech.round(1)} eye={eye.round(1)} up={updir}")
|
||||||
Reference in New Issue
Block a user