Files
TeslaRel410/restoration/source410/BT/MECHDMG.NOTES.md
T
CydandClaude Fable 5 d4f0aef6e1 BT410 Phase 5.3.17: the destruction cascade -- zone deaths DESTROY things
Mech::DamageZone::TakeDamage is the full binary cascade (@0049c690): LOD
artifact router with same-attacker clustering (@0049c40c, hysteresis 0.33),
artifact parent level = mean of children, Energy-hit generator shorts
through the crit plug binding (novice-exempt), weighted CriticalHit
(@0049ccc4), zone-death allotment push (SendSubsystemDamage @0049c9a8) and
the segment-table destruction descent (RecurseSegmentTable @0049cad4 --
SIBS + DESCEND via the engine EntitySegment iterators).

Supporting bricks: Mech's OWN handler table with the TakeDamage override
(latches lastInflictingID @0x43c; gyro feed staged; cylinder table
deferred); friend class Mech__DamageZone (authentic); subsystem private
zones ARMED (structureReference + armorByFacing[5] normalized -- crits are
measurable); ApplyDamageAndMeasure; ForceCriticalFailure sets
DestroyedState so the weapon FSMs' GetSimulationState()==1 hard gate went
live; Generator::ForceShort + PoweredSubsystem::ForceShortRecovery.

Fight-verified (mutual BT_SPAWN_ENEMY + BT_FORCE_ZONE=8 concentrated fire):
arm zone to 1.0 -> sibling searchlight zone dies (Searchlight2/ThermalSight
crits destroyed, HUD degrading) -> DESCEND kills the gun zone -> PPC_2 +
ERMLaser_2 + Condenser6 DESTROYED and FALL SILENT (0 shots after death;
undamaged PPC_1 keeps firing); vital-zone hit = MECH KILL; PPC hit shorted
GeneratorD. This art has zero artifact zones (redirect=0 across all 20 --
router verified dormant-correct). Smoke + novice regressions clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 11:13:28 -05:00

140 lines
8.0 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.
# MECHDMG.CPP — Mech::DamageZone (hull zone armor economy + destruction cascade)
**Status: RECONSTRUCTED (5.3.17) — streamed ctor, TakeDamage cascade, LOD
router, CriticalHit, SendSubsystemDamage, RecurseSegmentTable all live and
fight-verified. Remaining staged bits listed at the bottom.**
## The header is AUTHENTIC
`CODE/BT/BT/MECHDMG.HPP` (06/05/95 JM) **survives in the archive** and wins the
include path (`$C/BT` precedes `$S/BT`), so this TU compiles against the real
1995 interface: `MechCriticalSubsystem` with a `SlotOf<Subsystem*>` plug and
**Scalar** criticalWeight, `Logical` zone flags, `TableOf<PlugOf<int>*,int>`
redirectTable, `SlotOf<Mech__DamageZone*> parentArtifactZone`. Only the .CPP is
a reconstruction. (A from-scratch staged header was written and then DELETED
the same session once the survivor was found — do not resurrect it.)
## Why this TU exists at wave 5.3.16 (the bring-up crash)
`Entity::Entity` (engine, ENTITY.CPP:1032) only reads the zone COUNT from the
type-0x14 DamageZoneStream and allocates the raw `damageZones[count]` POINTER
array — **it never constructs the zones**. The derived entity must fill the
slots itself (CulturalIcon = the surviving engine reference, CULTURAL.CPP:349;
the Mech = binary Pass 3, FUN_004a1674). Two crashes taught this:
1. Unfilled slots are heap garbage → the FIRST TakeDamageMessage dispatched at
a mech called through a trash vtable (exception 0E, EIP inside the heap).
2. Filling with the engine BASE `::DamageZone` streamed ctor parses each zone
record SHORT (the BT tail below goes unread) → the stream skews → the next
zone's effect-site indices read garbage → `GetSegment()` NULL →
`Link::AddToPlug(NULL)` (exception 0E at AddToPlug+9). The class-scope
`DamageZone` typedef (= `Mech__DamageZone`) in the authentic mech ctor is
the load-bearing signal: mech zones are ALWAYS the subclass.
## The per-zone stream record (type 0x14, after the count word)
Engine base part (DAMAGE.CPP:234, authentic order, VERIFIED identical RP↔BT411):
`CString name` · per 5 effect-site classes `{int n; n×int segIdx}` (JointedMover
owners resolve them via GetSegment; non-jointed read 5 filler ints) ·
`Scalar defaultArmorPoints` · `Scalar damageScale[5]` · material lists.
BT mech tail (this ctor, BT411-verified @0049ce50):
`Logical descend, destroySiblings` · `int segmentIndex` · `Logical leftLeg,
rightLeg, vital` · `int critCount` · critCount × `{Scalar weight; Scalar
damagePct; int rosterIndex}` · `int redirectCount` · redirectCount × `int
childZone`.
## The armor economy (BT411 combat-damage.md, binary-verified)
- Stream authors armor POINTS; ctor normalizes in place:
`scale[type] = 1/(raw[type]×armorPoints)` (cells already `1/armorPoints`
pass through, tolerance 1e-4); leg zones halve every type's scale.
- Base `DamageZone::TakeDamage` then applies
`damageLevel += amount × scale[type]`, clamp [0,1], 1.0 = Burning/Destroyed.
- `damageType` indexes the 5 cells DIRECTLY: 0 Collision / 1 Ballistic /
2 Explosive / 3 Laser / 4 Energy. **burstCount is NOT in the formula**
one message = one application (it only feeds gyro bounce + splash falloff).
- Live-verified 5.3.16 (two-mech fight): PPC 11.77/hit type 4 (authored 12 ×
chargeRatio²), ER-M laser 3.43 type 3, SRM 5.83×6 type 2; repeat hits climb
linearly; 70-pt leg zones absorb at the halved scale; zones reach 1.0.
## Crit table binding
Streamed roster index → `subsystemPlug.Add(GetSubsystem(idx))`, gated
MasterInstance + DynamicFlag (binary @0049d0e1 — replicant plugs stay unbound,
criticals are master-authoritative) AND NULL-guarded: roster slot 0 (control
mapper) installs post-ctor, slot 1 (voltage bus) can be empty, and
`Link::AddToPlug` derefs the plug.
## The cascade (5.3.17, all binary-addressed via BT411, fight-verified)
- **`TakeDamage` (@0049c690)**: artifact zones (redirect table non-empty)
route via `LODDamageRouter`; real zones apply the engine armor model, then
refresh their artifact parent (`UpdateLODDamageLevel` = mean of children),
roll the special-damage short (Energy hit + a Generator crit pick →
`Generator::ForceShort()`, novice-exempt), and drive failure state: a
destroyed non-leg zone runs `RecurseSegmentTable`; a destroyed LEG or
VITAL zone is the mech kill (statusAlarm level 9 = the @0x714 body-graphic
death level; `Mech::IsMechDestroyed()`); half-gone legs set the gimp
graphic (right 4 / left 3). The binary's MovementMode(3||4)/IsDisabled
gates on the leg branches are STAGED as "not already destroyed" until the
gait FSM exists.
- **`LODDamageRouter` (@0049c40c)**: same-attacker clustering — reuse the
previous child inside [0.25s, 10s] (hysteresis 0.33 — a binary DOUBLE,
bytes 1f85eb51b81ed53f); new attacker or stale window re-rolls
(`RandomRedirect` @0049c600, uniform over the redirect table). Keyed by
`mech->lastInflictingID` (mech+0x43c), latched by the new
`Mech::TakeDamageMessageHandler` override.
- **`CriticalHit` (@0049ccc4)**: half the damage (cap 1.0) is the critical
bite; the rest runs the armor model. Weighted roll over criticalWeight;
the pick takes `ApplyDamageAndMeasure` charged against its
damagePercentage allotment. (Not yet called by the weapon path — the
aimed/critical fire distinction is the reticle wave.)
- **`SendSubsystemDamage` (@0049c9a8)**: zone death pins structure 1.0 and
pushes each crit entry's UNUSED allotment into the bound subsystem's own
zone; 1.0 → `ForceCriticalFailure` (+ mech kill if vital). NOTE: repeat
hits on a dead zone re-run the push (the binary is equally unguarded) —
contained equipment keeps degrading under continued fire (HUD climbing
0.33→0.67→0.99 in the verification run is this).
- **`RecurseSegmentTable` (@0049cad4)**: destruction descent via the ENGINE
EntitySegment API (`GetSegment` + `MakeDamageZoneIndexTable` /
`MakeChildIndexTable` — heap iterators, caller deletes). SIBS = other
zones on the same segment; DESCEND = child segments' zones; recursion
guarded by GetGraphicState()!=Destroyed (authentic for siblings; added
defensively on children).
- **Weapon hard-failure gate**: `ForceCriticalFailure` now also sets
`SetSimulationState(DestroyedState)` (MechSubsystem enum from the binary
PrintState strings: Destroyed=1 @0050df7f, Exploding=2 @0050df8c) — the
`GetSimulationState() == 1` check both weapon FSMs already carried goes
live: destroyed weapons fall silent.
## Fight verification (bhk1 vs bhk1, BT_FORCE_ZONE=8 concentrated fire)
Zone topology (BT_SKEL_DUMP `[zone]` dump): NO artifact zones in this art
(redirect=0 everywhere — matches the 1995 header comment); arms/guns carry
DESCEND+SIBS; all torso + leg zones VITAL; legs 70pts (halved scale ✓),
searchlights 25pts, utorso 124pts. Verified live: arm (zone 8) driven to 1.0
→ SIBS killed the searchlight zone (Searchlight2 + ThermalSight crits
destroyed, HUD degrading) → DESCEND killed the gun zone → **PPC_2 +
ERMLaser_2 + Condenser6 DESTROYED and the weapons STOP FIRING** (0 shots
after both mechs' PPC_2 died; undamaged PPC_1 kept firing); vital-zone kill
([death] MECH KILL on dz_reardtorso) in the random-spread run; PPC Energy
hit shorted GeneratorD through the crit plug binding. Dev hooks:
BT_FORCE_ZONE=n (pin every SendDamage hit), BT_SPAWN_ENEMY now MUTUAL (the
enemy returns fire under BT_FORCE_FIRE).
## Still staged / deferred
- `SetGraphicState` override = base pass-through (BT411 found the binary
override byte-identical to the base; the destroyed-skin video flip is the
render wave).
- Declared-not-defined (unreferenced): `ShortAttachedGenerators` (its logic
is inlined in TakeDamage per the binary), `UpdateLODDamageLevel` callers
beyond TakeDamage, `CreateStreamedDamageZone`, `GetSegmentIndex` (tool
side), `Heal`/death-reset family.
- The type-0x1e critical-descriptor stream + MechDeathHandler (band
descriptors, smoke/burn effects) — render/effects wave.
- The cylinder hit-location table (type 0x1d) for unaimed zone -1 hits —
the Mech handler currently lets the base drop them (authentic base
behavior); reticle/aimed fire is the render wave.