diff --git a/dpl3-revive/patha/vrview.py b/dpl3-revive/patha/vrview.py index 3dc177b..7de8c57 100644 --- a/dpl3-revive/patha/vrview.py +++ b/dpl3-revive/patha/vrview.py @@ -450,6 +450,20 @@ class SceneCache: lods.append((sw_in, sw_out, lg)) if not lods: continue + # BT's wire LOD bodies carry no usable switch distances (zeros -> + # the (0,1e9) fallback on every lod), and much of its content is + # authored ADDITIVE_LODS (BGF SV_SPECIAL: the arena walls are a + # posts-lod + panel-lod that draw TOGETHER). Range-select over + # identical windows always took lods[0] -- the wall posts without + # the wall. When every window is the degenerate fallback, draw + # the union of all lods; genuine distance windows keep switching. + if len(lods) > 1 and all(a == 0.0 and b >= 1e8 for a, b, _ in lods): + allg = [] + for _, _, lg in lods: + for g in lg: + if g not in allg: + allg.append(g) + lods = [(0.0, 1e9, allg)] # MUNGA: vehicle/mech part instances get their world pose through # the same dcs_link rig as the camera (torso 0x1f-animated) -- # nest-only chains left the player mech rendering at world origin. diff --git a/emulator/render-bridge/live_bridge.py b/emulator/render-bridge/live_bridge.py index d569c7d..1c05014 100644 --- a/emulator/render-bridge/live_bridge.py +++ b/emulator/render-bridge/live_bridge.py @@ -303,4 +303,5 @@ while True: 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)}", flush=True) + f"munga={board.munga} anim_abs={len(board.anim_abs)} " + f"backlog={len(pending)}B", flush=True) diff --git a/emulator/vpx-device/vpxlog.cpp b/emulator/vpx-device/vpxlog.cpp index ab8b7ae..e361565 100644 --- a/emulator/vpx-device/vpxlog.cpp +++ b/emulator/vpx-device/vpxlog.cpp @@ -581,13 +581,25 @@ static Bitu vpx_read(Bitu port, Bitu /*iolen*/) { no_pick = getenv("VPX_NO_PICK") ? 1 : 0; unsigned pinst = 0, pdcs = 0, pgg = 0, pgeom = 0; float pxyz[3] = { 0, 0, 0 }; - if (!no_pick && sect_armed && - raycast_pick(&pinst, &pdcs, &pgg, &pgeom, pxyz)) { - queue_sect_frame_reply(pinst, pdcs, pgg, pgeom, pxyz); + if (!no_pick && sect_armed) { + /* ALWAYS answer the armed pick, hit or + * miss. A miss = all-zero reply: the game + * clears targetEntity (L4VIDEO: no hit -> + * NULL) so aiming at open sky un-targets + * instead of freezing the LAST hit -- the + * old hit-only path made the aim go STALE + * whenever the ray cleared the arena + * walls (plain acks carry no sect data, + * so the game kept seconds-old targets). */ + bool hit = raycast_pick(&pinst, &pdcs, + &pgg, &pgeom, pxyz); + queue_sect_frame_reply(pinst, pdcs, pgg, + pgeom, pxyz); if (vpx_fp) { flush_run(); fprintf(vpx_fp, "# post-boot: frame ack " - "(9) + sect inst=%08X dcs=%08X\n", - pinst, pdcs); fflush(vpx_fp); } + "(9) + sect %s inst=%08X\n", + hit ? "hit" : "MISS", pinst); + fflush(vpx_fp); } } else { queue_render_ack(9); if (vpx_fp) { flush_run(); @@ -2201,6 +2213,27 @@ static float ray_tri(const float O[3], const float D[3], * and the world hit point. Same picking Dave's renderer does, self-contained. * Traversal mirrors scene_publish_frame: dcs -> instance -> object -> lod[0] -> * geogroup -> geometry -> polys, transformed by the DCS world matrix. */ +/* Pick outcome stats, printed to the VPXLOG every 256 calls: the aim only + * refreshes game-side when a sect reply lands, so a high miss rate IS the + * "stale aim" symptom. fail reasons: 1=no camera, 2=degenerate dir, 3=ray + * hit nothing (aim above the walls / into open sky). */ +static unsigned pick_calls = 0, pick_oks = 0, pick_bridge = 0; +static unsigned pick_fails[4]; + +static void pick_stat_print(void) { + if (++pick_calls % 256 || !vpx_fp) return; + fprintf(vpx_fp, "# pick stats: calls=%u ok=%u (bridge=%u) " + "fail nocam=%u dir=%u nohit=%u\n", + pick_calls, pick_oks, pick_bridge, + pick_fails[1], pick_fails[2], pick_fails[3]); + fflush(vpx_fp); +} +static void pick_stat_fail(int r) { pick_fails[r & 3]++; pick_stat_print(); } +static void pick_stat_ok(bool bridge) { + pick_oks++; if (bridge) pick_bridge++; + pick_stat_print(); +} + static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out, unsigned *geom_out, float xyz_out[3]) { /* Ray = the player's actual look: prefer the bridge's camera (cam chain @@ -2211,12 +2244,12 @@ static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out float O[3], D[3]; const bool from_bridge = bridge_cam_get(O, D); if (!from_bridge) { - if (!S.view.has_cam) return false; + if (!S.view.has_cam) { pick_stat_fail(1); return false; } O[0] = S.view.eye[0]; O[1] = S.view.eye[1]; O[2] = S.view.eye[2]; D[0] = -S.view.rot[6]; D[1] = -S.view.rot[7]; D[2] = -S.view.rot[8]; } float dl = sqrtf(D[0]*D[0] + D[1]*D[1] + D[2]*D[2]); - if (dl < 1e-6f) return false; + if (dl < 1e-6f) { pick_stat_fail(2); return false; } D[0] /= dl; D[1] /= dl; D[2] /= dl; /* Report the TRUE nearest hit -- terrain IS a valid target (you must be * able to fire into the ground and miss). Return ALL of the hit handles: @@ -2269,8 +2302,14 @@ static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out std::map >::const_iterator li = S.children.find(oi->second); if (li == S.children.end() || li->second.empty()) continue; + /* Walk ALL lod children, not just lod[0]: BT content is authored + * ADDITIVE_LODS (the arena wall = a posts lod + the actual wall + * panel in ANOTHER lod). Picking lod[0] only made the ray pass + * BETWEEN the posts of whatever the player aimed at -- 94% miss + * rate, stale/cleared targets, boo-beep on every trigger. */ + for (size_t ldx = 0; ldx < li->second.size(); ldx++) { std::map >::const_iterator ggi = - S.children.find(li->second[0]); /* lod[0] */ + S.children.find(li->second[ldx]); if (ggi == S.children.end()) continue; for (size_t g = 0; g < ggi->second.size(); g++) { unsigned gg = ggi->second[g]; /* geogroup handle */ @@ -2308,9 +2347,11 @@ static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out } } } + } /* all-lods walk */ } } - if (!best_inst) return false; + if (!best_inst) { pick_stat_fail(3); return false; } + pick_stat_ok(from_bridge); xyz_out[0] = O[0] + D[0] * best_t; xyz_out[1] = O[1] + D[1] * best_t; xyz_out[2] = O[2] + D[2] * best_t;