From 3364b65ae8542fdd3c99bb90edb32d88b981f0c1 Mon Sep 17 00:00:00 2001 From: Cyd Date: Tue, 21 Jul 2026 13:45:24 -0500 Subject: [PATCH] BT410 Phase 5.3.1: Mech::Simulate motion core -- mech integrates motion per-frame Installs Mech::Simulate as the mech's per-frame Performance (was DoNothingOnce). Reconstructs the load-bearing spine of the 1995 Simulate (mech4.cpp @004ab430): integrate the body velocity into localOrigin (linearPosition.AddScaled(.., worldLinearVelocity, dt)) and commit the Origin to the world transform with the engine idiom (localToWorld = localOrigin, per ENTITY.cpp:988 / MOVER.cpp:850). Uses the NAMED engine base Mover fields (localOrigin/localToWorld/ worldLinearVelocity) -- BT411's MechBaseLayoutCheck proved the WinTesla decomp's raw this+0x100/0x260 offsets actually stomp those base fields; the clean 1995 build addresses them by name. - MECH.HPP: Mech Performance typedef + SetPerformance + Simulate() decl. - MECH.CPP: SetPerformance(&Mech::Simulate) at ctor tail; Simulate body. Verified headlessly (BT_MECH_LOG "[sim] mech pos=" 1Hz): BT_DRIVE="5,0,10" advances the mech +5.0/s x, +10.0/s z (y fixed) = exactly the injected world velocity; no BT_DRIVE -> worldLinearVelocity is 0, mech holds spawn pose. Zero Fail/Exception either way. BT_DRIVE is a retained dev hook for headless motion. Deferred (locomotion wave): gait-cycle self-propulsion feeding worldLinearVelocity (IntegrateMotion->AdvanceBodyAnimation, steered by mapper throttle/turn), heading integration, terrain-height drop, cockpit telemetry filters. Co-Authored-By: Claude Fable 5 --- restoration/source410/BT/MECH.CPP | 86 ++++++++++++++++++++++++++ restoration/source410/BT/MECH.HPP | 17 +++++ restoration/source410/BT/MECH.NOTES.md | 34 ++++++++++ 3 files changed, 137 insertions(+) diff --git a/restoration/source410/BT/MECH.CPP b/restoration/source410/BT/MECH.CPP index f73a657b..41109675 100644 --- a/restoration/source410/BT/MECH.CPP +++ b/restoration/source410/BT/MECH.CPP @@ -310,6 +310,14 @@ Mech::Mech( << " weaponCount=" << weaponCount << endl << flush; } + // + // Install the per-frame body Performance. Until now the mech ran the base + // DoNothingOnce; from here the engine dispatches Mech::Simulate every frame + // (Mover -> Entity -> Simulation::PerformAndWatch) once the mission is + // RunningMission. + // + SetPerformance(&Mech::Simulate); + Check_Fpu(); } @@ -335,3 +343,81 @@ void } subsystemArray[0] = subsystem; } + +// +//############################################################################# +// Simulate -- the mech's per-frame body Performance (the Mover locomotion tick). +// +// FUNCTIONAL MOTION CORE (Phase 5.3, increment 1). A Mech is a Mover; its +// per-frame job is to advance its Origin and commit it to the world transform. +// This reconstructs the load-bearing spine of the 1995 Mech::Simulate +// (mech4.cpp @004ab430): integrate the body velocity into localOrigin, then +// rebuild localToWorld with the engine's own idiom (ENTITY.CPP:988 / +// MOVER.CPP:850, `localToWorld = localOrigin`). +// +// Still layered on top of this (next increments): +// * the gait-cycle self-propulsion that FEEDS worldLinearVelocity -- the +// authentic model drives forward speed from the walk/run animation cycle +// (IntegrateMotion -> AdvanceBodyAnimation -> cycleDistance) steered by the +// control-mapper demands (throttle/turn), not a raw velocity; +// * heading integration (rotate localOrigin.angularPosition by the turn rate); +// * the terrain-height drop (BoundingBoxTreeNode::FindBoundingBoxUnder) that +// rests the feet on the ground; +// * the cockpit telemetry FilteredScalars (head/aim/leg/torso angular rates). +// +// With no locomotion layer yet, worldLinearVelocity is zero for a freshly +// spawned mech, so it holds its pose -- identical to the prior DoNothing, but +// now on the real Simulate path. DEV hook BT_DRIVE="vx,vy,vz" injects a +// constant world velocity so the integrate + transform path is verifiable +// headlessly (no RIO/controls needed). See MECH.NOTES.md. +//############################################################################# +// +void + Mech::Simulate(Scalar time_slice) +{ + Check(this); + + Vector3D velocity = worldLinearVelocity; + + { + const char *drive = getenv("BT_DRIVE"); + if (drive != NULL) + { + float dx = 0.0f, dy = 0.0f, dz = 0.0f; + if (sscanf(drive, "%f,%f,%f", &dx, &dy, &dz) == 3) + { + velocity.x = dx; + velocity.y = dy; + velocity.z = dz; + } + } + } + + // + // Integrate position (pos = pos + velocity*dt) and commit the Origin to the + // world transform. + // + localOrigin.linearPosition.AddScaled( + localOrigin.linearPosition, + velocity, + time_slice + ); + localToWorld = localOrigin; + + if (getenv("BT_MECH_LOG")) + { + static Scalar reportAccum = 0.0f; + reportAccum += time_slice; + if (reportAccum >= 1.0f) + { + reportAccum = 0.0f; + DEBUG_STREAM << "[sim] mech pos=(" + << localOrigin.linearPosition.x << "," + << localOrigin.linearPosition.y << "," + << localOrigin.linearPosition.z << ")" + << endl << flush; + } + } + + Check_Fpu(); +} diff --git a/restoration/source410/BT/MECH.HPP b/restoration/source410/BT/MECH.HPP index 1a185191..8fe57579 100644 --- a/restoration/source410/BT/MECH.HPP +++ b/restoration/source410/BT/MECH.HPP @@ -187,6 +187,23 @@ void SetMappingSubsystem(Subsystem *mapper); + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Per-frame simulation (the mech's body Performance; installed by the ctor + // and dispatched each frame from Simulation::PerformAndWatch). + // + public: + typedef void + (Mech::*Performance)(Scalar time_slice); + void + SetPerformance(Performance performance) + { + Check(this); + activePerformance = (Simulation::Performance)performance; + } + + void + Simulate(Scalar time_slice); + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Resource creation // diff --git a/restoration/source410/BT/MECH.NOTES.md b/restoration/source410/BT/MECH.NOTES.md index aaded582..ba73f68e 100644 --- a/restoration/source410/BT/MECH.NOTES.md +++ b/restoration/source410/BT/MECH.NOTES.md @@ -150,3 +150,37 @@ VehicleDeadMessageHandler(non-death), the reconstructed tree now boots with (`LBE4ControlsManager::Execute` spinning). See BTPLAYER.NOTES.md. The remaining gap to "playable" is behavioral (mech2/3/4 per-frame sim + renderer + real control input), not structural — the boot path is complete. + +## PHASE 5.3 INCREMENT 1: Mech::Simulate MOTION CORE (2026-07-21) + +`Mech::Simulate(Scalar)` is reconstructed and installed as the mech's per-frame +Performance (`SetPerformance(&Mech::Simulate)` at the end of the ctor). Until now +the mech ran the base `DoNothingOnce`; it now runs real body code every frame +(dispatched via Mover→Entity→`Simulation::PerformAndWatch` once RunningMission). + +This increment is the **load-bearing spine** of the 1995 Simulate +(mech4.cpp @004ab430): integrate the body velocity into `localOrigin` +(`linearPosition.AddScaled(linearPosition, worldLinearVelocity, dt)`) and commit +the Origin to the world transform with the engine's own idiom +(`localToWorld = localOrigin`, per ENTITY.CPP:988 / MOVER.CPP:850). BT411's RE +was decisive here: the mech's motion operates on the ENGINE BASE Mover fields +(`localOrigin`/`projectedOrigin`/`previousOrigin`/`projectedVelocity`/ +`localToWorld`) — the raw `this+0x100/0x260/0x26c` offsets in the WinTesla decomp +actually *stomp* those base fields (their MechBaseLayoutCheck static_asserts). +So the clean reconstruction uses the NAMED base members directly. + +**Verified headlessly** (BT_MECH_LOG `[sim] mech pos=` 1 Hz): with +`BT_DRIVE="5,0,10"` the mech advances +5.0/s in x and +10.0/s in z (y fixed), +exactly the injected world velocity; with no BT_DRIVE `worldLinearVelocity` is +zero and the mech holds its spawn pose — no regression, zero Fail. `BT_DRIVE` +is a retained DEV hook (world-velocity injection) for headless motion tests. + +**Deferred to the next increments (the locomotion wave):** +1. gait-cycle self-propulsion that FEEDS `worldLinearVelocity` — the authentic + model derives forward speed from the walk/run animation cycle + (`IntegrateMotion`→`AdvanceBodyAnimation`→cycleDistance) steered by the + control-mapper throttle/turn demands, NOT a raw velocity; +2. heading integration (rotate `localOrigin.angularPosition` by the turn rate); +3. terrain-height drop (`BoundingBoxTreeNode::FindBoundingBoxUnder`) resting the + feet on the ground; +4. cockpit telemetry `FilteredScalar`s (head/aim/leg/torso angular rates).