Weapon visuals + aim: beams render from the guns; pick self-target fixes

Renderer (dpl3-revive/patha):
- Instance visibility honored at DRAW time (stored word 4 = live
  dpl_SetInstanceVisibility field): laser beams (4-frame pulses) and
  missile models now render; cache tags w3 0/1 instances 'gated'
- PSFX world bursts (dcs=0 muzzle/impact/explosion) rendered; psfx
  storage list-based so one-shot bursts stop collapsing onto one key
- fix_degenerate applied to INSTANCE chains (was camera-only): the
  beam chain rides four rank-2 rig DCSs; composed rank-2 drew beams
  away from the guns. Offline f1635 now shows both arm beams
  converging on the aim point
- First-person canopy cull: the always-armed cockpit model at the
  head drew as a vertical line down screen center (user-confirmed
  fixed live)

Device (vpx-device/vpxlog.cpp):
- CAM backchannel on the fifosock: bridge streams its validated
  camera (+ vehicle root DCS); raycast_pick aims along the player's
  real look instead of the static view-node pose, which had locked
  every missile/beam onto one fixed wrong world point
- raycast_pick skips hidden-until-armed instances and the player's
  own articulation subtree: picking own beams created a target
  feedback loop, picking own missiles retargeted salvos mid-flight,
  and a picked missile's deleted DCS froze the game (arena2 crash)

Sound (vpx-device/vweawe.cpp): stereo-linked peak limiter replaces
the hard output clamp (the "generator out" crackle); VWE_AWE_LOG
reports pre-limit peaks + duck counts, enabled in launch_pod.ps1.
Missile-fire crackle persists: per-voice EMU8000 clamp suspected.

Eggs/confs: TESTAR2.EGG + gauge_arena2_sound.conf (map=arena2 city
variant); cavern.egg (console-era reference); plasma display on COM2
in the gauge confs; gauge_arena_net_sound.conf (slirp net stack).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-08 00:09:03 -05:00
co-authored by Claude Fable 5
parent 2bb2ff7302
commit cbf7015620
16 changed files with 830 additions and 167 deletions
+89 -1
View File
@@ -463,8 +463,16 @@ class GLRenderer(vrview.Renderer):
FIX[:3, :3] = [[0, 0, -1], [0, 1, 0], [1, 0, 0]]
for inst in c.instances:
Mw = self.chain_matrix(board, inst['chain'])
if not vrview.inst_visible(board.nodes, inst):
continue
# fix_degenerate: see vrview draw loop (rank-2 rig DCSs skewed
# the laser beam off the gun)
Mw = self.chain_matrix(board, inst['chain'], fix_degenerate=True)
M = Mw if inst.get('hud') else Mw @ V
# first-person canopy cull (see SceneCache radius note in vrview)
if (inst.get('gated') and not inst.get('hud')
and np.linalg.norm(M[3, :3]) + inst.get('radius', 0) < 10.0):
continue
if inst['chain'] and inst['chain'][0] in board.anim:
M = FIX @ M
if inst['billboard']:
@@ -488,6 +496,7 @@ class GLRenderer(vrview.Renderer):
self._draw_geom(board, c, gh, mesh, M, Mw)
self._draw_particles(board, V, vp, self.skip / 60.0)
self._draw_psfx(board, V)
self._draw_hud2d_gl(board, vp)
# present: FBO -> window with the DAC gamma
@@ -686,3 +695,82 @@ class GLRenderer(vrview.Renderer):
self._points_vao(data).render(mgl.POINTS, vertices=len(data))
self._fbo.depth_mask = True
ctx.disable(mgl.BLEND)
def _draw_psfx(self, board, V):
"""Triggered PSFX (0x1d) as glowing additive discs, two kinds:
- DCS-attached bolts (weapon fire): the emitter rides the flying
projectile DCS, so the glow tracks the shot. We keep a short trail of
recent world positions and draw a fading tail, so the continuous
particle stream (rate ~300/s) reads as a streak, not a lone dot. The
bolt lives until the projectile DCS is deleted (impact).
- DCS=0 world bursts (muzzle flash, impact spark, explosion): a fixed
world-space burst at (px,py,pz); big-radius explosions expand and the
colour lerps from the start interior toward the end interior over the
burst's ttl, then it fades out."""
if not board.psfx:
return
cache = {}
rows, live = [], []
for e in board.psfx:
dcs = e['dcs']
if dcs:
nd = board.nodes.get(dcs)
if not nd or nd.get('type') != 5:
continue # projectile consumed (impact)
m = cache.get(dcs)
if m is None:
try:
m = self.chain_matrix(board, self._chain(board, dcs, links=True))
except Exception:
m = self.dcs_matrix(board, dcs)
cache[dcs] = m
pos = np.asarray(m[3, :3], float)
else:
e['ttl'] -= 1
if e['ttl'] <= 0:
continue
pos = np.asarray(e['pos'], float)
if not np.all(np.isfinite(pos)):
continue
e['age'] += 1
live.append(e)
frac = e['age'] / max(e['maxttl'], 1) # 0..1 over life
col = np.clip((1.0 - frac) * np.array(e['col'])
+ frac * np.array(e['ecol']), 0.0, 2.0)
a0 = min(e['alpha'], 1.0)
if dcs: # bolt + trail
tr = e.setdefault('trail', [])
tr.append(pos)
if len(tr) > 9:
del tr[0]
nt = len(tr)
for k, tp in enumerate(tr):
w = (k + 1) / nt # head brightest
rows.append([tp[0], tp[1], tp[2],
max(e['rad'] * (0.35 + 0.65 * w), 1.2),
col[0], col[1], col[2], a0 * w * w])
else: # world burst
grow = 1.0 + min(e['exp'], 12.0) * 0.09 * frac # explosions swell
fade = min(1.0, e['ttl'] / 10.0)
rows.append([pos[0], pos[1], pos[2], max(e['rad'] * grow, 1.5),
col[0], col[1], col[2], a0 * fade])
board.psfx = live
if not rows:
return
data = np.array(rows, '<f4')
# back-to-front for the order-dependent additive-over blend
pe_z = data[:, 0:3] @ V[:3, :3] + V[3, :3]
data = data[np.argsort(pe_z[:, 2])]
ctx, mgl = self.ctx, self.moderngl
prog = self._point_prog
self._mat(prog, 'u_mv', V)
self._set(prog, 'u_minpr', 0.6)
self._set(prog, 'u_maxpr', float(self.h * 0.22))
self._set(prog, 'u_particle', 1)
ctx.enable(mgl.BLEND)
ctx.blend_func = (mgl.ONE, mgl.ONE_MINUS_SRC_ALPHA)
self._fbo.depth_mask = False
self._points_vao(data).render(mgl.POINTS, vertices=len(data))
self._fbo.depth_mask = True
ctx.disable(mgl.BLEND)