Files
TeslaRel410/restoration/source410/BT/MECHDMG.NOTES.md
T
CydandClaude Fable 5 6da282738c BT410 5.3.121: the mode alarm comes home -- the decomp had the master perf all along, and the damage code writes the mode directly
The 'never decompiled' ledger note was wrong: part_013.c:3059 carries the
ENTIRE master performance @004a9b5c as plain C (and @004ab430 is the full
replicant perf; IntegrateMotion sits between them).  Reading it end to end
rewrote the movement-mode model:

- mech+0x2c is the entity MOVEMENT-MODE AlarmIndicator; +0x40 is its LEVEL
  cell -- everything previously read as 'raw simulationState' is this
  alarm.  MECHDMG writes it DIRECTLY: half-gone right leg -> 4 GimpRight
  @0x49c8fb, left -> 3 GimpLeft @0x49c926, leg destroyed while limping ->
  9 @0x49c88f/@0x49c8c5, vital -> 9 @0x49c83c, gated by the real
  InDeathTransition.  The staged statusAlarm->mode 'promotion' was a
  misconstruction -- retired; statusAlarm@0x714 is only the body-graphic
  clip selector.
- The master perf's block 7 is three STREAMED mode-request counters
  (+0x334 DefaultState / +0x338 GimpLeft / +0x33c GimpRight -- authentic
  names, traced as consumed), fed only by creation/update records; landed
  with ctor/Reset zeroes and the Reset mode drop.
- The duck driver moved to its authentic slot (counters -> duck -> limp
  standup -> volume swap -> advance) and its gate is renamed
  mobilityScale: +0x79c is the MAX LEG EFFECTIVENESS over the +0x7ac
  roster (scales throttle, zeroes turn, gates duck), not a crouch analog.
- The advance dispatch is death-guarded (InDeathTransition before the
  gimp/normal pick) -- and with mode 9 finally written on kills,
  InDeathTransition and the camera director's death cut-away are LIVE.

Corrected identities recorded in MECH4.NOTES.md: +0x3f4 = REVERSE/BRAKE
flag (not airborne) with fwd/rev accel pick and gyro lurch kicks; 5-notch
throttle quantizer; RealMaxSpeed(0x15) one-shot send; @0049fa1c =
combat-effectiveness census -> +0x414; @0049fe80 = damage averages ->
+0x354/8/c; block 8 = move/collide with self-dispatched TakeDamage
collision damage + stagger clip 0x20; the instability accumulator and the
five ring buffers.

Soaks: duck cycles green from the new slot (3 full cycles, crash-sig 0).
The lifecycle kill-cycle conf crashes -- PROVEN PRE-EXISTING by a stashed
control build (identical 0xFC fault, EIP moves with the relink); forensics
in MISSILE.NOTES.md, broke unnoticed somewhere in 5.3.26..120.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 19:17:03 -05:00

143 lines
8.3 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; half-gone legs set the limp. **5.3.121
CARRIER CORRECTION**: every one of those writes goes to the MOVEMENT-MODE
alarm at mech+0x2c (level cell +0x40) -- `FUN_0041bbd8(mech+0x2c, ...)`:
right-leg-half → 4 GimpRight @0x49c8fb, left → 3 GimpLeft @0x49c926, leg
destroyed while limping → 9 @0x49c88f/@0x49c8c5, vital → 9 @0x49c83c --
NOT the @0x714 body-graphic alarm (that one's level is the body CLIP
index; damage never touches it). The leg branch gate is the real
`InDeathTransition` (@0049fb54, modes 2||9), landed to match.
- **`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.