feat(D6.2a): port CMotionInterp motion normalization + unify local velocity/turn/jump
Ports retail's raw->interpreted motion normalization and routes the local player's velocity through it, so backward/strafe come from the retail source instead of hand-mirrored controller code (retires register TS-22 + UN-5). MotionInterpreter (decomp-verbatim, Ghidra/ACE-cross-checked): - adjust_motion (0x00528010): WalkBackward->WalkForward (x-0.649999976), TurnLeft->TurnRight (x-1), SideStepLeft->SideStepRight (x-1), sidestep scale 0.5*(WalkAnimSpeed/SidestepAnimSpeed), RunForward early-return, per-channel holdkey fallback to CurrentHoldKey. - apply_run_to_command (0x00527be0): WalkForward->RunForward when speed>0 + UNCONDITIONAL *runRate; TurnRight *1.5; SideStepRight *runRate clamp +/-3.0. - apply_raw_movement (0x005287e0): copy raw->interpreted, adjust the 3 channels with per-channel hold keys. - WalkAnimSpeed unified to the retail-exact 3.11999989f (was 3.12f). PlayerMovementController: build ONE RawMotionState from MovementInput at forward_speed=1.0 (apply_run_to_command applies the run rate — a pre-scaled speed would double-scale), run apply_raw_movement, then take grounded + jump velocity straight from get_state_velocity (now correct for all directions) and drive the keyboard turn omega from the interpreted turn_speed (BaseTurnRate x turn_speed = the same pi/2 formula the remote path uses; numerically identical to the old TurnRateFor). Deleted the hand-mirrored backward(x-0.65) / strafe(x+-1.25) formulas in both the grounded and jump blocks. Mouse turn and the auto-walk path are untouched. Retail-faithful behavior changes (confirm in smoke): strafe is now 1.25 * ~1.248 * runRate = ~1.56*runRate, clamped via SideStepSpeed<=3.0 so |v.X|<=3.75 (was 1.25*runRate, no scale/clamp); backward is unchanged (3.12*0.65*runRate); backward/strafe-left velocity now comes from the pipeline instead of returning zero. Forward run pace unchanged (4.0*runRate). Tests: MotionNormalizationTests (17, adjust_motion/apply_run_to_command) + MotionVelocityPipelineTests (10, apply_raw_movement->get_state_velocity golden velocities incl. the strafe clamp + turn speeds). Full suite green (3255). Register: TS-22 + UN-5 deleted (retired); TS-34 added (adjust_motion creature guard is a no-op — IWeenieObject has no IsCreature; correct for the only caller, the player). Pseudocode: docs/research/2026-07-01-d6-motion-interp-pseudocode.md. NEXT D6.2b: the echo-gated wire forward_speed=1.0 question (evidence suggests ACE relays rather than recomputes; verified via the smoke echo). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
4ed278369b
commit
0f099bb652
6 changed files with 801 additions and 149 deletions
|
|
@ -875,6 +875,40 @@ public sealed class PlayerMovementController
|
|||
TurnSpeed: null);
|
||||
}
|
||||
|
||||
// ── D6.2: normalize input into the interpreted motion state ───────────
|
||||
// ONE RawMotionState from MovementInput drives the local velocity + turn
|
||||
// (retail CMotionInterp::apply_raw_movement 0x005287e0). RAW speeds are
|
||||
// 1.0 — apply_run_to_command applies the run rate, so passing a pre-scaled
|
||||
// speed would double-scale. adjust_motion normalizes backward →
|
||||
// WalkForward (×-0.65) and strafe-left → SideStepRight (×-1×1.248), so
|
||||
// get_state_velocity below is correct for ALL directions (retires the
|
||||
// hand-mirrored formulas; register TS-22). Skipped during server
|
||||
// auto-walk, which drives the body itself.
|
||||
if (!autoWalkConsumedMotion)
|
||||
{
|
||||
var axisHold = input.Run ? HoldKey.Run : HoldKey.None;
|
||||
var raw = new RawMotionState
|
||||
{
|
||||
CurrentHoldKey = axisHold,
|
||||
ForwardCommand = input.Forward ? MotionCommand.WalkForward
|
||||
: input.Backward ? MotionCommand.WalkBackward
|
||||
: MotionCommand.Ready,
|
||||
ForwardHoldKey = (input.Forward || input.Backward) ? axisHold : HoldKey.Invalid,
|
||||
ForwardSpeed = 1.0f,
|
||||
SidestepCommand = input.StrafeRight ? MotionCommand.SideStepRight
|
||||
: input.StrafeLeft ? MotionCommand.SideStepLeft
|
||||
: 0u,
|
||||
SidestepHoldKey = (input.StrafeRight || input.StrafeLeft) ? axisHold : HoldKey.Invalid,
|
||||
SidestepSpeed = 1.0f,
|
||||
TurnCommand = input.TurnRight ? MotionCommand.TurnRight
|
||||
: input.TurnLeft ? MotionCommand.TurnLeft
|
||||
: 0u,
|
||||
TurnHoldKey = (input.TurnRight || input.TurnLeft) ? axisHold : HoldKey.Invalid,
|
||||
TurnSpeed = 1.0f,
|
||||
};
|
||||
_motion.apply_raw_movement(raw);
|
||||
}
|
||||
|
||||
// ── 1. Apply turning from keyboard + mouse ────────────────────────────
|
||||
// 2026-05-16 — retail-faithful turn rate.
|
||||
// Anchor: docs/research/named-retail/acclient_2013_pseudo_c.txt
|
||||
|
|
@ -886,11 +920,21 @@ public sealed class PlayerMovementController
|
|||
// Effective: walking ≈ 90°/s, running ≈ 135°/s.
|
||||
// Previously: WalkAnimSpeed*0.5 ≈ 89.4°/s — coincidentally
|
||||
// close to retail walking but no run differentiation.
|
||||
float keyboardTurnRate = RemoteMoveToDriver.TurnRateFor(input.Run);
|
||||
if (input.TurnRight)
|
||||
Yaw -= keyboardTurnRate * dt;
|
||||
if (input.TurnLeft)
|
||||
Yaw += keyboardTurnRate * dt;
|
||||
// D6.2: local keyboard turn rate now comes from the interpreted turn
|
||||
// state (adjust_motion normalized TurnLeft→TurnRight with sign +
|
||||
// apply_run_to_command ×RunTurnFactor when running). omega.Z =
|
||||
// BaseTurnRateRadPerSec × turn_speed — the same π/2 formula the remote
|
||||
// path uses (GameWindow ObservedOmega seed). Numerically identical to the
|
||||
// former TurnRateFor(input.Run); AP-9 (π/2 base rate) is unchanged.
|
||||
// Skipped during auto-walk (server drives the turn). Mouse turn stays a
|
||||
// direct Yaw delta (deliberately generates no turn command — avoids
|
||||
// MoveToState spam from mouse jitter).
|
||||
if (!autoWalkConsumedMotion
|
||||
&& _motion.InterpretedState.TurnCommand == MotionCommand.TurnRight)
|
||||
{
|
||||
Yaw -= RemoteMoveToDriver.BaseTurnRateRadPerSec
|
||||
* _motion.InterpretedState.TurnSpeed * dt;
|
||||
}
|
||||
Yaw -= input.MouseDeltaX * MouseTurnSensitivity;
|
||||
// Wrap yaw to [-PI, PI] so it doesn't grow unbounded.
|
||||
while (Yaw > MathF.PI) Yaw -= 2f * MathF.PI;
|
||||
|
|
@ -910,53 +954,10 @@ public sealed class PlayerMovementController
|
|||
// (Ready/Stand) velocity, freezing the body.
|
||||
if (!autoWalkConsumedMotion)
|
||||
{
|
||||
// Determine the dominant forward/backward command and speed.
|
||||
uint forwardCmd;
|
||||
float forwardCmdSpeed;
|
||||
if (input.Forward)
|
||||
{
|
||||
forwardCmd = input.Run ? MotionCommand.RunForward : MotionCommand.WalkForward;
|
||||
// When running, use the PlayerWeenie's RunRate as ForwardSpeed.
|
||||
// The retail server computes this from Run skill + encumbrance and
|
||||
// broadcasts it in UpdateMotion, but it doesn't echo to the sender.
|
||||
// We compute locally using the same formula.
|
||||
if (input.Run && _weenie.InqRunRate(out float runRate))
|
||||
forwardCmdSpeed = runRate;
|
||||
else
|
||||
forwardCmdSpeed = 1.0f;
|
||||
}
|
||||
else if (input.Backward)
|
||||
{
|
||||
forwardCmd = MotionCommand.WalkBackward;
|
||||
// K-fix3 (2026-04-26): backward also honors Run. Without
|
||||
// this, holding X with Run=true (default) still produced
|
||||
// walk-tier backward speed because forwardCmdSpeed was
|
||||
// hardcoded to 1.0. Now scale by runRate the same way
|
||||
// RunForward does.
|
||||
if (input.Run && _weenie.InqRunRate(out float runRateBack))
|
||||
forwardCmdSpeed = runRateBack;
|
||||
else
|
||||
forwardCmdSpeed = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
forwardCmd = MotionCommand.Ready;
|
||||
forwardCmdSpeed = 1.0f;
|
||||
}
|
||||
|
||||
// Update interpreted motion state (needed for animation + server messages).
|
||||
_motion.DoMotion(forwardCmd, forwardCmdSpeed);
|
||||
|
||||
// Sidestep.
|
||||
if (input.StrafeRight)
|
||||
_motion.DoInterpretedMotion(MotionCommand.SideStepRight, 1.0f, modifyInterpretedState: true);
|
||||
else if (input.StrafeLeft)
|
||||
_motion.DoInterpretedMotion(MotionCommand.SideStepLeft, 1.0f, modifyInterpretedState: true);
|
||||
else
|
||||
{
|
||||
_motion.StopInterpretedMotion(MotionCommand.SideStepRight, modifyInterpretedState: true);
|
||||
_motion.StopInterpretedMotion(MotionCommand.SideStepLeft, modifyInterpretedState: true);
|
||||
}
|
||||
// D6.2: the forward/sidestep command determination + DoMotion +
|
||||
// DoInterpretedMotion moved into the apply_raw_movement call above, so
|
||||
// the interpreted state (and thus get_state_velocity) is already
|
||||
// populated + normalized for all directions.
|
||||
|
||||
// Only replace velocity with motion interpreter output when grounded.
|
||||
// While airborne, the physics body's integrated velocity (from LeaveGround)
|
||||
|
|
@ -965,37 +966,13 @@ public sealed class PlayerMovementController
|
|||
if (_body.OnWalkable)
|
||||
{
|
||||
float savedWorldVz = _body.Velocity.Z;
|
||||
// D6.2: velocity for ALL directions comes from the normalized
|
||||
// interpreted state — backward (WalkForward ×-0.65) and strafe-left
|
||||
// (SideStepRight ×-1×1.248) are handled inside get_state_velocity now
|
||||
// that adjust_motion ran above. The hand-mirrored formulas are gone
|
||||
// (register TS-22 retired).
|
||||
var stateVel = _motion.get_state_velocity();
|
||||
|
||||
float localY = 0f;
|
||||
float localX = 0f;
|
||||
|
||||
// K-fix3 (2026-04-26): unified run-multiplier for backward
|
||||
// + strafe. Forward already scales correctly because it uses
|
||||
// stateVel.Y (which the motion state machine fed runRate
|
||||
// into via DoMotion). Backward + strafe bypass the state
|
||||
// machine and hardcoded speed; previously they capped at
|
||||
// walk speed regardless of Run, which made the ~2.4×
|
||||
// forward-vs-back/strafe ratio feel wrong. Now both scale
|
||||
// with the same runRate the forward branch uses.
|
||||
float runMul = 1.0f;
|
||||
if (input.Run && _weenie.InqRunRate(out float vrr))
|
||||
runMul = vrr;
|
||||
|
||||
if (input.Forward)
|
||||
localY = stateVel.Y;
|
||||
else if (input.Backward)
|
||||
localY = -(MotionInterpreter.WalkAnimSpeed * 0.65f * runMul);
|
||||
|
||||
// Strafe scales with the same runMul so sidestep matches
|
||||
// the forward pace at run speed (retail uses speed=1.0 for
|
||||
// SideStep + the same hold-key-driven run/walk multiplier).
|
||||
if (input.StrafeRight)
|
||||
localX = MotionInterpreter.SidestepAnimSpeed * runMul;
|
||||
else if (input.StrafeLeft)
|
||||
localX = -MotionInterpreter.SidestepAnimSpeed * runMul;
|
||||
|
||||
_body.set_local_velocity(new Vector3(localX, localY, savedWorldVz));
|
||||
_body.set_local_velocity(new Vector3(stateVel.X, stateVel.Y, savedWorldVz));
|
||||
}
|
||||
} // end of `if (!autoWalkConsumedMotion)` — section 2
|
||||
|
||||
|
|
@ -1029,56 +1006,13 @@ public sealed class PlayerMovementController
|
|||
float jumpVz = _motion.get_jump_v_z();
|
||||
_motion.LeaveGround();
|
||||
outJumpExtent = _jumpExtent;
|
||||
// BODY-LOCAL jump-launch velocity, computed directly from input.
|
||||
//
|
||||
// Why not read _body.Velocity? Because _motion.LeaveGround()
|
||||
// routes through get_leave_ground_velocity → get_state_velocity,
|
||||
// which is a faithful port of retail's FUN_00528960. Retail's
|
||||
// version only handles WalkForward (0x45000005) / RunForward
|
||||
// (0x44000007) / SideStepRight (0x6500000F); WalkBackwards
|
||||
// and SideStepLeft return zero. Retail papers over this in
|
||||
// adjust_motion (FUN_00528010) by translating
|
||||
// WalkBackwards → WalkForward + speed × -0.65
|
||||
// SideStepLeft → SideStepRight + speed × -1
|
||||
// before they reach InterpretedState — but we don't yet port
|
||||
// adjust_motion, so InterpretedState holds the un-translated
|
||||
// command and get_state_velocity returns (0,0,0) for it.
|
||||
// LeaveGround then writes (0,0,jumpZ) to the body, wiping the
|
||||
// correct strafe/backward velocity the controller had just set
|
||||
// a few lines up. Result: backward/strafe jumps go straight up.
|
||||
//
|
||||
// Until adjust_motion is ported, we mirror the grounded-velocity
|
||||
// computation from the block above and stuff the result into
|
||||
// outJumpVelocity directly. Local frame: +Y forward, +X right,
|
||||
// +Z up — matches retail's body-frame convention. Server
|
||||
// rotates body→world on receive, so observers see the jump
|
||||
// in the correct world direction.
|
||||
float jumpRunMul = 1.0f;
|
||||
if (input.Run && _weenie.InqRunRate(out float jvrr))
|
||||
jumpRunMul = jvrr;
|
||||
|
||||
// Forward uses get_state_velocity (which knows Walk vs Run vs
|
||||
// animation-cycle pacing). Backward / Strafe use the same
|
||||
// hardcoded scaled formulas the grounded-velocity block above
|
||||
// uses (lines 397-408).
|
||||
float localY = 0f;
|
||||
if (input.Forward)
|
||||
{
|
||||
var stateVel = _motion.get_state_velocity();
|
||||
localY = stateVel.Y;
|
||||
}
|
||||
else if (input.Backward)
|
||||
{
|
||||
localY = -(MotionInterpreter.WalkAnimSpeed * 0.65f * jumpRunMul);
|
||||
}
|
||||
|
||||
float localX = 0f;
|
||||
if (input.StrafeRight)
|
||||
localX = MotionInterpreter.SidestepAnimSpeed * jumpRunMul;
|
||||
else if (input.StrafeLeft)
|
||||
localX = -MotionInterpreter.SidestepAnimSpeed * jumpRunMul;
|
||||
|
||||
outJumpVelocity = new Vector3(localX, localY, jumpVz);
|
||||
// D6.2: get_state_velocity() is now correct for all directions
|
||||
// (apply_raw_movement normalized backward/strafe above), so the
|
||||
// jump-launch velocity is get_state_velocity() + the vertical jump
|
||||
// component. Retires the duplicated hand-mirrored formulas that
|
||||
// existed only until adjust_motion was ported (register TS-22).
|
||||
var jumpVel = _motion.get_state_velocity();
|
||||
outJumpVelocity = new Vector3(jumpVel.X, jumpVel.Y, jumpVz);
|
||||
|
||||
// Local-prediction fix: LeaveGround above wrote (0, 0, jumpZ)
|
||||
// to the body for backward/strafe-left (same get_state_velocity
|
||||
|
|
|
|||
|
|
@ -251,8 +251,10 @@ public interface IWeenieObject
|
|||
public sealed class MotionInterpreter
|
||||
{
|
||||
// ── animation speed constants (from ACE / confirmed by decompile globals) ─
|
||||
/// <summary>Walk animation base speed (_DAT_007c96e4 family).</summary>
|
||||
public const float WalkAnimSpeed = 3.12f;
|
||||
/// <summary>Walk animation base speed. Retail-exact (.rdata 0x007c891c =
|
||||
/// 3.11999989f); unified for both <see cref="get_state_velocity"/> and
|
||||
/// <see cref="adjust_motion"/>'s sidestep scale in D6.2.</summary>
|
||||
public const float WalkAnimSpeed = 3.11999989f;
|
||||
/// <summary>Run animation base speed (_DAT_007c96e0 family).</summary>
|
||||
public const float RunAnimSpeed = 4.0f;
|
||||
/// <summary>Sidestep animation base speed (_DAT_007c96e8 family).</summary>
|
||||
|
|
@ -264,6 +266,34 @@ public sealed class MotionInterpreter
|
|||
/// <summary>Maximum jump extent clamped by get_jump_v_z (_DAT_007938b0 = 1.0f).</summary>
|
||||
public const float MaxJumpExtent = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Backward-speed multiplier applied by <see cref="adjust_motion"/> when
|
||||
/// remapping WalkBackward → WalkForward (retail .rdata 0x007c8910).
|
||||
/// Retail-exact value; do not round to 0.65f.
|
||||
/// </summary>
|
||||
public const float BackwardsFactor = 0.649999976f;
|
||||
|
||||
/// <summary>
|
||||
/// Turn speed multiplier applied by <see cref="apply_run_to_command"/> for
|
||||
/// TurnRight when the hold key is Run (retail apply_run_to_command, no
|
||||
/// run-rate scaling and no clamp — just a flat ×1.5).
|
||||
/// </summary>
|
||||
public const float RunTurnFactor = 1.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Symmetric clamp on sidestep interpreted speed applied by
|
||||
/// <see cref="apply_run_to_command"/> for SideStepRight when running
|
||||
/// (retail apply_run_to_command SideStepRight clamp, ±3.0).
|
||||
/// </summary>
|
||||
public const float MaxSidestepAnimRate = 3.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Retail-exact sidestep scale factor used by <see cref="adjust_motion"/>
|
||||
/// when remapping to SideStepRight (retail SidestepFactor = 0.5).
|
||||
/// </summary>
|
||||
public const float SidestepFactor = 0.5f;
|
||||
|
||||
|
||||
// ── fields (matching struct layout from acclient_function_map.md) ─────────
|
||||
|
||||
/// <summary>The physics body this interpreter controls (struct offset +0x08).</summary>
|
||||
|
|
@ -284,6 +314,16 @@ public sealed class MotionInterpreter
|
|||
/// <summary>Stored run rate from last successful InqRunRate call (offset +0x7C).</summary>
|
||||
public float MyRunRate = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The physics object's currently-held movement key (retail
|
||||
/// <c>this->raw_state.current_holdkey</c>). <see cref="adjust_motion"/>
|
||||
/// falls back to this field when the per-channel holdKey argument passed
|
||||
/// in is <see cref="HoldKey.Invalid"/>. D6.2 (the raw-state orchestrator)
|
||||
/// is responsible for keeping this in sync with the raw motion state;
|
||||
/// default <see cref="HoldKey.None"/> is a safe no-op until then.
|
||||
/// </summary>
|
||||
public HoldKey CurrentHoldKey = HoldKey.None;
|
||||
|
||||
/// <summary>True when crouching-in-place for a standing long jump (offset +0x70).</summary>
|
||||
public bool StandingLongJump;
|
||||
|
||||
|
|
@ -647,6 +687,191 @@ public sealed class MotionInterpreter
|
|||
return velocity;
|
||||
}
|
||||
|
||||
// ── FUN_00528010 — adjust_motion ──────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Normalize one raw motion channel (forward, sidestep, or turn) into its
|
||||
/// canonical interpreted form: fold the "negative" commands (WalkBackward,
|
||||
/// TurnLeft, SideStepLeft) into their positive counterpart with a negated/
|
||||
/// scaled speed, then apply the Run hold-key promotion.
|
||||
///
|
||||
/// <para>
|
||||
/// Decompiled logic (FUN_00528010, <c>docs/research/named-retail/
|
||||
/// acclient_2013_pseudo_c.txt:305343-305400</c>):
|
||||
/// <code>
|
||||
/// if weenie != null and !weenie.IsCreature(): return
|
||||
/// switch cmd:
|
||||
/// RunForward: return // no scale, NO holdkey path
|
||||
/// WalkBackwards: cmd = WalkForward; speed *= -BackwardsFactor
|
||||
/// TurnLeft: cmd = TurnRight; speed *= -1
|
||||
/// SideStepLeft: cmd = SideStepRight; speed *= -1
|
||||
/// if cmd == SideStepRight:
|
||||
/// speed *= SidestepFactor * (WalkAnimSpeed / SidestepAnimSpeed) // ≈ 1.24799995
|
||||
/// if holdKey == Invalid: holdKey = raw.current_holdkey
|
||||
/// if holdKey == Run: apply_run_to_command(ref cmd, ref speed)
|
||||
/// </code>
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// GOTCHA: RunForward early-returns before the sidestep scale AND before
|
||||
/// the hold-key path — a running forward command is already normalized
|
||||
/// and must not be touched again. The sidestep negate (SideStepLeft →
|
||||
/// SideStepRight, ×-1) happens BEFORE the ×1.248 anim-rate scale, so the
|
||||
/// net multiplier for SideStepLeft is -1.248×speed, not -1×(1.248×speed)
|
||||
/// applied out of order (same result here, but the ORDER matters once
|
||||
/// apply_run_to_command's sign-dependent clamp is layered on top).
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Creature guard (retail <c>weenie.IsCreature()</c>) is UNWIRED.</b>
|
||||
/// <see cref="IWeenieObject"/> does not currently expose an
|
||||
/// <c>IsCreature</c> query, so this port always proceeds (equivalent to
|
||||
/// "no weenie" or "is a creature" — true for the player, which is the
|
||||
/// only caller as of D6.1). Flagged for D6.2 / a future IWeenieObject
|
||||
/// extension; do not fabricate a guess at the query here.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="motion">Raw motion command, normalized in place.</param>
|
||||
/// <param name="speed">Raw motion speed, normalized in place.</param>
|
||||
/// <param name="holdKey">
|
||||
/// The hold key for THIS channel (forward/sidestep/turn each carry their
|
||||
/// own hold key in the retail raw state). Pass <see cref="HoldKey.Invalid"/>
|
||||
/// to fall back to <see cref="CurrentHoldKey"/>.
|
||||
/// </param>
|
||||
public void adjust_motion(ref uint motion, ref float speed, HoldKey holdKey)
|
||||
{
|
||||
// Creature guard unwired — IWeenieObject has no IsCreature(); always proceed.
|
||||
|
||||
switch (motion)
|
||||
{
|
||||
case MotionCommand.RunForward:
|
||||
// Already normalized; no scale, no hold-key path.
|
||||
return;
|
||||
case MotionCommand.WalkBackward:
|
||||
motion = MotionCommand.WalkForward;
|
||||
speed *= -BackwardsFactor;
|
||||
break;
|
||||
case MotionCommand.TurnLeft:
|
||||
motion = MotionCommand.TurnRight;
|
||||
speed *= -1f;
|
||||
break;
|
||||
case MotionCommand.SideStepLeft:
|
||||
motion = MotionCommand.SideStepRight;
|
||||
speed *= -1f;
|
||||
break;
|
||||
}
|
||||
|
||||
if (motion == MotionCommand.SideStepRight)
|
||||
speed *= SidestepFactor * (WalkAnimSpeed / SidestepAnimSpeed);
|
||||
|
||||
if (holdKey == HoldKey.Invalid)
|
||||
holdKey = CurrentHoldKey;
|
||||
|
||||
if (holdKey == HoldKey.Run)
|
||||
apply_run_to_command(ref motion, ref speed);
|
||||
}
|
||||
|
||||
// ── FUN_00527be0 — apply_run_to_command ───────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Apply the Run hold-key promotion/scale to an already-normalized
|
||||
/// (positive-form) motion command.
|
||||
///
|
||||
/// <para>
|
||||
/// Decompiled logic (FUN_00527be0, <c>docs/research/named-retail/
|
||||
/// acclient_2013_pseudo_c.txt:305062-305123</c>):
|
||||
/// <code>
|
||||
/// speedMod = 1.0
|
||||
/// if weenie != null: speedMod = weenie.InqRunRate(out r) ? r : MyRunRate
|
||||
/// switch cmd:
|
||||
/// WalkForward:
|
||||
/// if speed > 0: cmd = RunForward // promote ONLY when moving forward
|
||||
/// speed *= speedMod // UNCONDITIONAL — applies to backward too
|
||||
/// TurnRight:
|
||||
/// speed *= RunTurnFactor // ×1.5, no run-rate, no clamp
|
||||
/// SideStepRight:
|
||||
/// speed *= speedMod
|
||||
/// if abs(speed) > MaxSidestepAnimRate: speed = speed > 0 ? Max : -Max
|
||||
/// </code>
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// GOTCHA: the WalkForward <c>speed *= speedMod</c> is unconditional —
|
||||
/// it applies even when speed is negative (backward), even though the
|
||||
/// command is NOT promoted to RunForward in that case (the promotion is
|
||||
/// sign-gated, the scale is not).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public void apply_run_to_command(ref uint motion, ref float speed)
|
||||
{
|
||||
float speedMod = 1.0f;
|
||||
if (WeenieObj is not null)
|
||||
speedMod = WeenieObj.InqRunRate(out float rate) ? rate : MyRunRate;
|
||||
|
||||
switch (motion)
|
||||
{
|
||||
case MotionCommand.WalkForward:
|
||||
if (speed > 0f)
|
||||
motion = MotionCommand.RunForward;
|
||||
speed *= speedMod;
|
||||
break;
|
||||
case MotionCommand.TurnRight:
|
||||
speed *= RunTurnFactor;
|
||||
break;
|
||||
case MotionCommand.SideStepRight:
|
||||
speed *= speedMod;
|
||||
if (MathF.Abs(speed) > MaxSidestepAnimRate)
|
||||
speed = speed > 0f ? MaxSidestepAnimRate : -MaxSidestepAnimRate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 0x005287e0 — apply_raw_movement ───────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>CMotionInterp::apply_raw_movement</c> orchestrator
|
||||
/// (<c>0x005287e0</c>, <c>docs/research/named-retail/acclient_2013_pseudo_c.txt:305817-305834</c>).
|
||||
/// Copies the raw motion state into the interpreted state, then normalizes
|
||||
/// each of the three channels (forward / sidestep / turn) IN PLACE via
|
||||
/// <see cref="adjust_motion"/> using that channel's own hold key.
|
||||
///
|
||||
/// <para>
|
||||
/// After this call, <see cref="get_state_velocity"/> yields correct velocity
|
||||
/// for ALL directions — backward (WalkBackward→WalkForward, ×-0.65) and
|
||||
/// strafe-left (SideStepLeft→SideStepRight, ×-1×1.248) are normalized here,
|
||||
/// which is the D6 fix that retires the hand-mirrored controller formulas
|
||||
/// (register TS-22). Callers must pass RAW speeds (forward_speed = 1.0), NOT
|
||||
/// pre-run-scaled speeds — <see cref="apply_run_to_command"/> applies the run
|
||||
/// rate, so a pre-scaled input would double-scale.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public void apply_raw_movement(RawMotionState raw)
|
||||
{
|
||||
CurrentHoldKey = raw.CurrentHoldKey;
|
||||
|
||||
// Copy raw -> interpreted. Retail copies 7 fields; our
|
||||
// InterpretedMotionState omits current_style (velocity doesn't use it).
|
||||
InterpretedState.ForwardCommand = raw.ForwardCommand;
|
||||
InterpretedState.ForwardSpeed = raw.ForwardSpeed;
|
||||
InterpretedState.SideStepCommand = raw.SidestepCommand;
|
||||
InterpretedState.SideStepSpeed = raw.SidestepSpeed;
|
||||
InterpretedState.TurnCommand = raw.TurnCommand;
|
||||
InterpretedState.TurnSpeed = raw.TurnSpeed;
|
||||
|
||||
// Normalize each channel with its OWN per-channel hold key.
|
||||
uint fCmd = InterpretedState.ForwardCommand; float fSpd = InterpretedState.ForwardSpeed;
|
||||
adjust_motion(ref fCmd, ref fSpd, raw.ForwardHoldKey);
|
||||
InterpretedState.ForwardCommand = fCmd; InterpretedState.ForwardSpeed = fSpd;
|
||||
|
||||
uint sCmd = InterpretedState.SideStepCommand; float sSpd = InterpretedState.SideStepSpeed;
|
||||
adjust_motion(ref sCmd, ref sSpd, raw.SidestepHoldKey);
|
||||
InterpretedState.SideStepCommand = sCmd; InterpretedState.SideStepSpeed = sSpd;
|
||||
|
||||
uint tCmd = InterpretedState.TurnCommand; float tSpd = InterpretedState.TurnSpeed;
|
||||
adjust_motion(ref tCmd, ref tSpd, raw.TurnHoldKey);
|
||||
InterpretedState.TurnCommand = tCmd; InterpretedState.TurnSpeed = tSpd;
|
||||
}
|
||||
|
||||
// ── FUN_00529210 — apply_current_movement ─────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue