Asking what an AmmoBin does with the gun heat alarm that 5.3.132 brought to life turned up an entire mechanic that has never run here, dead for two independent reasons: the watch link was unbound so the mirrored alarm never left NormalHeat, and our AmmoBin has no Performance at all where the binary installs one. The trigger: when the gun a bin feeds goes into HEAT FAILURE, the bin latches a randomised countdown. Heat clearing cancels it; expiry destroys the bin and detonates it. The damage is authored per round and scaled by rounds remaining, so a full bin guts the mech and a nearly empty one barely coughs. The blast is then SHARED -- divided by the number of targets, not applied whole to each -- and every victim is announced with the authentic string "ammo explosion damaging <name>", which is what confirmed the whole reading. One identity is deliberately left unpinned: what the collector iterates and what its 0x4e filter selects. It is not a classID, and the per-victim reads look more like damage zones than subsystems. Guessing would produce a plausible mechanic that damages the wrong things -- precisely the failure this project keeps catching -- so the collector gets read first and the whole thing lands in one piece. Worth noting the shape: two dead systems in series were hiding a third. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
708 lines
39 KiB
Markdown
708 lines
39 KiB
Markdown
# MECH4 — pre-TU reconstruction notes
|
||
|
||
**Status: no MECH4.CPP exists yet.** The ground snap, frame rejection,
|
||
knockdown stagger and collision economy that the binary keeps in mech4's
|
||
master performance are currently RE-HOSTED inside `Mech::Simulate`
|
||
([[MECH]], 5.3.98–100) at seams marked STAGED. This file carries the decode
|
||
work for the pieces not yet written, so it survives session boundaries.
|
||
|
||
## Mech::ProcessCollision (@004abb40) — decoded, not yet reconstructed
|
||
|
||
The override of the engine's protected virtual (`MOVER.CPP:1354`), called
|
||
per contact by `Mover::ProcessCollisionList`. The decomp
|
||
(part_012.c:15206–15416) reads as the BASE function plus four additions:
|
||
|
||
1. **The base head, verbatim**: `collisionVolume->ProcessCollision(collision,
|
||
worldLinearVelocity, lastCollisionList, &damage->surfaceNormal,
|
||
&penetration)`; on a miss NOTHING runs. Clamp penetration to the slice,
|
||
ratio = pen/dt, `damage->damageAmount = StaticBounce(old, dt, r, normal,
|
||
&elasticity, minimumBounceSpeed, &friction)` with the elasticity/friction
|
||
locals seeded from the mover's coefficients.
|
||
|
||
2. **The Mover-family branch** (owner = the collided solid's owner entity,
|
||
solid+0x1c; family test FUN_0041a1a4 against the "Mover" derivation GUID
|
||
@0x4e4518):
|
||
* SEPARATING-CONTACT GATE: `(ourWorldVel − other->WorldVel[vtbl+0x20])
|
||
· normal < −1e-4` (`_DAT_004ac044`) → return, no damage, no dispatch.
|
||
* If the other's classID == **0xbb9** (mech), build a TakeDamage message
|
||
(raw wire shape `{id, len, inflictor = DAT_0050b9ac global, zone −1,
|
||
the Damage verbatim}`) and Dispatch it AT THE OTHER — the mech-vs-mech
|
||
ram. Use our `Entity::TakeDamageMessage` ctor as the weapon path does.
|
||
|
||
3. **The CulturalIcon branch** (GUID @0x4e7038):
|
||
* `stopping = other->instanceFlags & 0x8000` (the StoppingCollisionVolume
|
||
flag, entity+0x28).
|
||
* Same separating gate against a ZERO other-velocity (icons are static).
|
||
* Dispatch the crunch TakeDamage at the icon (crushable props have no
|
||
zones → their base handler no-ops it, but the dispatch is real).
|
||
* If NOT stopping: `damage->damageAmount = 0.00123f` (bits 0x3aa137f4) —
|
||
the WALK-THROUGH SENTINEL. The caller (the response block in Simulate)
|
||
must treat exactly 0.00123f as "the move stands": restore the post-snap
|
||
saves, zero the amount, feed the gyro crunch (torque 0.4 along the
|
||
normalized force, upward impulse 0.2 — binary @4aa81e/@4aa86c).
|
||
|
||
4. **The material tail**: the elasticity/friction OUT values from
|
||
StaticBounce are compared against `_DAT_004ac048`; sets
|
||
`collisionTemporaryState` (+0x44c) to 1, or 2 when both match and the
|
||
state was 0. The state is pushed into the `collisionState` StateIndicator
|
||
once per frame by the response block (0→1 fires the impact sound). Needs
|
||
the `_DAT_004ac048` constant identified before writing (likely the
|
||
default-material coefficient — check the literal pool at 0x4ac048).
|
||
|
||
## Two hazards the donor fought that DO NOT apply here
|
||
|
||
* **Compounded StaticBounce reflections**: our engine's
|
||
`Mover::ProcessCollisionList` (MOVER.CPP:1203) restores
|
||
`worldLinearVelocity = initial_velocity` and position PER CONTACT before
|
||
each ProcessCollision call, and averages the results. The port's
|
||
`frameEntryWorldVelocity` economy guard has no counterpart hazard in this
|
||
engine — do not import it.
|
||
* **The AlarmIndicator ODR split**: a port-only problem; our tree reads
|
||
`MovementMode()` / the alarms directly.
|
||
|
||
## Already re-hosted in Mech::Simulate (move here when mech4.cpp exists)
|
||
|
||
| piece | binary | landed |
|
||
|---|---|---|
|
||
| ground probe + snap | @4aa630–4aa6cc | 5.3.98 |
|
||
| frame rejection + crash self-damage + knockdown | @4aa6cf–4aab0b | 5.3.99 |
|
||
| collision economy divert + distributor | @0x4a0368 → @0049ffcc | 5.3.100 (in the damage hub, where the binary has it) |
|
||
| status→mode limp promotion | master perf (un-decompiled) | 5.3.97, STAGED |
|
||
| forwardCycleRate re-pick (ground/airborne) | IntegrateMotion head | partially — the ctor sets it once; the per-frame re-pick needs +0x3f4 |
|
||
|
||
## Still to decode
|
||
|
||
* `FUN_004a9b5c` — the master performance. **ISOLATED AND MAPPED
|
||
(2026-08-04):** one single 5,676-byte body @004a9b5c..@004ab168 (one
|
||
`ret`), hidden inside the manifest's mis-sized @004a9770 entry (that fn
|
||
is really the stream-builder's 6.6KB name→SharedData strcmp resolver,
|
||
and Ghidra swallowed everything to @004ab188 behind it). Registration
|
||
proven from the ctor decomp: `(flags & 0xC) == 4` → replicant perf,
|
||
else master @004a9b5c. Recipe: extract CODE bytes (va 0x1000 → file
|
||
0xe00), `ndisasm -b 32 -o 0x4a9b5c`. **5.3.121 corrections: the DECOMP
|
||
has both perfs in plain C (master = part_013.c:3059, and @004ab430 IS
|
||
the complete replicant perf — my "@004ab9d8 hidden inside it" note was
|
||
its CONSTANT POOL @004ab9c8.. right after the body). Replicant perf
|
||
read (not yet landed): zero +0x1dc, IntegrateMotion(dt, flavor 1);
|
||
ret 1 (fresher update present) → quaternion slerp +0x10c toward the
|
||
update target +0x26c with t = dt/(update age + dt) + matching velocity
|
||
lerps (+0x1d0 vs +0x2a4, +0x1f4 vs +0x298); either path applies the
|
||
+0x598 error-offset vector smoothed over the @004ab9cc window while
|
||
the +0x5a4 latch holds, then position ← the DR anchor +0x260; without
|
||
flags&0x4000 it re-resolves its room via the world chain (@0040e36c on
|
||
the +0x2e8 box) → +0x2f0; same ground clamp; `FUN_00421b6c`; same five
|
||
ring publishes + acceleration tail; NO update-record writer.**
|
||
|
||
**The ordered call skeleton (111 calls)** — blocks in execution order,
|
||
matching our Simulate's structure and locating every staged gap:
|
||
1. controls/demand intake (mech.cpp helpers 0x4a4eab/0x4a4ee9/0x4a4f9e)
|
||
2. three cerr+SetLevel triplets — illegal-state alarm guards
|
||
3. **THE DUCK DRIVER** — DECODED + LANDED 5.3.120, live-verified
|
||
(BT_FORCE_DUCK cycles down/hold/up continuously on the rig). The
|
||
full system: DuckRequest message 0x1A (authentic name from the
|
||
binary's handler table; roster = RealMaxSpeed 0x15, BalanceCoolant
|
||
0x16, SetBurningState 0x17, ClearBurningState 0x18, EjectPilot 0x19,
|
||
DuckRequest 0x1A) latches one evaluation (+0x398, one-shot, Reset
|
||
clears); the phase machine reads the RAW simulation state, the
|
||
mapper's duck cell (+0x25c) and the analog demand (+0x79c, deadzone
|
||
1e-4 @0x4ab16c): gait 0/4 + input -> down (LEG slot 2, sqd), gait 1
|
||
-> up (slot 3, squ); limp modes 3/4 force stand-up; the
|
||
collisionVolumeState alarm (+0x4c4; 1=standing, 0=ducked; "Whoa! Bad
|
||
Collision Volume State!" guard, line 417) swaps the collision
|
||
template's maxY (ExtentBox +0xc == the box TOP; the ground probe's
|
||
minY untouched) between the ctor-captured height and 0.6x it (the
|
||
double @004a2d38); duckState publishes for the cockpit selector.
|
||
Still open here: the two MarkUpdate masks (8 then 1) ride our
|
||
ForceUpdate no-op, and the mapper duck cell's streamed binding id
|
||
awaits a BT_MAP_LOG audit on a duck-bound RES.
|
||
4. verify-guarded state read (mech4 accessors + Fail 0x40385c)
|
||
5. gyro feed a (vec pair → gyro_b 0x4b2de4, gyro_a 0x4b2d8c)
|
||
6. death GUARD on the advance dispatch: InDeathTransition 0x49fb54;
|
||
0x4a71f4 = AdvanceLegAnimationGimp (modes 3/4 + 0x580), 0x4a5028 =
|
||
AdvanceLegAnimation -- NOT a death advancer (5.3.121 correction)
|
||
7. ~~status→mode LIMP PROMOTION~~ -> the STREAMED MODE-REQUEST COUNTERS
|
||
(see the 5.3.121 section; landed)
|
||
8. update-record block (0x421bac/0x40e5f0/0x4227e0/0x41db7c/0x422bac +
|
||
Point3D copies) — the dead-reckon/update writer
|
||
9. collision → gyro crunch (gyro feeds + alarms)
|
||
10. knockdown/bump arms (EntityID pair + two SetAnim/Mark/SetLevel arms)
|
||
11. IntegrateMotion @004ab1c8 + position dirty marks
|
||
12. pose/joint tail: gyro_d/gyro_c → **Torso::UpdateJoints @004b66b4** →
|
||
five StateIndicator::SetState publishes + four GetState reads
|
||
13. frame close (position latch)
|
||
|
||
**MarkUpdate = FUN_004a4c54 (×20!)** — the update-record dirty-mask OR
|
||
(death strips motion bits); our `ForceUpdate(int)` is an EMPTY STUB, so
|
||
none of these marks exist in our tree yet (MP replication feed).
|
||
Next sitting: instruction-level read of blocks 3 and 7 (the two STAGED
|
||
items), then the mark masks.
|
||
* `@004ab430` ReplicantPerformance (1432B) — the replicant-side interior.
|
||
* `@004ab1c8` IntegrateMotion — decoded (see 5.3.95 notes) but only
|
||
partially re-hosted: the local-velocity/orientation integration and the
|
||
dead-reckon latency fold (+0x778/+0x77c) remain. **`+0x598` is a Point3D**
|
||
(its replicant branch writes it with a vector subtract), so
|
||
`motionEventName` in MECH.HPP is mistyped as CString — fix when this
|
||
lands.
|
||
|
||
|
||
## 5.3.121 -- THE DECOMP HAS THE MASTER PERF (ledger correction + full C read)
|
||
|
||
**`part_013.c:3059` = `@004a9b5c file=bt/mech4.cpp` -- the ENTIRE master
|
||
performance is in the BT411 decomp as plain C.** Every "never decompiled /
|
||
raw-disasm-only" note above is superseded; the asm read (scratchpad
|
||
masterperf.asm) and the C agree line for line where both were done. Rule
|
||
for the rest of this file: read the C first, keep ndisasm for byte-level
|
||
checks. (`@004ab1c8` IntegrateMotion and `@004ab188` -- its dead-reckon
|
||
position fold -- are decompiled right below it; the replicant-perf interior
|
||
sits inside `@004ab430` from part_013:3804 with the SAME five ring
|
||
publishes at :3948.)
|
||
|
||
### The movement-mode carrier (the big one)
|
||
|
||
* **mech+0x2c is the entity MOVEMENT-MODE AlarmIndicator; +0x40 is its
|
||
LEVEL cell.** Everything we had read as "raw simulationState at +0x40"
|
||
is this alarm's level. It is entity-BASE machinery: the engine seeds it
|
||
from the class descriptor's state count (`@0041bdf0`,
|
||
`FUN_0041b9ec(entity+0x2c, *(desc+0xc))`).
|
||
* Mode roster so far: 0 DefaultState, 2/9 death transition (InDeathTransition
|
||
`@0049fb54` == 2||9 exactly), 3 GimpLeft, 4 GimpRight, 5-8 the four fall
|
||
variants (the body machine `@004a5678` head arms body clips 0x1c-0x1f from
|
||
them, one-shot latched by +0x650), 9 killed. WHO WRITES 2 and 5-8 is
|
||
still undecoded (the death FSM brick). Mode 1: seen only as "not
|
||
standing" in duck gates; identity open.
|
||
* **The damage path writes the mode DIRECTLY**: TakeDamage `@0049c690`
|
||
calls `FUN_0041bbd8(mech+0x2c, ...)` -- half-gone right leg -> 4
|
||
(`@0x49c8fb`), left -> 3 (`@0x49c926`), leg destroyed while already
|
||
limping -> 9 (`@0x49c88f`/`@0x49c8c5`), vital zone -> 9 (`@0x49c83c`),
|
||
gated by the REAL `InDeathTransition`. The old model (statusAlarm@0x714
|
||
as damage carrier + a Simulate "promotion") was a misconstruction --
|
||
RETIRED in 5.3.121; MECHDMG now writes the mode, and the @0x714 alarm is
|
||
ONLY the body-graphic clip selector (level = body clip index 0..0x20,
|
||
0x3c-stride name table at 0x50cfe8 "StandingAnimation"...).
|
||
* **Block 7 truth**: not a promotion. Three STREAMED request counters --
|
||
+0x334 DefaultState / +0x338 GimpLeft / +0x33c GimpRight (authentic
|
||
names, traced to the debug stream as consumed) -- re-assert the mode
|
||
alarm every frame they are positive. Writers: the creation-stream ctor
|
||
`@004b3778` (record +0x1c0/1c4/1c8) and the update-record appliers;
|
||
zeroed by ctor `@004a1674` and Reset `@0049fb74` (which also drops the
|
||
mode alarm to 0 and zeroes +0x328/+0x32c/+0x330 -- those three cells
|
||
still unidentified). LANDED with the authentic traces; inert until the
|
||
MP stream feed exists.
|
||
|
||
### Corrected field identities (from the C)
|
||
|
||
* **+0x79c = the MOBILITY SCALE**, not a duck analog: zeroed each frame,
|
||
then MAX of item+0x31c over the +0x7ac leg roster (count via the
|
||
iterator); throttle command (mapper+0x128) *= it; |scale| <= 1e-4 also
|
||
ZEROES the turn command (mapper+0x12c). The duck-down pick reads it as
|
||
the "legs still work" gate. Our tree HOLDS it at 1.0 ([T1]) until the
|
||
leg-roster scan is reconstructed.
|
||
* **+0x3f4 = the REVERSE/BRAKE flag** (the "airborne re-pick" note was
|
||
wrong): set when speedCmd < 0 while +0x348 (current speed) > 0, cleared
|
||
otherwise; +0x344 (the accel rate the body advancers slew by) picks
|
||
+0x5b8 forward / +0x5bc reverse accel (sentinel -1.1f @0x4ab174 = whole
|
||
system disabled; IntegrateMotion re-derives the pick at its head).
|
||
Entering reverse: MarkUpdate(0x100), gyro kick (0,0,+/-rand)@0.4 (50%
|
||
sign flip via 0x408050 rand + 0.5f compare @0x4ab170). While reversing
|
||
above the walk threshold (+0x534 < +0x348): pitch kick (0,1,0)@0.2 on a
|
||
0.4s countdown (+0x5c4) -- the braking shudder. NOT YET LANDED.
|
||
* +0x654 = the legResetLatch (saved at frame top, cleared, compared at
|
||
frame end -> MarkUpdate(8) on change). +0x584 = squat-capable,
|
||
+0x588 = turn-clip-capable, +0x580 = gimp-clip-capable.
|
||
* +0x574/+0x578 = walk/run turn rates; turn attenuation: speed >= +0x538
|
||
-> t=(speed-+0x534)/(+0x34c-+0x534), lerp A->B, t>1 -> B/t^2; clamp >=0;
|
||
x mapper turn cmd (+0x12c); ZERO while fallen (+0x650) or legState
|
||
1/2/3; result -> +0x1d4.
|
||
* +0x348 = current speed, +0x34c = max run speed, +0x350 = min(?) speed
|
||
clamp in the shortfall term, +0x7a0 = real max speed (5-notch throttle
|
||
quantizer: cmd = round(5*cmd/+0x7a0 + 0.5)/5 * +0x7a0... exact rounding
|
||
via ftol of +0.5f), +0x6b4/+0x6b8 = body-channel demand/speed pair.
|
||
* Perf head: `FUN_0049fa1c` = the combat-effectiveness census -> +0x414:
|
||
walks roster slots 2.., classID 0xBBE (generator) charge fraction
|
||
+0x12c/+0x128, 0xBC1 count with state != 1 && +0x210 != 4 (working
|
||
coolant units), RTTI-gated weapon count (excluding launchers with
|
||
+0x364 == 7); +0x414 = 1 when weapons < +0x448 min, no coolant, charge
|
||
below threshold, or limping with the duck cell 0. Consumer unknown.
|
||
* Perf head one-shot: +0x7a4 set && +0x7a8 clear -> sends the
|
||
**RealMaxSpeed message (0x15, 0x20 bytes)** carrying +0x7a0, then
|
||
latches +0x7a8 -- the network share of the computed real max speed
|
||
(pairs with the 0x15 handler @0049f604, still to land).
|
||
* Link plug at +0x418: first bound entity's distance -> +0x400, presence
|
||
-> +0x3fc (identity of the link: open).
|
||
* Mission-list y-compare -> +0x394 (the old "verify-guarded read" block):
|
||
compares *(+0x67c) against a mission-chain entry's position y when
|
||
legState not 0/1 -- identity open (in-water / off-pad flag?).
|
||
|
||
### Block 8 (move/collide) -- decoded, NOT yet landed
|
||
|
||
Ground clamp `FUN_0040e5f0(+0x2f0, pos)` (penetration -> pos.y and the
|
||
+0x2e8 box y/z); save old basis/pos; `FUN_004227e0(this,0)` volume;
|
||
+0x44c = 0; `FUN_00422bac(this, vol, dt, &oldpos, &probe)` = THE MOVE.
|
||
Outcomes on probe[1] (local_1c8): == sentinel @0x4ab180 -> restore
|
||
pos+basis, gyro kick along normal @0.4 + pitch @0.2, CollisionState alarm
|
||
(+0x450, 3 levels) -> 0; <= 0 -> alarm 0; else REAL HIT: partner
|
||
material *(partner+0x24) -> +0x4a4, world->body normal -> +0x4a8, closing
|
||
speed -(n . v) -> +0x4b4 (the CollisionMaterialType / CollisionNormal /
|
||
CollisionSpeed attribute cells), alarm <- +0x44c, STOP at old pos
|
||
(basis <- identity const @0x4e0f74), then **collision damage = a
|
||
SELF-DISPATCHED TakeDamage message (id 0x12, 100 bytes, damage type
|
||
DAT_0050b9ac, attacker = own entity id +0x184)** via vtbl+0xc; slow
|
||
(speed^2 <= @0x4ab184) -> legs to 0 + legResetLatch=1 (+ MarkUpdate(1|
|
||
0x40) if +0x464 == 1), fast -> stagger clip 0x20 + MarkUpdate(1|0x20).
|
||
After: +0x464 changed over frame -> MarkUpdate(0x80); vel.y = pos.y -
|
||
oldpos.y (raw delta); `FUN_00421b6c`; IntegrateMotion(dt, 0); dirty
|
||
thresholds (+0x768 pos-xz^2, +0x76c turn-delta, +0x770 heading-delta ->
|
||
MarkUpdate(0x10)/(1); mapper cmd != +0x6b4 -> MarkUpdate(4)).
|
||
|
||
### The tail (decoded, partially ours already)
|
||
|
||
Instability accumulator +0x3f0, THREE terms then clamp 1.0:
|
||
(|accelvec +0x82c| / +0x784 * +0x788)^2 + speed-shortfall
|
||
(clamped cmd vs +0x348, relative) * +0x78c + (legState==4 ? +0x798 : 0);
|
||
-> gyro+0x3a8 when gyro exists. Gyro churn `FUN_004b2eac(gyro, dt)` only
|
||
when legState != 0 && +0x57c == 0, then `FUN_004b34ec(gyro)` always --
|
||
both gated !fallen(+0x650). Torso::UpdateJoints(+0x438, &+0x360) gated
|
||
!fallen. `FUN_0049fe80(this)` (a second roster census -- damage-side;
|
||
decode parked). Five RING BUFFERS (FUN_0043ade4 = ring push, 0x43ae47/
|
||
0x43ae0b = ring reduces): +0x7e0<-vel.z, +0x7ec<-vel.y, +0x7f8<-vel.x,
|
||
+0x804<-turn rate, +0x810<-dt; smoothed values -> acceleration vector
|
||
+0x1dc (delta/sum-dt vs prevs +0x81c..+0x828) -> copied to +0x82c (the
|
||
instability input). `FUN_00421b2c` opens AND closes the frame (called
|
||
twice); the mobility iterator is destroyed at the very end
|
||
(`FUN_004a4ffb`).
|
||
|
||
### Landed in 5.3.121
|
||
|
||
MECHDMG kill/limp writes retargeted to the mode (SetSimulationState);
|
||
IsMechDestroyed reads the mode (>= 9); the staged promotion replaced by
|
||
the authentic counter block (+ ctor/Reset zeroes + Reset mode drop); the
|
||
duck driver moved to its authentic slot (counters -> duck -> limp standup
|
||
-> volume swap -> advance) and its gate renamed mobilityScale; the
|
||
advance dispatch death-guarded. SIDE EFFECT: `Mech::InDeathTransition`
|
||
and the camera director's cut-away are now LIVE (mode 9 is finally
|
||
written on kills) -- the director path needs a rig eyeball when a pod is
|
||
back.
|
||
|
||
## 5.3.122 -- the message roster fills (three of the six landed)
|
||
|
||
Landed: **RealMaxSpeed** (@0049f604: adopt streamed +0x7a0 while +0x7a4
|
||
clear; wire payload at +0x1c, sender EntityID stamped at +0xc by Entity's
|
||
network send @0041f640 = {stamp id, msg+0x14 <- entity+0x194, EntityManager
|
||
(app+0x30) send @00434ea0}), **SetBurningState** (@0049f674: park the wreck
|
||
at (localHostID x 10000, localHostID x -1000) -- constants @0049f6fc/f8,
|
||
host id = HostManager::GetLocalHostID, THE accessor the instance dispatch
|
||
compares against ownerID -- then MODE 2 = BURNING, the second
|
||
InDeathTransition state's missing writer, + ForceUpdate(1) +
|
||
AlwaysExecute), **ClearBurningState** (@0049f700: mode 0 + same pair).
|
||
simulationFlags bit 1 == DontExecuteFlag (SIMULATE.HPP) -- the binary's
|
||
`&= ~2` IS AlwaysExecute().
|
||
|
||
Still to land from the roster:
|
||
|
||
* **BalanceCoolant @0049f728 -- LANDED 5.3.123** (equal-split press +
|
||
RedistributeCoolantShares @0049f788 + the heatable ctor sweep that
|
||
populates the heatableSubsystems chain + ctor/Reset redistribute calls;
|
||
soak: 20 heatables, first share 0.05, ctor + BT_PRESS_BALANCE both
|
||
renormalize, crash-sig 0). UNIFIED 5.3.124: MoveValve
|
||
steps the heatable-base coolantPriority and calls the mech's
|
||
RedistributeCoolantShares; valveState and the condenser-local
|
||
renormalization are retired (RecomputeValves survives only as the [T3]
|
||
conduction-flow bridge -- the fight-verified pump consumes a
|
||
CONDENSER-relative fraction, refreshed from the redistribute tail; the
|
||
ratios are numerically identical to the old math since non-condenser
|
||
priorities cancel). REGISTRATION CAVEAT: the @0x50e534 name table sits
|
||
in the HEAT TU statics but its owning class (heatable base vs
|
||
Condenser) is NOT proven -- our registration stays Condenser id 4 (the
|
||
live-verified streamed binding); revisit if a non-condenser valve
|
||
binding ever surfaces. ValveSetting attribute still publishes
|
||
coolantFlowScale (the displayed fractions are unchanged).
|
||
* **EjectPilot @0049f854 -- LANDED 5.3.125** with the census
|
||
(@0049fa1c -> combatIneffective, the master perf's opening call).
|
||
Census truths, all pinned: 0xBBE = the AGGREGATE HEATSINK BANK (its
|
||
coolant fraction +0x12c/+0x128 < 0.05 @0049fb50 = crippled term; my
|
||
earlier "generator charge" reading was wrong), 0xBC1 = GENERATOR
|
||
(working = state != 1 && generator state != 4 = GeneratorFailed --
|
||
our enum matches), weapons by RTTI with dry launchers excluded
|
||
(+0x364 == 7 == our NoAmmoState exactly), minimum weapon count =
|
||
mech+0x448 = CTOR-HARDCODED 2 (part_012.c:15749), limping without
|
||
the crouch cell completes the roster of crippled terms. The eject:
|
||
mode 10 + mapper ejectLatch (+0x258) + self-dispatched explosive
|
||
TakeDamage (type 2, zone -1 -> cylinder resolver, impact = own
|
||
position). STAGED, recorded: the damage amount (binary reads
|
||
*(*(mapper+0x208)+0x1c), a bound/cached pointer with NO game-side
|
||
writer in the decomp -- needs the mapper ctor binding-loop decode)
|
||
and the score notify (@00429078 accessor + app+0x20 vcall, event
|
||
code 5 -- same poster idiom as the player-side @004c1044 throttle).
|
||
Dev kit: BT_FORCE_CRIPPLED=1 + BT_PRESS_EJECT=1
|
||
(pod_render_eject.conf). THE AUTHENTIC MESSAGE ROSTER IS NOW 6/6.
|
||
Mode roster: 0 default, 2 burning, 3/4 gimp L/R, 5-8 falls, 9
|
||
killed, 10 ejected; writers of 5-8 still open.
|
||
|
||
## 5.3.127 -- THE SUPER STOP + THE INSTABILITY MODEL (landed)
|
||
|
||
**The model resource's own field names decoded these blocks.** The ctor
|
||
loads (part_012.c:15798-15804) tie the master perf's anonymous offsets to
|
||
the authored names our `Mech__ModelResource` already carried:
|
||
|
||
| model rec | mech | name (authentic, from the struct) |
|
||
|---|---|---|
|
||
| +0x44 | +0x344, +0x5b0, +0x5b8 | `maxAcceleration` (live rate + forward figure) |
|
||
| +0x48 | +0x5bc | **`superStopAcceleration`** |
|
||
| +0x4c | +0x5c0 | `throttleAdjustment` |
|
||
| +0x80 | +0x784 | `maxUnstableAcceleration` |
|
||
| +0x84 | +0x788 | `unstableAccelerationEffect` |
|
||
| +0x88 | +0x78c | **`unstableGunTheEngineEffect`** |
|
||
| +0x8c | +0x790 | `unstableSuperStopEffect` (streamed, consumer OPEN) |
|
||
| +0x90 | +0x794 | `unstableHighVelocityEffect` (streamed, consumer OPEN) |
|
||
| +0x94 | +0x798 | **`unstableStopedTurnEffect`** |
|
||
|
||
That naming is what made the instability formula legible: the term added
|
||
when the leg gait state is 4 is `unstableStopedTurnEffect` and gait 4 IS
|
||
turn-in-place; the speed-shortfall term is `unstableGunTheEngineEffect`.
|
||
The names confirm the arithmetic, and the arithmetic confirms the names.
|
||
|
||
**The super stop** (@0x4aa353): the throttle going negative while the
|
||
mech still rolls forward is a hard brake, not a reverse. Edge -> swap
|
||
+0x344 from the forward rate to `superStopAcceleration`, MarkUpdate(0x100),
|
||
and ONE random-signed lurch through both gyro channels at weight 0.4
|
||
(magnitude roll + a 0.5 coin flip @0x4ab170). While it holds above the
|
||
walk threshold, a (0,1,0)@0.2 pitch kick every 0.4s. Release restores the
|
||
forward rate. `superStopAcceleration == -1.1` (@0x4ab174) DISABLES the
|
||
system for that chassis -- the sentinel must survive the model read
|
||
unguarded.
|
||
|
||
**The instability accumulator** (tail @0x4aab8e): three weighted terms,
|
||
clamped to 1, into `unstablePercentage` (+0x3f0 -- ALREADY a published
|
||
cockpit attribute in our tree, so this lights a live gauge) and
|
||
republished to gyro+0x3a8. The gyro's own tip/sway machinery
|
||
(@004b275c) reads that sum against its +0x3bc threshold -- that consumer
|
||
is the next gyro brick. Term 1 rides the ring-buffer acceleration
|
||
(+0x82c) and stays inert [T1] until the rings land.
|
||
|
||
**CORRECTION to 5.3.122**: `realMaxSpeed` was a DUPLICATE member for
|
||
mech+0x7a0, which our tree already carried as `runSpeedMax` (the myomers
|
||
raise it). Merged; the 0x15 handler writes `runSpeedMax`. And the
|
||
+0x7a4 "we computed our own" flag is set by the ctor's MYOMERS sweep
|
||
(part_012.c:15874) -- i.e. a mech with myomers broadcasts its cap, a mech
|
||
without adopts the streamed one.
|
||
|
||
**Gyro primitives already existed**: FUN_004b2d8c == our
|
||
`Gyroscope::ApplyDamageImpulse` (eyeForce), FUN_004b2de4 == our
|
||
`ApplyDamageTorque` (bodyForce). OPEN, worth a look: the binary's torque
|
||
zeroes bodyForce.y then negates ONLY .x (+0x308); ours negates the whole
|
||
vector, so the .z sign may differ.
|
||
|
||
## 5.3.128 -- THE TELEMETRY RINGS (landed)
|
||
|
||
The five ring buffers ARE the engine's `AverageOf<Scalar>`, and our tree
|
||
already carried them: `telemetryFilter[5]`, sized 15 in the ctor since the
|
||
Mech-layout wave, fed by nothing until now. `FUN_0043ade4` = `Add` (with
|
||
wraparound), `FUN_0043ae0b` = `CalculateAverage`, and `FUN_0043ae47` =
|
||
**`CalculateOlympicAverage`** -- sum minus min minus max over count-2, the
|
||
authentic name for a judge-style trimmed mean.
|
||
|
||
Feeds: vel.z / vel.y / vel.x / turn rate / frame slice. The derived
|
||
acceleration is the change in FILTERED values over the FILTERED slice,
|
||
which is why instability answers a sustained shove and not one noisy frame.
|
||
|
||
TWO ODDITIES KEPT VERBATIM (the binary is explicit; do not tidy them):
|
||
the vel.y ring is fed every frame and never reduced, and the acceleration's
|
||
components are a mongrel -- x from mean vel.x, y from MEAN vel.z, z from
|
||
OLYMPIC vel.z. Only the LENGTH is consumed, so behaviour is unaffected.
|
||
|
||
LIVE RESULT + A FREE DIAGNOSTIC: `unstablePercentage` now moves over its
|
||
whole range (0 -> 1.0 clamp) on the rig. Walking, it pins near 0.84-0.99,
|
||
dominated by the gun-the-engine term -- because the mech demands 35.9 u/s
|
||
and the reconstructed gait delivers 7.0. Whether that gap is authentic
|
||
acceleration or a gait shortfall is a SEPARATE open question, and the new
|
||
gauge is now the instrument for answering it (BT_TELEMETRY_LOG).
|
||
|
||
## 5.3.129 -- THE MOBILITY SCAN (landed; the mobilityScale [T1] retired)
|
||
|
||
**Chain identity PROVEN, not inferred.** My 5.3.122 note pinned the
|
||
+0x7ac chain to Myomers "by static-layout adjacency to the string pool" --
|
||
a weak argument that nearly went wrong, because the first cell I checked
|
||
(+0x31c) is a MechWeapon trigger in the weapon TU. The decisive evidence
|
||
is arithmetic: the Myomers cap-raise @004b8ef0 (our
|
||
`Myomers::RegisterMaxOutput`) reads `seekVoltage` at +0x330 indexed by
|
||
`maxSeekVoltageIndex` at +0x32c. Walking OUR member order backwards --
|
||
maxSeek 0x32c, minSeek 0x328, recommended 0x324, current 0x320 -- lands
|
||
`speedEffect` exactly on +0x31c, the cell the scan maxes. GUID 0x51155c
|
||
== Myomers, confirmed. (0x511830 == MechWeapon was independently
|
||
confirmed by the census, which tests it and then tests 0x5121a8 for the
|
||
dry-launcher exclusion.)
|
||
|
||
The scan: max `speedEffect` over the chain -> the mobility scale, applied
|
||
straight to the mapper's command cells (throttle *= scale; a scale inside
|
||
the 1e-4 deadzone ALSO zeroes the turn -- a mech with dead muscles cannot
|
||
even pivot). It is the same cell the duck driver reads as "the legs still
|
||
work". Guarded by the myomer count, so a chassis without them is
|
||
untouched. SAFE BY CONSTRUCTION: the mapper reassigns its demands from
|
||
scratch in InterpretControls, which ticks before the mech's own
|
||
performance, so the in-place multiply cannot compound frame to frame.
|
||
|
||
The ctor's myomer sweep is also what sets +0x7a4 -- so a mech WITH myomers
|
||
computes and broadcasts its own top speed, and one without adopts the
|
||
streamed value. Both halves now land together.
|
||
|
||
Soak: `[mobility] scale 1 over 1 myomers` on an undamaged bhk1 -- throttle
|
||
unscaled, drive unchanged (demand 35.9, speed 7.0), zero faults.
|
||
|
||
## 5.3.130 -- two findings from re-reading landed code
|
||
|
||
**A REAL SIGN BUG, fixed.** `Gyroscope::ApplyDamageTorque` (binary
|
||
@004b2de4) ended with `bodyForce.Negate(bodyForce)`, but the binary
|
||
negates ONLY the x component in place (`*(+0x308) = -*(+0x308)`) after
|
||
zeroing y at +0x30c. `Vector3D::Negate` flips all three, so the z
|
||
component has carried the wrong sign on every damage shake and every
|
||
collision crunch since the function landed. Fixed to `bodyForce.x =
|
||
-bodyForce.x`. STILL WORTH A LOOK: `ApplyVerticalImpulse` ends with
|
||
`bodyForce.y = bodyForce.x`, which is odd enough to want its binary
|
||
address found and checked the same way.
|
||
|
||
**THE DAMAGE AVERAGES: computed, consumed by nobody -- NOT landed.**
|
||
`@0049fe80` (the master perf's second census) writes three means to
|
||
mech+0x354 / +0x358 / +0x35c: over the HULL zones, over every subsystem's
|
||
private zone, and over the MYOMERS' private zones (the 0x51155c test --
|
||
the same GUID the mobility scan proved). Default 0.0 (@0049ffc8) when a
|
||
count is empty.
|
||
|
||
A reader hunt found none. The apparent hits at part_013:9498 and 13728
|
||
are on OTHER objects at the same offsets -- the gyro's damage multipliers
|
||
inside its damage fan-out (@004b2980) and a myomers cell inside the
|
||
myomer simulation (@004b8d18) -- a reminder that offsets are per-class and
|
||
a bare `+ 0x354)` grep proves nothing. No Mech attribute row binds them
|
||
either. Landing them would be three loops feeding dead memory, so per the
|
||
project rule (record where the binary is silent, never invent a consumer)
|
||
this stays decoded and unlanded until a consumer turns up.
|
||
|
||
## 5.3.131 -- the instability loop CLOSES, and a family of watchers is found inert
|
||
|
||
**The loop is closed.** `@004b275c` -- the gyro's sway slew -- was ALREADY
|
||
reconstructed in our tree, and it already read the cell the master perf
|
||
writes: `swayBias`, which nothing had ever written. So the 5.3.127
|
||
`mechInstability` member I added on the gyro was a DUPLICATE of that same
|
||
cell (the second such mistake this arc, after `realMaxSpeed`/`runSpeedMax`
|
||
-- the lesson is the same: grep our own tree for an existing carve before
|
||
adding a member for a binary offset). Merged: the master perf now
|
||
publishes into `swayBias` through a setter, and the instability model
|
||
finally does something -- an unstable mech shakes its pilot harder,
|
||
through code that was already correct and waiting.
|
||
|
||
Also landed: the binary's impaired gate has THREE terms and ours had two.
|
||
A gyro DESTROYED outright (`gyro+0x40 == 1`) swings to the impaired sway
|
||
base, not only an unpowered or heat-failed one.
|
||
|
||
VERIFIED on the rig (BT_SWAY_LOG): bias arrives (0 -> 1.0 -> 0.90), the
|
||
angle slews toward base+bias and stays inside the authored [0, 1.2] band,
|
||
zero faults.
|
||
|
||
### NEW DEFECT FOUND, precisely located, NOT fixed here
|
||
|
||
The same log showed `impaired=1` for an entire clean run. Instrumenting
|
||
the three terms settled it: `state=0 heat=0` (so my new term is inert and
|
||
correct) but `watchdog=0` where Ready is 4 -- that term fires forever.
|
||
|
||
Chasing it up: `PowerWatcher::UpdateWatch` sets the watchdog from the
|
||
WATCHED subsystem's voltage state and falls back to 0 when
|
||
`watchedLink.Resolve()` is NULL. And `watchedLink` is
|
||
DEFAULT-CONSTRUCTED (HEAT.CPP:886) and **bound nowhere in the tree** --
|
||
not for PowerWatcher, not for HeatWatcher (HEAT.CPP:964 resolves the same
|
||
way).
|
||
|
||
So the whole WATCHER family is inert: every watcher resolves NULL, every
|
||
PowerWatcher reports its target unpowered, and the gyro has consequently
|
||
been sitting on its `percentageOnDestruction` sway base since the sway
|
||
model landed -- invisibly, until this log existed.
|
||
|
||
The fix needs the streamed watch index out of the watcher resource plus
|
||
the ctor bind, in the shape the crit-table binding already uses. That is
|
||
a proper brick, not a guess, and guessing a binding is exactly what this
|
||
project's rules forbid -- so it is recorded here for the next sitting.
|
||
|
||
## 5.3.132 -- THE WATCHERS ARE BOUND (the family stops being inert)
|
||
|
||
The missing piece found in 5.3.131 is landed. `HeatWatcher` has always
|
||
streamed `watchedSubsystem` and read it into a member; nothing ever turned
|
||
that index into the link `UpdateWatch` resolves. The Mech ctor now binds
|
||
them in a POST-WALK pass -- necessarily post-walk, because a watcher may
|
||
watch a subsystem with a HIGHER roster id, which does not exist yet while
|
||
the segment walk is still running -- with out-of-range and self-reference
|
||
skipped rather than trusted.
|
||
|
||
THE BINDINGS PROVE THEMSELVES. Every index resolves to a semantically
|
||
right target on bhk1, which a wrong offset could not do:
|
||
|
||
Gyroscope -> Avionics HUD -> Avionics
|
||
Torso -> Myomers Searchlight -> Avionics
|
||
AmmoBinAFC100 -> AFC100 AmmoBinLRM15_1 -> LRM15_1
|
||
AmmoBinLRM15_2 -> LRM15_2
|
||
|
||
The gyro and HUD watch the avionics bus, the torso watches the muscles
|
||
that move it, and every ammo bin watches its own gun.
|
||
|
||
### The next question, now sharply posed
|
||
|
||
With the link live the gyro's watchdog moved 0 -> 1, so it is no longer
|
||
the NULL fallback. But 1 is not Ready (4), and `UpdateWatch` can produce
|
||
1 two ways: the watched subsystem reporting NoVoltage, or the BROWNOUT
|
||
branch (watched Ready, but its generator's measured voltage at or below
|
||
`minVoltage * RatedVoltage`).
|
||
|
||
Avionics is classID 3011 -- a SENSOR instance, named "Avionics" in the art
|
||
-- and the roster-live line reports a Sensor at voltState 4. So the
|
||
watched subsystem looks READY, which points at the BROWNOUT branch firing
|
||
persistently. Next sitting: is `minVoltage` authored/read correctly on
|
||
these watchers, or is the reconstructed generator genuinely sitting at the
|
||
brownout threshold? Log the watched level and the measured/rated pair
|
||
side by side to separate them -- do not adjust a threshold to make an
|
||
alarm quiet.
|
||
|
||
## 5.3.133 -- THE BROWNOUT WAS A UNIT BUG (chain closed end to end)
|
||
|
||
The question posed in 5.3.132 is answered, and it was neither of the
|
||
guesses being weighed -- it was arithmetic. `BT_WATCH_LOG` printed the
|
||
whole comparison in one line:
|
||
|
||
'Gyroscope' watched='Avionics' level=4 minVoltage=50
|
||
measured=10000 rated=10000 brownout=1
|
||
|
||
Every term is legible there. The bus is READY and at FULL rated voltage
|
||
(10000 of 10000) -- so there was never a real brownout. The trip value
|
||
was 50, and the test `measured <= minVoltage * rated` reads
|
||
`10000 <= 50 * 10000`, which is true for any voltage whatsoever.
|
||
|
||
The field is called `minVoltagePercent`. It is a PERCENT, and we stored
|
||
it 1:1 -- our own comment had flagged the missing scale as "a tuning
|
||
value (stored 1:1 here until located)". The authored data admits no
|
||
other reading: bhk1 gives the Gyroscope and HUD 50 and the Searchlight
|
||
10, round percents that are absurd as raw fractions (5000% / 1000%).
|
||
Divided by 100 the gyro trips below 5000V of a 10000V bus, which is a
|
||
sane undervoltage threshold, and the Searchlight -- a light -- tolerates
|
||
far more sag before complaining, which is exactly the right ordering.
|
||
|
||
CHAIN NOW CLOSED, all four links verified live in one run:
|
||
watcher bound (5.3.132) -> watchdog reads the target's REAL state (4,
|
||
Ready) -> impaired FALSE for the entire run (31 samples, none impaired)
|
||
-> the gyro sways on `percentageOnNormal` instead of its
|
||
destruction-grade base.
|
||
|
||
NOTE ON PROVENANCE: the binary's own scale SITE was not located in code;
|
||
the /100 is inferred from the field name plus authored data that is
|
||
unambiguous. Recorded as such rather than claimed as a byte-verified
|
||
decode.
|
||
|
||
## 5.3.134 -- THE "GAIT SHORTFALL" WAS A CONFOUNDED MEASUREMENT
|
||
|
||
The top open question from 5.3.128 -- "the mech demands 35.9 u/s and the
|
||
gait delivers 7.0, authentic acceleration or a gait shortfall?" -- is
|
||
answered: **authentic acceleration, and the measurement was confounded by
|
||
my own soak harness.**
|
||
|
||
The run I drew that reading from was `pod_render_telem`, which still had
|
||
`BT_SUPERSTOP_SOAK` driving the throttle to -0.5 every six seconds. The
|
||
mech was never given time to reach top speed; every sample caught the
|
||
first seconds of a climb that was about to be thrown into reverse.
|
||
|
||
`BT_GAIT_LOG` (new, state-change-triggered) on a clean full-throttle run
|
||
shows the ladder working exactly as designed:
|
||
|
||
leg=5 speed=0 -> stand-to-walk
|
||
leg=7 speed=5.69
|
||
leg=6 speed=17.0 -> the walk-to-run step
|
||
leg=11 speed=17.7
|
||
leg=12 speed=25.5
|
||
leg=13 speed=35.7
|
||
leg=12 speed=44.35 target=44.837 legCycle=bodyCycle=44.837
|
||
|
||
The mech accelerates 0 -> 45 u/s through six clip transitions and settles
|
||
on its demand. `runSpeedMax` reads 80.06, raised off its unsourced
|
||
default by the myomers -- the 5.3.129 landing doing its job. Both
|
||
run-clip slots are bound (clip6=344, clip8=345).
|
||
|
||
So the instability model pinning high during a climb was CORRECT all
|
||
along: a mech demanding far more speed than it has IS gunning the engine.
|
||
Nothing to fix in the gait or the instability model.
|
||
|
||
**METHOD LESSON, the expensive kind:** I took a measurement from a rig
|
||
whose harness was actively fighting the thing being measured, and wrote
|
||
the result into the notes, a commit message and memory as an open defect.
|
||
The hedge ("authentic or shortfall?") was the only thing that saved it
|
||
from becoming received wisdom. Before recording a number as evidence,
|
||
check what else the conf is driving.
|
||
|
||
## 5.3.135 -- WHAT THE WATCHER FIXES ACTUALLY TURNED ON
|
||
|
||
Chasing the downstream of 5.3.132/133 (watchers bound, brownout unit bug
|
||
fixed) found a user-VISIBLE consequence nobody was looking for.
|
||
|
||
`HeatWatcher::WatchSimulation` mirrors the WATCHED subsystem's temperature
|
||
into the watcher's own 3-level heat alarm. With every link NULL it held
|
||
NormalHeat forever, so the mirror was dead: the gyro never felt a hot
|
||
avionics bus, the torso never felt hot myomers, no ammo bin ever felt its
|
||
own gun's heat. That mirror is now live for all seven watchers.
|
||
|
||
**THE HUD PAGE WAS PERMANENTLY HIDDEN.** `HUD.CPP:145` gates page
|
||
visibility on `watchdogAlarm.GetLevel() != PoweredSubsystem::Ready`
|
||
(4). Unbound, the watchdog read 0 -> hidden. Bound but with the percent
|
||
bug, it read 1 -> still hidden. It took BOTH fixes to reach 4:
|
||
|
||
[hud] page_visible=1 watchdog=4 heat=0
|
||
|
||
So the HUD page is drawing for the first time. **Flag for the next
|
||
cockpit A/B session**: the gauge-ab rig last scored 98.9% identical
|
||
against the shipped binary with this page missing on our side -- if the
|
||
shipped exe draws it, some of that residual 1.1% should close on its own,
|
||
and the comparison needs re-running rather than trusted from the old
|
||
number.
|
||
|
||
Still worth exercising now that the mirror is live: what an AmmoBin does
|
||
with its gun's heat alarm (a cook-off or a feed lockout would be the
|
||
obvious candidates, and the binary is the place to check before assuming
|
||
either).
|
||
|
||
## 5.3.136 -- THE AMMO COOK-OFF, decoded (the payoff of binding the watchers)
|
||
|
||
Asking what an AmmoBin does with its now-live gun heat alarm turned up a
|
||
whole mechanic that has never run in our tree, for TWO independent
|
||
reasons: the watch link was unbound (5.3.132) so the mirrored alarm never
|
||
left NormalHeat, AND our `AmmoBin` has no Performance at all -- the
|
||
binary installs one (ctor @004bd5c4 stores the triple @0x512654 ->
|
||
`@004bd394`, gated master+dynamic as usual).
|
||
|
||
**The trigger** (@004bd394): when the bin's mirrored heat alarm (+0x140)
|
||
reads 2 = FailureHeat -- i.e. THE GUN IT FEEDS is cooking -- and the bin
|
||
is not already spent (+0x1a8 != 2) and no countdown is pending, it latches
|
||
one: `cookoffAt = now + <randomised delay>` (time from @00414b60, the
|
||
delay through the ftol helper @004dcd94). If the heat clears the
|
||
countdown is CANCELLED; if it expires the bin destroys itself
|
||
(state alarm -> 1, dirty mark) and detonates.
|
||
|
||
**The detonation** (@004bd300 -> @004ac274), and the damage is authored
|
||
per round: `damage = roundsRemaining * perRoundDamage (+0x1f4)`, so a full
|
||
bin guts the mech and a nearly empty one barely coughs. @004ac274 then:
|
||
|
||
* drives the bin's own state alarm 2 then 1 with an update mark each,
|
||
* pins its PRIVATE damage zone to 1.0 (destroyed),
|
||
* collects a target set by walking @004acfa9(mech, -1) and keeping
|
||
entries whose `+4` field equals **0x4e**,
|
||
* **divides the damage by the number of targets** -- the blast is
|
||
SHARED, not applied whole to each,
|
||
* dispatches a 100-byte TakeDamage (id 0x12) at each, printing the
|
||
authentic `"ammo explosion damaging <name>"` for every victim,
|
||
* finally zeroes the ammo count and sets the bin's 6-level state alarm
|
||
(+0x194, ctor `FUN_0041b9ec(+0x194, 6)`) to 2.
|
||
|
||
**THE ONE THING TO PIN BEFORE LANDING**: what @004acfa9 iterates and what
|
||
`+4 == 0x4e` selects. 0x4e is not a classID (those are 0xBBx), and the
|
||
per-victim reads -- a name via `*(+0x15c) + 8` and an index at `+0x13c` --
|
||
look more like DAMAGE ZONES than subsystems. Getting this wrong would
|
||
produce a plausible mechanic that damages the wrong things, which is
|
||
exactly the failure mode this project keeps catching, so it is decoded no
|
||
further here. Read @004acfa9 and @004ad03a/@004ad0a4 (the collector and
|
||
its list) first, then land the whole thing in one piece.
|
||
|
||
Note the shape of the find: two dead systems in series hid a third. The
|
||
watcher binding did not just fix an alarm -- it exposed that the mech has
|
||
never been able to suffer an ammo explosion.
|