Pick: authentic per-instance INTERSECT mask replaces the interim gates

Instance body word 9 is the INTERSECT mask (verified live: terrain,
sky, buildings and enemy replicant mechs carry 0xffffffff; the player's
own mech parts, laser beams, missiles and effects carry 0). The raycast
now ANDs it against the query mask from the 0x26 arm -- exactly the
real board's exclusion. The min-range and hidden-until-armed gates
retire: point-blank enemy hulls are targetable again, and own-ordnance
exclusion is authored by the game itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-08 20:46:40 -05:00
co-authored by Claude Fable 5
parent c13613e259
commit 419c7d5d63
+26 -23
View File
@@ -72,6 +72,7 @@ static int in_len = 0, in_pos = 0;
* NULL => every weapon misfires ("boo-beep"). A real i860 answered this each
* frame; our HLE never did. We now carry a real scene hit in the frame ack. */
static bool sect_armed = false;
static unsigned sect_mask = 0xffffffffu; /* query mask from the 0x26 arm */
/* Real reticle raycast (Moller-Trumbore against the live scene, center ray). */
static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out,
unsigned *geom_out, float xyz_out[3]);
@@ -789,6 +790,7 @@ static struct VScene {
/* game (full DPL) hierarchy: instance placement + articulation */
std::map<unsigned, unsigned> inst_object; /* instance -> object */
std::map<unsigned, unsigned> inst_w3; /* instance -> display mode word3 */
std::map<unsigned, unsigned> inst_mask; /* instance -> INTERSECT mask */
std::map<unsigned, M16> dcs_mat; /* dcs -> local matrix */
std::map<unsigned, unsigned> dcs_parent; /* dcs_link child->parent */
/* wire lights: dpl3-revive decode -- lmodel (type 6) / light (type 0xe)
@@ -2113,6 +2115,11 @@ static void scene_burst(const unsigned char *p, size_t n) {
* 2=billboard, 0/1=HIDDEN until armed (the parked player mech
* is w3=1 pre-translocation). Stored for the assembly skip. */
if (nb >= 20) S.inst_w3[name] = rd_u32(d + 16);
/* INTERSECT mask @d+40 (verified live: buildings/floor/sky =
* 0xffffffff, the player's own mech parts + beams + missiles
* = 0). The AUTHENTIC pick exclusion: the game's sect query
* ANDs its mask against this. */
if (nb >= 44) S.inst_mask[name] = rd_u32(d + 40);
for (size_t o = 8; o + 3 < nb; o += 4) {
unsigned val = rd_u32(d + o);
if (val && val != 0xFFFFFFFFu) {
@@ -2196,8 +2203,11 @@ static void scene_burst(const unsigned char *p, size_t n) {
break;
case 38: /* set_sect_pixel: game armed the continuous reticle pick.
* From here the board is expected to return the hit in each
* frame reply -- that's what unblocks weapons fire. */
* frame reply -- that's what unblocks weapons fire.
* Payload: [xp][yp][radius][mask]; keep the query mask for
* the per-instance INTERSECT test. */
sect_armed = true;
if (nb >= 16) sect_mask = rd_u32(d + 12);
break;
default:
break;
@@ -2287,27 +2297,19 @@ static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out
M16 world; dcs_world(di->first, cache, world);
for (size_t ii = 0; ii < di->second.size(); ii++) {
unsigned inst = di->second[ii];
/* Skip hidden-until-armed instances (w3 0/1: laser beams,
* missile models, effects). Picking them is poison: the beam
* stretches to the pick point, the next pick hits the BEAM at
* that point and locks the target there (feedback loop); a
* volley's own missiles get picked and homed on; and a picked
* missile whose DCS deletes on impact hands the game a stale
* handle (the arena2 freeze). The reticle resolves the WORLD:
* terrain, buildings, vehicles. */
std::map<unsigned, unsigned>::const_iterator wi =
S.inst_w3.find(inst);
if (wi != S.inst_w3.end() && wi->second != 2 && wi->second != 3)
/* AUTHENTIC pick exclusion: per-instance INTERSECT mask
* (instance word 9) ANDed with the query mask from the 0x26
* arm -- exactly what the real board did. The game authors it
* for us: own mech parts, laser beams, missiles and effects
* carry mask 0 (never picked -- no beam feedback loops, no
* homing on own ordnance, no stale missile handles); terrain,
* sky, buildings and ENEMY mechs carry INTERSECT_ALL, so
* point-blank hulls are targetable again (this replaced the
* interim min-range and hidden-until-armed gates). */
std::map<unsigned, unsigned>::const_iterator mi =
S.inst_mask.find(inst);
if (mi != S.inst_mask.end() && (mi->second & sect_mask) == 0)
continue;
/* Own-mech exclusion is by MINIMUM RANGE, not ancestry: walking
* S.dcs_parent (dcs_link edges) falsely flagged the FLOOR and
* SKY as the player's subtree (links also chain sibling scene
* DCSs in this dialect) -- ground/sky aims went no-hit and every
* such trigger boo-beeped. The player's arms and muzzle flash
* all live within ~12 of the eye; everything legitimately
* targetable is farther. Sky/ground are REAL pickable entities
* (sky.mod, afloor): firing into them fires -- and misses --
* exactly like the period pods. */
std::map<unsigned, unsigned>::const_iterator oi =
S.inst_object.find(inst);
if (oi == S.inst_object.end()) continue;
@@ -2351,7 +2353,7 @@ static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out
m16_xform(world, &vv[o1], w1);
m16_xform(world, &vv[o2], w2);
float t = ray_tri(O, D, w0, w1, w2);
if (t > 15.0f && t < best_t) { /* min pick range */
if (t > 0.0f && t < best_t) {
best_t = t; best_inst = inst;
best_dcs = di->first; best_gg = gg; best_geo = geo;
}
@@ -2386,7 +2388,8 @@ static bool raycast_pick(unsigned *inst_out, unsigned *dcs_out, unsigned *gg_out
static void scene_reset(void) {
S.type.clear(); S.verts.clear(); S.polys.clear(); S.mat.clear();
S.ggmat.clear(); S.children.clear();
S.inst_object.clear(); S.inst_w3.clear(); S.dcs_mat.clear();
S.inst_object.clear(); S.inst_w3.clear(); S.inst_mask.clear();
S.dcs_mat.clear();
S.dcs_parent.clear(); S.lights.clear();
S.view = VFrame();
S.geom_active = false; S.conn_active = false;