Files
RP412/docs/DAMAGE-MODEL.md
T
CydandClaude Opus 5 f44cdbacca How a hit becomes lost armor and lost score
The damage model, end to end, read off the authentic surviving RP source
(VTV.cpp, WEAPSYS.cpp, RIVET.cpp, DEMOPACK.cpp, RPPLAYER.cpp) rather than
inferred. Companion to the BT doc of the same name; the shared engine
layers are the same and only summarised here.

The finding it is built around: RP's damage model is a physics and score
economy, not a subsystem-failure simulation. One armor pool at zone 0, no
criticals, no per-zone cascade. Collision armor is not authored at all -
it is calibrated from the vehicle's own mass and MaxImpactSpeed so that a
full-speed hit spends exactly the whole budget - and deathConstant
converts damage to score at that same exchange rate. Every point of
damage is simultaneously a transaction between two players, through a
two-second revenge window. Martian football, not a mech duel.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 09:38:16 -05:00

173 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 |