Locomotion: wire the AUTHENTIC per-mech turn-rate lerp (task #64b)
The master-perf disasm (0x4aa3d3-0x4aa4ff) computes the yaw rate as lerp(walkingTurnRate@0x574, runningTurnRate@0x578) by ground speed, not a constant. Wired it into the drive, replacing the bring-up constant kDriveTurnRate: - walkingTurnRate/runningTurnRate are now REAL named members. The ctor's `Wword(0x15d)/(0x15e) = model->...TurnRate * DegreesToRadians` writes were NO-OPS -- Wword() returns a shared static scratch cell, so the rates were silently discarded. Now stored + read by name. - The drive (mech4.cpp) computes authTurnRate: base = walkingTurnRate; above reverseSpeedMax(0x538) lerp walk->run across [walkStride 0x534 .. topSpeed 0x34c] with an over-run falloff runningTurnRate/t^2; clamp >=0; zeroed in the death/limbo/airborne leg states (1/2/3) or when deathAnimationLatched(0x650) is set. Falls back to kDriveTurnRate if the model gave no rates. The lerp is a RUN-speed refinement -- below reverseSpeedMax (walk / turn-in-place) the rate is the constant walkTR base; runningTurnRate < walkingTurnRate (a running mech is less maneuverable). Verified headless (DEV mech, BT_TURN_LOG): walkTR 1.309 rad/s (75 deg/s), runTR 0.873 (50 deg/s), turn-in-place + walk yaw at walkTR, no crash. [T1 logic / T2 runtime] Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
35c30bda67
commit
a83995a755
+15
-9
@@ -129,15 +129,21 @@ real logic. `animationClips[4]` = the `trn`/`trni` clip (loaders 13767/13971). P
|
|||||||
was the body channel leaking out-of-phase joints into the rendered skeleton every walk cycle (mj=1);
|
was the body channel leaking out-of-phase joints into the rendered skeleton every walk cycle (mj=1);
|
||||||
fixed at the source by `AdvanceBodyAnimation(dt, mj=0)` (see two-channel section). With mj=0 the
|
fixed at the source by `AdvanceBodyAnimation(dt, mj=0)` (see two-channel section). With mj=0 the
|
||||||
authentic cut renders cleanly.
|
authentic cut renders cleanly.
|
||||||
- **Turn rate (disasm 0x4aa3d3–0x4aa4ff) [T1]:** `spd = |linearSpeed|`; base `turnRate =
|
- **Turn rate (disasm 0x4aa3d3–0x4aa4ff) [T1] — WIRED (task #64b):** `spd = |linearSpeed|`; base
|
||||||
walkingTurnRate`(mech+0x574, rad/s). If `spd ≥ reverseSpeedMax`(0x538): `t = (spd − walkStride
|
`turnRate = walkingTurnRate`(mech+0x574, rad/s). If `spd ≥ reverseSpeedMax`(0x538): `t = (spd −
|
||||||
0x534)/(topSpeed 0x34c − walkStride)`; `turnRate = t≤1 ? lerp(walkingTurnRate, runningTurnRate
|
walkStride 0x534)/(topSpeed=reverseStrideLength 0x34c − walkStride)`; `turnRate = t≤1 ?
|
||||||
0x578, t) : runningTurnRate/t²` (over-run falloff). `turnRate = max(0, turnRate)`. Then
|
lerp(walkingTurnRate, runningTurnRate 0x578, t) : runningTurnRate/t²` (over-run falloff). `turnRate =
|
||||||
`angularVelocity`(0x1d4) `= turnDemand(mapper+0x12c) × turnRate`, ZEROED if legState∈{1,2,3} or
|
max(0, turnRate)`. Then yaw `= turnDemand(mapper+0x12c) × turnRate`, ZEROED if legState∈{1,2,3} or
|
||||||
latch 0x650 set. ⚠ **Not yet wired into the port** — the bring-up drive still yaws at the constant
|
the death latch (`deathAnimationLatched`@0x650) is set. **Note the lerp is a RUN-speed refinement**
|
||||||
`kDriveTurnRate` (mech4.cpp:2618/2628/3062); `walkingTurnRate`/`runningTurnRate` are stored
|
— below `reverseSpeedMax` (walk / turn-in-place) turnRate is the constant walkTR base; the walk→run
|
||||||
(mech.cpp:1334-5, Wword(0x15d)/(0x15e)→0x574/0x578, deg→rad) but only the ANIMATION dispatcher is
|
interpolation only engages above it, and `runningTurnRate < walkingTurnRate` (a running mech is less
|
||||||
wired. Wiring the rate lerp is the next locomotion polish item.
|
maneuverable). Port: `walkingTurnRate`/`runningTurnRate` are now REAL named members (mech.hpp, set
|
||||||
|
from the model deg→rad at mech.cpp:1334-5 — the old `Wword(0x15d/0x15e)` writes were no-ops into a
|
||||||
|
static scratch bank, so the values were silently discarded). The drive (mech4.cpp ~2617) computes
|
||||||
|
`authTurnRate` per the formula and yaws by it instead of the bring-up constant `kDriveTurnRate`
|
||||||
|
(kept as the fallback when a model supplies no rates). Diagnostic: `BT_TURN_LOG` → `[turnrate]`.
|
||||||
|
Verified headless (DEV mech): walkTR 1.309 rad/s (75°/s), runTR 0.873 (50°/s); turn-in-place + walk
|
||||||
|
yaw at walkTR, no crash. [T1 logic / T2 runtime]
|
||||||
- **Entry** (leg Standing case, disasm 0x4aa505 [T1]): arm trn when TURNING (`|angVel|>1e-4`, port
|
- **Entry** (leg Standing case, disasm 0x4aa505 [T1]): arm trn when TURNING (`|angVel|>1e-4`, port
|
||||||
proxy = turnDemand deadband ±0.05) AND `0 ≤ spd ≤ standSpeed` (the FULL sub-walk range — CORRECTS the
|
proxy = turnDemand deadband ±0.05) AND `0 ≤ spd ≤ standSpeed` (the FULL sub-walk range — CORRECTS the
|
||||||
`0.25·standSpeed` stand-in) AND `turnCapable`(hasCrashSet@0x588). **LOCKSTEP: arms BOTH channels the
|
`0.25·standSpeed` stand-in) AND `turnCapable`(hasCrashSet@0x588). **LOCKSTEP: arms BOTH channels the
|
||||||
|
|||||||
@@ -1325,14 +1325,13 @@ Mech::Mech(
|
|||||||
<< " turnVel=" << updateTurnVelocityDeadband
|
<< " turnVel=" << updateTurnVelocityDeadband
|
||||||
<< " turnAngle=" << updateTurnAngleDeadband << " (rad)" << std::endl;
|
<< " turnAngle=" << updateTurnAngleDeadband << " (rad)" << std::endl;
|
||||||
Wword(0x10c) = model->timeDelay; // rec+0xA4 -> mech+0x430
|
Wword(0x10c) = model->timeDelay; // rec+0xA4 -> mech+0x430
|
||||||
// NOTE: the decomp writes word slots this[0x15d]/this[0x15e]. Routed to the
|
// The AUTHENTIC per-mech turn rates (rec+0x60/0x64, deg->rad -> mech+0x574/0x578).
|
||||||
// scratch bank (unnamed pose-angle fields) -- writing through (float*)(this +
|
// The decomp writes word slots this[0x15d]/this[0x15e] (== 0x574/0x578); stored in
|
||||||
// 0x15d) would be typed-pointer arithmetic on Mech* (== this + 0x15d*sizeof(Mech)).
|
// real named members now (task #64b) -- read by the master-perf turn-rate lerp in
|
||||||
// The AUTHENTIC per-mech turn rates (rec+0x60/0x64, deg->rad ->
|
// the drive (mech4.cpp) that yaws at lerp(walkingTurnRate, runningTurnRate) by speed
|
||||||
// mech+0x574/0x578) -- still absorbed pending the turn-code wiring
|
// instead of the old bring-up constant kDriveTurnRate.
|
||||||
// (locomotion polish item: the port turns at a bring-up constant rate).
|
walkingTurnRate = model->walkingTurnRate * DegreesToRadians;
|
||||||
Wword(0x15d) = model->walkingTurnRate * DegreesToRadians;
|
runningTurnRate = model->runningTurnRate * DegreesToRadians;
|
||||||
Wword(0x15e) = model->runningTurnRate * DegreesToRadians;
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// SHADOW JOINT resolve (task #20; CORRECTED -- the earlier draft misread
|
// SHADOW JOINT resolve (task #20; CORRECTED -- the earlier draft misread
|
||||||
|
|||||||
@@ -848,6 +848,8 @@ protected:
|
|||||||
Scalar jumpWalkSpeedMax; // @0x540
|
Scalar jumpWalkSpeedMax; // @0x540
|
||||||
Scalar jumpRunStrideLength; // @0x544
|
Scalar jumpRunStrideLength; // @0x544
|
||||||
Scalar jumpWalkStrideLength; // @0x548
|
Scalar jumpWalkStrideLength; // @0x548
|
||||||
|
Scalar walkingTurnRate; // @0x574 (WalkingTurnRate, rad/s) -- the master
|
||||||
|
Scalar runningTurnRate; // @0x578 (RunningTurnRate, rad/s) perf turn-rate lerp
|
||||||
Scalar groundCycleRate; // @0x5b8
|
Scalar groundCycleRate; // @0x5b8
|
||||||
Scalar airborneCycleRate; // @0x5bc
|
Scalar airborneCycleRate; // @0x5bc
|
||||||
// Forward-throttle scale read by the controls mapper (@004afd10:
|
// Forward-throttle scale read by the controls mapper (@004afd10:
|
||||||
|
|||||||
@@ -2614,18 +2614,55 @@ void
|
|||||||
|
|
||||||
if (!gDriveSeeded) { gDriveHeading = 0.0f; gDriveSeeded = 1; }
|
if (!gDriveSeeded) { gDriveHeading = 0.0f; gDriveSeeded = 1; }
|
||||||
|
|
||||||
|
// --- AUTHENTIC turn rate (master-perf disasm 0x4aa3d3-0x4aa4ff [T1]) ---------
|
||||||
|
// The yaw rate is per-mech lerp(walkingTurnRate, runningTurnRate) by GROUND
|
||||||
|
// SPEED, not the old bring-up constant kDriveTurnRate. spd = horizontal speed
|
||||||
|
// (last frame's worldLinearVelocity); base = walkingTurnRate; above the low
|
||||||
|
// gate (reverseSpeedMax@0x538) lerp walk->run across [walkStride 0x534 ..
|
||||||
|
// topSpeed 0x34c], with an over-run falloff runningTurnRate/t^2 past topSpeed;
|
||||||
|
// clamp >=0. angularVelocity = turnDemand * turnRate, ZEROED in the death/
|
||||||
|
// limbo/airborne leg states (1/2/3) or when the death-anim latch is set.
|
||||||
|
// Falls back to the bring-up constant if the model supplied no rates.
|
||||||
|
Scalar authTurnRate = kDriveTurnRate;
|
||||||
|
if (walkingTurnRate > 0.0f)
|
||||||
|
{
|
||||||
|
const Scalar spd = (Scalar)sqrtf(worldLinearVelocity.x * worldLinearVelocity.x
|
||||||
|
+ worldLinearVelocity.z * worldLinearVelocity.z);
|
||||||
|
authTurnRate = walkingTurnRate; // base (walk / idle / turn-in-place)
|
||||||
|
if (spd >= reverseSpeedMax) // 0x538 low gate
|
||||||
|
{
|
||||||
|
const Scalar den = reverseStrideLength - walkStrideLength; // topSpeed - walkStride
|
||||||
|
const Scalar t = (den != 0.0f) ? (spd - walkStrideLength) / den : 0.0f;
|
||||||
|
if (t <= 1.0f)
|
||||||
|
authTurnRate = walkingTurnRate + (runningTurnRate - walkingTurnRate) * t; // walk->run
|
||||||
|
else
|
||||||
|
authTurnRate = runningTurnRate / (t * t); // over-run falloff
|
||||||
|
}
|
||||||
|
if (authTurnRate < 0.0f) authTurnRate = 0.0f;
|
||||||
|
const int ls = legAnimationState; // 0x3b0
|
||||||
|
if (deathAnimationLatched != 0 || ls == 1 || ls == 2 || ls == 3)
|
||||||
|
authTurnRate = 0.0f; // no yaw in death/limbo/airborne
|
||||||
|
}
|
||||||
|
if (getenv("BT_TURN_LOG"))
|
||||||
|
DEBUG_STREAM << "[turnrate] walkTR=" << walkingTurnRate
|
||||||
|
<< " runTR=" << runningTurnRate << " spd="
|
||||||
|
<< (Scalar)sqrtf(worldLinearVelocity.x*worldLinearVelocity.x
|
||||||
|
+ worldLinearVelocity.z*worldLinearVelocity.z)
|
||||||
|
<< " -> authTurnRate=" << authTurnRate
|
||||||
|
<< " (kDrive=" << kDriveTurnRate << ")\n" << std::flush;
|
||||||
|
|
||||||
// --- turn: integrate heading, write it onto the body orientation -----
|
// --- turn: integrate heading, write it onto the body orientation -----
|
||||||
gDriveHeading += turn * kDriveTurnRate * dt; // scalar mirror (drive log + fallback)
|
gDriveHeading += turn * authTurnRate * dt; // scalar mirror (drive log + fallback)
|
||||||
if (s_realControls)
|
if (s_realControls)
|
||||||
{
|
{
|
||||||
// AUTHENTIC ORIENTATION INTEGRATION (the IntegrateMotion-tail form):
|
// AUTHENTIC ORIENTATION INTEGRATION (the IntegrateMotion-tail form):
|
||||||
// compose the yaw RATE into the pose quaternion via the engine
|
// compose the yaw RATE into the pose quaternion via the engine
|
||||||
// integrate op (Quaternion::Add == FUN_00409f58) -- the same op the
|
// integrate op (Quaternion::Add == FUN_00409f58) -- the same op the
|
||||||
// binary's motion tail uses -- instead of rebuilding the orientation
|
// binary's motion tail uses -- instead of rebuilding the orientation
|
||||||
// from a scalar heading. The rate SCALE (kDriveTurnRate) remains a
|
// from a scalar heading. The rate SCALE is now the AUTHENTIC per-mech
|
||||||
// bring-up constant; the authentic per-mech turn-rate parameter is
|
// turn rate (authTurnRate above) -- lerp(walkingTurnRate, runningTurnRate)
|
||||||
// part of the deferred full-IntegrateMotion work.
|
// by ground speed, master-perf 0x4aa3d3 [T1].
|
||||||
Vector3D angStep(0.0f, turn * kDriveTurnRate * dt, 0.0f);
|
Vector3D angStep(0.0f, turn * authTurnRate * dt, 0.0f);
|
||||||
Quaternion prevPose = localOrigin.angularPosition;
|
Quaternion prevPose = localOrigin.angularPosition;
|
||||||
localOrigin.angularPosition.Add(prevPose, angStep);
|
localOrigin.angularPosition.Add(prevPose, angStep);
|
||||||
}
|
}
|
||||||
@@ -3059,7 +3096,7 @@ void
|
|||||||
worldLinearVelocity.x * worldLinearVelocity.x +
|
worldLinearVelocity.x * worldLinearVelocity.x +
|
||||||
worldLinearVelocity.z * worldLinearVelocity.z);
|
worldLinearVelocity.z * worldLinearVelocity.z);
|
||||||
localVelocity.linearMotion = Vector3D(0.0f, worldLinearVelocity.y, -fwdSpeed);
|
localVelocity.linearMotion = Vector3D(0.0f, worldLinearVelocity.y, -fwdSpeed);
|
||||||
localVelocity.angularMotion = Vector3D(0.0f, turn * kDriveTurnRate, 0.0f);
|
localVelocity.angularMotion = Vector3D(0.0f, turn * authTurnRate, 0.0f);
|
||||||
|
|
||||||
// CRASH motion suppression (spec: the binary zeroes localVelocity while
|
// CRASH motion suppression (spec: the binary zeroes localVelocity while
|
||||||
// crashed). While the stagger clip (legState 0x20) plays, freeze the
|
// crashed). While the stagger clip (legState 0x20) plays, freeze the
|
||||||
|
|||||||
Reference in New Issue
Block a user