diff --git a/context/combat-damage.md b/context/combat-damage.md index 2959dee..372dae1 100644 --- a/context/combat-damage.md +++ b/context/combat-damage.md @@ -471,8 +471,14 @@ radius for both MP missile launchers = **30** (`BT_ROSTER` dump). **Port impl (`mech4.cpp`):** `BTResolveSplashRadius` gates on `MissileLauncher::ClassDerivations`, resolves launcher → `BTWeaponAmmoBin` → `BTAmmoRoundModelResource` (+0x1e8) → GameModel +0x50. `BTApplySplashDamage` walks `BTGetTargetCandidates` (excludes shooter), skips the direct victim + -destroyed/zoneless mechs, applies the `dist>radius` gate + the burst falloff, delivers through the -shooter's msgmgr (cross-pod) or `Dispatch`. Hooked at BOTH detonation paths (world-impact + contact). +destroyed/zoneless mechs, applies the `dist>radius` gate + the burst falloff, and delivers via a +DIRECT `e->Dispatch` to each victim (T0 `EXPLODE.cpp:246`). Hooked at BOTH detonation paths +(world-impact + contact). **⚠ MUST NOT route splash through the shooter's `SubsystemMessageManager`: +`AddDamageMessage` CONSOLIDATES every damage message of a frame onto the FIRST hit entity +(`commonDamageInformation.entityHit`, messmgr.cpp:279) — so splash for the bystander landed on the +DIRECT victim instead (2026-07-13 bug: the direct victim took 8 direct + 8 mis-routed splash = 16 +applications, the bystander 0; a direct `Dispatch` splits them correctly 8/8).** Dispatch reroutes +cross-pod for a replicant victim on its own. **⚠ THE SALVO-LEAD FIX (task #62 bug, found 2026-07-13 by live regression) — the N-round trap.** **KEY FACT [T1]: `DamageZone::TakeDamage` (arcade `@0041e4e0` == WinTesla `DAMAGE.cpp:379`) is diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 5a91abf..f0ef179 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -1019,9 +1019,6 @@ void DEBUG_STREAM << "[splash] detonation radius=" << radius << " candidates=" << nc << " directVictim=" << (void *)directVictim << "\n" << std::flush; - SubsystemMessageManager *mgr = (weapon_subsys >= 0 && BTIsRegisteredMech(shooter)) - ? (SubsystemMessageManager *)((Mech *)shooter)->GetMessageManager() : 0; - for (int ci = 0; ci < nc; ++ci) { Entity *e = cand[ci]; @@ -1051,21 +1048,16 @@ void dmg.impactPoint = center; dmg.damageForce = Vector3D(dx, dy, dz); // EXPLODE.cpp:216 radial knockback - if (mgr != 0) - { - Entity::TakeDamageMessage td( - Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage), - shooter->GetEntityID(), -1 /*unaimed -> cylinder resolves*/, - dmg, weapon_subsys); - mgr->AddDamageMessage(e, &td); - } - else - { - Entity::TakeDamageMessage td( - Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage), - shooter->GetEntityID(), -1, dmg); - e->Dispatch(&td); - } + // DIRECT dispatch to EACH victim (T0 EXPLODE.cpp:246 -- target->Dispatch). + // NOT the shooter's SubsystemMessageManager: AddDamageMessage CONSOLIDATES + // every damage message of the frame onto ONE common entity (the first hit, + // messmgr.cpp:279) -- so routing splash through it delivered the bystander's + // blast onto the DIRECT victim instead (double-hitting the excluded mech). + // Dispatch reroutes cross-pod for a replicant victim on its own. + Entity::TakeDamageMessage td( + Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage), + shooter->GetEntityID(), -1 /*unaimed -> cylinder resolves*/, dmg); + e->Dispatch(&td); if (s_log) DEBUG_STREAM << "[splash] victim=" << e->GetEntityID() << " dist=" << dist << " radius=" << radius << " bursts=" << bursts