the rest of the Myomers wrapper: two live outputs recovered, and one branch that never fires
Follow-on tob70654d, which chained the base sim and stopped there. Decoding the remaining 0x17c bytes of @004b8b9c turned up four more blocks the port had dropped along with it. In binary order the registered Performance is: @004b8bab chain PoweredSubsystemSimulation (fixed inb70654d) @004b8bb9 un-powered self-repair of this myomer's own zone (dead -- see below) @004b8c1e republish outputVoltage@0x344 from the source @004b8c5a republish speedEffect@0x31C, the drive fed to the mover @004b8ceb run the inner integrator, ONLY when outputVoltage > 0 speedEffect is the interesting one. AvailableOutput scales by the Mech base speed and the wrapper divides by it again, so the two cancel and what lands in +0x31C is a 0..1 FRACTION of full drive carrying the gear ratio, the thermal degradation curve and the accumulated zone damage. Neither it nor outputVoltage was ever written before: outputVoltage sat wherever the ctor left it and speedEffect stayed pinned at its ctor 1.0f. Un-stubbed DamageStructureLevel() while in here. It was `return 0.0f`, which held AvailableOutput's (1 - damage) factor at 1, so a shot-up myomer drove exactly as well as a fresh one. Routed to the base's GetSubsystemDamageLevel() bridge, the same cell sensor.cpp:312 already reads. Measured dmg=0.6 -> speed=0.4. @004b8bb9 does not work, and did not work in 1995 either. It builds a Damage (Explosive, amount 0xbc343958 ~= -0.011f, impactPoint from owner+0x100, burst 1) and calls the ZONE's TakeDamage -- vtable +0x18, not the subsystem's +0x24 -- so the plain `damageLevel += amount * damageScale[type]`. Read on its own that is "a myomer you power down slowly heals". But a subsystem's private zone is built by the 2-arg `new DamageZone(this, 0)`, and DAMAGE.cpp:187-190 zeroes all five damageScale entries; Reset never touches them and the only other writer is Mech__DamageZone, the mech's streamed zones. The sum is always `+= amount * 0`. Seeded a zone to 0.6, held it at NoVoltage for ~1500 ticks: damageLevel never moved. Reconstructed and deliberately not "fixed" -- a working repair here would be behavior we invented. The crit path reaches subsystem damage by writing damageLevel directly, which is why subsystems still die. That generalises, so it is written up in context/combat-damage.md on its own: you cannot damage a subsystem's own zone through DamageZone::TakeDamage at all. Anything reconstructed later that means to hurt a subsystem has to go the way the crit path goes, or it will silently do nothing. Verified, self-damage runs across two death/respawn cycles: torso 96/96 samples elec=4, zero dips myomers 96/97 elec=4 (the odd one is the first tick, before the machine runs) healthy outV=10000 speed=1 un-powered outV=0 speed=0 BT_MYOMERS_LOG probes the four outputs; BT_MYOMERS_REPAIR_TEST=<level> seeds the zone and drops the subsystem into Manual so the repair branch can be watched without the AutoConnect hunt restoring power a frame later. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
b70654dd11
commit
819772f974
@@ -318,6 +318,21 @@ death). ALL EIGHT proxy-view sites in mechsub.cpp swept to the engine view
|
||||
`GetStatusFlags` (read the vptr as a float → always "intact") and `ApplyDamageAndMeasure` (the
|
||||
crit cascade's damage-measure read garbage). Gotcha §5 (alias fields), new archetype.
|
||||
|
||||
## ⚠ A subsystem's PRIVATE zone cannot be damaged via `TakeDamage` (2026-07-28) [T0]
|
||||
Each `MechSubsystem` owns a private `DamageZone` at `+0xE0`, built by the **2-arg trivial ctor**
|
||||
`new DamageZone(this, 0)`. That ctor (`engine/MUNGA/DAMAGE.cpp:187-190`) zeroes **all five**
|
||||
`damageScale[]` entries, `Reset()` never touches them, and the only other writer in the tree is
|
||||
`Mech__DamageZone` — the mech's *streamed* zones (`mechdmg.cpp:246-253`), a different class. Since
|
||||
`DamageZone::TakeDamage` is `damageLevel += damageAmount * damageScale[damageType]`, **any**
|
||||
`TakeDamage` against a subsystem's own zone is arithmetically a no-op, whatever the amount or type.
|
||||
Verified by experiment: a zone seeded to 0.6 and fed −0.011f for ~1500 ticks never moved.
|
||||
The crit path works because it **bypasses this entirely** and writes `damageLevel` directly
|
||||
(`DistributeCriticalHit` pins `*(this[0x38]+0x158) = 1.0f`; `ForceCriticalFailure` sets the state).
|
||||
Practical consequence: if you are reconstructing something that "damages a subsystem", route it the
|
||||
way the crit path does — a `TakeDamage` call there will silently do nothing. This is also why
|
||||
Myomers' Performance `@004b8bb9` (an un-powered self-repair) is dead code in the 1995 binary itself:
|
||||
see [[subsystems]] WAVE 6.
|
||||
|
||||
## Damage delivery + the real damage model
|
||||
`Entity::TakeDamageMessage(id, size, inflictingEntityID, zone, Damage&)` → `target->Dispatch`.
|
||||
**Base handler IGNORES zone==−1** (`Entity::TakeDamageMessageHandler`, ENTITY.cpp:878 — returns on
|
||||
|
||||
+23
-3
@@ -84,9 +84,29 @@ Making a base byte-exact GROWS every subclass — they must be re-based TOGETHER
|
||||
does not match the function you reconstructed, check whether the real target is a wrapper that
|
||||
chains the base sim — the whole family does (`Generator`/`PoweredSubsystem` lead with
|
||||
`HeatSink::HeatSinkSimulation`; the Torso's own perf @004b5cf0 leads with `UpdateWatch`).
|
||||
Still unreconstructed [T4]: @004b8bb9-0x4b8c1e, an effect when `electricalStateAlarm == 1` and the
|
||||
myomer's damage zone is < 1.0, building a small vector (const `0xbc343958` ≈ -0.011f) against the
|
||||
owner's `localOrigin@0x100` — almost certainly the un-powered movement penalty.
|
||||
**The full wrapper is now reconstructed** [T1/T2, 2026-07-28] — five blocks in binary order:
|
||||
`@004b8bab` chain `PoweredSubsystemSimulation`; `@004b8bb9` the un-powered self-repair (see below);
|
||||
`@004b8c1e` republish `outputVoltage@0x344` from the resolved source when the alarm is Ready;
|
||||
`@004b8c5a` republish `speedEffect@0x31C` = `AvailableOutput(clamped V) / OwnerBaseSpeed()` — the
|
||||
Mech base speed CANCELS, so `speedEffect` is a **0..1 fraction** of full drive carrying gear ratio,
|
||||
thermal curve and zone damage; `@004b8ceb` run the inner integrator **only when `outputVoltage > 0`**.
|
||||
Verified live: healthy `outV=10000 speed=1`, un-powered `outV=0 speed=0`, and 96/96 torso samples at
|
||||
`elec=4` across two death/respawn cycles.
|
||||
⚠ **`@004b8bb9` is DEAD CODE — in the original too** [T0]. It builds a `Damage`
|
||||
(type=`Explosive`, amount=`0xbc343958`≈-0.011f, impactPoint=`owner+0x100`, burst=1) and calls the
|
||||
**zone's** `TakeDamage` (zone vtable `+0x18`, *not* the subsystem's `+0x24`). Reading it as
|
||||
"an un-powered myomer heals" is the evident intent, but it can never fire: a subsystem's private zone
|
||||
comes from the 2-arg `new DamageZone(this,0)`, and `engine/MUNGA/DAMAGE.cpp:187-190` zeroes **all five**
|
||||
`damageScale[]` entries; `Reset()` never touches them and the only other writer is `Mech__DamageZone`
|
||||
(the mech's *streamed* zones — `mechdmg.cpp:246-253`). Since `TakeDamage` is
|
||||
`damageLevel += amount * damageScale[type]`, the sum is always `+= amount * 0`. Confirmed by
|
||||
experiment: seeded to 0.6 and held at NoVoltage ~1500 ticks, `damageLevel` never moved.
|
||||
Reconstructed and deliberately NOT "fixed" — inventing a working repair would be made-up behavior.
|
||||
**General rule:** you cannot damage a subsystem's own zone through `DamageZone::TakeDamage`; the crit
|
||||
path writes `damageLevel` directly (`DistributeCriticalHit` pins `*(this[0x38]+0x158) = 1.0f`).
|
||||
Also un-stubbed here: `Myomers::DamageStructureLevel()` returned a hardcoded `0.0f`, which pinned
|
||||
`AvailableOutput`'s `(1 - damage)` factor at 1 — a shot-up myomer drove exactly as well as a fresh
|
||||
one. Now routed to the base bridge `GetSubsystemDamageLevel()`; measured `dmg=0.6 → speed=0.4`.
|
||||
- **WAVE 7** — projectile/missile weapons (byte-exact; flying projectiles are a PORT reconstruction —
|
||||
the 2007 Entity is 0x1BC vs the binary's 0x300, so raw base-offset reads fail).
|
||||
- **TASK #56 (2026-07-11)** — Gyroscope LIVE byte-exact (ctor @004b3778, sizeof 0x3D0 locked;
|
||||
|
||||
Reference in New Issue
Block a user