Files
TeslaRel410/restoration/source410/BT/MECHDMG.NOTES.md
T
CydandClaude Fable 5 76cfc64353 BT410 Phase 5.3.16: targeting + damage -- two-mech fights are LIVE
The simulation is now a FIGHT. Mech target slot (+0x388), authentic
Loaded->Firing gate (viewFireEnable && HasActiveTarget), UpdateTargeting
range refresh, and SendDamage via the engine TakeDamageMessage.

MECHDMG.CPP is BORN against the AUTHENTIC surviving CODE/BT/BT/MECHDMG.HPP:
the Mech ctor's Pass-3 hull zone fill constructs Mech__DamageZone per zone
(the Entity base only allocates the raw pointer array -- unfilled slots were
crash #1; a base-class fill parses each record short and skews the stream --
crash #2). Streamed ctor parses the full BT tail (flags / Scalar-weighted
criticals with the Master+Dynamic plug gate / LOD redirect table) and
normalizes the armor economy: scale[type]=1/(raw x armorPoints), legs
halved -- BT411-verified @0049ce50.

Live two-mech verification (BT_SPAWN_ENEMY harness in DropZoneReply):
20 named hull zones stream on both mechs; PPC lands 11.77/hit (12 x
chargeRatio^2, type 4), ER-M laser 3.43 (type 3), SRM6 salvos 5.83 x 6
rounds on independent zones (type 2, one message per missile -- burstCount
is NOT in the zone formula); repeat hits climb linearly, leg zones absorb
at half scale, zones reach 1.0 Burning/Destroyed. No-target and novice
regressions hold (weapons load but never fire without a target).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 10:21:43 -05:00

4.6 KiB
Raw Blame History

MECHDMG.CPP — Mech::DamageZone (hull zone armor economy)

Status: PARTIAL (5.3.16) — streamed ctor / LOD parent pass / dtor reconstructed; TakeDamage + SetGraphicState are staged base pass-throughs.

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.

Staged / deferred (the mechdmg cascade wave)

  • TakeDamage override = base pass-through. Authentic (@0049c690): artifact zones route hits through the redirect table to a real child (LODDamageRouter/RandomRedirect/lastHitZone), parent recomputes its displayed level (UpdateLODDamageLevel), critical rolls (CriticalHit), generator shorting, leg-failure state.
  • SetGraphicState override = base pass-through (authentic also flips the segment video objects to destroyed skins).
  • Declared-not-defined (unreferenced, so they link): LODDamageRouter, RandomRedirect, UpdateLODDamageLevel, ShortAttachedGenerators, SendSubsystemDamage, RecurseSegmentTable, CriticalHit, CreateStreamedDamageZone, GetSegmentIndex.
  • lastHitZone staged 0 (authentic seeds via RandomRedirect when redirects exist); lastDamageTime staged 0 (authentic = TheTime tick / TicksPerSecond).
  • The type-0x1e critical-descriptor stream + MechDeathHandler (BT411 mech.cpp Pass-3 tail) are NOT loaded yet — same cascade wave.