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

8.3 KiB
Raw Blame History

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.