acdream/tests/AcDream.Runtime.Tests/Entities/InboundPhysicsStateControllerTests.cs
Erik 5db3de3c7a feat(runtime): execute initial placement continuations
The admission checkpoint (30012361) sealed accepted updates behind a
pending initial placement; nothing could apply them, so AcknowledgeAdoption
refused any non-empty FIFO and the residence system had no path to
completion. RuntimeInitialCreateContinuationExecutor is that missing
mechanism: a synchronous, retry-idempotent Execute transaction that adopts
the acknowledged initial placement exactly once (consuming the retained
completion so later authored placements for the key can begin), emits the
AfterEnterWorld hook request for the local player, replays deferred
missing-parent raw Creates and queued parent relations by parent GUID
(retail ProcessObjectNetBlobs order: whole-bucket detach, FIFO dispatch,
cancellation-aware restore), and drains the mixed continuation FIFO
strictly by sequence with retail route decisions taken at execution time
via ClassifyAcceptedPosition on live inputs (server-asserted wire contact,
data-driven animation proxy, live distance/options).

Apply bodies are shared with the legacy fused paths through new gate-less
instance seams on InboundPhysicsStateController that keep the one snapshot
store in lockstep; SameIncarnationCreate envelopes apply atomically with
per-stage idempotency and buffered publication after the final stage;
every abandonment path retires the residence through the lifetime choke
point and converges the ownership ledger (executor progress, deferred
buckets, replay windows, placement watches all folded into IsConverged).
Position/placement side effects are exactly-once under retry, external
mutations are detected via a field-masked executor baseline, and
AwaitingContinuationPlacement yields keep the FIFO head retryable.

Production routes are deliberately untouched: graphical and headless
Create still use legacy RegisterEntity, and no host calls Execute. The
cutover is the next checkpoint; AP-1/AD-1 remain open until it lands.
Register rows AD-59/AD-60/AP-130/AP-131/AP-132/TS-62/TS-63 document the
slice's deviations in this commit.

Reviewed: retail-conformance PASS + architecture/adversarial PASS after
five implementation rounds (wire-contact source, snapshot lockstep,
WeenieDescription merge, abandonment convergence, reentrant retirement
windows, acknowledged-completion leak, baseline precision, replay
containment/restore, queue-by-parent-GUID relation deferral all fixed at
root cause). Runtime tests 903/903; complete Release solution 10,696
passed / 4 intentional skips; focused executor gate 161/161.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 03:49:56 +02:00

572 lines
21 KiB
C#

using System.Numerics;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Runtime.Entities;
namespace AcDream.Runtime.Tests.Entities;
public sealed class InboundPhysicsStateControllerTests
{
[Fact]
public void SameGenerationCreate_RoutesChannelsIndependently()
{
var controller = new InboundPhysicsStateController();
CreateObject.ServerPosition original = Position(0x0101FFFFu, 10f);
CreateObject.ServerPosition incoming = Position(0x0101FFFFu, 20f);
controller.AcceptCreate(Spawn(0x70000001u, 1, 10, 10, original, 0x408u));
InboundCreateResult refresh = controller.AcceptCreate(
Spawn(0x70000001u, 1, 9, 11, incoming, 0x448u));
Assert.Equal(CreateObjectTimestampDisposition.ExistingGeneration, refresh.Disposition);
Assert.NotNull(refresh.SameGenerationEvents);
SameGenerationCreateObjectEvents events = refresh.SameGenerationEvents.Value;
Assert.True(controller.TryApplyPosition(
events.Position!.Value,
isLocalPlayer: false,
forcePositionRotation: null,
currentLocalVelocity: null,
out PositionTimestampDisposition positionResult,
out _,
out _));
Assert.Equal(PositionTimestampDisposition.Rejected, positionResult);
Assert.True(controller.TryApplyState(events.State, out _));
Assert.True(controller.TryGetSnapshot(0x70000001u, out WorldSession.EntitySpawn accepted));
Assert.Equal(original, accepted.Position);
Assert.Equal(0x448u, accepted.PhysicsState);
}
[Fact]
public void PickupRetainsGeneration_AndFreshPositionReturnsToWorld()
{
var controller = new InboundPhysicsStateController();
controller.AcceptCreate(Spawn(
0x70000002u, 7, 20, 1, Position(0x0101FFFFu, 10f), 0x408u));
Assert.True(controller.TryApplyPickup(
new PickupEvent.Parsed(0x70000002u, 7, 21), out WorldSession.EntitySpawn pickedUp));
Assert.Null(pickedUp.Position);
var update = new WorldSession.EntityPositionUpdate(
0x70000002u,
Position(0x0101FFFFu, 30f),
null,
null,
true,
7,
22,
0,
0);
Assert.True(controller.TryApplyPosition(
update, false, null, null, out var disposition, out WorldSession.EntitySpawn returned, out _));
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
Assert.Equal(30f, returned.Position!.Value.PositionX);
}
[Fact]
public void PositionPlacementAbsentAndPresentZeroBothApplyRetailZero()
{
var controller = new InboundPhysicsStateController();
WorldSession.EntitySpawn seed = Spawn(
0x70000009u, 7, 20, 1, Position(0x0101FFFFu, 10f), 0x408u);
seed = seed with
{
PlacementId = 7,
Physics = seed.Physics!.Value with { AnimationFrame = 7 },
};
controller.AcceptCreate(seed);
Assert.True(controller.TryApplyPosition(
new WorldSession.EntityPositionUpdate(
seed.Guid,
Position(0x0101FFFFu, 20f),
null,
null,
true,
7,
21,
0,
0),
false,
null,
null,
out PositionTimestampDisposition firstDisposition,
out WorldSession.EntitySpawn first,
out _));
Assert.Equal(PositionTimestampDisposition.Apply, firstDisposition);
Assert.Equal((uint)0, first.PlacementId);
Assert.Equal((uint)0, first.Physics!.Value.AnimationFrame);
Assert.True(controller.TryApplyPosition(
new WorldSession.EntityPositionUpdate(
seed.Guid,
Position(0x0101FFFFu, 30f),
null,
0,
true,
7,
22,
0,
0),
false,
null,
null,
out PositionTimestampDisposition secondDisposition,
out WorldSession.EntitySpawn second,
out _));
Assert.Equal(PositionTimestampDisposition.Apply, secondDisposition);
Assert.Equal((uint)0, second.PlacementId);
Assert.Equal((uint)0, second.Physics!.Value.AnimationFrame);
}
[Fact]
public void RemotePositionWithoutVelocityAppliesUnpackedZeroVector()
{
var controller = new InboundPhysicsStateController();
WorldSession.EntitySpawn seed = Spawn(
0x7000000Au, 7, 20, 1, Position(0x0101FFFFu, 10f), 0x408u);
seed = seed with
{
Physics = seed.Physics!.Value with { Velocity = new Vector3(4f, 5f, 6f) },
};
controller.AcceptCreate(seed);
Assert.True(controller.TryApplyPosition(
new WorldSession.EntityPositionUpdate(
seed.Guid,
Position(0x0101FFFFu, 20f),
null,
null,
true,
7,
21,
0,
0),
false,
null,
null,
out PositionTimestampDisposition disposition,
out WorldSession.EntitySpawn accepted,
out _));
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
Assert.Equal(Vector3.Zero, accepted.Physics!.Value.Velocity);
}
[Fact]
public void LocalNormalCorrectionRetainsLiveVelocity_ButFreshTeleportZerosIt()
{
var controller = new InboundPhysicsStateController();
WorldSession.EntitySpawn seed = WithTimestamps(
Spawn(0x5000000Au, 7, 20, 1, Position(0x0101FFFFu, 10f), 0x408u),
teleport: 10);
controller.AcceptCreate(seed);
Vector3 liveVelocity = new(4f, 5f, 6f);
Assert.True(controller.TryApplyPosition(
new WorldSession.EntityPositionUpdate(
seed.Guid,
Position(0x0101FFFFu, 20f),
new Vector3(90f, 80f, 70f),
null,
true,
7,
21,
10,
0),
true,
null,
liveVelocity,
out PositionTimestampDisposition normalDisposition,
out WorldSession.EntitySpawn normal,
out AcceptedPhysicsTimestamps normalTimestamps));
Assert.Equal(PositionTimestampDisposition.Apply, normalDisposition);
Assert.Equal(liveVelocity, normal.Physics!.Value.Velocity);
Assert.False(normalTimestamps.TeleportAdvanced);
Assert.True(controller.TryApplyPosition(
new WorldSession.EntityPositionUpdate(
seed.Guid,
Position(0x0101FFFFu, 30f),
new Vector3(9f, 8f, 7f),
null,
true,
7,
22,
11,
0),
true,
null,
liveVelocity,
out PositionTimestampDisposition teleportDisposition,
out WorldSession.EntitySpawn teleported,
out AcceptedPhysicsTimestamps teleportTimestamps));
Assert.Equal(PositionTimestampDisposition.Apply, teleportDisposition);
Assert.Equal(Vector3.Zero, teleported.Physics!.Value.Velocity);
Assert.True(teleportTimestamps.TeleportAdvanced);
}
[Fact]
public void ParentEventWaitsForParent_ThenStagesTimestampUntilValidatedCommit()
{
var controller = new InboundPhysicsStateController();
controller.AcceptCreate(Spawn(
0x70000003u, 3, 4, 1, Position(0x0101FFFFu, 10f), 0x408u));
var update = new ParentEvent.Parsed(
0x70000004u, 0x70000003u, 1, 2, 9, 5);
Assert.False(controller.TryApplyParent(update, out _));
controller.AcceptCreate(Spawn(
0x70000004u, 9, 1, 1, Position(0x0101FFFFu, 15f), 0x408u));
Assert.True(controller.TryApplyParent(update, out WorldSession.EntitySpawn staged));
Assert.Null(staged.ParentGuid);
Assert.NotNull(staged.Position);
Assert.Equal((ushort)5, staged.PositionSequence);
Assert.True(controller.TryCommitParent(
update.ChildGuid,
update.ParentGuid,
update.ParentLocation,
update.PlacementId,
update.ChildPositionSequence,
out WorldSession.EntitySpawn attached));
Assert.Equal(0x70000004u, attached.ParentGuid);
Assert.Null(attached.Position);
Assert.False(controller.TryApplyParent(update, out _));
}
[Fact]
public void LocalDeleteIsRejected_AndClearDropsSessionState()
{
var controller = new InboundPhysicsStateController();
controller.AcceptCreate(Spawn(
0x50000001u, 4, 1, 1, Position(0x0101FFFFu, 10f), 0x408u));
Assert.False(controller.TryDelete(new DeleteObject.Parsed(0x50000001u, 4), true));
Assert.True(controller.TryGetSnapshot(0x50000001u, out _));
controller.Clear();
Assert.Empty(controller.Snapshots);
Assert.False(controller.IsFreshTeleportStart(0x50000001u, 1));
}
[Fact]
public void DeleteForUnknownGenerationIsRejected()
{
var controller = new InboundPhysicsStateController();
Assert.False(controller.TryDelete(
new DeleteObject.Parsed(0x5000FFFFu, 9), isLocalPlayer: false));
}
[Fact]
public void TeleportStartIsComparedWithoutAdvancingTeleportTimestamp()
{
var controller = new InboundPhysicsStateController();
WorldSession.EntitySpawn spawn = Spawn(
0x50000002u, 2, 1, 1, Position(0x0101FFFFu, 10f), 0x408u);
PhysicsSpawnData physics = spawn.Physics!.Value with
{
Timestamps = spawn.Physics.Value.Timestamps with { Teleport = 10 },
};
controller.AcceptCreate(spawn with { Physics = physics });
Assert.True(controller.IsFreshTeleportStart(0x50000002u, 10));
Assert.False(controller.IsFreshTeleportStart(0x50000002u, 9));
Assert.True(controller.IsFreshTeleportStart(0x50000002u, 11));
Assert.True(controller.IsFreshTeleportStart(0x50000002u, 11));
}
[Fact]
public void RejectedServerControlStillMirrorsConsumedMovementTimestamp()
{
var controller = new InboundPhysicsStateController();
WorldSession.EntitySpawn spawn = WithTimestamps(
Spawn(0x70000005u, 3, 1, 1, Position(0x0101FFFFu, 10f), 0x408u),
movement: 1,
serverControl: 5);
controller.AcceptCreate(spawn);
bool applied = controller.TryApplyMotion(
new WorldSession.EntityMotionUpdate(
spawn.Guid,
new CreateObject.ServerMotionState(0x3d, 0x11),
3,
2,
4,
false),
retainPayload: true,
out _,
out _);
Assert.False(applied);
Assert.True(controller.TryGetSnapshot(spawn.Guid, out WorldSession.EntitySpawn retained));
Assert.Equal((ushort)2, retained.MovementSequence);
Assert.Equal((ushort)5, retained.ServerControlSequence);
Assert.Equal((ushort)2, retained.Physics!.Value.Timestamps.Movement);
Assert.Equal((ushort)5, retained.Physics.Value.Timestamps.ServerControlledMove);
Assert.Equal(spawn.MotionState, retained.MotionState);
}
[Fact]
public void AutonomousLocalEchoRetainsPayloadButMirrorsAcceptedTimestamps()
{
var controller = new InboundPhysicsStateController();
WorldSession.EntitySpawn spawn = WithTimestamps(
Spawn(0x50000006u, 3, 1, 1, Position(0x0101FFFFu, 10f), 0x408u),
movement: 1,
serverControl: 5) with
{
MotionState = new CreateObject.ServerMotionState(0x3d, 0x10),
};
controller.AcceptCreate(spawn);
Assert.True(controller.TryApplyMotion(
new WorldSession.EntityMotionUpdate(
spawn.Guid,
new CreateObject.ServerMotionState(0x3d, 0x12),
3,
2,
5,
true),
retainPayload: false,
out WorldSession.EntitySpawn retained,
out _));
Assert.Equal(spawn.MotionState, retained.MotionState);
Assert.Equal((ushort)2, retained.MovementSequence);
Assert.Equal((ushort)5, retained.ServerControlSequence);
Assert.Equal((ushort)2, retained.Physics!.Value.Timestamps.Movement);
}
[Fact]
public void StaleMovementTimestampLeavesLegacySnapshotByteIdentical()
{
var controller = new InboundPhysicsStateController();
WorldSession.EntitySpawn spawn = WithTimestamps(
Spawn(0x70000008u, 3, 1, 1, Position(0x0101FFFFu, 10f), 0x408u),
movement: 5,
serverControl: 5);
controller.AcceptCreate(spawn);
Assert.True(controller.TryGetSnapshot(spawn.Guid, out WorldSession.EntitySpawn before));
// MOVEMENT_TS 3 is OLDER than the stored 5:
// PhysicsTimestampGate.TryAcceptMovementEvent's
// AdvanceStrict(Movement, ...) fails before ever consulting
// SERVER_CONTROLLED_MOVE_TS - the gate is entirely untouched, and
// the canonical snapshot must not move either (the regression this
// guards: ApplyAcceptedMotion stamping the WIRE's own stale
// MovementSequence instead of the gate's unchanged post-call value).
bool applied = controller.TryApplyMotion(
new WorldSession.EntityMotionUpdate(
spawn.Guid,
new CreateObject.ServerMotionState(0x3d, 0x99),
InstanceSequence: 3,
MovementSequence: 3,
ServerControlSequence: 9,
IsAutonomous: false),
retainPayload: true,
out WorldSession.EntitySpawn accepted,
out _);
Assert.False(applied);
Assert.Equal(default, accepted);
Assert.True(controller.TryGetSnapshot(spawn.Guid, out WorldSession.EntitySpawn after));
Assert.Equal(before, after);
}
[Fact]
public void InstanceMismatchedMovementLeavesLegacySnapshotByteIdentical()
{
var controller = new InboundPhysicsStateController();
WorldSession.EntitySpawn spawn = WithTimestamps(
Spawn(0x70000009u, 3, 1, 1, Position(0x0101FFFFu, 10f), 0x408u),
movement: 1,
serverControl: 1);
controller.AcceptCreate(spawn);
Assert.True(controller.TryGetSnapshot(spawn.Guid, out WorldSession.EntitySpawn before));
// A different INSTANCE_TS: TryAcceptInstance fails immediately,
// before MOVEMENT_TS or SERVER_CONTROLLED_MOVE_TS are ever
// consulted. Nothing in the gate or the canonical snapshot may move
// - a stale duplicate or foreign-incarnation packet must not
// silently drag a live entity's movement timestamps forward.
bool applied = controller.TryApplyMotion(
new WorldSession.EntityMotionUpdate(
spawn.Guid,
new CreateObject.ServerMotionState(0x3d, 0x99),
InstanceSequence: 4,
MovementSequence: 9,
ServerControlSequence: 9,
IsAutonomous: false),
retainPayload: true,
out WorldSession.EntitySpawn accepted,
out _);
Assert.False(applied);
Assert.Equal(default, accepted);
Assert.True(controller.TryGetSnapshot(spawn.Guid, out WorldSession.EntitySpawn after));
Assert.Equal(before, after);
}
[Fact]
public void FreshForceWithOlderTeleportMirrorsForceButRejectsPose()
{
var controller = new InboundPhysicsStateController();
WorldSession.EntitySpawn spawn = WithTimestamps(
Spawn(0x50000007u, 3, 10, 1, Position(0x0101FFFFu, 10f), 0x408u),
teleport: 10,
forcePosition: 0);
controller.AcceptCreate(spawn);
Assert.True(controller.TryApplyPosition(
new WorldSession.EntityPositionUpdate(
spawn.Guid,
Position(0x0101FFFFu, 99f),
null,
null,
true,
3,
11,
9,
1),
isLocalPlayer: true,
forcePositionRotation: Quaternion.Identity,
currentLocalVelocity: Vector3.Zero,
out PositionTimestampDisposition disposition,
out WorldSession.EntitySpawn retained,
out _));
Assert.Equal(PositionTimestampDisposition.Rejected, disposition);
Assert.Equal(10f, retained.Position!.Value.PositionX);
Assert.Equal((ushort)10, retained.PositionSequence);
Assert.Equal((ushort)1, retained.Physics!.Value.Timestamps.ForcePosition);
Assert.Equal((ushort)10, retained.Physics.Value.Timestamps.Position);
}
[Fact]
public void ForcePositionStoresPreservedLocalHeadingForLaterHydration()
{
var controller = new InboundPhysicsStateController();
WorldSession.EntitySpawn spawn = WithTimestamps(
Spawn(0x50000008u, 3, 10, 1, Position(0x0101FFFFu, 10f), 0x408u),
teleport: 10,
forcePosition: 0);
controller.AcceptCreate(spawn);
Quaternion heading = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, 0.75f);
Vector3 liveVelocity = new(1f, 2f, 3f);
Assert.True(controller.TryApplyPosition(
new WorldSession.EntityPositionUpdate(
spawn.Guid,
Position(0x0101FFFFu, 99f),
new Vector3(90f, 80f, 70f),
null,
true,
3,
9,
10,
1),
isLocalPlayer: true,
forcePositionRotation: heading,
currentLocalVelocity: liveVelocity,
out PositionTimestampDisposition disposition,
out WorldSession.EntitySpawn retained,
out _));
Assert.Equal(PositionTimestampDisposition.ForcePosition, disposition);
Assert.Equal(99f, retained.Position!.Value.PositionX);
Assert.Equal(heading.W, retained.Position.Value.RotationW, precision: 6);
Assert.Equal(heading.X, retained.Position.Value.RotationX, precision: 6);
Assert.Equal(heading.Y, retained.Position.Value.RotationY, precision: 6);
Assert.Equal(heading.Z, retained.Position.Value.RotationZ, precision: 6);
Assert.Equal(retained.Position, retained.Physics!.Value.Position);
Assert.Equal(liveVelocity, retained.Physics.Value.Velocity);
}
private static WorldSession.EntitySpawn WithTimestamps(
WorldSession.EntitySpawn spawn,
ushort? movement = null,
ushort? serverControl = null,
ushort? teleport = null,
ushort? forcePosition = null)
{
PhysicsSpawnData physics = spawn.Physics!.Value;
PhysicsTimestamps timestamps = physics.Timestamps with
{
Movement = movement ?? physics.Timestamps.Movement,
ServerControlledMove = serverControl ?? physics.Timestamps.ServerControlledMove,
Teleport = teleport ?? physics.Timestamps.Teleport,
ForcePosition = forcePosition ?? physics.Timestamps.ForcePosition,
};
return spawn with
{
MovementSequence = timestamps.Movement,
ServerControlSequence = timestamps.ServerControlledMove,
Physics = physics with { Timestamps = timestamps },
};
}
private static WorldSession.EntitySpawn Spawn(
uint guid,
ushort instance,
ushort positionSequence,
ushort stateSequence,
CreateObject.ServerPosition? position,
uint state)
{
var timestamps = new PhysicsTimestamps(
positionSequence,
1,
stateSequence,
1,
0,
1,
0,
1,
instance);
var physics = new PhysicsSpawnData(
RawState: state,
Position: position,
Movement: null,
AnimationFrame: null,
SetupTableId: 0x02000001u,
MotionTableId: 0x09000001u,
SoundTableId: null,
PhysicsScriptTableId: null,
Parent: null,
Children: null,
Scale: null,
Friction: null,
Elasticity: null,
Translucency: null,
Velocity: null,
Acceleration: null,
AngularVelocity: null,
DefaultScriptType: null,
DefaultScriptIntensity: null,
Timestamps: timestamps);
return new WorldSession.EntitySpawn(
guid,
position,
0x02000001u,
Array.Empty<CreateObject.AnimPartChange>(),
Array.Empty<CreateObject.TextureChange>(),
Array.Empty<CreateObject.SubPaletteSwap>(),
null,
null,
"fixture",
null,
null,
0x09000001u,
PhysicsState: state,
InstanceSequence: instance,
MovementSequence: 1,
ServerControlSequence: 1,
PositionSequence: positionSequence,
Physics: physics);
}
private static CreateObject.ServerPosition Position(uint cell, float x) =>
new(cell, x, 10f, 5f, 1f, 0f, 0f, 0f);
}