feat(runtime): prepare authored SetPosition movers
This commit is contained in:
parent
237d1184d2
commit
442cb8f97b
8 changed files with 1516 additions and 70 deletions
|
|
@ -0,0 +1,715 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Net;
|
||||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.Physics;
|
||||
|
||||
namespace AcDream.Runtime.Tests.Physics;
|
||||
|
||||
public sealed class RuntimeSetPositionMoverPreparationTests
|
||||
{
|
||||
private const uint Cell = 0xA9B40021u;
|
||||
private const uint SetupId = 0x02000001u;
|
||||
|
||||
[Fact]
|
||||
public void PreparedMoverPreservesCompleteAuthoredRetailInput()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
float half = MathF.Sqrt(0.5f);
|
||||
var acceptedPosition = new CreateObject.ServerPosition(
|
||||
Cell,
|
||||
12.5f,
|
||||
23.5f,
|
||||
34.5f,
|
||||
half,
|
||||
0f,
|
||||
half,
|
||||
0f);
|
||||
RuntimeEntityRecord record = CreateRecord(
|
||||
lifetime,
|
||||
objScale: 3f,
|
||||
physicsScale: 2f,
|
||||
objectDescriptionFlags: 0x8u | 0x20u | 0x200000u
|
||||
| 0x2000000u | 0x100000u | 0x400000u,
|
||||
position: acceptedPosition);
|
||||
const PhysicsStateFlags state = PhysicsStateFlags.Gravity
|
||||
| PhysicsStateFlags.EdgeSlide
|
||||
| PhysicsStateFlags.PathClipped;
|
||||
lifetime.Entities.SetFinalPhysicsState(record, state);
|
||||
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
||||
ImmutableArray<FlatCollisionSphere> spheres =
|
||||
[
|
||||
new(new Vector3(1f, 2f, 3f), 0.1f),
|
||||
new(new Vector3(4f, 5f, 6f), 0.2f),
|
||||
new(new Vector3(7f, 8f, 9f), 0.3f),
|
||||
];
|
||||
FlatSetupCollision setup = Setup(spheres, 0.25f, -0.5f);
|
||||
var portal = default(RuntimePortalPlacementAuthority);
|
||||
var input = new RuntimeSetPositionMoverPreparation(
|
||||
RuntimeSetPositionMoverSetup.Resolved(SetupId, setup),
|
||||
RuntimeSetPositionOperationKind.RemoteAuthoritative,
|
||||
GameTime: 42.25d,
|
||||
PhysicsPlacementClass.Corpse,
|
||||
PhysicsSetPositionFlags.Line | PhysicsSetPositionFlags.Scatter,
|
||||
Line: new Vector3(3f, 4f, 5f),
|
||||
ScatterRadiusX: 6f,
|
||||
ScatterRadiusY: 7f,
|
||||
ScatterAttempts: 8u,
|
||||
ShadowWorldOffsetX: 9f,
|
||||
ShadowWorldOffsetY: 10f,
|
||||
Portal: portal);
|
||||
|
||||
RuntimeSetPositionMoverPreparationStatus status = lifetime.Physics
|
||||
.SetPosition.PrepareMover(token, input, out var command);
|
||||
|
||||
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared, status);
|
||||
Assert.Equal(spheres, command.Physics.Spheres);
|
||||
Assert.Equal(2f, command.Physics.Scale);
|
||||
Assert.Equal(0.5f, command.Physics.StepUpHeight);
|
||||
Assert.Equal(-1f, command.Physics.StepDownHeight);
|
||||
Assert.Equal(Cell, command.Physics.CellId);
|
||||
Assert.Equal(new Vector3(12.5f, 23.5f, 34.5f),
|
||||
command.Physics.CellLocalPosition);
|
||||
Assert.Equal(new Vector3(
|
||||
12.5f + 9f,
|
||||
23.5f + 10f,
|
||||
34.5f), command.Physics.Position);
|
||||
Assert.Equal(new Quaternion(0f, half, 0f, half),
|
||||
command.Physics.Orientation);
|
||||
Assert.Equal(state, command.Physics.MoverPhysicsState);
|
||||
Assert.Equal(
|
||||
ObjectInfoState.IsPlayer
|
||||
| ObjectInfoState.IsPK
|
||||
| ObjectInfoState.IsPKLite
|
||||
| ObjectInfoState.IsImpenetrable
|
||||
| ObjectInfoState.CanBypassMoveRestrictions,
|
||||
command.Physics.MoverFlags);
|
||||
Assert.False(command.Physics.MoverFlags.HasFlag(
|
||||
ObjectInfoState.EdgeSlide));
|
||||
Assert.False(command.Physics.MoverFlags.HasFlag(
|
||||
ObjectInfoState.PathClipped));
|
||||
Assert.False(command.Physics.MoverFlags.HasFlag(
|
||||
ObjectInfoState.FreeRotate));
|
||||
Assert.False(command.Physics.MoverFlags.HasFlag(
|
||||
ObjectInfoState.Contact));
|
||||
Assert.False(command.Physics.MoverFlags.HasFlag(
|
||||
ObjectInfoState.OnWalkable));
|
||||
Assert.Equal(record.Key!.Value.LocalEntityId,
|
||||
command.Physics.MovingEntityId);
|
||||
Assert.Equal(PhysicsPlacementClass.Corpse,
|
||||
command.Physics.PlacementClass);
|
||||
Assert.Equal(input.Flags, command.Physics.Flags);
|
||||
Assert.Equal(input.Line, command.Physics.Line);
|
||||
Assert.Equal(6f, command.Physics.ScatterRadiusX);
|
||||
Assert.Equal(7f, command.Physics.ScatterRadiusY);
|
||||
Assert.Equal(8u, command.Physics.ScatterAttempts);
|
||||
Assert.Equal(42.25d, command.GameTime);
|
||||
Assert.Equal(9f, command.ShadowWorldOffsetX);
|
||||
Assert.Equal(10f, command.ShadowWorldOffsetY);
|
||||
Assert.Equal(portal, command.Portal);
|
||||
Assert.Equal(record.VelocityAuthorityVersion,
|
||||
command.ExpectedVelocityAuthorityVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LocalPortalKindAndAuthorityArePreservedWithoutInference()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimeEntityRecord record = CreateRecord(lifetime);
|
||||
var portal = new RuntimePortalPlacementAuthority(
|
||||
Present: true,
|
||||
RevealGeneration: 17,
|
||||
TeleportSequence: 3,
|
||||
new RuntimeWorldHostProjectionToken(17, Cell));
|
||||
RuntimeEntityPlacementToken token = lifetime.Physics.SetPosition
|
||||
.BeginAuthoredPlacement(
|
||||
record,
|
||||
record.PositionAuthorityVersion,
|
||||
RuntimeSetPositionOperationKind.LocalAuthoritative,
|
||||
portal);
|
||||
var input = Input(ResolvedEmptySetup()) with
|
||||
{
|
||||
Kind = RuntimeSetPositionOperationKind.LocalAuthoritative,
|
||||
Portal = portal,
|
||||
};
|
||||
|
||||
RuntimeSetPositionMoverPreparationStatus status = lifetime.Physics
|
||||
.SetPosition.PrepareMover(token, input, out var command);
|
||||
|
||||
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared, status);
|
||||
Assert.Equal(RuntimeSetPositionOperationKind.LocalAuthoritative,
|
||||
command.Kind);
|
||||
Assert.Equal(portal, command.Portal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnavailableSetupRetriesWithoutInventingDummyWhileAbsentAndEmptyResolve()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimeEntityRecord record = CreateRecord(lifetime, setupTableId: null);
|
||||
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
||||
|
||||
RuntimeSetPositionMoverPreparationStatus unavailable = lifetime.Physics
|
||||
.SetPosition.PrepareMover(
|
||||
token,
|
||||
Input(RuntimeSetPositionMoverSetup.Unavailable),
|
||||
out RuntimeSetPositionCommand unavailableCommand);
|
||||
|
||||
Assert.Equal(
|
||||
RuntimeSetPositionMoverPreparationStatus.RetrySetupUnavailable,
|
||||
unavailable);
|
||||
Assert.Equal(default, unavailableCommand);
|
||||
Assert.Equal(1, lifetime.Physics.CaptureOwnership()
|
||||
.AwaitingSetPositionPreparationCount);
|
||||
|
||||
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
|
||||
lifetime.Physics.SetPosition.PrepareMover(
|
||||
token,
|
||||
Input(RuntimeSetPositionMoverSetup.ResolvedAbsent),
|
||||
out RuntimeSetPositionCommand absent));
|
||||
Assert.Empty(absent.Physics.Spheres);
|
||||
|
||||
record = CreateRecord(lifetime, guid: 0x70002002u);
|
||||
token = Begin(lifetime, record);
|
||||
FlatSetupCollision authoredEmpty = Setup(
|
||||
ImmutableArray<FlatCollisionSphere>.Empty,
|
||||
0f,
|
||||
0f);
|
||||
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
|
||||
lifetime.Physics.SetPosition.PrepareMover(
|
||||
token,
|
||||
Input(RuntimeSetPositionMoverSetup.Resolved(
|
||||
SetupId,
|
||||
authoredEmpty)),
|
||||
out RuntimeSetPositionCommand empty));
|
||||
Assert.Empty(empty.Physics.Spheres);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KnownSetupMustResolveTheExactCanonicalDid()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimeEntityRecord record = CreateRecord(lifetime);
|
||||
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
||||
|
||||
Assert.Equal(
|
||||
RuntimeSetPositionMoverPreparationStatus.RetrySetupUnavailable,
|
||||
lifetime.Physics.SetPosition.PrepareMover(
|
||||
token,
|
||||
Input(RuntimeSetPositionMoverSetup.Unavailable),
|
||||
out _));
|
||||
Assert.Equal(
|
||||
RuntimeSetPositionMoverPreparationStatus.InvalidData,
|
||||
lifetime.Physics.SetPosition.PrepareMover(
|
||||
token,
|
||||
Input(RuntimeSetPositionMoverSetup.ResolvedAbsent),
|
||||
out _));
|
||||
Assert.Equal(
|
||||
RuntimeSetPositionMoverPreparationStatus.InvalidData,
|
||||
lifetime.Physics.SetPosition.PrepareMover(
|
||||
token,
|
||||
Input(RuntimeSetPositionMoverSetup.Resolved(
|
||||
SetupId + 1u,
|
||||
Setup(ImmutableArray<FlatCollisionSphere>.Empty, 0f, 0f))),
|
||||
out _));
|
||||
|
||||
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
|
||||
lifetime.Physics.SetPosition.PrepareMover(
|
||||
token,
|
||||
Input(ResolvedEmptySetup()),
|
||||
out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AbsentSetupRejectsInventedCollisionPayload()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimeEntityRecord record = CreateRecord(
|
||||
lifetime,
|
||||
setupTableId: null);
|
||||
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
||||
|
||||
Assert.Equal(
|
||||
RuntimeSetPositionMoverPreparationStatus.InvalidData,
|
||||
lifetime.Physics.SetPosition.PrepareMover(
|
||||
token,
|
||||
Input(RuntimeSetPositionMoverSetup.Resolved(
|
||||
SetupId,
|
||||
Setup(ImmutableArray<FlatCollisionSphere>.Empty, 0f, 0f))),
|
||||
out _));
|
||||
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
|
||||
lifetime.Physics.SetPosition.PrepareMover(
|
||||
token,
|
||||
Input(RuntimeSetPositionMoverSetup.ResolvedAbsent),
|
||||
out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthoredEmptySetupStillSuppliesScaledStepHeights()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimeEntityRecord record = CreateRecord(
|
||||
lifetime,
|
||||
objScale: 2f);
|
||||
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
||||
FlatSetupCollision authoredEmpty = Setup(
|
||||
ImmutableArray<FlatCollisionSphere>.Empty,
|
||||
0.25f,
|
||||
-0.5f);
|
||||
|
||||
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
|
||||
lifetime.Physics.SetPosition.PrepareMover(
|
||||
token,
|
||||
Input(RuntimeSetPositionMoverSetup.Resolved(
|
||||
SetupId,
|
||||
authoredEmpty)),
|
||||
out RuntimeSetPositionCommand command));
|
||||
Assert.Empty(command.Physics.Spheres);
|
||||
Assert.Equal(0.5f, command.Physics.StepUpHeight);
|
||||
Assert.Equal(-1f, command.Physics.StepDownHeight);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthoredTokenRejectsManualUnsealedSubmission()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimeEntityRecord record = CreateRecord(lifetime);
|
||||
AttachBody(lifetime, record);
|
||||
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
||||
RuntimeSetPositionCommand invented = new(
|
||||
new PhysicsSetPositionRequest(
|
||||
new Vector3(1f, 2f, 3f),
|
||||
Quaternion.Identity,
|
||||
Cell,
|
||||
new Vector3(1f, 2f, 3f),
|
||||
ImmutableArray<FlatCollisionSphere>.Empty,
|
||||
1f,
|
||||
0f,
|
||||
0f,
|
||||
record.FinalPhysicsState,
|
||||
ObjectInfoState.None,
|
||||
record.Key!.Value.LocalEntityId),
|
||||
RuntimeSetPositionOperationKind.RemoteAuthoritative,
|
||||
1d,
|
||||
record.VelocityAuthorityVersion);
|
||||
|
||||
RuntimeSetPositionOutcome outcome = lifetime.Physics.SetPosition
|
||||
.SubmitPreparedPlacement(token, invented);
|
||||
|
||||
Assert.Equal(RuntimeSetPositionStatus.Rejected, outcome.Status);
|
||||
Assert.Equal(0, lifetime.Physics.SetPosition.PendingProjectionCount);
|
||||
Assert.Equal(1, lifetime.Physics.CaptureOwnership()
|
||||
.AwaitingSetPositionPreparationCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RepreparationReplacesTheExactSealedCommand()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimeEntityRecord record = CreateRecord(lifetime);
|
||||
AttachBody(lifetime, record);
|
||||
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
||||
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
|
||||
lifetime.Physics.SetPosition.PrepareMover(
|
||||
token,
|
||||
Input(ResolvedEmptySetup()) with { GameTime = 1d },
|
||||
out RuntimeSetPositionCommand first));
|
||||
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
|
||||
lifetime.Physics.SetPosition.PrepareMover(
|
||||
token,
|
||||
Input(ResolvedEmptySetup()) with { GameTime = 2d },
|
||||
out RuntimeSetPositionCommand second));
|
||||
Assert.NotEqual(first, second);
|
||||
|
||||
RuntimeSetPositionOutcome stale = lifetime.Physics.SetPosition
|
||||
.SubmitPreparedPlacement(token, first);
|
||||
Assert.Equal(RuntimeSetPositionStatus.Rejected, stale.Status);
|
||||
|
||||
RuntimeSetPositionOutcome current = lifetime.Physics.SetPosition
|
||||
.SubmitPreparedPlacement(token, second);
|
||||
Assert.NotEqual(RuntimeSetPositionStatus.Rejected, current.Status);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, null, 1f)]
|
||||
[InlineData(0f, null, 0f)]
|
||||
[InlineData(-2f, null, -2f)]
|
||||
[InlineData(3f, 0f, 0f)]
|
||||
[InlineData(3f, -4f, -4f)]
|
||||
public void ScalePreservesAbsentZeroNegativeAndPhysicsPrecedence(
|
||||
float? objScale,
|
||||
float? physicsScale,
|
||||
float expected)
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimeEntityRecord record = CreateRecord(
|
||||
lifetime,
|
||||
objScale,
|
||||
physicsScale);
|
||||
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
||||
|
||||
RuntimeSetPositionMoverPreparationStatus status = lifetime.Physics
|
||||
.SetPosition.PrepareMover(
|
||||
token,
|
||||
Input(RuntimeSetPositionMoverSetup.Resolved(SetupId, Setup(
|
||||
[new FlatCollisionSphere(Vector3.Zero, 0.5f)],
|
||||
0.25f,
|
||||
-0.5f))),
|
||||
out RuntimeSetPositionCommand command);
|
||||
|
||||
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared, status);
|
||||
Assert.Equal(expected, command.Physics.Scale);
|
||||
Assert.Equal(0.25f * expected, command.Physics.StepUpHeight);
|
||||
Assert.Equal(-0.5f * expected, command.Physics.StepDownHeight);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(float.NaN)]
|
||||
[InlineData(float.PositiveInfinity)]
|
||||
[InlineData(float.NegativeInfinity)]
|
||||
public void NonFiniteScaleRejectsWhenAuthoredSphereConsumesIt(float scale)
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimeEntityRecord record = CreateRecord(lifetime, scale, null);
|
||||
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
||||
|
||||
RuntimeSetPositionMoverPreparationStatus status = lifetime.Physics
|
||||
.SetPosition.PrepareMover(
|
||||
token,
|
||||
Input(RuntimeSetPositionMoverSetup.Resolved(
|
||||
SetupId,
|
||||
Setup(
|
||||
[new FlatCollisionSphere(Vector3.Zero, 0.5f)],
|
||||
0f,
|
||||
0f))),
|
||||
out RuntimeSetPositionCommand command);
|
||||
|
||||
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.InvalidData, status);
|
||||
Assert.Equal(default, command);
|
||||
Assert.Equal(1, lifetime.Physics.CaptureOwnership()
|
||||
.AwaitingSetPositionPreparationCount);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(float.NaN)]
|
||||
[InlineData(float.PositiveInfinity)]
|
||||
[InlineData(float.NegativeInfinity)]
|
||||
public void NonFiniteScaleIsNotConsumedByResolvedAbsentDummy(float scale)
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimeEntityRecord record = CreateRecord(
|
||||
lifetime,
|
||||
objScale: scale,
|
||||
setupTableId: null);
|
||||
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
||||
|
||||
RuntimeSetPositionMoverPreparationStatus status = lifetime.Physics
|
||||
.SetPosition.PrepareMover(
|
||||
token,
|
||||
Input(RuntimeSetPositionMoverSetup.ResolvedAbsent),
|
||||
out RuntimeSetPositionCommand command);
|
||||
|
||||
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared, status);
|
||||
Assert.Equal(scale, command.Physics.Scale);
|
||||
Assert.Equal(0f, command.Physics.StepUpHeight);
|
||||
Assert.Equal(0f, command.Physics.StepDownHeight);
|
||||
Assert.Empty(command.Physics.Spheres);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreparationDoesNotMutateCanonicalOrPresentationState()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimeEntityRecord record = CreateRecord(lifetime);
|
||||
var body = new PhysicsBody
|
||||
{
|
||||
Position = new Vector3(1f, 2f, 3f),
|
||||
Orientation = Quaternion.Identity,
|
||||
State = PhysicsStateFlags.Gravity,
|
||||
InWorld = true,
|
||||
};
|
||||
body.SnapToCell(0xA8B40001u, body.Position, body.Position);
|
||||
lifetime.Entities.SetPhysicsBody(record, body);
|
||||
lifetime.Entities.SetFullCell(record, 0xA8B40001u, 0xA8B4FFFFu);
|
||||
Vector3 priorPosition = body.Position;
|
||||
uint priorCell = record.FullCellId;
|
||||
ulong priorSpatial = record.SpatialAuthorityVersion;
|
||||
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
||||
|
||||
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
|
||||
lifetime.Physics.SetPosition.PrepareMover(
|
||||
token,
|
||||
Input(ResolvedEmptySetup()),
|
||||
out _));
|
||||
|
||||
Assert.Same(body, record.PhysicsBody);
|
||||
Assert.Equal(priorPosition, body.Position);
|
||||
Assert.Equal(priorCell, record.FullCellId);
|
||||
Assert.Equal(priorSpatial, record.SpatialAuthorityVersion);
|
||||
Assert.True(body.InWorld);
|
||||
Assert.Equal(0, lifetime.Physics.SetPosition.PendingProjectionCount);
|
||||
Assert.Equal(1, lifetime.Physics.CaptureOwnership()
|
||||
.AwaitingSetPositionPreparationCount);
|
||||
Assert.False(lifetime.Physics.SetPosition
|
||||
.TryGetPreparedMoverSphereCount(record, out _));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(AuthorityReplacement.Position)]
|
||||
[InlineData(AuthorityReplacement.Velocity)]
|
||||
[InlineData(AuthorityReplacement.Vector)]
|
||||
[InlineData(AuthorityReplacement.State)]
|
||||
[InlineData(AuthorityReplacement.ObjectDescription)]
|
||||
[InlineData(AuthorityReplacement.Create)]
|
||||
public void AnyAuthorityReplacementRejectsPreparedSubmissionWithoutMutation(
|
||||
AuthorityReplacement replacement)
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimeEntityRecord record = CreateRecord(lifetime);
|
||||
var body = new PhysicsBody
|
||||
{
|
||||
Position = new Vector3(1f, 2f, 3f),
|
||||
Orientation = Quaternion.Identity,
|
||||
State = record.FinalPhysicsState,
|
||||
InWorld = true,
|
||||
};
|
||||
body.SnapToCell(0xA8B40001u, body.Position, body.Position);
|
||||
lifetime.Entities.SetPhysicsBody(record, body);
|
||||
lifetime.Entities.SetFullCell(record, 0xA8B40001u, 0xA8B4FFFFu);
|
||||
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
||||
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared,
|
||||
lifetime.Physics.SetPosition.PrepareMover(
|
||||
token,
|
||||
Input(ResolvedEmptySetup()),
|
||||
out RuntimeSetPositionCommand command));
|
||||
Vector3 priorPosition = body.Position;
|
||||
uint priorCell = record.FullCellId;
|
||||
|
||||
ReplaceAuthority(lifetime, record, replacement);
|
||||
RuntimeSetPositionOutcome outcome = lifetime.Physics.SetPosition
|
||||
.SubmitPreparedPlacement(token, command);
|
||||
|
||||
Assert.Equal(RuntimeSetPositionStatus.Rejected, outcome.Status);
|
||||
Assert.Equal(priorPosition, body.Position);
|
||||
Assert.Equal(priorCell, record.FullCellId);
|
||||
Assert.Equal(0, lifetime.Physics.SetPosition.PendingProjectionCount);
|
||||
Assert.False(lifetime.Physics.SetPosition
|
||||
.TryGetPreparedMoverSphereCount(record, out _));
|
||||
|
||||
RuntimeEntityPlacementToken replacementToken = Begin(lifetime, record);
|
||||
Assert.True(replacementToken.IsValid);
|
||||
Assert.NotEqual(token, replacementToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThirdAuthoredSphereIsPreservedButNotConsumedByCoreValidation()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimeEntityRecord record = CreateRecord(lifetime);
|
||||
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
||||
FlatSetupCollision setup = Setup(
|
||||
[
|
||||
new FlatCollisionSphere(Vector3.Zero, 0.1f),
|
||||
new FlatCollisionSphere(Vector3.UnitZ, 0.2f),
|
||||
new FlatCollisionSphere(
|
||||
new Vector3(float.NaN, 0f, 0f),
|
||||
0.3f),
|
||||
], 0f, 0f);
|
||||
|
||||
RuntimeSetPositionMoverPreparationStatus status = lifetime.Physics
|
||||
.SetPosition.PrepareMover(
|
||||
token,
|
||||
Input(RuntimeSetPositionMoverSetup.Resolved(SetupId, setup)),
|
||||
out _);
|
||||
|
||||
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared, status);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SessionReplacementInvalidatesExactTokenAndClearsPreparationAuthority()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimeEntityRecord record = CreateRecord(lifetime);
|
||||
RuntimeEntityPlacementToken token = Begin(lifetime, record);
|
||||
|
||||
_ = lifetime.BeginSessionClear();
|
||||
RuntimeSetPositionMoverPreparationStatus status = lifetime.Physics
|
||||
.SetPosition.PrepareMover(
|
||||
token,
|
||||
Input(RuntimeSetPositionMoverSetup.ResolvedAbsent),
|
||||
out RuntimeSetPositionCommand command);
|
||||
|
||||
Assert.Equal(
|
||||
RuntimeSetPositionMoverPreparationStatus.RejectedAuthority,
|
||||
status);
|
||||
Assert.Equal(default, command);
|
||||
RuntimeSetPositionOwnershipSnapshot ownership = lifetime.Physics
|
||||
.SetPosition.CaptureOwnership();
|
||||
Assert.Equal(0, ownership.MoverPreparationAuthorityCount);
|
||||
Assert.Equal(0, ownership.ActiveOperationCount);
|
||||
}
|
||||
|
||||
private static RuntimeEntityPlacementToken Begin(
|
||||
RuntimeEntityObjectLifetime lifetime,
|
||||
RuntimeEntityRecord record) => lifetime.Physics.SetPosition
|
||||
.BeginAuthoredPlacement(
|
||||
record,
|
||||
record.PositionAuthorityVersion,
|
||||
RuntimeSetPositionOperationKind.RemoteAuthoritative);
|
||||
|
||||
private static RuntimeSetPositionMoverPreparation Input(
|
||||
RuntimeSetPositionMoverSetup setup) => new(
|
||||
setup,
|
||||
RuntimeSetPositionOperationKind.RemoteAuthoritative,
|
||||
GameTime: 1d,
|
||||
PhysicsPlacementClass.Ordinary,
|
||||
PhysicsSetPositionFlags.Placement);
|
||||
|
||||
private static RuntimeSetPositionMoverSetup ResolvedEmptySetup() =>
|
||||
RuntimeSetPositionMoverSetup.Resolved(
|
||||
SetupId,
|
||||
Setup(ImmutableArray<FlatCollisionSphere>.Empty, 0f, 0f));
|
||||
|
||||
private static FlatSetupCollision Setup(
|
||||
ImmutableArray<FlatCollisionSphere> spheres,
|
||||
float stepUp,
|
||||
float stepDown) => new(
|
||||
ImmutableArray<FlatCollisionCylinder>.Empty,
|
||||
spheres,
|
||||
height: 0f,
|
||||
radius: 0f,
|
||||
stepUp,
|
||||
stepDown);
|
||||
|
||||
private static RuntimeEntityRecord CreateRecord(
|
||||
RuntimeEntityObjectLifetime lifetime,
|
||||
float? objScale = null,
|
||||
float? physicsScale = null,
|
||||
uint? objectDescriptionFlags = null,
|
||||
uint guid = 0x70002001u,
|
||||
uint? setupTableId = SetupId,
|
||||
CreateObject.ServerPosition? position = null)
|
||||
{
|
||||
CreateObject.ServerPosition acceptedPosition = position ?? new(
|
||||
Cell,
|
||||
1f,
|
||||
2f,
|
||||
3f,
|
||||
1f,
|
||||
0f,
|
||||
0f,
|
||||
0f);
|
||||
var timestamps = new PhysicsTimestamps(
|
||||
Position: 1,
|
||||
Movement: 1,
|
||||
State: 1,
|
||||
Vector: 1,
|
||||
Teleport: 0,
|
||||
ServerControlledMove: 1,
|
||||
ForcePosition: 0,
|
||||
ObjDesc: 1,
|
||||
Instance: 1);
|
||||
var physics = new PhysicsSpawnData(
|
||||
RawState: (uint)PhysicsStateFlags.Gravity,
|
||||
Position: acceptedPosition,
|
||||
Movement: null,
|
||||
AnimationFrame: null,
|
||||
SetupTableId: setupTableId,
|
||||
MotionTableId: 0x09000001u,
|
||||
SoundTableId: null,
|
||||
PhysicsScriptTableId: null,
|
||||
Parent: null,
|
||||
Children: null,
|
||||
Scale: physicsScale,
|
||||
Friction: null,
|
||||
Elasticity: null,
|
||||
Translucency: null,
|
||||
Velocity: null,
|
||||
Acceleration: null,
|
||||
AngularVelocity: null,
|
||||
DefaultScriptType: null,
|
||||
DefaultScriptIntensity: null,
|
||||
Timestamps: timestamps);
|
||||
var spawn = new WorldSession.EntitySpawn(
|
||||
Guid: guid,
|
||||
Position: acceptedPosition,
|
||||
SetupTableId: setupTableId,
|
||||
AnimPartChanges: Array.Empty<CreateObject.AnimPartChange>(),
|
||||
TextureChanges: Array.Empty<CreateObject.TextureChange>(),
|
||||
SubPalettes: Array.Empty<CreateObject.SubPaletteSwap>(),
|
||||
BasePaletteId: null,
|
||||
ObjScale: objScale,
|
||||
Name: "mover-preparation-fixture",
|
||||
ItemType: null,
|
||||
MotionState: null,
|
||||
MotionTableId: 0x09000001u,
|
||||
PhysicsState: (uint)PhysicsStateFlags.Gravity,
|
||||
ObjectDescriptionFlags: objectDescriptionFlags,
|
||||
InstanceSequence: 1,
|
||||
MovementSequence: 1,
|
||||
ServerControlSequence: 1,
|
||||
PositionSequence: 1,
|
||||
Physics: physics);
|
||||
return lifetime.RegisterEntity(spawn).Canonical!;
|
||||
}
|
||||
|
||||
private static void AttachBody(
|
||||
RuntimeEntityObjectLifetime lifetime,
|
||||
RuntimeEntityRecord record)
|
||||
{
|
||||
var body = new PhysicsBody
|
||||
{
|
||||
Position = new Vector3(1f, 2f, 3f),
|
||||
Orientation = Quaternion.Identity,
|
||||
State = record.FinalPhysicsState,
|
||||
InWorld = true,
|
||||
};
|
||||
body.SnapToCell(Cell, body.Position, body.Position);
|
||||
lifetime.Entities.SetPhysicsBody(record, body);
|
||||
lifetime.Entities.SetFullCell(record, Cell, Cell & 0xFFFF0000u);
|
||||
}
|
||||
|
||||
private static void ReplaceAuthority(
|
||||
RuntimeEntityObjectLifetime lifetime,
|
||||
RuntimeEntityRecord record,
|
||||
AuthorityReplacement replacement)
|
||||
{
|
||||
switch (replacement)
|
||||
{
|
||||
case AuthorityReplacement.Position:
|
||||
lifetime.Entities.AdvancePositionAuthority(record);
|
||||
break;
|
||||
case AuthorityReplacement.Velocity:
|
||||
lifetime.Entities.AdvanceMovementAuthority(record);
|
||||
break;
|
||||
case AuthorityReplacement.Vector:
|
||||
lifetime.Entities.AdvanceVectorAuthority(record);
|
||||
break;
|
||||
case AuthorityReplacement.State:
|
||||
_ = lifetime.Entities.ApplyRawPhysicsState(
|
||||
record,
|
||||
(uint)PhysicsStateFlags.Frozen);
|
||||
break;
|
||||
case AuthorityReplacement.ObjectDescription:
|
||||
lifetime.Entities.AdvanceObjDescAuthority(record);
|
||||
break;
|
||||
case AuthorityReplacement.Create:
|
||||
lifetime.Entities.AdvanceCreateAuthority(record);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(replacement));
|
||||
}
|
||||
}
|
||||
|
||||
public enum AuthorityReplacement
|
||||
{
|
||||
Position,
|
||||
Velocity,
|
||||
Vector,
|
||||
State,
|
||||
ObjectDescription,
|
||||
Create,
|
||||
}
|
||||
}
|
||||
|
|
@ -57,7 +57,17 @@ public sealed class RuntimeSetPositionStateTests
|
|||
record.PositionAuthorityVersion,
|
||||
RuntimeSetPositionOperationKind.LocalAuthoritative,
|
||||
invalidPortal);
|
||||
Assert.True(portalToken.IsValid);
|
||||
Assert.False(portalToken.IsValid);
|
||||
|
||||
var matchingPortal = invalidPortal with
|
||||
{
|
||||
Projection = new RuntimeWorldHostProjectionToken(7, SourceCell),
|
||||
};
|
||||
Assert.True(lifetime.Physics.SetPosition.BeginAuthoredPlacement(
|
||||
record,
|
||||
record.PositionAuthorityVersion,
|
||||
RuntimeSetPositionOperationKind.LocalAuthoritative,
|
||||
matchingPortal).IsValid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -445,6 +455,146 @@ public sealed class RuntimeSetPositionStateTests
|
|||
placed.Token));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LegacyDeferredWakeCannotBypassNewAuthoredSealAfterAuthorityStales()
|
||||
{
|
||||
PhysicsEngine engine = FlatEngine(SourceLandblock, 0f);
|
||||
using var lifetime = new RuntimeEntityObjectLifetime(engine);
|
||||
RuntimeEntityRecord record = CreateRecord(lifetime, 0x7000112Bu, 1);
|
||||
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
||||
var observer = new PlacementObserver();
|
||||
using IDisposable subscription = lifetime.Events.SubscribePlacement(
|
||||
observer);
|
||||
RuntimeSetPositionOutcome deferred = lifetime.Physics.SetPosition.Apply(
|
||||
record,
|
||||
record.PositionAuthorityVersion,
|
||||
Command(CrossLandblockRequest()));
|
||||
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
|
||||
deferred.Projection));
|
||||
|
||||
// The direct legacy command was valid when parked, but any accepted
|
||||
// authority replacement requires a new authored mover preparation.
|
||||
lifetime.Entities.AdvanceObjDescAuthority(record);
|
||||
AddFlatLandblock(engine, DestinationLandblock, 192f);
|
||||
lifetime.Physics.SetPosition.CommitCollisionGeneration(
|
||||
DestinationLandblock,
|
||||
generation: 1,
|
||||
ready: true);
|
||||
|
||||
Assert.False(body.InWorld);
|
||||
Assert.True(lifetime.Physics.SetPosition.TryGetAwaitingPreparationToken(
|
||||
record,
|
||||
out RuntimeEntityPlacementToken token));
|
||||
Assert.Equal(RuntimeEntityPlacementPreparationKind.LegacyDirect,
|
||||
token.PreparationKind);
|
||||
RuntimeSetPositionOutcome bypass = lifetime.Physics.SetPosition
|
||||
.SubmitPreparedPlacement(token, Command(CrossLandblockRequest()));
|
||||
Assert.Equal(RuntimeSetPositionStatus.Rejected, bypass.Status);
|
||||
Assert.False(body.InWorld);
|
||||
|
||||
RuntimeSetPositionCommand prepared = PrepareAuthoredCommand(
|
||||
lifetime,
|
||||
token,
|
||||
[new FlatCollisionSphere(Vector3.Zero, 0.4f)]);
|
||||
RuntimeSetPositionOutcome resumed = lifetime.Physics.SetPosition
|
||||
.SubmitPreparedPlacement(token, prepared);
|
||||
Assert.Equal(PhysicsResidenceDisposition.Committed, resumed.Residence);
|
||||
RuntimePlacementProjectionSnapshot placed = observer.Deltas[^1].Placement;
|
||||
Assert.Equal(RuntimePlacementProjectionKind.Place, placed.Kind);
|
||||
Assert.True(body.InWorld);
|
||||
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
|
||||
placed.Token));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(DeferredPhysicsStateMutation.ChildNoDraw)]
|
||||
[InlineData(DeferredPhysicsStateMutation.StopMissile)]
|
||||
[InlineData(DeferredPhysicsStateMutation.DirectFinalState)]
|
||||
public void DeferredWakeRequiresRepreparationAfterFinalPhysicsStateMutation(
|
||||
DeferredPhysicsStateMutation mutation)
|
||||
{
|
||||
PhysicsEngine engine = FlatEngine(SourceLandblock, 0f);
|
||||
using var lifetime = new RuntimeEntityObjectLifetime(engine);
|
||||
RuntimeEntityRecord record = CreateRecord(lifetime, 0x7000112Cu, 1);
|
||||
PhysicsStateFlags initial = PhysicsStateFlags.Gravity;
|
||||
if (mutation is DeferredPhysicsStateMutation.StopMissile)
|
||||
{
|
||||
initial |= PhysicsStateFlags.Missile
|
||||
| PhysicsStateFlags.AlignPath
|
||||
| PhysicsStateFlags.PathClipped;
|
||||
}
|
||||
PhysicsBody body = AttachBody(lifetime, record, SourceCell, initial);
|
||||
RuntimeSetPositionOutcome deferred = lifetime.Physics.SetPosition.Apply(
|
||||
record,
|
||||
record.PositionAuthorityVersion,
|
||||
Command(CrossLandblockRequest()));
|
||||
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
|
||||
deferred.Projection));
|
||||
ulong priorMutation = record.PhysicsStateMutationVersion;
|
||||
|
||||
switch (mutation)
|
||||
{
|
||||
case DeferredPhysicsStateMutation.ChildNoDraw:
|
||||
lifetime.Entities.SetChildNoDraw(record, noDraw: true);
|
||||
break;
|
||||
case DeferredPhysicsStateMutation.StopMissile:
|
||||
Assert.True(lifetime.Entities.StopMissileAfterCollision(
|
||||
record,
|
||||
requireCurrentMissile: true));
|
||||
break;
|
||||
case DeferredPhysicsStateMutation.DirectFinalState:
|
||||
lifetime.Entities.SetFinalPhysicsState(
|
||||
record,
|
||||
record.FinalPhysicsState | PhysicsStateFlags.Frozen);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(mutation));
|
||||
}
|
||||
Assert.Equal(priorMutation + 1UL,
|
||||
record.PhysicsStateMutationVersion);
|
||||
if (mutation is DeferredPhysicsStateMutation.DirectFinalState)
|
||||
Assert.NotEqual(record.FinalPhysicsState, body.State);
|
||||
else
|
||||
Assert.Equal(record.FinalPhysicsState, body.State);
|
||||
|
||||
AddFlatLandblock(engine, DestinationLandblock, 192f);
|
||||
lifetime.Physics.SetPosition.CommitCollisionGeneration(
|
||||
DestinationLandblock,
|
||||
generation: 1,
|
||||
ready: true);
|
||||
|
||||
Assert.False(body.InWorld);
|
||||
Assert.True(lifetime.Physics.SetPosition.TryGetAwaitingPreparationToken(
|
||||
record,
|
||||
out _));
|
||||
Assert.Equal(1, lifetime.Physics.CaptureOwnership()
|
||||
.AwaitingSetPositionPreparationCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DirectFinalPhysicsStateMutationAdvancesOnlyOnChangeAndLeavesBodyToItsOwner()
|
||||
{
|
||||
using var lifetime = new RuntimeEntityObjectLifetime();
|
||||
RuntimeEntityRecord record = CreateRecord(lifetime, 0x7000112Du, 1);
|
||||
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
|
||||
PhysicsStateFlags original = record.FinalPhysicsState;
|
||||
ulong version = record.PhysicsStateMutationVersion;
|
||||
|
||||
lifetime.Entities.SetFinalPhysicsState(record, original);
|
||||
Assert.Equal(version, record.PhysicsStateMutationVersion);
|
||||
Assert.Equal(original, body.State);
|
||||
|
||||
PhysicsStateFlags changed = original | PhysicsStateFlags.Frozen;
|
||||
lifetime.Entities.SetFinalPhysicsState(record, changed);
|
||||
Assert.Equal(version + 1UL, record.PhysicsStateMutationVersion);
|
||||
Assert.Equal(changed, record.FinalPhysicsState);
|
||||
Assert.Equal(original, body.State);
|
||||
|
||||
lifetime.Entities.SetFinalPhysicsState(record, changed);
|
||||
Assert.Equal(version + 1UL, record.PhysicsStateMutationVersion);
|
||||
Assert.Equal(original, body.State);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RejectedPreparationKeepsValidatedMoverAndExactTokenRetryable()
|
||||
{
|
||||
|
|
@ -1943,15 +2093,12 @@ public sealed class RuntimeSetPositionStateTests
|
|||
new FlatCollisionSphere(new Vector3(0.4f, 0f, 0f), 0.35f),
|
||||
new FlatCollisionSphere(new Vector3(-0.3f, 0f, 0.5f), 0.2f),
|
||||
];
|
||||
RuntimeSetPositionCommand preparedCommand = PrepareAuthoredCommand(
|
||||
lifetime,
|
||||
token,
|
||||
authored);
|
||||
RuntimeSetPositionOutcome supplied = lifetime.Physics.SetPosition
|
||||
.SubmitPreparedPlacement(
|
||||
token,
|
||||
Command(Request(
|
||||
landblock | 0x0101u,
|
||||
new Vector3(999f, 999f, 999f)) with
|
||||
{
|
||||
Spheres = authored,
|
||||
}));
|
||||
.SubmitPreparedPlacement(token, preparedCommand);
|
||||
|
||||
Assert.Equal(RuntimeSetPositionStatus.DeferredCell, supplied.Status);
|
||||
Assert.Equal(0, lifetime.Physics.CaptureOwnership()
|
||||
|
|
@ -1998,21 +2145,29 @@ public sealed class RuntimeSetPositionStateTests
|
|||
Assert.Single(observer.Deltas).Placement.Token));
|
||||
observer.Deltas.Clear();
|
||||
|
||||
RuntimeSetPositionCommand malformed = Command(Request(
|
||||
exactCell,
|
||||
new Vector3(10f, 18f, 7f)) with
|
||||
{
|
||||
Spheres =
|
||||
[
|
||||
new FlatCollisionSphere(
|
||||
new Vector3(float.NaN, 0f, 0f),
|
||||
0.4f),
|
||||
],
|
||||
});
|
||||
RuntimeSetPositionOutcome acceptedMalformed = lifetime.Physics
|
||||
.SetPosition.SubmitPreparedPlacement(token, malformed);
|
||||
Assert.Equal(RuntimeSetPositionStatus.DeferredCell,
|
||||
acceptedMalformed.Status);
|
||||
var malformedSetup = new FlatSetupCollision(
|
||||
ImmutableArray<FlatCollisionCylinder>.Empty,
|
||||
[new FlatCollisionSphere(
|
||||
new Vector3(float.NaN, 0f, 0f),
|
||||
0.4f)],
|
||||
height: 0f,
|
||||
radius: 0f,
|
||||
stepUpHeight: 0.4f,
|
||||
stepDownHeight: 0.4f);
|
||||
Assert.Equal(
|
||||
RuntimeSetPositionMoverPreparationStatus.InvalidData,
|
||||
lifetime.Physics.SetPosition.PrepareMover(
|
||||
token,
|
||||
new RuntimeSetPositionMoverPreparation(
|
||||
RuntimeSetPositionMoverSetup.Resolved(
|
||||
0x02000001u,
|
||||
malformedSetup),
|
||||
RuntimeSetPositionOperationKind.RemoteAuthoritative,
|
||||
GameTime: 10d,
|
||||
PhysicsPlacementClass.Ordinary,
|
||||
PhysicsSetPositionFlags.Placement
|
||||
| PhysicsSetPositionFlags.Slide),
|
||||
out _));
|
||||
|
||||
RuntimeCollisionAdmission first = lifetime.Physics
|
||||
.BeginCollisionAdmission(landblock);
|
||||
|
|
@ -2032,44 +2187,25 @@ public sealed class RuntimeSetPositionStateTests
|
|||
RuntimeSetPositionOwnershipSnapshot invalidWake = lifetime.Physics
|
||||
.SetPosition.CaptureOwnership();
|
||||
Assert.Equal(1, invalidWake.AwaitingPreparationCount);
|
||||
Assert.Equal(1, invalidWake.DeferredBucketCount);
|
||||
Assert.Equal(0, invalidWake.DeferredBucketCount);
|
||||
Assert.Equal(1, invalidWake.LostDeadlineCount);
|
||||
Assert.Empty(observer.Deltas);
|
||||
Assert.False(lifetime.Physics.SetPosition.TryGetPreparedMoverSphereCount(
|
||||
record,
|
||||
out _));
|
||||
|
||||
RuntimeSetPositionCommand corrected = Command(Request(
|
||||
exactCell,
|
||||
new Vector3(10f, 18f, 7f)) with
|
||||
{
|
||||
Spheres =
|
||||
[
|
||||
new FlatCollisionSphere(Vector3.Zero, 0.4f),
|
||||
],
|
||||
});
|
||||
RuntimeSetPositionCommand corrected = PrepareAuthoredCommand(
|
||||
lifetime,
|
||||
token,
|
||||
[new FlatCollisionSphere(Vector3.Zero, 0.4f)]);
|
||||
RuntimeSetPositionOutcome correctedPending = lifetime.Physics
|
||||
.SetPosition.SubmitPreparedPlacement(token, corrected);
|
||||
Assert.Equal(RuntimeSetPositionStatus.DeferredCell,
|
||||
correctedPending.Status);
|
||||
Assert.Equal(PhysicsResidenceDisposition.DeferredCell,
|
||||
Assert.Equal(PhysicsResidenceDisposition.Committed,
|
||||
correctedPending.Residence);
|
||||
Assert.Equal(exactCell, correctedPending.ExactCellId);
|
||||
|
||||
RuntimeCollisionAdmission second = lifetime.Physics
|
||||
.BeginCollisionAdmission(landblock);
|
||||
using PreparedLandblockCollisionGeneration preparedSecond = lifetime
|
||||
.Physics.PrepareCollisionGeneration(second);
|
||||
lifetime.Physics.StageCollisionAssets(
|
||||
second,
|
||||
preparedSecond,
|
||||
CollisionAssets(landblock));
|
||||
AddSyntheticCell(preparedSecond.DataCache, exactCell);
|
||||
Assert.True(CommitPrepared(
|
||||
lifetime.Physics,
|
||||
second,
|
||||
preparedSecond).Committed);
|
||||
|
||||
RuntimePlacementProjectionSnapshot placed = Assert.Single(observer.Deltas)
|
||||
.Placement;
|
||||
Assert.Equal(RuntimePlacementProjectionKind.Place, placed.Kind);
|
||||
|
|
@ -2178,6 +2314,34 @@ public sealed class RuntimeSetPositionStateTests
|
|||
ShadowWorldOffsetX: request.CellId == SourceCell ? 0f : 192f,
|
||||
ShadowWorldOffsetY: 0f);
|
||||
|
||||
private static RuntimeSetPositionCommand PrepareAuthoredCommand(
|
||||
RuntimeEntityObjectLifetime lifetime,
|
||||
RuntimeEntityPlacementToken token,
|
||||
ImmutableArray<FlatCollisionSphere> spheres)
|
||||
{
|
||||
var setup = new FlatSetupCollision(
|
||||
ImmutableArray<FlatCollisionCylinder>.Empty,
|
||||
spheres,
|
||||
height: 0f,
|
||||
radius: 0f,
|
||||
stepUpHeight: 0.4f,
|
||||
stepDownHeight: 0.4f);
|
||||
var preparation = new RuntimeSetPositionMoverPreparation(
|
||||
RuntimeSetPositionMoverSetup.Resolved(0x02000001u, setup),
|
||||
RuntimeSetPositionOperationKind.RemoteAuthoritative,
|
||||
GameTime: 10d,
|
||||
PhysicsPlacementClass.Ordinary,
|
||||
PhysicsSetPositionFlags.Placement
|
||||
| PhysicsSetPositionFlags.Slide);
|
||||
Assert.Equal(
|
||||
RuntimeSetPositionMoverPreparationStatus.Prepared,
|
||||
lifetime.Physics.SetPosition.PrepareMover(
|
||||
token,
|
||||
preparation,
|
||||
out RuntimeSetPositionCommand command));
|
||||
return command;
|
||||
}
|
||||
|
||||
private static PhysicsSetPositionRequest CrossLandblockRequest() =>
|
||||
Request(
|
||||
SourceCell,
|
||||
|
|
@ -2480,6 +2644,13 @@ public sealed class RuntimeSetPositionStateTests
|
|||
internal void JumpUtc(TimeSpan duration) => _utcNow += duration;
|
||||
}
|
||||
|
||||
public enum DeferredPhysicsStateMutation
|
||||
{
|
||||
ChildNoDraw,
|
||||
StopMissile,
|
||||
DirectFinalState,
|
||||
}
|
||||
|
||||
private enum CancellationChannel : uint
|
||||
{
|
||||
Position,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue