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:
Erik 2026-08-02 05:22:37 +02:00
parent 27e05b99e4
commit 67f63e85e5
14 changed files with 1639 additions and 11 deletions

View file

@ -155,6 +155,44 @@ public sealed class RuntimePlacementPresentationSinkTests
Assert.Equal(priorVisible, record.IsSpatiallyVisible);
}
[Fact]
public void ExecutorCompleted_IsAckOnlyNoOpEvenWhenTokenIsStaleOrSidecarIsGone()
{
// F1: mirrors Discard_IsAckOnlyNoOpEvenWhenTokenIsStaleOrSidecarIsGone
// exactly - proves ExecutorCompleted is acknowledged unconditionally
// (never wedges the FIFO on a record-lookup failure) and never
// mutates presentation state, even under a completely bogus token.
Fixture fixture = Fixture.Create();
LiveEntityRecord record = fixture.Materialize(Spawn(Guid, 1, SourceCell));
WorldEntity entity = record.WorldEntity!;
RuntimePlacementProjectionSnapshot completion = Placement(
fixture,
record,
RuntimePlacementProjectionKind.ExecutorCompleted,
new Vector3(900f),
Quaternion.CreateFromAxisAngle(Vector3.UnitX, 1f)) with
{
Token = Placement(fixture, record,
RuntimePlacementProjectionKind.Place,
Vector3.Zero,
Quaternion.Identity).Token with
{
SessionLifetimeVersion = ulong.MaxValue,
PositionAuthorityVersion = ulong.MaxValue,
ExactCellId = 0xDEAD0001u,
},
};
Vector3 priorPosition = entity.Position;
Quaternion priorRotation = entity.Rotation;
bool priorVisible = record.IsSpatiallyVisible;
Assert.True(fixture.Sink.TryApply(in completion));
Assert.Equal(priorPosition, entity.Position);
Assert.Equal(priorRotation, entity.Rotation);
Assert.Equal(priorVisible, record.IsSpatiallyVisible);
}
[Fact]
public void Place_RejectsStaleCanonicalVersionsWithoutChangingSidecar()
{

View file

@ -479,6 +479,56 @@ public sealed class HeadlessSessionHostTests
Assert.True(projection.TryApply(in discard));
}
[Fact]
public void ExecutorCompletedReceiptIsAcknowledgeOnlyRegardlessOfRecordValidity()
{
// F1: mirrors PlacementReceiptUsesExactIncarnationAndDiscardIsAckOnly's
// stale-token half - proves ExecutorCompleted is acknowledged
// unconditionally (never gated by the record-lookup/portal-shape
// checks Place/Withdraw depend on), so a genuinely stale/mismatched
// token can never wedge the FIFO behind it.
var operations = new FixtureSessionOperations();
using var credential = new HeadlessCredentialSecret(
"fixture",
"password");
using var host = new HeadlessSessionHost(
Descriptor(),
credential,
new HeadlessDiagnosticWriter(TextWriter.Null),
operations);
GameRuntime runtime = host.Runtime;
RuntimeEntityRecord record = runtime.EntityObjects
.RegisterEntity(Spawn(0x50000006u))
.Canonical!;
Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn(
record,
record.CreateIntegrationVersion,
record.Snapshot,
replaceGeneration: false));
var sink = new HeadlessRuntimePlacementProjectionSink(runtime);
RuntimePlacementProjectionSnapshot completion = Placement(
runtime,
record,
RuntimePlacementProjectionKind.ExecutorCompleted,
Vector3.One,
Quaternion.Identity);
RuntimePlacementProjectionSnapshot stale = completion with
{
Token = completion.Token with
{
Entity = completion.Token.Entity with
{
Incarnation = unchecked((ushort)(
completion.Token.Entity.Incarnation + 1)),
},
SessionLifetimeVersion = ulong.MaxValue,
},
};
Assert.True(sink.TryApply(in completion));
Assert.True(sink.TryApply(in stale));
}
[Fact]
public void SessionEventRouteOwnsOneObserverAndUnsubscribesBeforeNetworkDetach()
{

View file

@ -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);
}
}

View file

@ -2379,6 +2379,82 @@ public sealed class RuntimeInitialCreateResidenceStateTests
Assert.Single(retained.Continuations).Kind);
}
// ---------------------------------------------------------------
// C0-4: TryCommitParent/CommitWithdrawal cancellation asymmetries
// ---------------------------------------------------------------
[Fact]
public void TryCommitParent_CancelsActiveInitialResidenceAndItsPendingPlacement()
{
var engine = new PhysicsEngine { DataCache = new PhysicsDataCache() };
engine.AddLandblock(
Landblock,
new TerrainSurface(new byte[81], new float[256]),
Array.Empty<CellSurface>(),
Array.Empty<PortalPlane>(),
worldOffsetX: 0f,
worldOffsetY: 0f);
using var lifetime = new RuntimeEntityObjectLifetime(engine);
Bind(lifetime, 200UL);
const uint guid = 0x70004001u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, setupId: null),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
// Unparented -> the classifier's route performs SetPosition, so
// Own() has already begun the lease's own placement operation.
// Submit it (a live Place receipt now sits unacknowledged) so
// cancellation has an ACTUAL outstanding receipt to discard, not
// merely an unpublished AwaitingPreparation operation.
Assert.True(lease.Placement.IsValid);
AttachDormantBody(lifetime, canonical);
RuntimeSetPositionOutcome outcome = lifetime.Physics.SetPosition
.SubmitPreparedPlacement(
lease.Placement,
Prepare(lifetime, lease, RuntimeSetPositionMoverSetup.ResolvedAbsent));
Assert.Equal(
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
outcome.Status);
Assert.Equal(1, lifetime.Physics.SetPosition.PendingProjectionCount);
var discards = new List<RuntimePlacementProjectionSnapshot>();
using IDisposable subscription = lifetime.Events.SubscribePlacement(
new PlacementObserver(delta =>
{
if (delta.Placement.Kind is RuntimePlacementProjectionKind.Discard)
discards.Add(delta.Placement);
}));
var relation = new ParentAttachmentRelation(
ParentGuid: 0x70004100u,
ChildGuid: guid,
ParentLocation: 1u,
PlacementId: 1u,
ParentInstanceSequence: 1,
ChildPositionSequence: 1);
Assert.True(lifetime.TryCommitParent(relation, null, out _));
Assert.Equal(0, lifetime.CaptureOwnership()
.InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership()
.ActiveOperationCount);
// The old Place receipt is REPLACED by a Discard at the same
// sequence, not removed outright - still awaiting host ack.
Assert.Equal(1, lifetime.Physics.SetPosition.PendingProjectionCount);
Assert.False(lifetime.Physics.SetPosition.IsPlacementCurrent(
lease.Placement));
RuntimePlacementProjectionSnapshot discard = Assert.Single(discards);
Assert.Equal(canonical.Key, discard.Token.Entity);
Assert.Equal(outcome.Projection.Sequence, discard.Token.Sequence);
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
discard.Token));
Assert.Equal(0, lifetime.Physics.SetPosition.PendingProjectionCount);
}
private static RuntimeSetPositionCommand Prepare(
RuntimeEntityObjectLifetime lifetime,
in RuntimeInitialCreateResidenceLease lease,

View file

@ -340,6 +340,51 @@ public sealed class RuntimeCharacterStateTests
Assert.False(state.CaptureOwnership().MovementSkillsAreReset);
}
// ---------------------------------------------------------------
// C0-2: retail CommandInterpreter::autonomy_level/UsePositionFromServer
// ---------------------------------------------------------------
[Fact]
public void AutonomyLevel_DefaultsToFullAndMirrorsRetailUsePositionFromServer()
{
using var state = new RuntimeCharacterState();
Assert.Equal(RuntimeCharacterState.FullAutonomyLevel, state.AutonomyLevel);
Assert.False(state.UsePositionFromServer);
Assert.True(state.CaptureOwnership().AutonomyIsDefault);
Assert.True(state.TrySetAutonomyLevel(0u));
Assert.Equal(0u, state.AutonomyLevel);
Assert.True(state.UsePositionFromServer);
Assert.False(state.CaptureOwnership().AutonomyIsDefault);
Assert.True(state.TrySetAutonomyLevel(1u));
Assert.True(state.UsePositionFromServer);
// Retail's SetAutonomyLevel rejects anything above 2; the level and
// the derived UsePositionFromServer gate stay exactly as they were.
Assert.False(state.TrySetAutonomyLevel(3u));
Assert.Equal(1u, state.AutonomyLevel);
Assert.True(state.TrySetAutonomyLevel(RuntimeCharacterState.FullAutonomyLevel));
Assert.False(state.UsePositionFromServer);
Assert.True(state.CaptureOwnership().AutonomyIsDefault);
}
[Fact]
public void ResetSession_RestoresAutonomyLevelToFull()
{
using var state = new RuntimeCharacterState();
Assert.True(state.TrySetAutonomyLevel(0u));
Assert.True(state.UsePositionFromServer);
state.ResetSession();
Assert.Equal(RuntimeCharacterState.FullAutonomyLevel, state.AutonomyLevel);
Assert.False(state.UsePositionFromServer);
Assert.True(state.CaptureOwnership().AutonomyIsDefault);
}
private static ActiveEnchantmentRecord MakeVitae(uint spellId, float val) =>
new(
spellId, LayerId: 0u, Duration: -1f, CasterGuid: 0u,

View file

@ -1,5 +1,7 @@
using System.Collections.Immutable;
using System.Numerics;
using AcDream.Content;
using AcDream.Content.Pak;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
@ -2247,6 +2249,263 @@ public sealed class RuntimeSetPositionStateTests
placed.Token));
}
// ---------------------------------------------------------------
// C0-3: exact-Setup mover chain end-to-end
// ---------------------------------------------------------------
[Fact]
public void TryPrepareAndSubmitAuthoredPlacement_ChainsSetupReadThroughPrepareMoverToSubmit()
{
PhysicsEngine engine = FlatEngine(SourceLandblock, 0f);
using var lifetime = new RuntimeEntityObjectLifetime(engine);
RuntimeEntityRecord record = CreateRecord(lifetime, 0x70001101u, 1);
AttachBody(lifetime, record, SourceCell);
RuntimeEntityPlacementToken token = lifetime.Physics.SetPosition
.BeginAuthoredPlacement(
record,
record.PositionAuthorityVersion,
RuntimeSetPositionOperationKind.RemoteAuthoritative);
Assert.True(token.IsValid);
// Spawn's own default SetupTableId (0x02000001u) - the exact
// CanonicalSetupTableId CapturePreparationAuthority already trusts;
// the chain must read THIS id, not a caller-supplied one.
ImmutableArray<FlatCollisionSphere> spheres =
[
new FlatCollisionSphere(new Vector3(0f, 0f, 0.5f), 0.4f),
new FlatCollisionSphere(new Vector3(0f, 0f, 1.2f), 0.4f),
];
var source = new FakeCollisionSource(
0x02000001u,
new FlatSetupCollision(
ImmutableArray<FlatCollisionCylinder>.Empty,
spheres,
height: 0f,
radius: 0f,
stepUpHeight: 0.4f,
stepDownHeight: 0.35f));
Assert.Equal(
RuntimeSetPositionMoverPreparationStatus.Prepared,
lifetime.Physics.SetPosition.TryPrepareAndSubmitAuthoredPlacement(
record,
token,
RuntimeSetPositionOperationKind.RemoteAuthoritative,
PhysicsSetPositionFlags.Placement
| PhysicsSetPositionFlags.Slide,
source,
gameTime: 10d,
out RuntimeSetPositionOutcome outcome));
Assert.Equal(
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
outcome.Status);
Assert.Equal(1, source.ReadCount);
Assert.True(lifetime.Physics.SetPosition.TryGetPreparedMoverSphereCount(
record,
out int sphereCount));
Assert.Equal(2, sphereCount);
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
outcome.Projection));
}
[Fact]
public void TryPrepareAndSubmitAuthoredPlacement_YieldsRetryOnAMissingSetupReadWithoutMutatingStage()
{
PhysicsEngine engine = FlatEngine(SourceLandblock, 0f);
using var lifetime = new RuntimeEntityObjectLifetime(engine);
RuntimeEntityRecord record = CreateRecord(lifetime, 0x70001102u, 1);
AttachBody(lifetime, record, SourceCell);
RuntimeEntityPlacementToken token = lifetime.Physics.SetPosition
.BeginAuthoredPlacement(
record,
record.PositionAuthorityVersion,
RuntimeSetPositionOperationKind.RemoteAuthoritative);
Assert.True(token.IsValid);
var source = new FakeCollisionSource(
0x02000001u,
setup: null,
status: PreparedAssetReadStatus.Missing);
Assert.Equal(
RuntimeSetPositionMoverPreparationStatus.RetrySetupUnavailable,
lifetime.Physics.SetPosition.TryPrepareAndSubmitAuthoredPlacement(
record,
token,
RuntimeSetPositionOperationKind.RemoteAuthoritative,
PhysicsSetPositionFlags.Placement
| PhysicsSetPositionFlags.Slide,
source,
gameTime: 10d,
out RuntimeSetPositionOutcome outcome));
Assert.Equal(default, outcome);
Assert.Equal(1, source.ReadCount);
// Never manufactured a fallback while the read is in flight - the
// token can still be prepared once the asset lands.
Assert.True(lifetime.Physics.SetPosition.IsPlacementCurrent(token));
Assert.False(lifetime.Physics.SetPosition.TryGetPreparedMoverSphereCount(
record,
out _));
}
// ---------------------------------------------------------------
// C0-1: executor completion on the SAME ordered placement stream
// ---------------------------------------------------------------
[Fact]
public void PublishExecutorCompletion_PublishesAcknowledgeOnlyReceiptAndConverges()
{
PhysicsEngine engine = FlatEngine(SourceLandblock, 0f);
using var lifetime = new RuntimeEntityObjectLifetime(engine);
RuntimeEntityRecord record = CreateRecord(lifetime, 0x70001201u, 1);
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
var observer = new PlacementObserver();
using IDisposable subscription =
lifetime.Events.SubscribePlacement(observer);
RuntimePlacementProjectionToken token =
lifetime.Physics.SetPosition.PublishExecutorCompletion(record);
Assert.True(token.IsValid);
Assert.Equal(record.Key, token.Entity);
RuntimePlacementProjectionSnapshot published =
Assert.Single(observer.Deltas).Placement;
Assert.Equal(RuntimePlacementProjectionKind.ExecutorCompleted,
published.Kind);
Assert.Equal(token, published.Token);
Assert.Equal(body.Position, published.WorldPosition);
Assert.Equal(1, lifetime.Physics.SetPosition.PendingProjectionCount);
// Acknowledge-only, exactly like Discard - no operation to resume.
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(token));
Assert.Equal(0, lifetime.Physics.SetPosition.PendingProjectionCount);
// A stale re-acknowledge of the already-consumed receipt fails.
Assert.False(lifetime.Physics.SetPosition.AcknowledgeProjection(token));
}
[Fact]
public void PublishExecutorCompletion_RespectsExactHeadOrderingAcrossEntities()
{
PhysicsEngine engine = FlatEngine(SourceLandblock, 0f);
using var lifetime = new RuntimeEntityObjectLifetime(engine);
RuntimeEntityRecord first = CreateRecord(lifetime, 0x70001202u, 1);
AttachBody(lifetime, first, SourceCell);
RuntimeEntityRecord second = CreateRecord(lifetime, 0x70001203u, 1);
AttachBody(lifetime, second, SourceCell);
RuntimePlacementProjectionToken firstToken =
lifetime.Physics.SetPosition.PublishExecutorCompletion(first);
RuntimePlacementProjectionToken secondToken =
lifetime.Physics.SetPosition.PublishExecutorCompletion(second);
Assert.Equal(2, lifetime.Physics.SetPosition.PendingProjectionCount);
// The exact head (first) must acknowledge before the second, matching
// the ordered-stream contract every other Kind already honors.
Assert.False(lifetime.Physics.SetPosition.AcknowledgeProjection(
secondToken));
Assert.Equal(2, lifetime.Physics.SetPosition.PendingProjectionCount);
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
firstToken));
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
secondToken));
Assert.Equal(0, lifetime.Physics.SetPosition.PendingProjectionCount);
}
// ---------------------------------------------------------------
// C0-4: TryCommitParent/CommitWithdrawal cancellation asymmetries
// (the ordinary Physics.SetPosition.Forget half, isolated from any
// initial-create residence)
// ---------------------------------------------------------------
[Fact]
public void TryCommitParent_CancelsASeparateActiveOrdinaryPendingPlacement()
{
PhysicsEngine engine = FlatEngine(SourceLandblock, 0f);
using var lifetime = new RuntimeEntityObjectLifetime(engine);
const uint guid = 0x70001301u;
RuntimeEntityRecord record = CreateRecord(lifetime, guid, 1);
AttachBody(lifetime, record, SourceCell);
RuntimeSetPositionOutcome outcome = lifetime.Physics.SetPosition.Apply(
record,
record.PositionAuthorityVersion,
Command(Request(SourceCell, new Vector3(12f, 18f, 7f))));
Assert.Equal(
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
outcome.Status);
Assert.Equal(1, lifetime.Physics.SetPosition.PendingProjectionCount);
var discards = new List<RuntimePlacementProjectionSnapshot>();
using IDisposable subscription = lifetime.Events.SubscribePlacement(
new PlacementObserver(delta =>
{
if (delta.Placement.Kind is RuntimePlacementProjectionKind.Discard)
discards.Add(delta.Placement);
}));
var relation = new ParentAttachmentRelation(
ParentGuid: 0x70001400u,
ChildGuid: guid,
ParentLocation: 1u,
PlacementId: 1u,
ParentInstanceSequence: 1,
ChildPositionSequence: 1);
Assert.True(lifetime.TryCommitParent(relation, null, out _));
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership()
.ActiveOperationCount);
// The old Place receipt is REPLACED by a Discard at the same
// sequence, not removed outright - it still awaits host
// acknowledgement, exactly like every other cancelled-in-flight
// receipt.
Assert.Equal(1, lifetime.Physics.SetPosition.PendingProjectionCount);
RuntimePlacementProjectionSnapshot discard = Assert.Single(discards);
Assert.Equal(record.Key, discard.Token.Entity);
Assert.Equal(outcome.Projection.Sequence, discard.Token.Sequence);
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
discard.Token));
Assert.Equal(0, lifetime.Physics.SetPosition.PendingProjectionCount);
}
[Fact]
public void CommitWithdrawal_CancelsAnActiveOrdinaryPendingPlacementSymmetricallyWithPickup()
{
PhysicsEngine engine = FlatEngine(SourceLandblock, 0f);
using var lifetime = new RuntimeEntityObjectLifetime(engine);
const uint guid = 0x70001302u;
RuntimeEntityRecord record = CreateRecord(lifetime, guid, 1);
AttachBody(lifetime, record, SourceCell);
RuntimeSetPositionOutcome outcome = lifetime.Physics.SetPosition.Apply(
record,
record.PositionAuthorityVersion,
Command(Request(SourceCell, new Vector3(12f, 18f, 7f))));
Assert.Equal(
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
outcome.Status);
Assert.Equal(1, lifetime.Physics.SetPosition.PendingProjectionCount);
var discards = new List<RuntimePlacementProjectionSnapshot>();
using IDisposable subscription = lifetime.Events.SubscribePlacement(
new PlacementObserver(delta =>
{
if (delta.Placement.Kind is RuntimePlacementProjectionKind.Discard)
discards.Add(delta.Placement);
}));
Assert.True(lifetime.CommitWithdrawal(record));
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership()
.ActiveOperationCount);
Assert.Equal(1, lifetime.Physics.SetPosition.PendingProjectionCount);
RuntimePlacementProjectionSnapshot discard = Assert.Single(discards);
Assert.Equal(record.Key, discard.Token.Entity);
Assert.Equal(outcome.Projection.Sequence, discard.Token.Sequence);
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
discard.Token));
Assert.Equal(0, lifetime.Physics.SetPosition.PendingProjectionCount);
}
private static void VerifyPositionChannelCancellation(
CancellationChannel channel)
{
@ -2618,6 +2877,73 @@ public sealed class RuntimeSetPositionStateTests
}
}
/// <summary>
/// C0-3 test double: a minimal <see cref="IPreparedCollisionSource"/>
/// serving exactly one Setup id (matching <c>CanonicalSetupTableId</c>'s
/// field), so <see cref="TryPrepareAndSubmitAuthoredPlacement_ChainsSetupReadThroughPrepareMoverToSubmit"/>
/// can prove <c>ReadSetupCollision</c> -&gt; <c>PrepareMover</c> -&gt;
/// <c>SubmitPreparedPlacement</c> actually chains, not merely that each
/// step works in isolation (the existing preparer tests' coverage).
/// </summary>
private sealed class FakeCollisionSource(
uint expectedSetupTableId,
FlatSetupCollision? setup,
PreparedAssetReadStatus status = PreparedAssetReadStatus.Loaded)
: IPreparedCollisionSource
{
internal int ReadCount { get; private set; }
public PreparedAssetPresence ProbeCollision(
PakAssetType type, uint sourceFileId) =>
PreparedAssetPresence.Available;
public PreparedCollisionReadResult<FlatGfxObjCollisionAsset>
ReadGfxObjCollision(
uint sourceFileId,
CancellationToken cancellationToken = default) =>
throw new NotSupportedException(
"Only ReadSetupCollision is exercised by C0-3.");
public PreparedCollisionReadResult<FlatSetupCollision>
ReadSetupCollision(
uint sourceFileId,
CancellationToken cancellationToken = default)
{
ReadCount++;
Assert.Equal(expectedSetupTableId, sourceFileId);
return status switch
{
PreparedAssetReadStatus.Loaded when setup is not null =>
PreparedCollisionReadResult<FlatSetupCollision>.Loaded(
setup),
PreparedAssetReadStatus.Corrupt =>
PreparedCollisionReadResult<FlatSetupCollision>.Corrupt,
_ => PreparedCollisionReadResult<FlatSetupCollision>.Missing,
};
}
public PreparedCollisionReadResult<FlatCellStructureCollisionAsset>
ReadCellStructureCollision(
uint sourceFileId,
CancellationToken cancellationToken = default) =>
throw new NotSupportedException(
"Only ReadSetupCollision is exercised by C0-3.");
public PreparedCollisionReadResult<FlatEnvCellTopology>
ReadEnvCellTopology(
uint sourceFileId,
CancellationToken cancellationToken = default) =>
throw new NotSupportedException(
"Only ReadSetupCollision is exercised by C0-3.");
public PreparedCollisionSourceStats CollisionStats =>
new(ReadCount, ReadCount, ReadCount, 0, 0);
public void Dispose()
{
}
}
private sealed class CollisionReportObserver
: IRuntimeCollisionReportObserver
{