MP: world structures (garages/walls) TARGETABLE -- boresight ray-tests the static collision tree (task #50)

The boresight's non-mech pick only sampled the VISUAL heightfield (BTGroundRayHit),
which on arena1 is a single flat 'sky'-named ground mesh (and class-42 BuildTables
likewise holds only 'sky') -- so shots passed THROUGH the garages/walls and only mechs
moved the HUD range axis (user-reported regression: "buildings used to move the range
axis; now only mechs do, and you can only fire at a peer").

Real fix: the boresight now ALSO ray-tests the ZONE'S STATIC COLLISION SOLID TREE --
the same geometry that already blocks the mech's walk. Authentic engine mechanism:
Mover::FindBoxedSolidHitBy already tests the static world via
zone->GetCollisionRoot()->FindBoundingBoxHitBy(line). Factored its "static world" tail
into Mover::FindStaticSolidHitBy(Line*) (static solids only, no movers), wrapped by
Mech::WorldStructurePick(start,dir,range,&hit) (builds the world-space Line, reads the
entry point back via line->FindEnd since HitByBounded clips line->length=enter).

Boresight pick order is now: closest MECH (PickRayHit, damage zones + lock) -> closest
STRUCTURE (WorldStructurePick; occludes a mech BEHIND it; designates the gBTTerrainEntity
sentinel + entry point, so the range caret reads the structure distance and NO lock ring
draws, mech4.cpp:4529) -> flat ground (BTGroundRayHit) -> sky (fire-at-nothing). Also
un-skips arena1's misnamed-'sky' flat ground so the ground tier works (btvisgnd
geometry-aware skip + [mapent]/[rendent] census).

Verified headless: a BT_WSWEEP horizontal ray-fan on arena1 tracks position (3/24 hits
near the boundary -> 17-21/24 inside the interior garage cluster -> 3/24 past it =
DISCRETE interior solids, not an enclosing box), no crash/assert/AV. Interactive aim
(BTGetAimRay) can't run headless (no window -> noRay), so the sweep is the headless proof;
interactive aim is user-verified.

Note: FindBoundingBoxUnder (the ground/containedByNode BoundingBoxTree) is DOWNWARD-only
(gravity/ground-snap: *height = FindDistanceBelowBounded), useless for a horizontal
boresight; the static SOLID tree's FindBoundingBoxHitBy is the only ray-vs-world query.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-15 10:08:45 -05:00
co-authored by Claude Opus 4.8
parent ee6e19e86e
commit de8f6d02c1
9 changed files with 222 additions and 4 deletions
+83 -4
View File
@@ -4325,6 +4325,39 @@ void
}
}
// WORLD-STRUCTURE SWEEP PROBE (BT_WSWEEP, task #50 verification): once a
// second cast a horizontal fan of rays from the eyepoint against the
// STATIC collision solid tree (WorldStructurePick -> FindStaticSolidHitBy)
// and log the hit count + nearest range. Confirms arena1's structures
// (garages/walls) ARE in the collision tree the boresight now queries --
// independent of the render sentinel or interactive aim. Diag, off by default.
if (getenv("BT_WSWEEP"))
{
static float s_ws = 0.0f; s_ws += dt;
if (s_ws >= 1.0f)
{
s_ws = 0.0f;
Point3D eye = localOrigin.linearPosition; eye.y += 30.0f;
int hits = 0; float nearest = 1e30f; float ndir = -1.0f;
for (int a = 0; a < 24; ++a)
{
double th = (double)a * (6.2831853071795862 / 24.0);
Vector3D d((float)cos(th), 0.0f, (float)sin(th));
Point3D hp;
if (WorldStructurePick(eye, d, 2000.0f, &hp))
{
++hits;
float dx = hp.x-eye.x, dz = hp.z-eye.z;
float r = (float)Sqrt(dx*dx + dz*dz);
if (r < nearest) { nearest = r; ndir = (float)(th * 57.29578); }
}
}
DEBUG_STREAM << "[wsweep] eye=(" << eye.x << "," << eye.z
<< ") struct-hits=" << hits << "/24 nearest="
<< (hits ? nearest : -1.0f) << " dir=" << ndir << "deg\n" << std::flush;
}
}
float rs[3], rd[3];
if (!BTGetAimRay(gBTAimX, gBTAimY, rs, rd))
{
@@ -4356,16 +4389,62 @@ void
hotPoint = hp;
}
}
if (hotTarget != 0)
// WORLD-STRUCTURE pick (task #50): ray-test the STATIC collision
// solid tree (garages/walls/props -- the geometry that already
// BLOCKS the mech's walk) along the boresight. A structure CLOSER
// than the mech hit occludes it (the shot stops at the wall); with
// no mech, a structure hit IS the designation. This is the
// authentic non-mech world pick: Mech::WorldStructurePick ->
// Mover::FindStaticSolidHitBy, the "test against the static world"
// half of the engine's own FindBoxedSolidHitBy (MOVER.cpp) -- so a
// shot lands on exactly the geometry the walk collides against
// (the user's "structures block the mech, but weapons pass through").
Point3D structPoint;
float structDist = 1e30f;
bool haveStruct = false;
if (gBTTerrainEntity != 0)
{
pickTarget = hotTarget;
Point3D sp;
if (WorldStructurePick(rayStart, rayDir, 1200.0f, &sp))
{
float dx = sp.x-rs[0], dy = sp.y-rs[1], dz = sp.z-rs[2];
structDist = dx*dx + dy*dy + dz*dz;
structPoint = sp;
haveStruct = true;
static float s_wp = 0.0f; s_wp += dt;
if (getenv("BT_GROUND_LOG") && s_wp >= 1.0f)
{
s_wp = 0.0f;
DEBUG_STREAM << "[wpick] structure hit at ("
<< sp.x << "," << sp.y << "," << sp.z << ") dist="
<< (float)Sqrt(structDist)
<< (hotTarget ? " (mech also under boresight)" : "")
<< "\n" << std::flush;
}
}
}
if (hotTarget != 0 && (!haveStruct || bestDist <= structDist))
{
pickTarget = hotTarget; // mech is the closest hit
pickPoint = hotPoint;
++gAimHits;
}
else if (haveStruct)
{
// a world structure is the closest hit (garage/wall) -- or it
// occludes a mech behind it. Designate it as the non-mech world
// pick (the geometry rides in the POINT; the sentinel entity has
// no damage-zone table -> no lock ring, HudSim part_013.c:5620).
pickTarget = gBTTerrainEntity;
pickPoint = structPoint;
++gAimGround;
}
else
{
// the ground downrange of the guns (max = the pod's HUD
// range scale; past it the shot is a sky shot -> no target)
// no mech, no structure -> the flat ground downrange of the
// guns (max = the pod's HUD range scale; past it the shot is a
// sky shot -> no target)
extern bool BTGroundRayHit(float,float,float, float,float,float,
float, float*,float*,float*);
float hx, hy, hz;