fix(motion): restore retail object manager order
Process animation completion at the retail process_hooks boundary, then run targeting, movement, PartArray completion, and PositionManager in the named UpdateObjectInternal order for local, remote, hidden, and position-less animated objects. Retire TS-42 with deterministic conformance coverage.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
0f996db747
commit
c5ab99081c
17 changed files with 493 additions and 98 deletions
|
|
@ -127,6 +127,29 @@ public sealed class RetailLocalPlayerFrameControllerTests
|
|||
Assert.Equal(1f / 60f, timeAfterOneTick);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TargetManager_ObservesPostTransitionPlayerPosition()
|
||||
{
|
||||
PlayerMovementController controller = CreateController();
|
||||
Vector3 initial = controller.Position;
|
||||
Vector3 positionSeenByTargetManager = new(float.NaN);
|
||||
var local = new RetailLocalPlayerFrameController(
|
||||
canPresentPlayer: () => true,
|
||||
getController: () => controller,
|
||||
captureInput: () => new MovementInput(Forward: true),
|
||||
resolveLocalEntityId: () => 7u,
|
||||
handleTargeting: () => positionSeenByTargetManager = controller.Position,
|
||||
isHidden: () => false,
|
||||
project: (_, _, _) => { },
|
||||
sendPreNetwork: (_, _, _) => { },
|
||||
sendPostNetwork: (_, _) => { });
|
||||
|
||||
local.AdvanceBeforeNetwork(PhysicsBody.MaxQuantum);
|
||||
|
||||
Assert.NotEqual(initial, controller.Position);
|
||||
Assert.Equal(controller.Position, positionSeenByTargetManager);
|
||||
}
|
||||
|
||||
private static PlayerMovementController CreateController()
|
||||
{
|
||||
var engine = new PhysicsEngine();
|
||||
|
|
|
|||
|
|
@ -56,6 +56,35 @@ public sealed class RemotePhysicsUpdaterTests
|
|||
Assert.Equal(entity.Position, root.Translation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TickHidden_RunsPartArrayManagerTailWithoutAdvancingSequence()
|
||||
{
|
||||
var updater = new RemotePhysicsUpdater(
|
||||
new PhysicsEngine(),
|
||||
(_, _) => (0.48f, 1.835f),
|
||||
(_, _, _, _) => { });
|
||||
var motion = new GameWindow.RemoteMotion();
|
||||
motion.Body.Orientation = Quaternion.Identity;
|
||||
var entity = new WorldEntity
|
||||
{
|
||||
Id = 2u,
|
||||
ServerGuid = 0x70000002u,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
Position = Vector3.Zero,
|
||||
Rotation = Quaternion.Identity,
|
||||
MeshRefs = Array.Empty<MeshRef>(),
|
||||
};
|
||||
int partArrayTailCalls = 0;
|
||||
|
||||
updater.TickHidden(
|
||||
motion,
|
||||
entity,
|
||||
0.1f,
|
||||
() => partArrayTailCalls++);
|
||||
|
||||
Assert.Equal(1, partArrayTailCalls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TickHidden_CrossCellCommitUpdatesCanonicalCellBeforeUnhide()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ public sealed class EntityEffectPoseRegistryTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void AnimationHookQueue_CompletesMotionStateEvenWhenPoseWasRemoved()
|
||||
public void AnimationHookQueue_CompletesMotionStateAtCaptureEvenWhenPoseWasRemoved()
|
||||
{
|
||||
var poses = new EntityEffectPoseRegistry();
|
||||
var queue = new AnimationHookFrameQueue(new AnimationHookRouter(), poses);
|
||||
|
|
@ -134,12 +134,31 @@ public sealed class EntityEffectPoseRegistryTests
|
|||
11u,
|
||||
sequencer,
|
||||
new AnimationHook[] { new AnimationDoneHook() });
|
||||
|
||||
Assert.Empty(sequencer.Manager.PendingAnimations);
|
||||
queue.Drain();
|
||||
|
||||
Assert.Empty(sequencer.Manager.PendingAnimations);
|
||||
Assert.Equal(0, queue.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnimationHookQueue_DeferredDrainDoesNotOwnPartArrayUseTime()
|
||||
{
|
||||
var poses = new EntityEffectPoseRegistry();
|
||||
var queue = new AnimationHookFrameQueue(new AnimationHookRouter(), poses);
|
||||
var sequencer = new AnimationSequencer(
|
||||
new Setup(),
|
||||
new MotionTable(),
|
||||
new NullAnimationLoader());
|
||||
sequencer.Manager.AddToQueue(0x10000001u, ticks: 0u);
|
||||
|
||||
queue.Capture(12u, sequencer, Array.Empty<AnimationHook>());
|
||||
queue.Drain();
|
||||
|
||||
Assert.Single(sequencer.Manager.PendingAnimations);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Publish_UsesOutdoorEffectCellWithoutChangingRenderParent()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
using AcDream.Core.Physics;
|
||||
|
||||
namespace AcDream.Core.Tests.Physics;
|
||||
|
||||
public sealed class RetailObjectManagerTailTests
|
||||
{
|
||||
[Fact]
|
||||
public void Run_UsesNamedRetailManagerOrder()
|
||||
{
|
||||
var calls = new List<string>();
|
||||
|
||||
RetailObjectManagerTail.Run(
|
||||
checkDetection: () => calls.Add("detection"),
|
||||
handleTargeting: () => calls.Add("target"),
|
||||
movementUseTime: () => calls.Add("movement"),
|
||||
partArrayHandleMovement: () => calls.Add("part-array"),
|
||||
positionUseTime: () => calls.Add("position"));
|
||||
|
||||
Assert.Equal(
|
||||
["detection", "target", "movement", "part-array", "position"],
|
||||
calls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Run_SkipsMissingManagersWithoutDuplicatingNeighbours()
|
||||
{
|
||||
var calls = new List<string>();
|
||||
|
||||
RetailObjectManagerTail.Run(
|
||||
checkDetection: null,
|
||||
handleTargeting: () => calls.Add("target"),
|
||||
movementUseTime: null,
|
||||
partArrayHandleMovement: () => calls.Add("part-array"),
|
||||
positionUseTime: null);
|
||||
|
||||
Assert.Equal(["target", "part-array"], calls);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue