Combat: critical-subsystem plugs BOUND -- zone destruction damages carried subsystems (task #2)

The binding was in the zone ctor all along: Ghidra dropped the two arg
pushes @0049d0e1 (Slot::AddImplementation(subsystemArray[streamedIndex])),
making it read as a bare Resolve().  DZSlot stand-in -> engine SlotOf<T>;
SendSubsystemDamage rewritten to the recovered @0049c9a8 body (allotment
into the subsystem's OWN private zone; vital -> graphicAlarm 9); CriticalHit
-> real ApplyDamageAndMeasure; parentArtifactZone.Add revived (LOD damage
averaging); videoObjectFlag renamed vitalSubsystem (+0xE4).  BT_CRIT_PROBE
diag added.  Verified: 66 plugs bound/mech; probe-destroyed zone -> crits
damaged/DESTROYED, statusAlarm + destroyed-skin chain fire; MP kill + solo
un-regressed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-11 12:10:43 -05:00
co-authored by Claude Fable 5
parent a652ddcdbc
commit 87c25b9206
6 changed files with 172 additions and 63 deletions
+18 -7
View File
@@ -179,13 +179,24 @@ authentic path scoped.
(the authentic death-row removal — needs the mech render tree unhooked from the renderer first;
the inert wreck is the safe stand-in until then).
Do NOT issue DestroyEntityMessage on death (before the render-tree teardown lands).
- **Critical-subsystem plugs UNBOUND (43 skips/mech) [T3].** `MechCriticalSubsystem::subsystemPlug`
never gets WIRED to its live subsystem (the ctor's Resolve() at mechdmg.cpp:276 only READS; the
binding write is elsewhere/unreconstructed), so `SendSubsystemDamage` skips every entry via the
unbound-plug guard (mechdmg.cpp, added 2026-07-08 after a live NULL-deref AV in
`SendSubsystemDamage+0x93` — the 1995 binary derefs unchecked because there every plug is bound).
Consequence: zone destruction does NOT yet propagate damage into carried subsystems (generator/
gyro failures etc.). Also latent there: `SubProxy2` raw-offset reads (databinding trap) once s≠0.
- **Critical-subsystem plugs BOUND — damage propagates (task #2, 2026-07-11) [T1/T2].**
The "binding write is elsewhere" reading was WRONG: the binding is IN the zone ctor loop
(@0049d0e1-0049d10d) — **Ghidra dropped the two argument pushes** ("type propagation not
settling"), making `Slot::AddImplementation(roster[streamedIndex])` read as a bare no-arg
call (mislabeled Resolve). The stream's third per-entry dword IS the roster index into
`subsystemArray` (mech+0x128). Port: the `DZSlot` stand-in (always-null Resolve) replaced
with the engine `SlotOf<T>` (binary vtables 0050bb84/0050bb7c ARE its instantiations); bind
gated MasterInstance+DynamicFlag (`simulationFlags@+0x28`); replicant plugs stay unbound
(master-authoritative). `SendSubsystemDamage` rewritten to the recovered @0049c9a8 logic:
the unused crit allotment lands in the subsystem's OWN private 0x160 DamageZone (the old
`SubProxy2` cast to a mech zone was wrong), ≥1.0 → statusAlarm Destroyed + gated PrintState
+ zone valve, vital → `graphicAlarm 9` (mech kill); `CriticalHit` now calls the real
`ApplyDamageAndMeasure` (@4ac07c); the artifact `parentArtifactZone.Add` back-pointer bind
also revived (LOD damage averaging). `videoObjectFlag` was `vitalSubsystem` (+0xE4,
res+0x48 "VitalSubsystem"). VERIFIED live (`BT_CRIT_PROBE=<zone>` diag): 66 plugs bound at
ctor per mech, zone destroyed → crits damaged/DESTROYED with authentic accumulation.
Residue: roster idx 0/1 (mapper installed post-ctor / voltage-bus stub) stay unbound with a
logged skip — none of the 8 mechs' streams crit-list them in practice.
- **Two dead Wword comparison branches (multiplayer)**: `mech.cpp:1511` + `:1613` (replicant
leg-state/stability sync in ReadUpdateRecord) — always-false BTVal comparisons ([[reconstruction-gotchas]] §2);
needs slots 0xf/0x10 promoted to named members when P6 replication work resumes.