#175 FIXED: missiles detonate on the static world again -- the flight sweep's missing half. The push simulator's world test marched only the VISUAL heightfield (BTGroundRayHit), so every round tunneled through geometry that blocks the walk but is not heightfield -- the arena1-garage class, and the field case (LRM duel through a grass mound, user-observed on the #83 bench). The binary's FUN_0042291c swept the real collision world every tick. Fix: the flight step now also rays the STATIC COLLISION SOLID TREE (WorldStructurePick -> Mover::FindStaticSolidHitBy -- the same static-world tail of the engine collider, already proven by the weapon-pick work), earliest hit wins vs the heightfield march; a struck solid's OWNING entity takes the direct zone=-1 dispatch exactly as the binary missile contact does (@004be078 -- trucks/props are missile-killable); salvo-lead world bursts keep the detonation + cluster splash (near-miss splash vs cover, authentic). BT_WORLDHIT=0 restores heightfield-only (A/B lever). Receipt upgraded: [projectile] WORLD burst (terrain|struct[, owner-dispatched]) + dmg/lead. Verified on the exact reported scenario (grass standoff duel, mound in the line): 185/160 WORLD-burst receipts per node -- ALL struct-class (confirming the mound is solid-tree geometry the old test was blind to), direct mech IMPACTs healthy at 20/18 when the line cleared, user-witnessed: rounds burst on the mound instead of passing through, lofted rounds still arc over (the authored seeker loft). Companion bench hook from the same session: BT_DESIGNATE=enemy (no HUD ring -- designation slots only).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bw71WVsccjwW7uKRg4aWD
This commit is contained in:
Joe DiPrima
2026-08-13 22:44:26 -05:00
co-authored by Claude Fable 5
parent df188e737f
commit 7b120531ea
+82 -2
View File
@@ -1916,6 +1916,19 @@ static void
// query every frame, FUN_0042291c, and DETONATES on geometry). Ray the
// flight step against the terrain/cave solids -- a lofted round in a
// low cavern bursts on the CEILING instead of punching through it.
// (#175) TWO queries, earliest hit wins: the visual-heightfield march
// (BTGroundRayHit) alone let rounds tunnel through everything that
// blocks the WALK but is not heightfield -- the arena1-garage class
// (open-questions: the visual mesh is not the collision truth), and
// the field case that filed the ticket (LRM duel through a mound).
// The STATIC COLLISION SOLID TREE (WorldStructurePick ->
// Mover::FindStaticSolidHitBy) is the same static-world tail of the
// engine collider FUN_0042291c swept, so the round now detonates on
// exactly the geometry that stops a walking mech. A struck solid's
// OWNING entity takes the direct dispatch (binary missile contact
// @004be078 dispatches at the struck entity -- trucks/props are
// missile-killable); plain terrain owners ignore it.
// BT_WORLDHIT=0 restores the heightfield-only behavior (A/B lever).
{
extern bool BTGroundRayHit(float,float,float, float,float,float,
float, float*,float*,float*);
@@ -1926,8 +1939,47 @@ static void
Vector3D rd;
rd.x = p.vel.x/p.speed; rd.y = p.vel.y/p.speed; rd.z = p.vel.z/p.speed;
float hx, hy, hz;
int worldHit = 0; // 1 = heightfield, 2 = static solid
Entity *structOwner = 0;
if (BTGroundRayHit(prev.x, prev.y, prev.z, rd.x, rd.y, rd.z,
step + 1.0f, &hx, &hy, &hz))
worldHit = 1;
{
static int s_wh = -1;
if (s_wh < 0)
{
const char *wv = getenv("BT_WORLDHIT");
s_wh = (wv != 0 && *wv == '0') ? 0 : 1;
}
if (s_wh && p.shooter != 0 && BTIsRegisteredMech(p.shooter))
{
Point3D sHit;
Entity *sOwn = 0;
Vector3D sd(rd.x, rd.y, rd.z);
if (((Mech *)p.shooter)->WorldStructurePick(prev, sd,
step + 1.0f, &sHit, &sOwn))
{
// earlier than the heightfield hit (or the only hit)?
int take = 1;
if (worldHit == 1)
{
const float gd2 = (hx-prev.x)*(hx-prev.x)
+ (hy-prev.y)*(hy-prev.y) + (hz-prev.z)*(hz-prev.z);
const float sd2 = (float)((sHit.x-prev.x)*(sHit.x-prev.x)
+ (sHit.y-prev.y)*(sHit.y-prev.y)
+ (sHit.z-prev.z)*(sHit.z-prev.z));
take = (sd2 < gd2);
}
if (take)
{
worldHit = 2;
structOwner = sOwn;
hx = (float)sHit.x; hy = (float)sHit.y; hz = (float)sHit.z;
}
}
}
}
if (worldHit != 0)
{
// burst on the rock: the round's own DETONATION (the binary
// missile detonates on ANY geometry, @004bef78) + a tight
@@ -1955,12 +2007,40 @@ static void
BTApplySplashDamage(p.shooter, p.weaponSubsys, hp, 0, sdmg);
}
}
// (#175) a struck STRUCTURE's owning entity takes the direct
// dispatch, exactly as the binary missile contact does
// (@004be078: zone=-1 TakeDamageMessage straight at the
// struck entity, no class test) -- missiles kill trucks/
// props. Plain terrain owners have no zones and ignore it.
int ownerDispatched = 0;
if (worldHit == 2 && structOwner != 0
&& !BTIsRegisteredMech(structOwner)
&& structOwner->damageZoneCount > 0
&& p.damage > 0.0f)
{
ownerDispatched = 1;
Damage wdmg;
wdmg.damageType = (Enumeration)p.damageType;
wdmg.damageAmount = p.damage;
wdmg.burstCount = 1;
wdmg.impactPoint.x = hx; wdmg.impactPoint.y = hy;
wdmg.impactPoint.z = hz;
Entity::TakeDamageMessage take_damage(
Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage),
(p.shooter != 0) ? p.shooter->GetEntityID() : EntityID::Null,
-1 /*icon handler maps -1 -> zone 0*/, wdmg);
structOwner->Dispatch(&take_damage);
}
extern void BTPfxTrailPuff(int, float, float, float, float, float, float, int);
for (int pf = 0; pf < 4; ++pf)
BTPfxTrailPuff(0, hx, hy, hz, -rd.x, -rd.y, -rd.z, 2);
if (getenv("BT_PROJ_LOG"))
DEBUG_STREAM << "[projectile] WORLD burst at(" << hx << ","
<< hy << "," << hz << ")" << std::endl;
DEBUG_STREAM << "[projectile] WORLD burst ("
<< ((worldHit == 2) ? "struct" : "terrain")
<< (ownerDispatched ? " owner-dispatched" : "")
<< ") at(" << hx << "," << hy << "," << hz << ")"
<< " dmg=" << p.damage << " lead=" << p.salvoLead
<< std::endl;
p.active = 0;
continue;
}