feat(physics): PositionManager combiner class + 6 unit tests (L.3.2)

Pure-function ComputeOffset(dt, pos, seqVel, ori, interp, maxSpeed) →
Vector3. Combines animation root motion (seqVel × dt rotated by body
orientation) with InterpolationManager.AdjustOffset world-space
correction. Mirrors retail CPhysicsObj::UpdateObjectInternal
(acclient @ 0x00513730).

Composed into RemoteMotion in subsequent task (L.3.1+L.3.2 Task 3);
not yet consumed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-03 10:13:02 +02:00
parent d063ac884d
commit 08fbbef3c4
2 changed files with 234 additions and 0 deletions

View file

@ -0,0 +1,55 @@
using System.Numerics;
namespace AcDream.Core.Physics;
/// <summary>
/// Per-frame combiner for remote-entity motion: animation root motion
/// + InterpolationManager catch-up correction. Pure function — no
/// side effects, no hidden state.
///
/// Mirrors retail CPhysicsObj::UpdateObjectInternal (acclient @ 0x00513730):
/// rootOffset = CPartArray::Update(dt) // animation
/// PositionManager::adjust_offset(rootOffset) // adds correction
/// frame.origin += rootOffset
///
/// In acdream the animation root motion is sourced from
/// AnimationSequencer.CurrentVelocity (body-local velocity from the
/// active locomotion cycle). We rotate that by the body's orientation
/// to get a world-space delta, then add the InterpolationManager's
/// world-space correction.
/// </summary>
public sealed class PositionManager
{
/// <summary>
/// Compute the per-frame world-space delta to add to body.Position.
/// </summary>
/// <param name="dt">Per-frame delta time, seconds.</param>
/// <param name="currentBodyPosition">Body's current world-space position.</param>
/// <param name="seqVel">
/// Body-local velocity from the active animation cycle
/// (from <c>AnimationSequencer.CurrentVelocity</c>); pass
/// <c>Vector3.Zero</c> if the entity has no sequencer or is on a
/// non-locomotion cycle.
/// </param>
/// <param name="ori">Body orientation; used to rotate seqVel from body-local to world.</param>
/// <param name="interp">The remote's InterpolationManager (for AdjustOffset call).</param>
/// <param name="maxSpeed">From <c>MotionInterpreter.GetMaxSpeed()</c> — passed to AdjustOffset for the catch-up clamp.</param>
public Vector3 ComputeOffset(
double dt,
Vector3 currentBodyPosition,
Vector3 seqVel,
Quaternion ori,
InterpolationManager interp,
float maxSpeed)
{
// Step 1: animation root motion (body-local → world).
Vector3 rootMotionLocal = seqVel * (float)dt;
Vector3 rootMotionWorld = Vector3.Transform(rootMotionLocal, ori);
// Step 2: interpolation correction (world-space already).
Vector3 correction = interp.AdjustOffset(dt, currentBodyPosition, maxSpeed);
// Step 3: combined delta.
return rootMotionWorld + correction;
}
}