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>
4.6 KiB
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:
- Unfilled slots are heap garbage → the FIRST TakeDamageMessage dispatched at a mech called through a trash vtable (exception 0E, EIP inside the heap).
- Filling with the engine BASE
::DamageZonestreamed 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-scopeDamageZonetypedef (=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 already1/armorPointspass through, tolerance 1e-4); leg zones halve every type's scale. - Base
DamageZone::TakeDamagethen appliesdamageLevel += amount × scale[type], clamp [0,1], 1.0 = Burning/Destroyed. damageTypeindexes 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)
TakeDamageoverride = 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.SetGraphicStateoverride = 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. lastHitZonestaged 0 (authentic seeds via RandomRedirect when redirects exist);lastDamageTimestaged 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.