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>
8.7 KiB
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):
StaticBouncecomputes the energy loss = the damage amount; a keel-angle grace applies — surface normals within 45° of the keel (collisionNormal.y > 0.7) takebottomArmorScale(authored, default 0.1×) — belly scrapes are cheap, wall slams are not. - VTV-on-VTV ram: the RAMMER computes
DynamicBouncedamage locally, applies it to itself, and dispatches the same record at the victim. The victim's handler does NOT trust the amount: it re-runsDynamicBouncefrom 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:
- Runs the engine armor model.
damageLevel ≥ 1.0→VTV::BurningState— the vehicle is DEAD but keeps tumbling physically (burn + torque) until reset.- On the ALIVE→BURNING transition, sends
VehicleDeadMessageto the linked player. - 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 asDamageScoreLossPointType— 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 asDamageBonusPointType(sign-flipped by the team/deathBonusrules; 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 runsDeathShutdown(FootballResetfor Crusher/Blocker roles,RegularResetotherwise) and respawns via the DropZone machinery. - Role twists (Martian football): the Runner carries a
scoreMultiplierthat 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 |