Full migration of the 2236-line monolithic CLAUDE.md into the progressive-context knowledge graph (per spark-lesson / expert-seed.md), so the deep RE knowledge loads on-demand instead of every session. ZERO CONTEXT LOST: - docs/PROGRESS_LOG.md = the complete old CLAUDE.md, VERBATIM (byte-identical) -- the lossless safety net + the "full detail" quick-lookup fallback. - 18 context/*.md topic files (1343 lines) digest every section (§1-3 -> project-overview, §4 -> content-archives, §5 -> asset-formats/bgf-format, §5a -> source-completeness, §5b/§8 -> wintesla-port, §7/§10 -> locomotion, §10a -> build-and-run, §10b -> reconstruction-method, §10c -> combat-damage + reconstruction-gotchas, §10d -> subsystems, render notes -> rendering, gauges -> gauges-hud, MP -> multiplayer, §9 -> open-questions). - reference/glossary.yaml (53 terms). decomp-reference.md = the offsets/ClassIDs/addresses hub. CLAUDE.md (160 lines) = router: identity, answer/reason protocols, quick-lookup table, evidence tiers (T0 engine-truth / T1 decompiled+verified / T2 reconstructed+runtime / T3 guarded / T4 hypothesis), conventions + DO-NOT (the systemic bug classes), structure. Retains the load-bearing work directives (build recipe pointer, "keep current" mandate). Knowledge graph validates CLEAN (scratchpad/checkctx.py -- all [[links]] + quick-lookup + docs refs resolve; [[name]] -> topic file or glossary term). docs/*.md ledgers stay as the detailed logs; context/*.md are the curated digests that route into them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
75 lines
4.8 KiB
Markdown
75 lines
4.8 KiB
Markdown
---
|
||
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.
|