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>
173 lines
8.7 KiB
Markdown
173 lines
8.7 KiB
Markdown
# 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 |
|