--- id: combat-damage title: "Combat & Damage — targeting, firing, damage zones, death" status: established source_sections: "PROGRESS_LOG.md §10c; docs/HARD_PROBLEMS.md (P5)" related_topics: [subsystems, decomp-reference, reconstruction-gotchas, locomotion, rendering] key_terms: [damage-zone, TakeDamage, viewpoint-entity, master, replicant] open_questions: - "Per-impact aim (cylinder lookup); collision damage application; DeathShutdown + collapse anim" --- # Combat & Damage The drive→target→fire→damage→destroy loop. Full detail: `docs/PROGRESS_LOG.md §10c`; the death/ teardown forensics: `docs/HARD_PROBLEMS.md` (P5). ## No AI — BT is PvP-only (verified, not a recovery gap) The mission system is NETWORKED and BT never had NPCs/AI. Every mech is driven by a `BTPlayer` (or a `BTCameraDirector` spectator). The BT registry knows only Mech/Projectile/Missile/BTPlayer/BTTeam; no Bot/Brain/AI class exists; `BTL4OPT.EXE` has no AI strings. `Crusher/Blocker/Runner` are RED PLANET classes, NOT BT. ⇒ a solo mission has no enemy by design; `BT_SPAWN_ENEMY` is a TEST DUMMY (uncontrolled Mech). The authentic opponent path is [[multiplayer]]. [T1] ## Entity spawn + the per-mech gate `Mech::Make(MakeMessage*)` (`FUN_004a2d48`) allocs `sizeof(Mech)` + runs the ctor; the base Entity ctor self-registers it (renders + ticks). `Mech::PerformAndWatch` runs for EVERY mech → gate player-only logic on `this == application->GetViewpointEntity()` (else a spawned enemy is driven by player input). [T1] ## Targeting (mech offsets, [[decomp-reference]] §3) `mech+0x37c` = target world Point3D; `mech+0x388` = target `Entity*` (the `HasActiveTarget()` gate); `mech+0x38c` = targeted sub-zone (−1=whole). Weapons cache hasTarget/targetPoint/muzzlePoint, refreshed each frame. [T1] ## Firing arc + muzzles There is NO firing-arc field in the binary (`Emitter::FireWeapon` fires whenever HasActiveTarget) — the 1995 game got away with it because it was **cockpit-only** (a beam behind you is off-camera). The port's external camera exposes it, so the arc is gated on the **torso twist range** (`Torso::GetHorizontalReach`, 0 for the twist-disabled Blackhawk) + a base aim tolerance (`BT_FIRE_ARC`, a labeled PORT presentation param). Beams fire from the mech's real gun-port SITE segments (siterugunport etc.) resolved via `GetSegment(name)→segmentToEntity×localToWorld`. [T2] ## Weapon fire is a PORT reconstruction (the dpl_* beam layer is unported) `Emitter::FireWeapon` builds a muzzle→target beam but the `dpl_*` beam renderable was never ported → the beam is drawn by `BTPushBeam`/`BTDrawBeams` (L4VIDEO — an additive quad / the real `ermlaser.bgf` tube with the scrolling `bexp` grit). Fire rate is decoded-exact (DischargeTime 0.2 + RechargeRate 2.2 per ER-M laser; [[decomp-reference]] §5). Flying projectiles (LRM/autocannon) are also a PORT reconstruction (`BTPushProjectile` — the 2007 Entity is too small for the binary's raw integrator offsets). [T2] ## Damage delivery + the real damage model `Entity::TakeDamageMessage(id, size, inflictingEntityID, zone, Damage&)` → `target->Dispatch`. **Base handler IGNORES zone==−1** (the Mech cylinder-lookup override for unaimed hits is not reconstructed — send a VALID zone). `Mech__DamageZone::TakeDamage` → engine armor model (`damageLevel += damageAmount*damageScale[type]`) → 1.0 = zone destroyed → death. The Mech ctor populates the inherited `damageZones[]` from the DamageZoneStream (type 0x14) resource. `class Damage{ damageType (Collision/Ballistic/Explosive/Laser/Energy), damageAmount, damageForce, surfaceNormal, impactPoint, burstCount }`. Verified: `structure` climbs, vital zone destroyed → `*** TARGET DESTROYED ***`. [T2] Two non-field fixes were needed: **message-handler chaining** (an unchained set drops TakeDamage silently) + **entity validity** (`SetValidFlag()` on a manually-spawned entity) — see [[reconstruction-gotchas]] §9. [T2] ## Death — the wreck STAYS (P5 CLOSED) A killed mech does NOT disappear — BT death is a STATE transition (`SetGraphicState(DestroyedGraphicState)` + death anim + effect/splash), the mech becomes a persistent WRECK (RP analog: `VTV::DeathShutdown`). **NEVER issue `DestroyEntityMessage` on death** — the teardown crash was an artifact of forcing a removal the original never does. The actual bug was ONE thing: an EXPLICIT `JointedMover::~JointedMover()` in the reconstructed `~Mech` ran the whole base-dtor chain TWICE (the dtor-epilogue rule, [[reconstruction-gotchas]] §7) → a double-free of `collisionLists` + the segment table. Removing it fixed BOTH the death-row crash AND app-exit crashes. [T2] ## Key Relationships - Weapons/roster: [[subsystems]]. Aim source: [[locomotion]] (drive/facing). Effects: [[rendering]]. - P5 forensics: `docs/HARD_PROBLEMS.md`. Data: [[decomp-reference]] §4-5.