Aim pipeline: all-LODs raycast, always-answer picks, own-mech exclusion
The stale-aim saga, root-caused live with the operator: - The device raycast walked only lod[0] of each object; BT content is authored ADDITIVE_LODS (arena wall = posts lod + panel lod), so the aim ray flew BETWEEN the skeleton pieces of whatever was targeted -- 94% miss rate (pick stats now log this). - Misses fell back to a plain frame ack carrying no sect data, so the game kept a seconds-old target: "aim updates every few seconds", stationary shots stale. The pick is now answered EVERY armed frame; a miss is a valid all-zero reply that clears targetEntity (aiming at open sky un-targets -- authentic misfire, not stale fire). - Raycast walks ALL lod children; own-articulation exclusion via the CAM backchannel 7th field (vehicle root DCS) stops mid-volley picks of the player's own muzzle flash / arms. Renderer: same ADDITIVE_LODS fix -- when every LOD switch window is the degenerate (0,1e9) fallback, draw the union of all lods. The arena wall ring (user-confirmed live) went from floating post slivers to the full textured wall. Bridge reports its wire backlog. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -450,6 +450,20 @@ class SceneCache:
|
|||||||
lods.append((sw_in, sw_out, lg))
|
lods.append((sw_in, sw_out, lg))
|
||||||
if not lods:
|
if not lods:
|
||||||
continue
|
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
|
# MUNGA: vehicle/mech part instances get their world pose through
|
||||||
# the same dcs_link rig as the camera (torso 0x1f-animated) --
|
# the same dcs_link rig as the camera (torso 0x1f-animated) --
|
||||||
# nest-only chains left the player mech rendering at world origin.
|
# nest-only chains left the player mech rendering at world origin.
|
||||||
|
|||||||
@@ -303,4 +303,5 @@ while True:
|
|||||||
last_report = time.time()
|
last_report = time.time()
|
||||||
print(f"frames={frames} nodes={len(board.nodes)} "
|
print(f"frames={frames} nodes={len(board.nodes)} "
|
||||||
f"uploads={len(board.uploads)} tex={len(board.tex)} "
|
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)
|
||||||
|
|||||||
@@ -581,13 +581,25 @@ static Bitu vpx_read(Bitu port, Bitu /*iolen*/) {
|
|||||||
no_pick = getenv("VPX_NO_PICK") ? 1 : 0;
|
no_pick = getenv("VPX_NO_PICK") ? 1 : 0;
|
||||||
unsigned pinst = 0, pdcs = 0, pgg = 0, pgeom = 0;
|
unsigned pinst = 0, pdcs = 0, pgg = 0, pgeom = 0;
|
||||||
float pxyz[3] = { 0, 0, 0 };
|
float pxyz[3] = { 0, 0, 0 };
|
||||||
if (!no_pick && sect_armed &&
|
if (!no_pick && sect_armed) {
|
||||||
raycast_pick(&pinst, &pdcs, &pgg, &pgeom, pxyz)) {
|
/* ALWAYS answer the armed pick, hit or
|
||||||
queue_sect_frame_reply(pinst, pdcs, pgg, pgeom, pxyz);
|
* 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();
|
if (vpx_fp) { flush_run();
|
||||||
fprintf(vpx_fp, "# post-boot: frame ack "
|
fprintf(vpx_fp, "# post-boot: frame ack "
|
||||||
"(9) + sect inst=%08X dcs=%08X\n",
|
"(9) + sect %s inst=%08X\n",
|
||||||
pinst, pdcs); fflush(vpx_fp); }
|
hit ? "hit" : "MISS", pinst);
|
||||||
|
fflush(vpx_fp); }
|
||||||
} else {
|
} else {
|
||||||
queue_render_ack(9);
|
queue_render_ack(9);
|
||||||
if (vpx_fp) { flush_run();
|
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.
|
* and the world hit point. Same picking Dave's renderer does, self-contained.
|
||||||
* Traversal mirrors scene_publish_frame: dcs -> instance -> object -> lod[0] ->
|
* Traversal mirrors scene_publish_frame: dcs -> instance -> object -> lod[0] ->
|
||||||
* geogroup -> geometry -> polys, transformed by the DCS world matrix. */
|
* 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,
|
static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out,
|
||||||
unsigned *geom_out, float xyz_out[3]) {
|
unsigned *geom_out, float xyz_out[3]) {
|
||||||
/* Ray = the player's actual look: prefer the bridge's camera (cam chain
|
/* 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];
|
float O[3], D[3];
|
||||||
const bool from_bridge = bridge_cam_get(O, D);
|
const bool from_bridge = bridge_cam_get(O, D);
|
||||||
if (!from_bridge) {
|
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];
|
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];
|
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]);
|
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;
|
D[0] /= dl; D[1] /= dl; D[2] /= dl;
|
||||||
/* Report the TRUE nearest hit -- terrain IS a valid target (you must be
|
/* 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:
|
* 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<unsigned, std::vector<unsigned> >::const_iterator li =
|
std::map<unsigned, std::vector<unsigned> >::const_iterator li =
|
||||||
S.children.find(oi->second);
|
S.children.find(oi->second);
|
||||||
if (li == S.children.end() || li->second.empty()) continue;
|
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<unsigned, std::vector<unsigned> >::const_iterator ggi =
|
std::map<unsigned, std::vector<unsigned> >::const_iterator ggi =
|
||||||
S.children.find(li->second[0]); /* lod[0] */
|
S.children.find(li->second[ldx]);
|
||||||
if (ggi == S.children.end()) continue;
|
if (ggi == S.children.end()) continue;
|
||||||
for (size_t g = 0; g < ggi->second.size(); g++) {
|
for (size_t g = 0; g < ggi->second.size(); g++) {
|
||||||
unsigned gg = ggi->second[g]; /* geogroup handle */
|
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[0] = O[0] + D[0] * best_t;
|
||||||
xyz_out[1] = O[1] + D[1] * best_t;
|
xyz_out[1] = O[1] + D[1] * best_t;
|
||||||
xyz_out[2] = O[2] + D[2] * best_t;
|
xyz_out[2] = O[2] + D[2] * best_t;
|
||||||
|
|||||||
Reference in New Issue
Block a user