refactor(animation): extract live PartArray presenter

Move final live part composition, exact-incarnation schedule handoff, MotionDone binding, and presentation diagnostics out of GameWindow while preserving the retail frame order. Reject stale schedule and completion ABA at the new owner boundary.

Co-Authored-By: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-21 09:37:02 +02:00
parent 9760ff5a8d
commit f004ac5562
14 changed files with 963 additions and 438 deletions

View file

@ -1,5 +1,5 @@
using RemoteMotion = AcDream.App.Rendering.GameWindow.RemoteMotion;
using AnimatedEntity = AcDream.App.Rendering.GameWindow.AnimatedEntity;
using LiveEntityAnimationState = AcDream.App.Rendering.LiveEntityAnimationState;
namespace AcDream.App.Physics;
@ -40,13 +40,13 @@ internal sealed class RemotePhysicsUpdater
private readonly AcDream.Core.Physics.PhysicsEngine _physicsEngine;
private readonly System.Func<uint, AcDream.Core.World.WorldEntity, (float Radius, float Height)> _getSetupCylinder;
private readonly System.Action<uint, AnimatedEntity, RemoteMotion, System.Numerics.Vector3> _applyServerControlledVelocityCycle;
private readonly System.Action<uint, LiveEntityAnimationState, RemoteMotion, System.Numerics.Vector3> _applyServerControlledVelocityCycle;
private readonly List<AcDream.App.World.LiveEntityRecord> _spatialRemoteSnapshot = new();
internal RemotePhysicsUpdater(
AcDream.Core.Physics.PhysicsEngine physicsEngine,
System.Func<uint, AcDream.Core.World.WorldEntity, (float Radius, float Height)> getSetupCylinder,
System.Action<uint, AnimatedEntity, RemoteMotion, System.Numerics.Vector3> applyServerControlledVelocityCycle)
System.Action<uint, LiveEntityAnimationState, RemoteMotion, System.Numerics.Vector3> applyServerControlledVelocityCycle)
{
_physicsEngine = physicsEngine;
_getSetupCylinder = getSetupCylinder;
@ -58,7 +58,7 @@ internal sealed class RemotePhysicsUpdater
/// <summary>
/// Advances the retained PositionManager path for every hidden remote,
/// including objects without an <c>AnimatedEntity</c> (missiles commonly
/// including objects without an <c>LiveEntityAnimationState</c> (missiles commonly
/// have no PartArray). Retail gates this path on the live CPhysicsObj, not
/// on whether a render-animation owner exists.
/// </summary>
@ -93,7 +93,7 @@ internal sealed class RemotePhysicsUpdater
|| record.WorldEntity is not { } entity)
continue;
AnimatedEntity? animation = record.AnimationRuntime as AnimatedEntity;
LiveEntityAnimationState? animation = record.AnimationRuntime as LiveEntityAnimationState;
AcDream.Core.Physics.RetailObjectActivityResult activity =
AcDream.Core.Physics.RetailObjectActivityGate.Evaluate(
record.ObjectClock,
@ -165,7 +165,7 @@ internal sealed class RemotePhysicsUpdater
/// </summary>
public bool Tick(
RemoteMotion rm,
AnimatedEntity ae,
LiveEntityAnimationState ae,
float dt,
AcDream.Core.Physics.Motion.MotionDeltaFrame rootMotionLocalFrame,
int liveCenterX,
@ -188,14 +188,14 @@ internal sealed class RemotePhysicsUpdater
/// Canonical ordinary-object tick. Render animation is optional: retail
/// walks <c>CPhysics::object_maint</c>, so a live object with a
/// MovementManager or PositionManager must continue even when it has no
/// <c>AnimatedEntity</c> presentation component.
/// <c>LiveEntityAnimationState</c> presentation component.
/// </summary>
public bool Tick(
RemoteMotion rm,
AcDream.Core.World.WorldEntity entity,
float objectScale,
AcDream.Core.Physics.AnimationSequencer? sequencer,
AnimatedEntity? animationForVelocityCycle,
LiveEntityAnimationState? animationForVelocityCycle,
float dt,
AcDream.Core.Physics.Motion.MotionDeltaFrame rootMotionLocalFrame,
int liveCenterX,