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 <noreply@anthropic.com>
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user