Combat: FIX weapon effectiveRange -- it degraded with heatLoad, not host-zone damage (task #50)

MechWeapon::UpdateTargeting computed effectiveRange = (1 - heatLoad) * weaponRange,
reading the weapon's own inherited HeatableSubsystem heatLoad. The authentic decomp
(@004b9bdc:6983) reads *(weapon+0xE0)+0x158 = Subsystem::damageZone->damageLevel --
i.e. effectiveRange = (1 - HOST-ZONE DAMAGE) * weaponRange. Same @0xE0-DamageZone-vs-
heat misattribution already corrected in HeatSink::UpdateCoolant (heat.cpp:803).

Impact: for a charge/discharge weapon (ER laser) the weapon's OWN heatLoad swings
0..1 every fire cycle, so effectiveRange collapsed toward 0 and the weapon was
perpetually "out of range" -> Emitter::FireWeapon's `if (dist <= effectiveRange)`
gate skipped SendDamageMessage -> NO damage submission and hence NO impact explosion.
The beam still rendered (beamFlag/beamEndpoint set before the gate), so the shot LOOKED
like a hit but did nothing -- the user-reported "lackluster/absent laser hits, esp.
the ER medium, on mechs AND buildings". PPCs mostly worked only because their heatLoad
happened to sit low/stable.

Fix: read the QUALIFIED this->Subsystem::damageZone->damageLevel (the MechSubsystem
shadow is a shim -- heat.cpp:812) so an UNDAMAGED weapon holds its full, STABLE
weaponRange, and range shortens only as the weapon's host zone takes battle damage.

Verified (parked in range of a building, autofire): laser effRange 500 STABLE
(was fluctuating 0/59/340/424 -> mostly out of range); impact explosions 13 in 22s
(11 laser id=16 + 2 PPC), up from ~2. Lasers now consistently damage + spawn FX.

Also adds env-gated diagnostics used to root-cause this: [fireW] range trace +
per-weapon explID (emitter.cpp), and BT_FIRE_AT_STRUCT (mech4.cpp) which designates
the nearest world structure so weapon-vs-structure fire can be tested without the
screen aim ray.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-15 11:31:47 -05:00
co-authored by Claude Opus 4.8
parent de8f6d02c1
commit aab7a8a137
4 changed files with 72 additions and 2 deletions
+37
View File
@@ -4502,6 +4502,43 @@ void
}
}
// DETERMINISTIC FIRE-AT-STRUCTURE TEST (BT_FIRE_AT_STRUCT, task #50 verify):
// the headless aim ray (BTGetAimRay) is flaky without a focused window, so
// this bypasses it -- designate the NEAREST world structure (360 sweep) as
// the target every frame so BT_AUTOFIRE can be tested against a real, stable,
// in-range building. Diag only, off by default.
if (getenv("BT_FIRE_AT_STRUCT"))
{
extern Entity *gBTTerrainEntity;
Point3D eye = localOrigin.linearPosition; eye.y += 30.0f;
Point3D best; float bestR = 1e30f; bool got = false;
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))
{
float dx = hp.x-eye.x, dz = hp.z-eye.z;
float r = (float)Sqrt(dx*dx + dz*dz);
if (r < bestR) { bestR = r; best = hp; got = true; }
}
}
if (got && gBTTerrainEntity != 0)
{
MECH_TARGET_ENTITY(this) = gBTTerrainEntity;
MECH_TARGET_SUBIDX(this) = -1;
MECH_TARGET_POS(this) = best;
static float s_fas = 0.0f; s_fas += dt;
if (getenv("BT_FIRE_LOG") && s_fas >= 1.0f)
{
s_fas = 0.0f;
DEBUG_STREAM << "[fireat] nearest struct (" << best.x << ","
<< best.y << "," << best.z << ") r=" << bestR << "\n" << std::flush;
}
}
}
// HUD feeds: the range caret + the hotbox (world point + state) + the
// recovered-Execute instruments (compass, twist tape, group mask).
// The range caret tracks the PICK (authentic: :5639 computes it from