diff --git a/restoration/source410/BT/MECH4.NOTES.md b/restoration/source410/BT/MECH4.NOTES.md index eda91b3a..1bf90b4d 100644 --- a/restoration/source410/BT/MECH4.NOTES.md +++ b/restoration/source410/BT/MECH4.NOTES.md @@ -1,85 +1,116 @@ -# 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. Ghidra never decompiled it - (objdump territory). Owns: the turn-in-place dispatcher (arms state 4 on - both channels in lockstep), the status→mode promotion, likely the - `runSpeedMax` (+0x7a0) writer, the gyro rumble timer. -* `@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. +# 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 + @004ab9d8 (also hidden, inside @004ab430's claimed range); else master + @004a9b5c. Recipe: extract CODE bytes (va 0x1000 → file 0xe00), + `ndisasm -b 32 -o 0x4a9b5c`. + + **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.