From 1b041d5a9e1273fea7c6d217453acb5413dd26c3 Mon Sep 17 00:00:00 2001 From: Cyd Date: Thu, 9 Jul 2026 00:13:11 -0500 Subject: [PATCH] Bridge: frame coalescing -- the view can no longer fall behind real time The main loop rendered EVERY draw_scene in arrival order, so any render slowdown played the mission slower than real time and the view drifted seconds behind the game (user: "renderer fell way out of sync"); the backpressure hides in the socket, so backlog= stayed 0. Now each pass slices all complete records, applies every state record in order, and presents only the newest frame; superseded presents are counted in the new skipped= report field. Live verdict over a full mission: frames=10169 skipped=3 backlog=0B -- locked to the wire. Also from the tuner session: seat trim granularity 0.1 (0.5 was too jumpy) and a HUD size trim (vrview_gl u_scale + VRVIEW_HUDSCALE env, live +/- keys) -- tuners stay enabled for pilot feedback; seat trim turns out to be per-mech (Mad Cat vs Thor want different values). Co-Authored-By: Claude Fable 5 --- dpl3-revive/patha/vrview_gl.py | 8 ++- emulator/render-bridge/live_bridge.py | 83 +++++++++++++++++++-------- 2 files changed, 67 insertions(+), 24 deletions(-) diff --git a/dpl3-revive/patha/vrview_gl.py b/dpl3-revive/patha/vrview_gl.py index a326003..5120365 100644 --- a/dpl3-revive/patha/vrview_gl.py +++ b/dpl3-revive/patha/vrview_gl.py @@ -201,6 +201,7 @@ void main() { // Division DAC gamma, once over the final image HUD_VS = """ #version 330 uniform vec4 u_rect; // view rect x0 y0 x1 y1 (dpl2d coordinate space) +uniform float u_scale; // HUD size trim (VRVIEW_HUDSCALE / live +/- keys) in vec2 in_pos; void main() { vec2 p = vec2((in_pos.x - u_rect.x) / (u_rect.z - u_rect.x), @@ -209,10 +210,14 @@ void main() { // twist dial UNDER the reticle and the range bar filling top-to-bottom; // mapping straight onto GL's Y-up NDC rendered the whole HUD flipped. p.y = -p.y; - gl_Position = vec4(p, 0.0, 1.0); + gl_Position = vec4(p * u_scale, 0.0, 1.0); } """ +# HUD size trim, live-adjustable from the bridge (+/- keys); pin with +# VRVIEW_HUDSCALE once the right size is found vs period footage. +HUDSCALE = [float(os.environ.get('VRVIEW_HUDSCALE', '1.0'))] + HUD_FS = """ #version 330 uniform vec3 u_col; @@ -566,6 +571,7 @@ class GLRenderer(vrview.Renderer): self._set(self._hud_prog, 'u_rect', (float(vp['x0']), float(vp['y0']), float(vp['x1']), float(vp['y1']))) + self._set(self._hud_prog, 'u_scale', float(HUDSCALE[0])) MODES = {'strip': mgl.LINE_STRIP, 'segs': mgl.LINES, 'poly': mgl.TRIANGLE_FAN, 'points': mgl.POINTS} for kind, col, wd, pts in prims: diff --git a/emulator/render-bridge/live_bridge.py b/emulator/render-bridge/live_bridge.py index ef9eb37..62fa130 100644 --- a/emulator/render-bridge/live_bridge.py +++ b/emulator/render-bridge/live_bridge.py @@ -21,11 +21,14 @@ 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 TRIM on top of the camera position (UP/DOWN arrows = +-5, -# LEFT/RIGHT = +-1, live; render window must be focused). The chain camera -# carries the true cockpit eye so the default trim is 0; the vehicle-root -# fallback adds VEH_EYE below (12 was the hand-tuned value). +# eye TRIM on top of the camera position (live; render window must be +# focused): UP/DOWN = height +-1, LEFT/RIGHT = seat forward/back +-0.5 +# along the look direction (user: the canopy reads as "sitting too far +# back" -- tune, then pin with FP_UPOFF / FP_FWDOFF). The chain camera +# carries the true cockpit eye so the default trims are 0; the vehicle- +# root fallback adds VEH_EYE below (12 was the hand-tuned value). UPOFF = [float(os.environ.get('FP_UPOFF', '0'))] +FWDOFF = [float(os.environ.get('FP_FWDOFF', '0'))] VEH_EYE = 12.0 # the un-overridden munga cam-chain method (fp_cam overrides r.cam_matrix # every frame, so grab the bound original once) @@ -137,6 +140,7 @@ def fp_cam(board, cache): cs, sn = math.cos(th), math.sin(th) fwd = np.array([cs * fwd[0] + sn * fwd[2], fwd[1], -sn * fwd[0] + cs * fwd[2]]) + eye = eye + fwd * FWDOFF[0] # seat forward/back trim back = -fwd right = np.cross(worldup, back) rn = np.linalg.norm(right) @@ -180,11 +184,26 @@ def render(board): 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) + hstep = {pg.K_UP: 1, pg.K_DOWN: -1}.get(ev.key) + fstep = {pg.K_RIGHT: 0.1, pg.K_LEFT: -0.1}.get(ev.key) + sstep = {pg.K_EQUALS: 0.05, pg.K_MINUS: -0.05, + pg.K_KP_PLUS: 0.05, pg.K_KP_MINUS: -0.05}.get(ev.key) + if hstep: + UPOFF[0] += hstep + if fstep: + FWDOFF[0] += fstep + if sstep: + try: + import vrview_gl + vrview_gl.HUDSCALE[0] = max( + 0.2, vrview_gl.HUDSCALE[0] + sstep) + print(f"HUD scale = {vrview_gl.HUDSCALE[0]:.2f}", + flush=True) + except Exception: + pass + if hstep or fstep: + print(f"eye trim: height {UPOFF[0]:+.1f} " + f"forward {FWDOFF[0]:+.1f}", flush=True) r.cache.maybe_rebuild(board) M = fp_cam(board, r.cache) if M is not None: @@ -270,11 +289,20 @@ print("tailing live wire -> Dave's renderer; drive the pod") pending = b'' frames = 0 +skipped = 0 last_report = time.time() while True: chunk = read_chunk() if chunk: pending += chunk + # Slice ALL complete records out of the buffer first, so we know which + # draw_scene is the newest. Rendering every draw_scene in arrival order + # means any render slowdown plays the mission slower than real time and + # the view drifts seconds behind the game (backpressure hides in the + # socket, not in len(pending)). State records all still apply, in order; + # only superseded frame PRESENTS are skipped -- the view latches to the + # freshest frame no matter how slow GL is. + records = [] off = 0 n = len(pending) while n - off >= 8: @@ -284,22 +312,31 @@ while True: ln = struct.unpack_from('= 4: - action = struct.unpack_from(' present a frame + pending = pending[off:] + last_draw = -1 + for i, body in enumerate(records): + if len(body) >= 4 and struct.unpack_from(' present a frame + if i == last_draw: render(board) frames += 1 - pending = pending[off:] + else: + skipped += 1 if not chunk: try: r.pump() except KeyboardInterrupt: break @@ -308,7 +345,7 @@ while True: time.sleep(0.02) # socket mode already waited in recv if time.time() - last_report > 4: last_report = time.time() - print(f"frames={frames} nodes={len(board.nodes)} " + print(f"frames={frames} skipped={skipped} nodes={len(board.nodes)} " f"uploads={len(board.uploads)} tex={len(board.tex)} " f"munga={board.munga} anim_abs={len(board.anim_abs)} " f"backlog={len(pending)}B", flush=True)