feat(runtime): bridge executor completion to the placement stream
Cutover slice C0 (docs/plans/2026-08-02-placement-cutover.md): the seam work that lets C3 flip hosts onto a complete receipt stream instead of growing one mid-cutover. The executor's Released exit now publishes an acknowledge-only ExecutorCompleted receipt through the one placement projection stream — registered before observer dispatch, correlated to the full execution receipt, reaped exactly once on acknowledgement/ discard/session-clear, and counted in the convergence ledger. All three production placement sinks acknowledge-and-ignore the new kind via early returns proven behavior-preserving for every existing kind; without them the first such receipt at cutover would permanently wedge the exact-head FIFO behind sinks that return false. Provably inert today: the publisher has no production caller. Execute's live inputs now derive from Runtime's own owners bound at GameRuntime construction: UsePositionFromServer is retail's exact autonomy_level != 2 (CommandInterpreter::UsePositionFromServer 0x006B3B40, startup-only knob), and PlayerDistance uses the live movement controller's position with a null-safe fallback to the caller struct — never a fabricated origin. TryPrepareAndSubmitAuthoredPlacement chains the prepared-collision Setup read through PrepareMover to submission with zero validation-semantics changes. TryCommitParent and CommitWithdrawal gain the sibling cancellation flow (residence + ordinary placement family); TryCommitParent deliberately omits LeaveWorld — retail's set_parent performs its single gated leave_world (0x00515A90) and a second would have no counterpart. Not fully dormant: the two cancellation fixes change Runtime paths production already calls (today as no-op-adjacent hardening, since nothing upstream begins a residence yet); everything else is reachable only by tests. Reviewed: retail-conformance PASS + architecture/ adversarial PASS after one fix round (sink wedge, completion-receipt lifecycle, null-controller distance). Runtime 921/921; complete Release solution 10,716 passed / 4 intentional skips. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
27e05b99e4
commit
67f63e85e5
14 changed files with 1639 additions and 11 deletions
|
|
@ -4223,6 +4223,480 @@ public sealed class RuntimeInitialCreateContinuationExecutorTests
|
|||
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership().ActiveOperationCount);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// F. C0-1: executor-completion bridge / C0-2: live-input binding
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void ExecutorCompletion_PublishesOnTheSamePlacementStreamCorrelatedWithTheFullReceipt()
|
||||
{
|
||||
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
|
||||
Bind(lifetime, 400UL);
|
||||
const uint guid = 0x70024000u;
|
||||
RuntimeEntityRecord canonical = lifetime
|
||||
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
|
||||
.Canonical!;
|
||||
Assert.True(lifetime.TryGetInitialCreateResidence(
|
||||
canonical,
|
||||
out RuntimeInitialCreateResidenceLease lease));
|
||||
AttachDormantBody(lifetime, canonical);
|
||||
CompleteInitialPlacement(lifetime, lease);
|
||||
|
||||
var observed = new List<RuntimePlacementProjectionSnapshot>();
|
||||
using IDisposable subscription = lifetime.Events.SubscribePlacement(
|
||||
new PlacementObserver(delta => observed.Add(delta.Placement)));
|
||||
|
||||
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
|
||||
lifetime, canonical, lease.Token, NoContact);
|
||||
|
||||
RuntimePlacementProjectionSnapshot completion = Assert.Single(observed);
|
||||
Assert.Equal(
|
||||
RuntimePlacementProjectionKind.ExecutorCompleted,
|
||||
completion.Kind);
|
||||
Assert.Equal(canonical.Key, completion.Token.Entity);
|
||||
Assert.True(lifetime.InitialCreateExecution.TryGetCompletionReceipt(
|
||||
completion.Token,
|
||||
out RuntimeInitialCreateExecutionReceipt correlated));
|
||||
Assert.Equal(receipt, correlated);
|
||||
|
||||
// Acknowledge-only, exact-head, same as every other Kind.
|
||||
Assert.Equal(1, lifetime.Physics.SetPosition.PendingProjectionCount);
|
||||
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
|
||||
completion.Token));
|
||||
Assert.Equal(0, lifetime.Physics.SetPosition.PendingProjectionCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExecutorCompletion_ObservedOnlyAfterAnyContinuationPlacementInFifoOrder()
|
||||
{
|
||||
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
|
||||
Bind(lifetime, 401UL);
|
||||
const uint guid = 0x70024001u;
|
||||
RuntimeEntityRecord canonical = lifetime
|
||||
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
|
||||
.Canonical!;
|
||||
Assert.True(lifetime.TryGetInitialCreateResidence(
|
||||
canonical,
|
||||
out RuntimeInitialCreateResidenceLease lease));
|
||||
AttachDormantBody(lifetime, canonical);
|
||||
CompleteInitialPlacement(lifetime, lease);
|
||||
|
||||
// A teleport-advanced Position continuation performs its OWN
|
||||
// authored SetPosition - the continuation placement C0-1's contract
|
||||
// says already flows through the channel unchanged.
|
||||
WorldSession.EntityPositionUpdate update = PositionUpdate(
|
||||
guid, positionSequence: 2, teleportSequence: 1,
|
||||
forcePositionSequence: 0, positionX: 40f);
|
||||
Assert.True(lifetime.TryApplyPosition(
|
||||
update, isLocalPlayer: true, null, null, true, null,
|
||||
out PositionTimestampDisposition disposition, out _, out _));
|
||||
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
|
||||
|
||||
var observed = new List<RuntimePlacementProjectionSnapshot>();
|
||||
using IDisposable subscription = lifetime.Events.SubscribePlacement(
|
||||
new PlacementObserver(delta => observed.Add(delta.Placement)));
|
||||
|
||||
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
|
||||
lifetime, canonical, lease.Token, NoContact);
|
||||
|
||||
Assert.Equal(
|
||||
[
|
||||
RuntimePlacementProjectionKind.Place,
|
||||
RuntimePlacementProjectionKind.ExecutorCompleted,
|
||||
],
|
||||
observed.Select(static s => s.Kind));
|
||||
Assert.Equal(canonical.Key, observed[0].Token.Entity);
|
||||
Assert.True(lifetime.InitialCreateExecution.TryGetCompletionReceipt(
|
||||
observed[1].Token,
|
||||
out RuntimeInitialCreateExecutionReceipt correlated));
|
||||
Assert.Equal(receipt, correlated);
|
||||
// The continuation Place receipt was already acknowledged by
|
||||
// RunToCompletion's own CompletePendingContinuationPlacement helper
|
||||
// before Execute ever reached Completed - only the fresh
|
||||
// ExecutorCompleted receipt is still outstanding.
|
||||
Assert.Equal(1, lifetime.Physics.SetPosition.PendingProjectionCount);
|
||||
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
|
||||
observed[1].Token));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExecutorCompletion_ReceiptIsReadableFromWithinTheSameSynchronousOnPlacementDispatch()
|
||||
{
|
||||
// F2: registration must happen BEFORE PublishPlacement's synchronous
|
||||
// observer dispatch - a subscriber reading the correlation back from
|
||||
// inside its OWN OnPlacement callback must already find it, not only
|
||||
// after RunToCompletion returns.
|
||||
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
|
||||
Bind(lifetime, 410UL);
|
||||
const uint guid = 0x70024010u;
|
||||
RuntimeEntityRecord canonical = lifetime
|
||||
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
|
||||
.Canonical!;
|
||||
Assert.True(lifetime.TryGetInitialCreateResidence(
|
||||
canonical,
|
||||
out RuntimeInitialCreateResidenceLease lease));
|
||||
AttachDormantBody(lifetime, canonical);
|
||||
CompleteInitialPlacement(lifetime, lease);
|
||||
|
||||
RuntimeInitialCreateExecutionReceipt? observedFromInsideDispatch = null;
|
||||
using IDisposable subscription = lifetime.Events.SubscribePlacement(
|
||||
new PlacementObserver(delta =>
|
||||
{
|
||||
if (delta.Placement.Kind
|
||||
is not RuntimePlacementProjectionKind.ExecutorCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Assert.True(lifetime.InitialCreateExecution.TryGetCompletionReceipt(
|
||||
delta.Placement.Token,
|
||||
out RuntimeInitialCreateExecutionReceipt receipt));
|
||||
observedFromInsideDispatch = receipt;
|
||||
}));
|
||||
|
||||
RuntimeInitialCreateExecutionReceipt receiptReturned = RunToCompletion(
|
||||
lifetime, canonical, lease.Token, NoContact);
|
||||
|
||||
Assert.NotNull(observedFromInsideDispatch);
|
||||
Assert.Equal(receiptReturned, observedFromInsideDispatch!.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExecutorCompletion_ConvergenceLedgerCountsAnUnacknowledgedReceiptAsOutstandingDebtUntilAcknowledged()
|
||||
{
|
||||
// F2: PendingCompletionReceiptCount mirrors
|
||||
// RuntimeSetPositionOwnershipSnapshot.PendingProjectionAcknowledgementCount's
|
||||
// existing "unacknowledged receipt is outstanding debt" shape for
|
||||
// the SAME underlying receipt stream - non-zero while unacknowledged,
|
||||
// reaped to zero exactly on acknowledge (never before, never left
|
||||
// dangling after).
|
||||
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
|
||||
Bind(lifetime, 411UL);
|
||||
const uint guid = 0x70024011u;
|
||||
RuntimeEntityRecord canonical = lifetime
|
||||
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
|
||||
.Canonical!;
|
||||
Assert.True(lifetime.TryGetInitialCreateResidence(
|
||||
canonical,
|
||||
out RuntimeInitialCreateResidenceLease lease));
|
||||
AttachDormantBody(lifetime, canonical);
|
||||
CompleteInitialPlacement(lifetime, lease);
|
||||
Assert.Equal(
|
||||
0,
|
||||
lifetime.CaptureOwnership().PendingCompletionReceiptCount);
|
||||
|
||||
RunToCompletion(lifetime, canonical, lease.Token, NoContact);
|
||||
|
||||
Assert.Equal(
|
||||
1,
|
||||
lifetime.CaptureOwnership().PendingCompletionReceiptCount);
|
||||
Assert.True(lifetime.Physics.SetPosition.TryPeekProjection(
|
||||
out RuntimePlacementProjectionSnapshot completion));
|
||||
Assert.Equal(
|
||||
RuntimePlacementProjectionKind.ExecutorCompleted,
|
||||
completion.Kind);
|
||||
|
||||
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
|
||||
completion.Token));
|
||||
|
||||
Assert.Equal(
|
||||
0,
|
||||
lifetime.CaptureOwnership().PendingCompletionReceiptCount);
|
||||
Assert.False(lifetime.InitialCreateExecution.TryGetCompletionReceipt(
|
||||
completion.Token,
|
||||
out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExecutorCompletion_CorrelationEntryIsReapedByDiscardProgress()
|
||||
{
|
||||
// F2: DiscardProgress (reached via ForgetInitialCreateResidence in
|
||||
// production) must reap this correlation cache too - it is exactly
|
||||
// the kind of executor-introduced state that method already owns
|
||||
// cleaning up. The drain already removed _progress[key] before
|
||||
// publishing the completion (see ExecuteCore's Released case), so
|
||||
// this proves DiscardProgress reaps _completionReceipts
|
||||
// UNCONDITIONALLY, not only when _progress still tracks the key.
|
||||
// Left deliberately UNACKNOWLEDGED in _pendingProjection (a single-
|
||||
// entity lifetime, so there is no exact-head contention to worry
|
||||
// about) - proving DiscardProgress reaps the correlation cache
|
||||
// independently of the normal acknowledge path.
|
||||
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
|
||||
Bind(lifetime, 413UL);
|
||||
const uint guid = 0x70024013u;
|
||||
RuntimeEntityRecord canonical = lifetime
|
||||
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
|
||||
.Canonical!;
|
||||
Assert.True(lifetime.TryGetInitialCreateResidence(
|
||||
canonical,
|
||||
out RuntimeInitialCreateResidenceLease lease));
|
||||
AttachDormantBody(lifetime, canonical);
|
||||
CompleteInitialPlacement(lifetime, lease);
|
||||
RunToCompletion(lifetime, canonical, lease.Token, NoContact);
|
||||
Assert.Equal(
|
||||
1,
|
||||
lifetime.CaptureOwnership().PendingCompletionReceiptCount);
|
||||
|
||||
lifetime.InitialCreateExecution.DiscardProgress(canonical.Key!.Value);
|
||||
|
||||
Assert.Equal(
|
||||
0,
|
||||
lifetime.CaptureOwnership().PendingCompletionReceiptCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExecutorCompletion_CorrelationEntryIsReapedByDiscardAll()
|
||||
{
|
||||
// F2: a full session clear (DiscardAll, reached via
|
||||
// RuntimeInitialCreateResidenceState.Clear's call site) must never
|
||||
// carry this correlation cache across a reset.
|
||||
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
|
||||
Bind(lifetime, 414UL);
|
||||
const uint guid = 0x70024014u;
|
||||
RuntimeEntityRecord canonical = lifetime
|
||||
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
|
||||
.Canonical!;
|
||||
Assert.True(lifetime.TryGetInitialCreateResidence(
|
||||
canonical,
|
||||
out RuntimeInitialCreateResidenceLease lease));
|
||||
AttachDormantBody(lifetime, canonical);
|
||||
CompleteInitialPlacement(lifetime, lease);
|
||||
RunToCompletion(lifetime, canonical, lease.Token, NoContact);
|
||||
Assert.Equal(
|
||||
1,
|
||||
lifetime.CaptureOwnership().PendingCompletionReceiptCount);
|
||||
|
||||
lifetime.InitialCreateExecution.DiscardAll();
|
||||
|
||||
Assert.Equal(
|
||||
0,
|
||||
lifetime.CaptureOwnership().PendingCompletionReceiptCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BindLiveInputs_DrivesClassificationFromTheBoundSourcesInsteadOfTheCallerStruct()
|
||||
{
|
||||
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
|
||||
Bind(lifetime, 402UL);
|
||||
const uint guid = 0x70024002u;
|
||||
RuntimeEntityRecord canonical = lifetime
|
||||
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
|
||||
.Canonical!;
|
||||
Assert.True(lifetime.TryGetInitialCreateResidence(
|
||||
canonical,
|
||||
out RuntimeInitialCreateResidenceLease lease));
|
||||
AttachDormantBody(lifetime, canonical);
|
||||
CompleteInitialPlacement(lifetime, lease);
|
||||
|
||||
WorldSession.EntityPositionUpdate update = PositionUpdate(
|
||||
guid, positionSequence: 2, teleportSequence: 0,
|
||||
forcePositionSequence: 0, positionX: 15f, isGrounded: true);
|
||||
Assert.True(lifetime.TryApplyPosition(
|
||||
update, isLocalPlayer: true, null, null, false, null,
|
||||
out PositionTimestampDisposition disposition, out _, out _));
|
||||
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
|
||||
|
||||
bool usePositionFromServer = true;
|
||||
// Position 15 units from a local player parked far away (100 units)
|
||||
// is irrelevant here (PlayerDistance only matters for the
|
||||
// Remote/Projectile near/far branch, not LocalPlayer's own
|
||||
// interpolate gate) - it exists purely to prove the DISTANCE source
|
||||
// is read at all.
|
||||
lifetime.InitialCreateExecution.BindLiveInputs(
|
||||
() => usePositionFromServer,
|
||||
() => new Vector3(115f, 20f, 7f));
|
||||
|
||||
// The caller-supplied struct says UsePositionFromServer:false - if
|
||||
// the bound source is actually driving classification, retail's
|
||||
// "UsePositionFromServer && wire-contact" local-ordinary gate must
|
||||
// still interpolate.
|
||||
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
|
||||
lifetime, canonical, lease.Token, NoContact);
|
||||
RuntimeInitialCreateExecutedAction positionAction = Assert.Single(
|
||||
receipt.Trace,
|
||||
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.Position);
|
||||
Assert.Equal(
|
||||
RuntimeAuthoritativePositionDisposition.Interpolate,
|
||||
positionAction.PositionDisposition);
|
||||
|
||||
// C0-1: Completing the first entity's drain also published its own
|
||||
// ExecutorCompleted receipt on the SAME exact-head stream - it must
|
||||
// be acknowledged before a SECOND entity's own placement receipt can
|
||||
// ever become the head.
|
||||
Assert.True(lifetime.Physics.SetPosition.TryPeekProjection(
|
||||
out RuntimePlacementProjectionSnapshot firstCompletion));
|
||||
Assert.Equal(
|
||||
RuntimePlacementProjectionKind.ExecutorCompleted,
|
||||
firstCompletion.Kind);
|
||||
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
|
||||
firstCompletion.Token));
|
||||
|
||||
// Flip the bound source off and confirm the SAME struct now takes
|
||||
// the non-interpolating branch - proves it is read live, not cached
|
||||
// at bind time.
|
||||
usePositionFromServer = false;
|
||||
const uint secondGuid = 0x70024003u;
|
||||
RuntimeEntityRecord second = lifetime
|
||||
.RegisterEntityWithInitialResidence(Spawn(secondGuid, 1), isLocalPlayer: true)
|
||||
.Canonical!;
|
||||
Assert.True(lifetime.TryGetInitialCreateResidence(
|
||||
second,
|
||||
out RuntimeInitialCreateResidenceLease secondLease));
|
||||
AttachDormantBody(lifetime, second);
|
||||
CompleteInitialPlacement(lifetime, secondLease);
|
||||
WorldSession.EntityPositionUpdate secondUpdate = PositionUpdate(
|
||||
secondGuid, positionSequence: 2, teleportSequence: 0,
|
||||
forcePositionSequence: 0, positionX: 16f, isGrounded: true);
|
||||
Assert.True(lifetime.TryApplyPosition(
|
||||
secondUpdate, isLocalPlayer: true, null, null, false, null,
|
||||
out PositionTimestampDisposition secondDisposition, out _, out _));
|
||||
Assert.Equal(PositionTimestampDisposition.Apply, secondDisposition);
|
||||
RuntimeInitialCreateExecutionReceipt secondReceipt = RunToCompletion(
|
||||
lifetime, second, secondLease.Token, NoContact);
|
||||
RuntimeInitialCreateExecutedAction secondPositionAction = Assert.Single(
|
||||
secondReceipt.Trace,
|
||||
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.Position);
|
||||
Assert.Equal(
|
||||
RuntimeAuthoritativePositionDisposition.NoPositionOperation,
|
||||
secondPositionAction.PositionDisposition);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BindLiveInputs_ThrowsOnASecondBindAndUnboundExecutorsUseTheCallerStructUnchanged()
|
||||
{
|
||||
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
|
||||
lifetime.InitialCreateExecution.BindLiveInputs(
|
||||
static () => true, static () => Vector3.Zero);
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
lifetime.InitialCreateExecution.BindLiveInputs(
|
||||
static () => false, static () => Vector3.Zero));
|
||||
|
||||
// A SEPARATE, never-bound lifetime still honors the caller-supplied
|
||||
// struct verbatim - the existing bare-lifetime test contract is
|
||||
// unchanged by this slice.
|
||||
using RuntimeEntityObjectLifetime unbound = EngineLifetime();
|
||||
Bind(unbound, 403UL);
|
||||
const uint guid = 0x70024004u;
|
||||
RuntimeEntityRecord canonical = unbound
|
||||
.RegisterEntityWithInitialResidence(Spawn(guid, 1), isLocalPlayer: true)
|
||||
.Canonical!;
|
||||
Assert.True(unbound.TryGetInitialCreateResidence(
|
||||
canonical,
|
||||
out RuntimeInitialCreateResidenceLease lease));
|
||||
AttachDormantBody(unbound, canonical);
|
||||
CompleteInitialPlacement(unbound, lease);
|
||||
WorldSession.EntityPositionUpdate update = PositionUpdate(
|
||||
guid, positionSequence: 2, teleportSequence: 0,
|
||||
forcePositionSequence: 0, positionX: 15f, isGrounded: true);
|
||||
Assert.True(unbound.TryApplyPosition(
|
||||
update, isLocalPlayer: true, null, null, false, null,
|
||||
out PositionTimestampDisposition disposition, out _, out _));
|
||||
Assert.Equal(PositionTimestampDisposition.Apply, disposition);
|
||||
|
||||
RuntimeInitialCreateExecutionReceipt receipt = RunToCompletion(
|
||||
unbound, canonical, lease.Token, NoContact);
|
||||
RuntimeInitialCreateExecutedAction positionAction = Assert.Single(
|
||||
receipt.Trace,
|
||||
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.Position);
|
||||
// NoContact (UsePositionFromServer:false) -> not interpolated.
|
||||
Assert.Equal(
|
||||
RuntimeAuthoritativePositionDisposition.NoPositionOperation,
|
||||
positionAction.PositionDisposition);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BindLiveInputs_PlayerDistanceIsReadFromANonNullBoundSourceAndFallsBackToTheCallerStructWhenNull()
|
||||
{
|
||||
// F3: proves BOTH directions of the nullable local-player-position
|
||||
// source. Entity 1: the bound source returns a REAL near position -
|
||||
// the caller struct claims a FAR distance (200f), so if the bound
|
||||
// source is genuinely read (not ignored), the entity's own near
|
||||
// distance must win and classify Interpolate. Entity 2: the SAME
|
||||
// bound source now returns null (e.g. the login-window drain before
|
||||
// RuntimeLocalPlayerMovementState.Controller exists) - it must fall
|
||||
// back to the caller struct's FAR distance exactly like an unbound
|
||||
// source would, never fabricate Vector3.Zero (which would compute a
|
||||
// small, misleadingly-near distance to the entity's own position and
|
||||
// wrongly classify Interpolate instead of the far SetPositionSimple
|
||||
// hard-snap).
|
||||
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
|
||||
Bind(lifetime, 421UL);
|
||||
Vector3? boundPosition = new Vector3(30f, 20f, 7f);
|
||||
lifetime.InitialCreateExecution.BindLiveInputs(
|
||||
static () => false,
|
||||
() => boundPosition);
|
||||
|
||||
const uint nearGuid = 0x70024021u;
|
||||
RuntimeEntityRecord near = lifetime
|
||||
.RegisterEntityWithInitialResidence(Spawn(nearGuid, 1), isLocalPlayer: false)
|
||||
.Canonical!;
|
||||
Assert.True(lifetime.TryGetInitialCreateResidence(
|
||||
near, out RuntimeInitialCreateResidenceLease nearLease));
|
||||
AttachDormantBody(lifetime, near);
|
||||
CompleteInitialPlacement(lifetime, nearLease);
|
||||
WorldSession.EntityPositionUpdate nearUpdate = PositionUpdate(
|
||||
nearGuid, positionSequence: 2, teleportSequence: 0,
|
||||
forcePositionSequence: 0, positionX: 25f, isGrounded: true);
|
||||
Assert.True(lifetime.TryApplyPosition(
|
||||
nearUpdate, isLocalPlayer: false, null, null, false, null,
|
||||
out PositionTimestampDisposition nearDisposition, out _, out _));
|
||||
Assert.Equal(PositionTimestampDisposition.Apply, nearDisposition);
|
||||
var farStruct = new RuntimeInitialCreateExecutionInputs(
|
||||
UsePositionFromServer: false, PlayerDistance: 200f);
|
||||
|
||||
RuntimeInitialCreateExecutionReceipt nearReceipt = RunToCompletion(
|
||||
lifetime, near, nearLease.Token, farStruct);
|
||||
|
||||
RuntimeInitialCreateExecutedAction nearAction = Assert.Single(
|
||||
nearReceipt.Trace,
|
||||
static a => a.Kind is RuntimeInitialCreateExecutedActionKind.Position);
|
||||
Assert.Equal(
|
||||
RuntimeAuthoritativePositionDisposition.Interpolate,
|
||||
nearAction.PositionDisposition);
|
||||
|
||||
// C0-1: the completed near entity published its own ExecutorCompleted
|
||||
// receipt on the SAME exact-head stream - acknowledge it before the
|
||||
// far entity's own Place receipt can ever become the head.
|
||||
Assert.True(lifetime.Physics.SetPosition.TryPeekProjection(
|
||||
out RuntimePlacementProjectionSnapshot nearCompletion));
|
||||
Assert.Equal(
|
||||
RuntimePlacementProjectionKind.ExecutorCompleted,
|
||||
nearCompletion.Kind);
|
||||
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
|
||||
nearCompletion.Token));
|
||||
|
||||
boundPosition = null;
|
||||
const uint farGuid = 0x70024022u;
|
||||
RuntimeEntityRecord far = lifetime
|
||||
.RegisterEntityWithInitialResidence(Spawn(farGuid, 1), isLocalPlayer: false)
|
||||
.Canonical!;
|
||||
Assert.True(lifetime.TryGetInitialCreateResidence(
|
||||
far, out RuntimeInitialCreateResidenceLease farLease));
|
||||
AttachDormantBody(lifetime, far);
|
||||
CompleteInitialPlacement(lifetime, farLease);
|
||||
WorldSession.EntityPositionUpdate farUpdate = PositionUpdate(
|
||||
farGuid, positionSequence: 2, teleportSequence: 0,
|
||||
forcePositionSequence: 0, positionX: 25f, isGrounded: true);
|
||||
Assert.True(lifetime.TryApplyPosition(
|
||||
farUpdate, isLocalPlayer: false, null, null, false, null,
|
||||
out PositionTimestampDisposition farDisposition, out _, out _));
|
||||
Assert.Equal(PositionTimestampDisposition.Apply, farDisposition);
|
||||
|
||||
RuntimeInitialCreateExecutionStatus farStatus = lifetime
|
||||
.InitialCreateExecution.Execute(
|
||||
far, farLease.Token, farStruct, out _);
|
||||
|
||||
Assert.Equal(
|
||||
RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement,
|
||||
farStatus);
|
||||
RuntimeEntityKey farKey = far.Key!.Value;
|
||||
Assert.True(lifetime.InitialCreateExecution.TryGetPendingContinuationRoute(
|
||||
farKey, out RuntimeAuthoritativePositionRoute farRoute));
|
||||
Assert.Equal(
|
||||
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
|
||||
farRoute.Disposition);
|
||||
Assert.True(farRoute.StopInterpolating);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Harness
|
||||
// ---------------------------------------------------------------
|
||||
|
|
@ -4461,4 +4935,12 @@ public sealed class RuntimeInitialCreateContinuationExecutorTests
|
|||
{
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class PlacementObserver(
|
||||
Action<RuntimePlacementDelta> onPlacement)
|
||||
: IRuntimePlacementObserver
|
||||
{
|
||||
public void OnPlacement(in RuntimePlacementDelta delta) =>
|
||||
onPlacement(delta);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue