Files
TeslaRel410/emulator/render-bridge/phase0_render.py
T
CydandClaude Fable 5 0f5b2e28da 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>
2026-07-06 13:34:02 -05:00

81 lines
2.9 KiB
Python

#!/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)