BT410 notes: the master performance found whole -- 5.6KB @004a9b5c isolated from a Ghidra mis-merge, full call skeleton mapped

Not code -- evidence. The mech's master Performance was never decompiled
because Ghidra sized the stream-builder's strcmp resolver at 6.6KB and
swallowed everything behind it; the ctor's member-pointer constants prove
@004a9b5c (master) and @004ab9d8 (replicant, hidden the same way inside
@004ab430's range). One body, one ret, 1509 instructions, and its 111-call
skeleton maps block-for-block onto our fragment-built Mech::Simulate --
validating the 5.3.90-104 arc -- while locating exactly what remains
staged: the duck driver (three SetAnim/Mark/SetLevel arms), the
status->mode limp promotion, the gyro rumble feed points, and twenty
FUN_004a4c54 update-record dirty marks our empty ForceUpdate never makes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-08-06 16:13:22 -05:00
co-authored by Claude Fable 5
parent 6da6ec89dd
commit 06bc863b33
+116 -85
View File
@@ -1,85 +1,116 @@
# MECH4 — pre-TU reconstruction notes # MECH4 — pre-TU reconstruction notes
**Status: no MECH4.CPP exists yet.** The ground snap, frame rejection, **Status: no MECH4.CPP exists yet.** The ground snap, frame rejection,
knockdown stagger and collision economy that the binary keeps in mech4's knockdown stagger and collision economy that the binary keeps in mech4's
master performance are currently RE-HOSTED inside `Mech::Simulate` master performance are currently RE-HOSTED inside `Mech::Simulate`
([[MECH]], 5.3.98100) at seams marked STAGED. This file carries the decode ([[MECH]], 5.3.98100) at seams marked STAGED. This file carries the decode
work for the pieces not yet written, so it survives session boundaries. work for the pieces not yet written, so it survives session boundaries.
## Mech::ProcessCollision (@004abb40) — decoded, not yet reconstructed ## Mech::ProcessCollision (@004abb40) — decoded, not yet reconstructed
The override of the engine's protected virtual (`MOVER.CPP:1354`), called The override of the engine's protected virtual (`MOVER.CPP:1354`), called
per contact by `Mover::ProcessCollisionList`. The decomp per contact by `Mover::ProcessCollisionList`. The decomp
(part_012.c:1520615416) reads as the BASE function plus four additions: (part_012.c:1520615416) reads as the BASE function plus four additions:
1. **The base head, verbatim**: `collisionVolume->ProcessCollision(collision, 1. **The base head, verbatim**: `collisionVolume->ProcessCollision(collision,
worldLinearVelocity, lastCollisionList, &damage->surfaceNormal, worldLinearVelocity, lastCollisionList, &damage->surfaceNormal,
&penetration)`; on a miss NOTHING runs. Clamp penetration to the slice, &penetration)`; on a miss NOTHING runs. Clamp penetration to the slice,
ratio = pen/dt, `damage->damageAmount = StaticBounce(old, dt, r, normal, ratio = pen/dt, `damage->damageAmount = StaticBounce(old, dt, r, normal,
&elasticity, minimumBounceSpeed, &friction)` with the elasticity/friction &elasticity, minimumBounceSpeed, &friction)` with the elasticity/friction
locals seeded from the mover's coefficients. locals seeded from the mover's coefficients.
2. **The Mover-family branch** (owner = the collided solid's owner entity, 2. **The Mover-family branch** (owner = the collided solid's owner entity,
solid+0x1c; family test FUN_0041a1a4 against the "Mover" derivation GUID solid+0x1c; family test FUN_0041a1a4 against the "Mover" derivation GUID
@0x4e4518): @0x4e4518):
* SEPARATING-CONTACT GATE: `(ourWorldVel other->WorldVel[vtbl+0x20]) * SEPARATING-CONTACT GATE: `(ourWorldVel other->WorldVel[vtbl+0x20])
· normal < 1e-4` (`_DAT_004ac044`) → return, no damage, no dispatch. · normal < 1e-4` (`_DAT_004ac044`) → return, no damage, no dispatch.
* If the other's classID == **0xbb9** (mech), build a TakeDamage message * If the other's classID == **0xbb9** (mech), build a TakeDamage message
(raw wire shape `{id, len, inflictor = DAT_0050b9ac global, zone 1, (raw wire shape `{id, len, inflictor = DAT_0050b9ac global, zone 1,
the Damage verbatim}`) and Dispatch it AT THE OTHER — the mech-vs-mech the Damage verbatim}`) and Dispatch it AT THE OTHER — the mech-vs-mech
ram. Use our `Entity::TakeDamageMessage` ctor as the weapon path does. ram. Use our `Entity::TakeDamageMessage` ctor as the weapon path does.
3. **The CulturalIcon branch** (GUID @0x4e7038): 3. **The CulturalIcon branch** (GUID @0x4e7038):
* `stopping = other->instanceFlags & 0x8000` (the StoppingCollisionVolume * `stopping = other->instanceFlags & 0x8000` (the StoppingCollisionVolume
flag, entity+0x28). flag, entity+0x28).
* Same separating gate against a ZERO other-velocity (icons are static). * Same separating gate against a ZERO other-velocity (icons are static).
* Dispatch the crunch TakeDamage at the icon (crushable props have no * Dispatch the crunch TakeDamage at the icon (crushable props have no
zones → their base handler no-ops it, but the dispatch is real). zones → their base handler no-ops it, but the dispatch is real).
* If NOT stopping: `damage->damageAmount = 0.00123f` (bits 0x3aa137f4) — * If NOT stopping: `damage->damageAmount = 0.00123f` (bits 0x3aa137f4) —
the WALK-THROUGH SENTINEL. The caller (the response block in Simulate) the WALK-THROUGH SENTINEL. The caller (the response block in Simulate)
must treat exactly 0.00123f as "the move stands": restore the post-snap 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 saves, zero the amount, feed the gyro crunch (torque 0.4 along the
normalized force, upward impulse 0.2 — binary @4aa81e/@4aa86c). normalized force, upward impulse 0.2 — binary @4aa81e/@4aa86c).
4. **The material tail**: the elasticity/friction OUT values from 4. **The material tail**: the elasticity/friction OUT values from
StaticBounce are compared against `_DAT_004ac048`; sets StaticBounce are compared against `_DAT_004ac048`; sets
`collisionTemporaryState` (+0x44c) to 1, or 2 when both match and the `collisionTemporaryState` (+0x44c) to 1, or 2 when both match and the
state was 0. The state is pushed into the `collisionState` StateIndicator 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 once per frame by the response block (0→1 fires the impact sound). Needs
the `_DAT_004ac048` constant identified before writing (likely the the `_DAT_004ac048` constant identified before writing (likely the
default-material coefficient — check the literal pool at 0x4ac048). default-material coefficient — check the literal pool at 0x4ac048).
## Two hazards the donor fought that DO NOT apply here ## Two hazards the donor fought that DO NOT apply here
* **Compounded StaticBounce reflections**: our engine's * **Compounded StaticBounce reflections**: our engine's
`Mover::ProcessCollisionList` (MOVER.CPP:1203) restores `Mover::ProcessCollisionList` (MOVER.CPP:1203) restores
`worldLinearVelocity = initial_velocity` and position PER CONTACT before `worldLinearVelocity = initial_velocity` and position PER CONTACT before
each ProcessCollision call, and averages the results. The port's each ProcessCollision call, and averages the results. The port's
`frameEntryWorldVelocity` economy guard has no counterpart hazard in this `frameEntryWorldVelocity` economy guard has no counterpart hazard in this
engine — do not import it. engine — do not import it.
* **The AlarmIndicator ODR split**: a port-only problem; our tree reads * **The AlarmIndicator ODR split**: a port-only problem; our tree reads
`MovementMode()` / the alarms directly. `MovementMode()` / the alarms directly.
## Already re-hosted in Mech::Simulate (move here when mech4.cpp exists) ## Already re-hosted in Mech::Simulate (move here when mech4.cpp exists)
| piece | binary | landed | | piece | binary | landed |
|---|---|---| |---|---|---|
| ground probe + snap | @4aa6304aa6cc | 5.3.98 | | ground probe + snap | @4aa6304aa6cc | 5.3.98 |
| frame rejection + crash self-damage + knockdown | @4aa6cf4aab0b | 5.3.99 | | frame rejection + crash self-damage + knockdown | @4aa6cf4aab0b | 5.3.99 |
| collision economy divert + distributor | @0x4a0368 → @0049ffcc | 5.3.100 (in the damage hub, where the binary has it) | | 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 | | 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 | | forwardCycleRate re-pick (ground/airborne) | IntegrateMotion head | partially — the ctor sets it once; the per-frame re-pick needs +0x3f4 |
## Still to decode ## Still to decode
* `FUN_004a9b5c` — the master performance. Ghidra never decompiled it * `FUN_004a9b5c` — the master performance. **ISOLATED AND MAPPED
(objdump territory). Owns: the turn-in-place dispatcher (arms state 4 on (2026-08-04):** one single 5,676-byte body @004a9b5c..@004ab168 (one
both channels in lockstep), the status→mode promotion, likely the `ret`), hidden inside the manifest's mis-sized @004a9770 entry (that fn
`runSpeedMax` (+0x7a0) writer, the gyro rumble timer. is really the stream-builder's 6.6KB name→SharedData strcmp resolver,
* `@004ab430` ReplicantPerformance (1432B) — the replicant-side interior. and Ghidra swallowed everything to @004ab188 behind it). Registration
* `@004ab1c8` IntegrateMotion — decoded (see 5.3.95 notes) but only proven from the ctor decomp: `(flags & 0xC) == 4` → replicant perf
partially re-hosted: the local-velocity/orientation integration and the @004ab9d8 (also hidden, inside @004ab430's claimed range); else master
dead-reckon latency fold (+0x778/+0x77c) remain. **`+0x598` is a Point3D** @004a9b5c. Recipe: extract CODE bytes (va 0x1000 → file 0xe00),
(its replicant branch writes it with a vector subtract), so `ndisasm -b 32 -o 0x4a9b5c`.
`motionEventName` in MECH.HPP is mistyped as CString — fix when this
lands. **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**: three arms of {mech2 SetAnim 0x4a7fc4,
MarkUpdate ×2, AlarmIndicator::SetLevel} — duck down/hold/up
4. verify-guarded state read (mech4 accessors + Fail 0x40385c)
5. gyro feed a (vec pair → gyro_b 0x4b2de4, gyro_a 0x4b2d8c)
6. death arm: InDeathTransition 0x49fb54 → the 1.9KB mech2 advancer
0x4a71f4 under it (death-clip arming)
7. **status→mode LIMP PROMOTION** (0x4a5028 + SetAnim/Mark/SetLevel)
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.