fix(runtime): preserve hidden setposition collision ownership
This commit is contained in:
parent
9b0f59bd1b
commit
0fbc7a1fb7
8 changed files with 343 additions and 43 deletions
|
|
@ -883,23 +883,17 @@ public sealed class ShadowObjectRegistry
|
|||
DataCache = DataCache,
|
||||
};
|
||||
staging.InstallOwnerState(source);
|
||||
if (suspendOwner)
|
||||
{
|
||||
if (!staging.Suspend(entityId))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
staging.CommitSetPosition(
|
||||
entityId,
|
||||
worldPosition,
|
||||
worldRotation,
|
||||
seedCellId,
|
||||
worldOffsetX,
|
||||
worldOffsetY,
|
||||
action,
|
||||
crossCellIds);
|
||||
}
|
||||
staging.CommitSetPosition(
|
||||
entityId,
|
||||
worldPosition,
|
||||
worldRotation,
|
||||
seedCellId,
|
||||
worldOffsetX,
|
||||
worldOffsetY,
|
||||
action,
|
||||
crossCellIds);
|
||||
if (suspendOwner && !staging.Suspend(entityId))
|
||||
return false;
|
||||
if (!staging.TryCaptureOwnerState(
|
||||
entityId,
|
||||
out PreparedShadowOwnerState? replacement)
|
||||
|
|
|
|||
|
|
@ -752,6 +752,12 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|
|||
transition = Entities.ApplyRawPhysicsState(
|
||||
canonical,
|
||||
update.PhysicsState);
|
||||
if (canonical.Key is { } key)
|
||||
{
|
||||
Physics.Engine.ShadowObjects.UpdatePhysicsState(
|
||||
key.LocalEntityId,
|
||||
(uint)canonical.FinalPhysicsState);
|
||||
}
|
||||
ulong stateVersion = canonical.StateAuthorityVersion;
|
||||
ulong physicsMutationVersion =
|
||||
canonical.PhysicsStateMutationVersion;
|
||||
|
|
|
|||
|
|
@ -438,6 +438,12 @@ internal sealed class RuntimeCollisionReportingState : IDisposable
|
|||
bool reported = false;
|
||||
for (int index = 0; index < receipt.Actions.Length; index++)
|
||||
{
|
||||
if (IsExactSetPositionBatchOwnerHidden(receipt))
|
||||
{
|
||||
return new(
|
||||
SetPositionCollisionBatchDispatchStatus.Completed,
|
||||
reported);
|
||||
}
|
||||
if (receipt.Owner.PositionAuthorityVersion
|
||||
!= receipt.OwnerPositionAuthorityVersion
|
||||
|| _owners.TryGetValue(
|
||||
|
|
@ -463,6 +469,12 @@ internal sealed class RuntimeCollisionReportingState : IDisposable
|
|||
action, receipt.PhysicsTime, receipt.BatchId);
|
||||
}
|
||||
|
||||
if (IsExactSetPositionBatchOwnerHidden(receipt))
|
||||
{
|
||||
return new(
|
||||
SetPositionCollisionBatchDispatchStatus.Completed,
|
||||
reported);
|
||||
}
|
||||
if (receipt.Owner.PositionAuthorityVersion
|
||||
!= receipt.OwnerPositionAuthorityVersion
|
||||
|| _owners.TryGetValue(
|
||||
|
|
@ -498,6 +510,14 @@ internal sealed class RuntimeCollisionReportingState : IDisposable
|
|||
reported);
|
||||
}
|
||||
|
||||
private bool IsExactSetPositionBatchOwnerHidden(
|
||||
in SetPositionCollisionBatchReceipt receipt) =>
|
||||
_entities.IsCurrent(receipt.Owner)
|
||||
&& receipt.Owner.PositionAuthorityVersion
|
||||
== receipt.OwnerPositionAuthorityVersion
|
||||
&& ReferenceEquals(receipt.Owner.PhysicsBody, receipt.OwnerBody)
|
||||
&& (receipt.OwnerBody.State & PhysicsStateFlags.Hidden) != 0;
|
||||
|
||||
internal bool DiscardSetPositionBatch(
|
||||
in SetPositionCollisionBatchReceipt receipt) =>
|
||||
receipt.IsValid
|
||||
|
|
|
|||
|
|
@ -3199,8 +3199,7 @@ public sealed class RuntimePhysicsState : IDisposable
|
|||
&& record.PositionAuthorityVersion == positionAuthorityVersion
|
||||
&& record.SpatialAuthorityVersion == spatialAuthorityVersion
|
||||
&& ReferenceEquals(record.PhysicsBody, body)
|
||||
&& body.InWorld
|
||||
&& (body.State & PhysicsStateFlags.Hidden) == 0;
|
||||
&& body.InWorld;
|
||||
}
|
||||
|
||||
private void OnCollisionOwnerMutated(uint ownerId, ulong version)
|
||||
|
|
|
|||
|
|
@ -392,8 +392,7 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
body,
|
||||
placementCommitVersion,
|
||||
fullCellId,
|
||||
requireSpatialRoot: false)
|
||||
&& owner.IsCollisionReportingEligible(record, body);
|
||||
requireSpatialRoot: false);
|
||||
}
|
||||
|
||||
private readonly RuntimePhysicsState _physics;
|
||||
|
|
@ -3389,21 +3388,21 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
body,
|
||||
canonicalCommitVersion,
|
||||
committedCellId,
|
||||
requireSpatialRoot: false)
|
||||
|| !IsCollisionReportingEligible(record, body))
|
||||
requireSpatialRoot: false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool reportingCurrent = _physics.HandleSetPositionCollisionReports(
|
||||
record,
|
||||
operation.PositionAuthorityVersion,
|
||||
operation.SpatialAuthorityVersion,
|
||||
operation.Command.GameTime,
|
||||
operation.PreviousContact,
|
||||
operation.PreviousOnWalkable,
|
||||
collidedWithEnvironment,
|
||||
collidedObjectIds,
|
||||
out _);
|
||||
bool reportingCurrent = !IsCollisionReportingEligible(record, body)
|
||||
|| _physics.HandleSetPositionCollisionReports(
|
||||
record,
|
||||
operation.PositionAuthorityVersion,
|
||||
operation.SpatialAuthorityVersion,
|
||||
operation.Command.GameTime,
|
||||
operation.PreviousContact,
|
||||
operation.PreviousOnWalkable,
|
||||
collidedWithEnvironment,
|
||||
collidedObjectIds,
|
||||
out _);
|
||||
if (!reportingCurrent
|
||||
|| !IsCanonicalPlacementCommitCurrent(
|
||||
operation,
|
||||
|
|
@ -3411,8 +3410,7 @@ internal sealed class RuntimeSetPositionState : IDisposable
|
|||
body,
|
||||
canonicalCommitVersion,
|
||||
committedCellId,
|
||||
requireSpatialRoot: false)
|
||||
|| !IsCollisionReportingEligible(record, body))
|
||||
requireSpatialRoot: false))
|
||||
return false;
|
||||
body.FramesStationaryFall = result.FramesStationaryFall;
|
||||
if (IsVelocityCurrent(operation))
|
||||
|
|
|
|||
|
|
@ -38,6 +38,33 @@ public sealed class ShadowSetPositionCommitTests
|
|||
Assert.Single(registry.GetObjectsInCell(Cell9)).Position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreparedSuspendedMoveRetainsNewCanonicalPosition()
|
||||
{
|
||||
var registry = RegisteredSingle();
|
||||
var moved = new Vector3(36f, 12f, 50f);
|
||||
Assert.True(registry.TryPrepareSetPosition(
|
||||
1u,
|
||||
moved,
|
||||
Quaternion.Identity,
|
||||
Cell9,
|
||||
0f,
|
||||
0f,
|
||||
PhysicsShadowCommitAction.Replace,
|
||||
[Cell9],
|
||||
provenShapeless: false,
|
||||
suspendOwner: true,
|
||||
out var prepared));
|
||||
|
||||
Assert.Single(registry.GetObjectsInCell(Cell1));
|
||||
Assert.True(registry.TryApplySetPosition(prepared!, out _));
|
||||
Assert.Empty(registry.GetObjectsInCell(Cell1));
|
||||
Assert.Empty(registry.GetObjectsInCell(Cell9));
|
||||
Assert.Equal(1, registry.SuspendedRegistrationCount);
|
||||
Assert.Equal(moved, prepared!.OwnerState!.Registration.EntityWorldPos);
|
||||
Assert.Equal(Cell9, prepared.OwnerState.Registration.SeedCellId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreparedCrossPrefixDispatchesMembershipThenMutationOnce()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1883,7 +1883,7 @@ public sealed class RuntimeCollisionReportingStateTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void HitGroundHiddenTransitionCannotResumeCollisionTracking()
|
||||
public void HitGroundHiddenTransitionCommitsPlacementWithoutResumingCollisionTracking()
|
||||
{
|
||||
using var lifetime = Lifetime();
|
||||
RuntimeEntityRecord owner = Entity(
|
||||
|
|
@ -1937,11 +1937,20 @@ public sealed class RuntimeCollisionReportingStateTests
|
|||
PlacementCommand(owner, new Vector3(16f, 12f, 7f)));
|
||||
|
||||
Assert.True(hidden);
|
||||
Assert.Equal(RuntimeSetPositionStatus.Cancelled, outcome.Status);
|
||||
Assert.Equal(
|
||||
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
|
||||
outcome.Status);
|
||||
Assert.True(lifetime.Physics.SetPosition.TryPeekProjection(
|
||||
out RuntimePlacementProjectionSnapshot projection));
|
||||
Assert.True(owner.FinalPhysicsState.HasFlag(PhysicsStateFlags.Hidden));
|
||||
Assert.Equal(Cell, owner.FullCellId);
|
||||
Assert.Equal(projection.WorldPosition, owner.PhysicsBody!.Position);
|
||||
Assert.True(owner.PhysicsBody.InWorld);
|
||||
Assert.Empty(observer.Reports);
|
||||
Assert.Equal(0, lifetime.Physics.CollisionReports.CaptureOwnership()
|
||||
.TrackedObjectCount);
|
||||
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
|
||||
outcome.Projection));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -2010,7 +2019,7 @@ public sealed class RuntimeCollisionReportingStateTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void CollisionReportHiddenTransitionStopsResponseAndShadowReflood()
|
||||
public void CollisionReportHiddenTransitionPreservesCommittedPlacement()
|
||||
{
|
||||
using var lifetime = Lifetime();
|
||||
RuntimeEntityRecord owner = Entity(
|
||||
|
|
@ -2023,7 +2032,6 @@ public sealed class RuntimeCollisionReportingStateTests
|
|||
RegisterDynamicShadow(lifetime, owner);
|
||||
RegisterDynamicShadow(lifetime, target);
|
||||
uint targetId = target.Key!.Value.LocalEntityId;
|
||||
Vector3 initial = owner.PhysicsBody!.Position;
|
||||
lifetime.Physics.Engine.TransitionCellCollisionTestHook =
|
||||
(transition, phase, _, _) =>
|
||||
{
|
||||
|
|
@ -2062,14 +2070,217 @@ public sealed class RuntimeCollisionReportingStateTests
|
|||
PlacementCommand(owner, new Vector3(18f, 12f, 7f)));
|
||||
|
||||
Assert.True(hidden);
|
||||
Assert.Equal(RuntimeSetPositionStatus.Cancelled, outcome.Status);
|
||||
Assert.Equal(
|
||||
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
|
||||
outcome.Status);
|
||||
Assert.True(owner.FinalPhysicsState.HasFlag(PhysicsStateFlags.Hidden));
|
||||
Assert.Equal(0, lifetime.Physics.CollisionReports.CaptureOwnership()
|
||||
.TrackedObjectCount);
|
||||
ShadowEntry shadow = Assert.Single(
|
||||
lifetime.Physics.Engine.ShadowObjects.AllEntriesForDebug(),
|
||||
Assert.Equal(Cell, owner.FullCellId);
|
||||
Assert.Equal(new Vector3(18f, 12f, 7f), owner.PhysicsBody!.Position);
|
||||
ShadowObjectRegistry shadows = lifetime.Physics.Engine.ShadowObjects;
|
||||
uint ownerId = owner.Key!.Value.LocalEntityId;
|
||||
ShadowEntry hiddenShadow = Assert.Single(
|
||||
shadows.AllEntriesForDebug(),
|
||||
entry => entry.EntityId == ownerId);
|
||||
Assert.Equal(owner.PhysicsBody.Position, hiddenShadow.Position);
|
||||
Assert.Equal(0, shadows.SuspendedRegistrationCount);
|
||||
Assert.True(shadows.TryGetCollisionOwner(
|
||||
ownerId,
|
||||
out uint hiddenShadowState,
|
||||
out _));
|
||||
Assert.Equal((uint)owner.FinalPhysicsState, hiddenShadowState);
|
||||
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
|
||||
outcome.Projection));
|
||||
|
||||
Assert.True(lifetime.TryApplyState(
|
||||
new SetState.Parsed(
|
||||
owner.ServerGuid,
|
||||
(uint)PhysicsStateFlags.ReportCollisions,
|
||||
owner.Incarnation,
|
||||
StateSequence: 3),
|
||||
acknowledgeProjection: null,
|
||||
out _,
|
||||
out _));
|
||||
Assert.Equal(0, shadows.SuspendedRegistrationCount);
|
||||
ShadowEntry restored = Assert.Single(
|
||||
shadows.AllEntriesForDebug(),
|
||||
entry => entry.EntityId == ownerId);
|
||||
Assert.Equal(owner.PhysicsBody.Position, restored.Position);
|
||||
Assert.Equal((uint)owner.FinalPhysicsState, restored.State);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreparedCollisionBatchStopsAfterCallbackHidesOwner()
|
||||
{
|
||||
using var lifetime = Lifetime();
|
||||
RuntimeEntityRecord owner = Entity(
|
||||
lifetime,
|
||||
0x7000207Eu,
|
||||
1,
|
||||
PhysicsStateFlags.ReportCollisions);
|
||||
RuntimeEntityRecord first = Entity(
|
||||
lifetime, 0x7000207Fu, 1, PhysicsStateFlags.None);
|
||||
RuntimeEntityRecord second = Entity(
|
||||
lifetime, 0x70002080u, 1, PhysicsStateFlags.None);
|
||||
RegisterDynamicShadow(lifetime, owner);
|
||||
RegisterDynamicShadow(lifetime, first);
|
||||
RegisterDynamicShadow(lifetime, second);
|
||||
bool hidden = false;
|
||||
var observer = new CollisionObserver(report =>
|
||||
{
|
||||
if (hidden
|
||||
|| report.Kind is not RuntimeCollisionReportKind.ObjectCollision
|
||||
|| report.Recipient != owner.Key)
|
||||
{
|
||||
return;
|
||||
}
|
||||
hidden = lifetime.TryApplyState(
|
||||
new SetState.Parsed(
|
||||
owner.ServerGuid,
|
||||
(uint)(PhysicsStateFlags.ReportCollisions
|
||||
| PhysicsStateFlags.Hidden),
|
||||
owner.Incarnation,
|
||||
StateSequence: 2),
|
||||
acknowledgeProjection: null,
|
||||
out _,
|
||||
out _);
|
||||
});
|
||||
using IDisposable subscription = lifetime.Physics.CollisionReports
|
||||
.Subscribe(observer);
|
||||
Assert.True(lifetime.Physics.CollisionReports.TryPrepareSetPositionBatch(
|
||||
owner,
|
||||
owner.PhysicsBody!,
|
||||
physicsTime: 12d,
|
||||
previousContact: false,
|
||||
previousOnWalkable: false,
|
||||
finalOnWalkable: false,
|
||||
collidedWithEnvironment: false,
|
||||
[
|
||||
first.Key!.Value.LocalEntityId,
|
||||
second.Key!.Value.LocalEntityId,
|
||||
],
|
||||
out var prepared));
|
||||
Assert.True(lifetime.Physics.CollisionReports.TryInstallSetPositionBatch(
|
||||
prepared!, out var receipt));
|
||||
|
||||
SetPositionCollisionBatchDispatchResult result = lifetime.Physics
|
||||
.CollisionReports.DispatchSetPositionBatchResult(receipt);
|
||||
|
||||
Assert.True(hidden);
|
||||
Assert.Equal(SetPositionCollisionBatchDispatchStatus.Completed,
|
||||
result.Status);
|
||||
RuntimeCollisionReport report = Assert.Single(
|
||||
observer.Reports,
|
||||
candidate => candidate.Kind
|
||||
is RuntimeCollisionReportKind.ObjectCollision);
|
||||
Assert.Equal(first.Key, report.Other);
|
||||
RuntimeCollisionReportingOwnershipSnapshot ownership = lifetime.Physics
|
||||
.CollisionReports.CaptureOwnership();
|
||||
Assert.Equal(0, ownership.OwnerCount);
|
||||
Assert.Equal(0, ownership.TrackedObjectCount);
|
||||
Assert.Equal(0, ownership.ReversePeerCount);
|
||||
Assert.Equal(0, ownership.PendingSetPositionDispatchCount);
|
||||
ShadowObjectRegistry shadows = lifetime.Physics.Engine.ShadowObjects;
|
||||
Assert.Equal(0, shadows.SuspendedRegistrationCount);
|
||||
Assert.Contains(
|
||||
shadows.AllEntriesForDebug(),
|
||||
entry => entry.EntityId == owner.Key!.Value.LocalEntityId);
|
||||
Assert.Equal(initial, shadow.Position);
|
||||
Assert.True(shadows.TryGetCollisionOwner(
|
||||
owner.Key!.Value.LocalEntityId,
|
||||
out uint shadowState,
|
||||
out _));
|
||||
Assert.Equal((uint)owner.FinalPhysicsState, shadowState);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoDrawOwnerStillPublishesItsCollisionReport()
|
||||
{
|
||||
using var lifetime = Lifetime();
|
||||
RuntimeEntityRecord owner = Entity(
|
||||
lifetime,
|
||||
0x7000207Au,
|
||||
1,
|
||||
PhysicsStateFlags.ReportCollisions | PhysicsStateFlags.NoDraw);
|
||||
RuntimeEntityRecord target = Entity(
|
||||
lifetime, 0x7000207Bu, 1, PhysicsStateFlags.None);
|
||||
RegisterDynamicShadow(lifetime, owner);
|
||||
RegisterDynamicShadow(lifetime, target);
|
||||
uint targetId = target.Key!.Value.LocalEntityId;
|
||||
lifetime.Physics.Engine.TransitionCellCollisionTestHook =
|
||||
(transition, phase, _, _) =>
|
||||
{
|
||||
if (phase is TransitionCellCollisionPhase.Environment)
|
||||
{
|
||||
transition.CollisionInfo.CollideObjectGuids.Add(targetId);
|
||||
transition.CollisionInfo.SetCollisionNormal(Vector3.UnitX);
|
||||
}
|
||||
return TransitionState.OK;
|
||||
};
|
||||
var observer = new CollisionObserver();
|
||||
using IDisposable subscription = lifetime.Physics.CollisionReports
|
||||
.Subscribe(observer);
|
||||
|
||||
RuntimeSetPositionOutcome outcome = lifetime.Physics.SetPosition.Apply(
|
||||
owner,
|
||||
owner.PositionAuthorityVersion,
|
||||
PlacementCommand(owner, new Vector3(19f, 12f, 7f)));
|
||||
|
||||
Assert.Equal(
|
||||
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
|
||||
outcome.Status);
|
||||
RuntimeCollisionReport report = Assert.Single(
|
||||
observer.Reports,
|
||||
report => report.Kind is RuntimeCollisionReportKind.ObjectCollision);
|
||||
Assert.Equal(owner.Key, report.Recipient);
|
||||
Assert.Equal(target.Key, report.Other);
|
||||
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
|
||||
outcome.Projection));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoDrawOwnerStillAllowsReciprocalPeerCollisionReport()
|
||||
{
|
||||
using var lifetime = Lifetime();
|
||||
RuntimeEntityRecord owner = Entity(
|
||||
lifetime, 0x7000207Cu, 1, PhysicsStateFlags.NoDraw);
|
||||
RuntimeEntityRecord target = Entity(
|
||||
lifetime,
|
||||
0x7000207Du,
|
||||
1,
|
||||
PhysicsStateFlags.ReportCollisions);
|
||||
RegisterDynamicShadow(lifetime, owner);
|
||||
RegisterDynamicShadow(lifetime, target);
|
||||
uint targetId = target.Key!.Value.LocalEntityId;
|
||||
lifetime.Physics.Engine.TransitionCellCollisionTestHook =
|
||||
(transition, phase, _, _) =>
|
||||
{
|
||||
if (phase is TransitionCellCollisionPhase.Environment)
|
||||
{
|
||||
transition.CollisionInfo.CollideObjectGuids.Add(targetId);
|
||||
transition.CollisionInfo.SetCollisionNormal(Vector3.UnitX);
|
||||
}
|
||||
return TransitionState.OK;
|
||||
};
|
||||
var observer = new CollisionObserver();
|
||||
using IDisposable subscription = lifetime.Physics.CollisionReports
|
||||
.Subscribe(observer);
|
||||
|
||||
RuntimeSetPositionOutcome outcome = lifetime.Physics.SetPosition.Apply(
|
||||
owner,
|
||||
owner.PositionAuthorityVersion,
|
||||
PlacementCommand(owner, new Vector3(20f, 12f, 7f)));
|
||||
|
||||
Assert.Equal(
|
||||
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
|
||||
outcome.Status);
|
||||
RuntimeCollisionReport report = Assert.Single(
|
||||
observer.Reports,
|
||||
report => report.Kind is RuntimeCollisionReportKind.ObjectCollision);
|
||||
Assert.Equal(target.Key, report.Recipient);
|
||||
Assert.Equal(owner.Key, report.Other);
|
||||
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
|
||||
outcome.Projection));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -113,6 +113,42 @@ public sealed class RuntimeSetPositionStateTests
|
|||
Assert.Equal(1, ownership.PreparedMoverCount);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PhysicsStateFlags.Hidden)]
|
||||
[InlineData(PhysicsStateFlags.Hidden | PhysicsStateFlags.NoDraw)]
|
||||
public void HiddenObjectCommitsResidenceWhileSuppressingCollisionReports(
|
||||
PhysicsStateFlags suppressedState)
|
||||
{
|
||||
PhysicsEngine engine = FlatEngine(SourceLandblock, 0f);
|
||||
using var lifetime = new RuntimeEntityObjectLifetime(engine);
|
||||
RuntimeEntityRecord record = CreateRecord(lifetime, 0x70001022u, 1);
|
||||
PhysicsStateFlags state = PhysicsStateFlags.Gravity | suppressedState;
|
||||
PhysicsBody body = AttachBody(lifetime, record, SourceCell, state);
|
||||
var collisionObserver = new CollisionReportObserver();
|
||||
using IDisposable collisionSubscription = lifetime.Physics
|
||||
.CollisionReports.Subscribe(collisionObserver);
|
||||
|
||||
RuntimeSetPositionOutcome outcome = lifetime.Physics.SetPosition.Apply(
|
||||
record,
|
||||
record.PositionAuthorityVersion,
|
||||
Command(Request(SourceCell, new Vector3(12f, 18f, 7f))));
|
||||
|
||||
Assert.Equal(
|
||||
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
|
||||
outcome.Status);
|
||||
Assert.True(body.InWorld);
|
||||
Assert.Equal(SourceCell, record.FullCellId);
|
||||
Assert.Equal(new Vector3(12f, 18f, 7f), body.Position);
|
||||
Assert.True(lifetime.Physics.IsSpatialRoot(record));
|
||||
Assert.Empty(collisionObserver.Reports);
|
||||
RuntimeCollisionReportingOwnershipSnapshot reporting = lifetime
|
||||
.Physics.CollisionReports.CaptureOwnership();
|
||||
Assert.Equal(0, reporting.OwnerCount);
|
||||
Assert.Equal(0, reporting.PendingSetPositionDispatchCount);
|
||||
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
|
||||
outcome.Projection));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PublicPlacementChannelObservesRetriesAndAcknowledgesExactToken()
|
||||
{
|
||||
|
|
@ -2582,6 +2618,15 @@ public sealed class RuntimeSetPositionStateTests
|
|||
}
|
||||
}
|
||||
|
||||
private sealed class CollisionReportObserver
|
||||
: IRuntimeCollisionReportObserver
|
||||
{
|
||||
internal List<RuntimeCollisionReport> Reports { get; } = [];
|
||||
|
||||
public void OnCollisionReport(in RuntimeCollisionReport report) =>
|
||||
Reports.Add(report);
|
||||
}
|
||||
|
||||
private sealed class EntityObserver : IRuntimeEntityObjectObserver
|
||||
{
|
||||
internal List<RuntimeEntityDelta> Deltas { get; } = [];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue