diff --git a/docs/DAMAGE-MODEL.md b/docs/DAMAGE-MODEL.md new file mode 100644 index 0000000..5c4aee6 --- /dev/null +++ b/docs/DAMAGE-MODEL.md @@ -0,0 +1,172 @@ +# Red Planet — the damage model, end to end + +How a hit becomes lost armor, lost SCORE, and a burning VTV. Unlike BT, +this analysis reads the AUTHENTIC surviving game source (`RP/` in this +tree — VTV.cpp, WEAPSYS.cpp, RIVET.cpp, DEMOPACK.cpp, RPPLAYER.cpp) on top +of the shared MUNGA engine. The BT companion doc is +`TeslaRel410/restoration/source410/BT/DAMAGE-MODEL.md`; the engine layers +(Damage record, DamageZone, Entity message routing) are identical and only +summarized here. + +The headline difference: **RP's damage model is a physics + score economy, +not a subsystem-failure simulation**. There are no critical tables, no +per-zone cascade, no equipment destruction. Damage is dominated by KINETIC +events (ramming, walls, crushing doors), armor is calibrated from the +vehicle's physics constants, and every point of damage is simultaneously a +score transaction between players — Martian football, not a mech duel. + +``` +laser raycast / rivet impact / demo-pack blast / collision bounce + │ + ▼ + TakeDamageMessage (zone 0 — the hull) + │ + ▼ + VTV::TakeDamageMessageHandler + ├─ collision? recompute damage locally via DynamicBounce (+ knockback) + ├─ otherwise apply damageForce/mass to velocity (knockback impulse) + └─ chain to Entity → damageZones[0]->TakeDamage + │ + ▼ + VTV__DamageZone::TakeDamage + ├─ engine armor model: level += amount × scale[type], 1.0 = dead + ├─ level ≥ 1.0 → VTV::BurningState (+ VehicleDead to the player) + └─ EVERY hit forwarded to the linked RPPlayer + │ + ▼ + RPPlayer::TakeDamageMessageHandler + ├─ victim loses damageAmount as SCORE (collision × deathConstant) + └─ top attacker in the 2-second revenge window GAINS the same +``` + +## 1. Shared engine layers (identical to BT) + +The `Damage` record (`MUNGA/DAMAGE.HPP`): 5 types (Collision, Ballistic, +Explosive, Laser, Energy), `damageAmount`, `damageForce`, `surfaceNormal`, +`impactPoint`, `burstCount`. `DamageZone::TakeDamage` integrates +`damageLevel += amount × damageScale[type]`, clamps [0,1], 1.0 = Burning + +Destroyed graphic. `Entity::TakeDamageMessageHandler` routes +`damageZones[zone]->TakeDamage` and DROPS zone −1. + +RP never needs the −1 path in combat: **every producer passes zone 0**. +The VTV streams its zone array from the type-20 resource like any entity +(multiple zones can exist for graphics), but combat, collision, suicide and +out-of-world damage all land on zone 0 — the hull is one armor pool. There +is no RP analog of BT's cylinder hit-location table, no aimed-zone fire, +and no criticals: `VTVSubsystem` adds only controls plumbing (compare BT's +`MechSubsystem`, which owns a private armored zone per equipment piece). + +## 2. Producers — where Damage records are born + +**LaserGun** (`WEAPSYS.cpp`, front + rear lasers): a RAYCAST along the +vehicle ±Z axis (`FindBoxedSolidHitBy`), explosion entity spawned at the +beam end, `TakeDamageMessage` (zone 0) at the struck entity. Authored +`DamageAmount` is per-second; the ctor pre-multiplies by the firing cycle +(`laserDuration + pulseDuration`) so each completed cycle delivers one +quantum. Charge drains per cycle; type Laser. + +**RivetGun** (`RIVET.cpp`): a real projectile entity. On striking a MOVER +the authored amount is scaled **kinetically**: +`amount ×= (|v_closing|/350)²` — rear-ending a fleeing VTV tickles, a +head-on rivet hurts. Static world hits use the authored amount. Explosion +spawned at impact; zone 0. + +**DemoPack** (`DEMOPACK.cpp`): deployed charge. On detonation every entity +within `blastRadius` takes the FULL authored amount (no falloff), type +Explosive, `damageForce` = normalized blast direction × `concussiveForce` +(the knockback shove). Compare the engine's generic `Explosion` splash +(EXPLODE.CPP), which decays `burstCount/r^1.2` — the demo pack is a flat +binary yes/no blast. + +**Collisions — the star of the show** (`VTV.cpp` MoveVTV + +`MUNGA/MOVER.CPP`): the per-frame integrator collides the hull volume +(ray-cast ahead when moving faster than one hull diameter per frame). +- **Static world** (walls/terrain): `StaticBounce` computes the energy + loss = the damage amount; a keel-angle grace applies — surface normals + within 45° of the keel (`collisionNormal.y > 0.7`) take + `bottomArmorScale` (authored, default **0.1×**) — belly scrapes are + cheap, wall slams are not. +- **VTV-on-VTV ram**: the RAMMER computes `DynamicBounce` damage locally, + applies it to itself, and dispatches the same record at the victim. The + victim's handler does NOT trust the amount: it re-runs `DynamicBounce` + from its own frame (relative velocity vs the rammer, its own + elasticity), ignores the message if the relative motion doesn't oppose + the passed normal, and applies its own bounce physics. Both sides also + enter each other's revenge queue. +- **Doors**: velocity is adjusted by the door's motion; caught between + both doors = CRUSH (the closing-door instant-kill). +- **Out of world**: leaving the arena volume = instant full Explosive + budget (`1/scale`) + a random tumble. +- **Suicide**: the Kavorkian button (authentic name) applies exactly the + full Explosive budget to zone 0. + +**Knockback**: non-collision damage applies `damageForce / moverMass` to +the victim's velocity — lasers, rivets and demo packs physically shove the +target. While BURNING, collision forces convert to torque (the death +tumble, clamped ±20). + +## 3. Armor calibration — physics, not points + +The authored `.dmg` per-type points stream in as in BT, but the VTV zone +ctor **overwrites the Collision scale from the vehicle's physics**: + + scale[Collision] = 2000 / (mass × (maxImpactSpeed/3.6)² × (1 − e²)) + +i.e. the hull is calibrated so a full-speed impact (authored +`MaxImpactSpeed`, km/h → m/s) spends exactly the whole budget — armor is +defined in impact-speed terms, matched to what `StaticBounce`/ +`DynamicBounce` return (kinetic energy with the elastic fraction +restituted). The companion constant + + deathConstant = 2000 × deathScoreLoss / ((1 − e²) × deathSpeed² × mass) + +converts collision damage to SCORE at the same exchange rate: dying at +`DeathSpeed` costs exactly `DeathScoreLoss` points. + +## 4. The victim — `VTV::TakeDamageMessageHandler` + zone override + +Handler order: (1) look up the inflicting entity's player and push an +`OffensivePlayer{player, damageType, timestamp}` onto OUR revenge queue; +(2) Collision → local `DynamicBounce` recompute (see above), else → +knockback impulse; (3) chain to the Entity base → zone 0. + +`VTV__DamageZone::TakeDamage` then: +1. Runs the engine armor model. +2. `damageLevel ≥ 1.0` → `VTV::BurningState` — the vehicle is DEAD but + keeps tumbling physically (burn + torque) until reset. +3. On the ALIVE→BURNING transition, sends `VehicleDeadMessage` to the + linked player. +4. Forwards a COPY of every damage record to the linked `RPPlayer` — the + score pipe. Damage and score are the same event in RP. + +## 5. The score economy — `RPPlayer::TakeDamageMessageHandler` + +- Victim: `score_loss = damageAmount` (Collision × `deathConstant`), + posted as `DamageScoreLossPointType` — **damage is score loss**. +- The revenge queue (`offensivePlayerList`, entries expire after **2.0 s**) + is consulted: the TOP attacker — most recent player to damage us — gains + the same amount as `DamageBonusPointType` (sign-flipped by the + team/`deathBonus` rules; negatives become drop-zone-hit penalties), and + the arcade console is notified (`ConsolePlayerVTVDamagedMessage`). +- On vehicle death the same queue decides KILL credit + (`VehicleDeadMessageHandler`), then the VTV runs `DeathShutdown` + (`FootballReset` for Crusher/Blocker roles, `RegularReset` otherwise) + and respawns via the DropZone machinery. +- Role twists (Martian football): the Runner carries a `scoreMultiplier` + that DOUBLES per score-zone pass (reset to 1 on death) — damage taken + during the drop-zone penalty window is scored differently; Crusher and + Blocker are RPPlayer roles, not weapons — crushing is done with the + hull, priced by the collision physics above. + +## 6. RP vs BT — the design fork from one engine + +| | Red Planet | BattleTech 4.10 | +|---|---|---| +| hull zones in combat | one pool (zone 0) | many, vital/leg flags, LOD shells | +| unaimed hit resolve | none needed | cylinder height×angle table | +| criticals / equipment | none | per-zone crit tables → subsystem kill | +| collision armor | derived from physics constants | authored points (halved scales for legs) | +| ram damage | victim recomputes locally | inflictor-authoritative message | +| knockback | damageForce/mass on every hit | none (gyro shake only) | +| death | zone 0 full → Burning + tumble | vital/leg zone full → mech kill | +| meaning of damage | score transaction (2 s revenge window) | armor attrition + equipment failure |