#95/#84: missile salvos deliver their FULL authored damage, and stop double-exploding
THE SALVO DAMAGE (#95). Players measured an LRM 15 landing "3ish points". The logs agreed: [projectile] IMPACT damage=3.33333 (Oracle, LRM15) and 3.5 (Rajel, LRM10). Those are right PER MISSILE -- 50/15 and 35/10 -- and the bench shows why the salvo still under-delivers: each launcher pushes N rounds of which exactly ONE carries damage. Two individually-correct changes composed into an N-fold shortfall: * The ctor does what the binary's MissileLauncher ctor does (@0x3ac/@0x3d4): damageData.burstCount = missileCount; damageData.damageAmount /= missileCount; The record holds the PER-MISSILE amount plus the count; the arcade reconstitutes amount x burstCount when it applies the hit. * Task #62 then correctly stopped the port applying the hit once per visual round (that was ~missileCount-x too lethal) by damaging only the lead round -- but handed it the already-divided amount. Our DamageZone::TakeDamage is `damageLevel += amount * scale` and drops burstCount, so the salvo delivered amount/missileCount. Since the port collapses the cluster to one damaging round, multiply the count back in there. Bench: an SRM6 salvo now lands amt=35 (the authored total) taking a zone 0 -> 0.556, where it previously landed 5.83. THE DOUBLE EXPLOSION (#84). Oracle: "missile appear to register hit explosions twice, once where target was and again where the target is." There are two spawn sites: BTSpawnRoundDetonation at the round's own impact point, and the message manager's bundled explosion at the CONSOLIDATED point a frame later. The duplicate was known and thought harmless -- "among a rippled volley it is invisible" -- which held only while a salvo landed N detonations. A projectile now marks its weapon (MarkRoundDetonated) and the consolidation skips queueing a second blast for it; direct-fire weapons never mark, so lasers/AC keep the bundled explosion they rely on. Bench: 4 missile impacts -> 4 SKIPPED, while 11 direct-fire hits still queue normally. SWEPT CONTACT (#84 tail). Contact was a 10-unit sphere sampled only at the END of each step. With the authored thruster live (#84) field rounds arrive at v=955 -- a ~16 unit step at 60fps, larger than the radius -- so samples can straddle the target. (Pre-#84 rounds flew ~100-300 = a 1.7-5 unit step and could never skip it: the velocity fix exposed this, it did not cause it.) Now tests the whole segment travelled and bursts at the point of NEAREST APPROACH, which also stops the detonation being flung past the target at speed. RETRACTION: I posted tunnelling as the leading explanation for the lost salvo. The bench disproves it -- zero fizzles, and the "missing" rounds are the dmg=0 visual rounds of the cluster, which never registered damage by design. The sweep is kept as speed-independent robustness, not as the #95 fix. Bench: scratchpad/night8/salvo.sh (BT_PROJ_LOG + BT_FIRE_LOG). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
839b040619
commit
efc3e9ff1a
@@ -1636,8 +1636,37 @@ static void
|
||||
// contact"). Proximity = the hit; the flight-cap expiry is a FIZZLE --
|
||||
// no damage, matching the binary (a missile that dies mid-air detonates
|
||||
// nothing; only the world-collision hit spawns the Damage entity).
|
||||
Scalar dx = p.targetPos.x - p.pos.x, dy = p.targetPos.y - p.pos.y, dz = p.targetPos.z - p.pos.z;
|
||||
const int contact = (dx*dx + dy*dy + dz*dz < (10.0f*10.0f));
|
||||
// SWEPT contact (issue #84/#95). This was a 10-unit sphere sampled at the
|
||||
// END of the step only. Now that the authored thruster is applied (#84),
|
||||
// field logs show rounds arriving at v=955 -- a ~16 unit step at 60fps,
|
||||
// LARGER than the contact radius, so consecutive samples can straddle the
|
||||
// target and the round flies on untouched. (Pre-#84 rounds flew a constant
|
||||
// ~100-300 = a 1.7-5 unit step, which could never skip the sphere -- so the
|
||||
// velocity fix exposed this, it did not cause it.) Test the whole segment
|
||||
// travelled this frame instead, and burst at the point of NEAREST APPROACH
|
||||
// rather than wherever the step happened to end -- which also stops the
|
||||
// detonation being flung past the target at high speed.
|
||||
Point3D hitPos = p.pos;
|
||||
Scalar contactD2;
|
||||
{
|
||||
const Scalar sx = p.pos.x - prev.x, sy = p.pos.y - prev.y, sz = p.pos.z - prev.z;
|
||||
const Scalar seg2 = sx*sx + sy*sy + sz*sz;
|
||||
Scalar t = 0.0f;
|
||||
if (seg2 > 1.0e-6f)
|
||||
{
|
||||
const Scalar wx = p.targetPos.x - prev.x,
|
||||
wy = p.targetPos.y - prev.y,
|
||||
wz = p.targetPos.z - prev.z;
|
||||
t = (wx*sx + wy*sy + wz*sz) / seg2;
|
||||
if (t < 0.0f) t = 0.0f; else if (t > 1.0f) t = 1.0f;
|
||||
}
|
||||
hitPos.x = prev.x + t*sx; hitPos.y = prev.y + t*sy; hitPos.z = prev.z + t*sz;
|
||||
const Scalar cx = p.targetPos.x - hitPos.x,
|
||||
cy = p.targetPos.y - hitPos.y,
|
||||
cz = p.targetPos.z - hitPos.z;
|
||||
contactD2 = cx*cx + cy*cy + cz*cz;
|
||||
}
|
||||
const int contact = (contactD2 < (10.0f*10.0f));
|
||||
if (!contact && p.traveled >= p.range)
|
||||
{
|
||||
if (getenv("BT_PROJ_LOG"))
|
||||
@@ -1647,7 +1676,7 @@ static void
|
||||
}
|
||||
if (contact)
|
||||
{
|
||||
BTSpawnRoundDetonation(p.shooter, p.weaponSubsys, p.pos);
|
||||
BTSpawnRoundDetonation(p.shooter, p.weaponSubsys, hitPos);
|
||||
Entity *tgt = p.target;
|
||||
// Deliver to the projectile's target mech -- the launcher set p.target
|
||||
// from the shooter's 0x388 slot (the picked victim; any peer mech in
|
||||
@@ -1666,7 +1695,7 @@ static void
|
||||
dmg.damageType = (Enumeration)p.damageType; // issue: was hardcoded Explosive
|
||||
dmg.damageAmount = p.damage;
|
||||
dmg.burstCount = 1;
|
||||
dmg.impactPoint = p.pos;
|
||||
dmg.impactPoint = hitPos; // nearest approach, not the step end
|
||||
// Route through the SHOOTER's SubsystemMessageManager with the
|
||||
// firing launcher's roster index (task #7 bundling): the
|
||||
// consolidation resolves roster[id]+0x3E4 = the weapon's
|
||||
@@ -1679,6 +1708,14 @@ static void
|
||||
mgr = (SubsystemMessageManager *)((Mech *)p.shooter)->GetMessageManager();
|
||||
if (mgr != 0)
|
||||
{
|
||||
// issue #84: this round already spawned its OWN detonation
|
||||
// at hitPos above. Tell the manager, so the consolidation
|
||||
// does not ALSO queue this weapon's explosion at the bundle's
|
||||
// consolidated point a frame later -- the double blast
|
||||
// players see as "once where the target was, again where it
|
||||
// is". Direct-fire weapons never mark, so lasers/AC keep
|
||||
// the bundled explosion they depend on.
|
||||
mgr->MarkRoundDetonated(p.weaponSubsys);
|
||||
Entity::TakeDamageMessage take_damage(
|
||||
Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage),
|
||||
p.shooter->GetEntityID(), -1 /*unaimed -> cylinder resolves*/,
|
||||
|
||||
Reference in New Issue
Block a user