feat(physics): port retail complete object frame pipeline
Restore the named-retail object update order across local, remote, static, projectile, animation, shadow, teleport, and effect lifetimes. Separate authoritative root commits from spatial rebucketing, preserve per-owner hook/FIFO ordering, and remove update-path allocations with exact lifecycle and residency gates. Add deterministic conformance, adversarial lifetime, GUID-reuse, pending-cell, quaternion, timestamp, and allocation coverage. Release build is warning-free and all 6,446 tests pass with five intentional skips; retail, architecture, and adversarial reviews are clean. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
31a0889f08
commit
f961d70023
77 changed files with 12513 additions and 1871 deletions
|
|
@ -1,4 +1,5 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.Physics.Motion;
|
||||
|
||||
namespace AcDream.Core.Physics;
|
||||
|
||||
|
|
@ -7,10 +8,10 @@ namespace AcDream.Core.Physics;
|
|||
/// by <c>CSequence::update</c> + InterpolationManager catch-up correction.
|
||||
/// Pure function — no side effects or hidden state.
|
||||
///
|
||||
/// Mirrors retail CPhysicsObj::UpdateObjectInternal (acclient @ 0x00513730):
|
||||
/// rootOffset = CPartArray::Update(dt) // animation
|
||||
/// PositionManager::adjust_offset(rootOffset) // adds correction
|
||||
/// frame.origin += rootOffset
|
||||
/// Mirrors retail <c>CPhysicsObj::UpdatePositionInternal</c> (0x00512C30):
|
||||
/// CPartArray writes one complete local Frame, then PositionManager mutates
|
||||
/// that same Frame. Active interpolation replaces it with
|
||||
/// <c>Position::subtract2</c>; later managers receive the result.
|
||||
///
|
||||
/// The animation root motion is the complete body-local <c>Frame.Origin</c>
|
||||
/// accumulated by <c>CPartArray::Update</c>: authored PosFrames plus the
|
||||
|
|
@ -30,6 +31,50 @@ namespace AcDream.Core.Physics;
|
|||
/// </summary>
|
||||
public sealed class RemoteMotionCombiner
|
||||
{
|
||||
/// <summary>
|
||||
/// Compose retail's complete per-object delta frame. Interpolation, when
|
||||
/// active, replaces the PartArray frame via
|
||||
/// <c>Position::subtract2</c>; otherwise the authored root frame remains.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> when interpolation replaced the root frame.</returns>
|
||||
public bool ComposeOffset(
|
||||
double dt,
|
||||
Vector3 currentBodyPosition,
|
||||
Quaternion ori,
|
||||
MotionDeltaFrame rootMotionLocalFrame,
|
||||
InterpolationManager interp,
|
||||
float maxSpeed,
|
||||
MotionDeltaFrame output,
|
||||
Vector3? terrainNormal = null,
|
||||
bool inContact = true)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(rootMotionLocalFrame);
|
||||
ArgumentNullException.ThrowIfNull(interp);
|
||||
ArgumentNullException.ThrowIfNull(output);
|
||||
|
||||
output.Origin = rootMotionLocalFrame.Origin;
|
||||
output.Orientation = rootMotionLocalFrame.Orientation;
|
||||
bool interpolationOverwrote = interp.AdjustOffset(
|
||||
dt,
|
||||
currentBodyPosition,
|
||||
ori,
|
||||
maxSpeed,
|
||||
output,
|
||||
inContact);
|
||||
|
||||
if (!interpolationOverwrote
|
||||
&& terrainNormal.HasValue
|
||||
&& terrainNormal.Value.Z > 0.01f)
|
||||
{
|
||||
Vector3 rootMotionWorld = Vector3.Transform(output.Origin, ori);
|
||||
Vector3 normal = terrainNormal.Value;
|
||||
rootMotionWorld -= normal * Vector3.Dot(rootMotionWorld, normal);
|
||||
output.Origin = MoveToMath.GlobalToLocalVec(ori, rootMotionWorld);
|
||||
}
|
||||
|
||||
return interpolationOverwrote;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute the per-frame world-space delta to add to body.Position.
|
||||
/// </summary>
|
||||
|
|
@ -90,11 +135,21 @@ public sealed class RemoteMotionCombiner
|
|||
// AdjustOffset returns Vector3.Zero in two cases mapped to retail's
|
||||
// early-return: empty queue OR distance < DesiredDistance (0.05m).
|
||||
// In both, body falls back to animation root motion.
|
||||
Vector3 correction = interp.AdjustOffset(dt, currentBodyPosition, maxSpeed);
|
||||
if (correction.LengthSquared() > 0f)
|
||||
return correction;
|
||||
|
||||
Vector3 rootMotionWorld = Vector3.Transform(rootMotionLocalDelta, ori);
|
||||
var root = new MotionDeltaFrame
|
||||
{
|
||||
Origin = rootMotionLocalDelta,
|
||||
};
|
||||
var output = new MotionDeltaFrame();
|
||||
ComposeOffset(
|
||||
dt,
|
||||
currentBodyPosition,
|
||||
ori,
|
||||
root,
|
||||
interp,
|
||||
maxSpeed,
|
||||
output,
|
||||
terrainNormal: null);
|
||||
Vector3 rootMotionWorld = Vector3.Transform(output.Origin, ori);
|
||||
|
||||
// Slope projection (queue-empty fallback only). Locomotion cycles
|
||||
// bake Z=0 in body-local, so without projection the body's Z stays
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue