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>
194 lines
11 KiB
Markdown
194 lines
11 KiB
Markdown
# MECHSUB — reconstruction notes (phase 2 kickoff, 2026-07-19)
|
||
|
||
**Status: base-class ANALYSIS done; the reconstruction must be done FRESH against
|
||
the 4.10 structure, NOT backdated from BT411 (its base class + damage model
|
||
diverge). Key findings below de-risk the actual code.**
|
||
|
||
MechSubsystem is the base of the ~30-class subsystem roster (sensor, gyro, torso,
|
||
hud, generator, myomers, weapons…) the Mech ctor's segment-table walk
|
||
instantiates. The staged MECHSUB.HPP is interface-only (one `Mech *owner`
|
||
member, no bodies). Reconstructing the base is phase-2 step 1.
|
||
|
||
## Dependencies (all survive in the 4.10 archive)
|
||
|
||
- `Subsystem` (CODE/RP/MUNGA/SUBSYSTM.HPP) — the base.
|
||
- `DamageZone` (CODE/RP/MUNGA/DAMAGE.HPP/.CPP) — the engine damage zone.
|
||
- `Mech__DamageZone : public DamageZone` (CODE/BT/BT/MECHDMG.HPP) + the BT damage
|
||
model (MechCriticalSubsystem, redirect tables, critical hits).
|
||
- `AlarmIndicator` (just reconstructed, MUNGA/ALARM.HPP) — statusAlarm.
|
||
|
||
## Finding 1 — the base Subsystem DECLARES the zone but does NOT create it (RESOLVED)
|
||
|
||
SUBSYSTM.HPP:181 declares `DamageZone *damageZone;` in the **base** Subsystem and
|
||
its TakeDamage (SUBSYSTM.HPP:177-178) delegates `damageZone->TakeDamage(damage)`.
|
||
The base ctors ARE reconstructed — **staged source410/MUNGA/SUBSYSTM.CPP** (built,
|
||
in munga.lib; 38 Subsystem:: syms in the map). Both base ctors set
|
||
`damageZone = NULL` (resource ctor line 63) — the base does **not** allocate the
|
||
zone. So the DERIVED MechSubsystem creates the `Mech__DamageZone` (BT411's
|
||
zone-creation is structurally right here) — but the ARMOUR it seeds must follow
|
||
Finding 2 (per-type streamed), not BT411's per-facing hand-seed.
|
||
|
||
## Finding 2 — CRITICAL: the damage model diverged
|
||
|
||
The 4.10 base DamageZone (DAMAGE.HPP/CPP) is a **per-damage-TYPE** model:
|
||
- members: `defaultArmorPoints` (Scalar) + `damageScale[Damage::DamageTypeCount]`
|
||
where DamageTypeCount = 5 = {Collision, Ballistic, Explosive, Laser, Energy}.
|
||
- TakeDamage: `damageLevel += damage.damageAmount * damageScale[damage.damageType]`
|
||
(DAMAGE.CPP:413); the zone STREAMS its own armour (`*stream >> defaultArmorPoints;
|
||
*stream >> damageScale[ii]`, DAMAGE.CPP:335,340).
|
||
|
||
BT411's reconstruction is a **per-FACING** model: `armorByFacing[5]` (front/back/
|
||
left/right/top) + `structureReference`, **manually seeded** by MechSubsystem's
|
||
resource ctor with an armour→absorption-coefficient inversion. Those field names
|
||
(`armour[5]`, `structureReference`) exist only in BT411's `ReconDamageZone`
|
||
proxy — NOT in the surviving 4.10 DamageZone/Mech__DamageZone. The staged
|
||
`MechSubsystem__SubsystemResource` (armorByFacing[5]+structureReference) was
|
||
copied from BT411 and is likewise suspect for 4.10.
|
||
|
||
**Consequence:** BT411's MechSubsystem ctor (heat.cpp/mechsub.cpp) CANNOT be
|
||
backdated verbatim — it would install a damage model the 4.10 engine's DamageZone
|
||
doesn't implement, corrupting every subsystem's damage behaviour. The 4.10
|
||
MechSubsystem must let the base Subsystem + DamageZone stream ctor handle armour
|
||
(per-type), not hand-seed per-facing values.
|
||
|
||
## Corrected reconstruction approach (fully scoped — ready to write)
|
||
|
||
1. ~~Pin the base Subsystem ctor~~ **DONE (Finding 1)** — staged SUBSYSTM.CPP sets
|
||
`damageZone = NULL`; MechSubsystem creates the `Mech__DamageZone`.
|
||
2. Reconstruct MechSubsystem: chain the base Subsystem, `new Mech__DamageZone(this,
|
||
index)`, add the mech-specific members (statusAlarm = AlarmIndicator, vital
|
||
flag, alarmModel, criticalReference, collisionCriticalHitWeight,
|
||
printSimulationState, configureActivePress) and the TakeDamage override, using
|
||
the 4.10 **per-type** damage model — let the zone stream its own armour
|
||
(defaultArmorPoints + damageScale[5]); do NOT hand-seed BT411's per-facing
|
||
armorByFacing[5]/structureReference.
|
||
3. Re-derive `MechSubsystem__SubsystemResource` against the 4.10 DamageZone stream
|
||
format (the staged struct's per-facing fields are BT411-copied and suspect).
|
||
4. Then the roster (GAUSS/PPC/SENSOR source + .TCP partials + decomp).
|
||
|
||
Analysis complete; step 2 is the next code to write. This worksheet is the
|
||
deliverable that keeps the subsystem foundation on the 4.10 damage model rather
|
||
than BT411's divergent one.
|
||
|
||
## Progress + roster hierarchy findings (2026-07-19)
|
||
|
||
**DONE:** `MechSubsystem` base (MECHSUB.HPP/.CPP) and `HeatableSubsystem`
|
||
(HEAT.HPP/.CPP) reconstructed + compile-verified. Verified vertical slice so far:
|
||
`Subsystem → MechSubsystem → HeatableSubsystem`.
|
||
|
||
**The subsystem hierarchy is DEEPER than the staged headers show.** From BT411:
|
||
|
||
```
|
||
MechSubsystem (done)
|
||
└ HeatableSubsystem (done; ctor chains MechSubsystem + ResetToInitialState
|
||
-> currentTemperature=300, heatLoad=0)
|
||
└ HeatSink (MISSING from the staged tree -- not in source410 nor
|
||
CODE; VDATA HeatSinkClassID=0xBC5. Must be added.)
|
||
└ PoweredSubsystem (staged POWERSUB.HPP wrongly derives it from
|
||
HeatableSubsystem -- real base is HeatSink; FIX the
|
||
staged header. Also its ctor has embedded AlarmIndicators
|
||
(electricalStateAlarm 5-level, modeAlarm 3-level) and a
|
||
voltageSource SlotOf.)
|
||
└ Sensor (surviving header CODE/BT/BT/SENSOR.HPP; leaf)
|
||
└ Generator, ... (other leaves)
|
||
```
|
||
|
||
**PoweredSubsystem has inter-subsystem wiring** — its ctor resolves a voltage
|
||
source (generator) by indexing the OWNER MECH's subsystem roster
|
||
(`owner->GetSubsystem(res->voltageSourceIndex)`), not the skeleton. So powered
|
||
subsystems must be created AFTER the generator in the segment-walk order, and the
|
||
Mech must expose GetSubsystemCount()/GetSubsystem(). This ordering constraint is
|
||
a segment-walk (phase-4 ctor) concern; note it there.
|
||
|
||
## Complete subsystem family tree (mapped 2026-07-19) + build status
|
||
|
||
```
|
||
MechSubsystem DONE (MECHSUB.HPP/.CPP)
|
||
├─ HeatableSubsystem DONE (HEAT)
|
||
│ └─ HeatSink DONE (HEAT)
|
||
│ ├─ PoweredSubsystem DONE (POWERSUB)
|
||
│ │ ├─ Sensor DONE (SENSOR; interface survives in CODE)
|
||
│ │ └─ Myomers todo (myomers.hpp)
|
||
│ ├─ Generator DONE (POWERSUB; source, GNRATOR.TCP partial)
|
||
│ └─ Condenser todo (heat.hpp)
|
||
└─ HeatWatcher (: MechSubsystem) todo -- sibling branch to HeatableSubsystem
|
||
└─ PowerWatcher (: HeatWatcher) todo
|
||
├─ Torso todo (torso.hpp) -- Mech sinkSourceSubsystem
|
||
├─ HUD todo (hud.hpp) -- Mech hudSubsystem
|
||
└─ Gyroscope todo (gyro.hpp) -- Mech gyroSubsystem
|
||
```
|
||
|
||
Plus the WEAPON family (separate subtree): MechWeapon → {PPC, Gauss, ProjectileWeapon
|
||
→ Missile/…}. PPC/GAUSS source survives in CODE/BT/BT; AMMOBIN/EMITTER/MISSILE/
|
||
PROJTILE/PROJWEAP have .TCP partials; the rest decomp-only.
|
||
|
||
**Pattern established** (6 classes verified): each = statics
|
||
(ClassDerivations(parent, "Name") + DefaultData reusing Subsystem::MessageHandlers/
|
||
AttributeIndex/StateCount), ctor chaining the parent + member init from the
|
||
resource, dtor, TestClass/TestInstance/ResetToInitialState real, per-frame
|
||
Simulation method + CreateStreamedSubsystem staged with Fail. Embedded alarms =
|
||
AlarmIndicator(N); filters = AverageOf<Scalar>; owner->GetInstance() !=
|
||
ReplicantInstance gates the master performance install.
|
||
|
||
## ROSTER CORE COMPLETE (2026-07-20) — 16 classes verified
|
||
|
||
All committed + compile-verified: MechSubsystem, HeatableSubsystem, HeatSink,
|
||
HeatWatcher, PowerWatcher, PoweredSubsystem, Generator, Condenser, Sensor,
|
||
Myomers, Gyroscope, HUD, Torso, MechWeapon, Emitter (+ surviving PPC/GaussRifle),
|
||
ProjectileWeapon, MissileLauncher, AmmoBin. Tree links clean.
|
||
|
||
## Phase-4 blueprint: the Mech-ctor segment-walk factory (mech.cpp:1146-1330)
|
||
|
||
The Mech ctor allocates `subsystemArray[subsystemCount]` (+GetSubsystem/Count),
|
||
then loops segments (from id 2) switching on `seg->classID` to `new <Class>(this,
|
||
id, seg)`, caching the key ones. **The VDATA ClassID enum names are MISLABELED vs
|
||
the real class** (BT411's mapping):
|
||
|
||
| ClassID | enum name | REAL class | built? |
|
||
|---|---|---|---|
|
||
| 0xBBD | CockpitClassID | Condenser | ✅ |
|
||
| 0xBBE | SensorClassID | HeatSink bank | ~ (HeatSink ✅; bank variant TODO) |
|
||
| 0xBC0 | CondenserClassID | **Reservoir** | ❌ TODO |
|
||
| 0xBC1 | GeneratorClassID | Generator | ✅ |
|
||
| 0xBC2 | PoweredSubsystemClassID | PoweredSubsystem | ✅ |
|
||
| 0xBC3 | MyomersClassID | Sensor | ✅ |
|
||
| 0xBC4 | GyroClassID | Gyroscope | ✅ (cache gyroSubsystem) |
|
||
| 0xBC5 | SinkSourceClassID | Torso | ✅ (cache sinkSourceSubsystem) |
|
||
| 0xBC6 | ActuatorClassID | Myomers | ✅ |
|
||
| 0xBC8 | WeaponEmitterClassID | Emitter | ✅ (weaponCount++) |
|
||
| 0xBCB | JumpJetClassID | AmmoBin | ✅ |
|
||
| 0xBCD | MechWeaponClassID | ProjectileWeapon | ✅ |
|
||
| 0xBCE | MissileWeaponClassID | GaussRifle | ✅ |
|
||
| 0xBD0 | BallisticWeaponClassID | MissileLauncher | ✅ |
|
||
| 0xBD3 | SubsystemMessageManagerID | **SubsystemMessageManager** | ❌ TODO |
|
||
| 0xBD4 | GaussWeaponClassID | Emitter (PPC) | ✅ |
|
||
| 0xBD6 | MechTechClassID | HUD | ✅ (cache hudSubsystem) |
|
||
| 0xBD8 | LegSubsystemClassID | **Searchlight** (: PowerWatcher) | ❌ TODO |
|
||
| 0xBDC | HeatableClassID | **MechTech** | ❌ TODO |
|
||
| 0xBDE | DisplayClassID | **ThermalSight** (: PowerWatcher) | ❌ TODO |
|
||
|
||
Remaining subsystem classes for a full walk: Reservoir, HeatSink-bank variant,
|
||
Searchlight, MechTech, ThermalSight, SubsystemMessageManager, Actuator (fallback).
|
||
Then the Mech member layout (subsystemArray/subsystemCount + the capability chains
|
||
controllable/heatable/weaponRoster/damageable + the alarms/reticle/state-indicators
|
||
/SequenceControllers/NameFilter/CStrings embedded members) must be named in
|
||
MECH.HPP for the ctor to set them.
|
||
|
||
## 5.3.17 — the subsystem side of the damage cascade
|
||
|
||
- The subsystem's PRIVATE zone is now ARMED (binary @4abf28): owned by the
|
||
SUBSYSTEM (`new DamageZone(this, 0)` — Joint.cpp:512 proves subsystems are
|
||
valid zone owners), named, `defaultArmorPoints = structureReference`,
|
||
`damageScale[5] = armorByFacing[]` normalized with the hull-zone rule
|
||
(1/(raw×points); guard: structureReference<=0 leaves the zone inert).
|
||
This makes crits MEASURABLE.
|
||
- `ApplyDamageAndMeasure` (@4ac07c): TakeDamage + return the zone-level
|
||
delta (the crit accountant charges it).
|
||
- `ForceCriticalFailure` now also sets `SetSimulationState(DestroyedState)` —
|
||
the subsystem SIMULATION state enum (Destroyed=1/Exploding=2, from the
|
||
binary PrintState string pool) added to MECHSUB.HPP; both weapon FSMs'
|
||
`GetSimulationState() == 1` hard gate is now live (destroyed weapons stop
|
||
firing — fight-verified).
|
||
- `IsVitalSubsystem()` accessor (vital crit death = mech kill in
|
||
SendSubsystemDamage).
|