feat(runtime): public initial-Create completion surface for hosts

Cutover slice C3-1 (the C3 flip's Runtime prerequisite, landed separately
after the flip itself was halted with structural findings — see the plan's
C3a/b/c decomposition). Hosts can now read the executor-completion facts
they must bind at cutover through one public, generation-gated channel
accessor: RuntimePlacementProjectionChannel.TryGetInitialCreateCompletion
returns RuntimeInitialCreatePlacementCompletion — the teleport-hook phase,
resident cell, replay outcomes, and per-Position route facts (disposition,
constrain phase, hook phase, stop-interpolation/zero-velocity/preserve-
heading/send-position flags) via public 1:1 mirror enums of the internal
classifier vocabulary. The projection is built once at completion, cached
in the same reaped entry as the internal receipt (identical acknowledge/
discard/clear lifecycle, ledger-covered), and read allocation-free.
Mirror maps enumerate every value explicitly with throwing catch-alls,
guarded by a sabotage-verified arity/round-trip reflection test. Doc
comments pin the two consumption rules: unparent/placement-frame are
already applied to the canonical snapshot (hosts must not re-apply), and
array order — not Sequence — is the authoritative Position-fact ordering.

Reviewed: architecture PASS + retail-conformance PASS (mirrors verified
member-for-member against the retail phase semantics; the route-fact
selection confirmed to cover exactly the host-bindable deferrals).
Runtime 932/932; complete Release solution 10,727 passed / 4 skips.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-02 08:17:12 +02:00
parent a32aba35d1
commit fe02c4f56d
4 changed files with 588 additions and 9 deletions

View file

@ -174,7 +174,8 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
InitialCreateExecution.ForgetCompletionReceipt(key, sequence));
Placements = new RuntimePlacementProjectionChannel(
Events,
Physics.SetPosition);
Physics.SetPosition,
InitialCreateExecution);
}
internal RuntimeEntityObjectLifetime(
@ -226,7 +227,8 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
InitialCreateExecution.ForgetCompletionReceipt(key, sequence));
Placements = new RuntimePlacementProjectionChannel(
Events,
Physics.SetPosition);
Physics.SetPosition,
InitialCreateExecution);
}
internal RuntimeEntityObjectLifetime(
@ -278,7 +280,8 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
InitialCreateExecution.ForgetCompletionReceipt(key, sequence));
Placements = new RuntimePlacementProjectionChannel(
Events,
Physics.SetPosition);
Physics.SetPosition,
InitialCreateExecution);
}
public RuntimeEntityDirectory Entities { get; }

View file

@ -194,6 +194,99 @@ internal readonly record struct RuntimeInitialCreateExecutionReceipt(
ImmutableArray<RuntimeInitialCreateExecutedAction> Trace,
int ReplayedDeferredChildCount);
/// <summary>
/// C3-1: public projection of <see cref="RuntimeTeleportHookPhase"/>. A
/// separate public enum (rather than widening the internal one's
/// accessibility) keeps the classifier/executor's internal vocabulary free to
/// evolve without becoming a host-facing contract; values map 1:1 today.
/// </summary>
public enum RuntimeInitialCreateTeleportHookPhase : byte
{
None,
BeforePositionOperation,
AfterPositionOperation,
AfterEnterWorld,
}
/// <summary>C3-1: public projection of <see cref="RuntimeAuthoritativePositionDisposition"/>.</summary>
public enum RuntimeInitialCreatePositionDisposition : byte
{
RejectedAuthority,
RejectedData,
AwaitFreshPosition,
NoPositionOperation,
Interpolate,
SetPosition,
SetPositionSimple,
}
/// <summary>C3-1: public projection of <see cref="RuntimePositionConstrainPhase"/>.</summary>
public enum RuntimeInitialCreatePositionConstrainPhase : byte
{
None,
BeforePositionOperation,
AfterPositionOperation,
}
/// <summary>
/// C3-1: one Position continuation's route facts from the executor's trace,
/// projected to a public shape so a host can bind constrain/interpolation
/// presentation (retail's <c>ConstrainTo</c> placement, stop-interpolate,
/// zero-velocity, preserve-heading, send-position-immediately) without
/// reaching into internal Runtime route-classifier types.
/// Review addendum (2026-08-02): the route's <c>UnparentBeforeRouting</c>
/// ("unset_parent") and <c>ApplyPlacementFrameBeforeRouting</c>
/// ("SetPlacementFrame") facts are deliberately NOT projected here - the
/// executor's own merge (<c>ApplyAcceptedPositionSnapshot</c>'s
/// <c>clearParent</c>/<c>installPlacementFrame</c> parameters) already
/// applies both directly to the canonical snapshot, synchronously, before
/// the trace entry carrying this fact is even built. A host reading this
/// record must NOT re-apply either one - the facts this type DOES carry
/// (<see cref="ConstrainPhase"/>, <see cref="StopInterpolating"/>,
/// <see cref="ZeroVelocity"/>, <see cref="PreserveHeading"/>,
/// <see cref="SendPositionImmediately"/>, <see cref="HookPhase"/>) are
/// exactly the bindings still DEFERRED to the host at presentation time;
/// everything already-applied is intentionally excluded.
/// </summary>
public readonly record struct RuntimeInitialCreatePositionRouteFact(
ulong Sequence,
RuntimeInitialCreatePositionDisposition Disposition,
RuntimeInitialCreateTeleportHookPhase HookPhase,
RuntimeInitialCreatePositionConstrainPhase ConstrainPhase,
bool StopInterpolating,
bool ZeroVelocity,
bool PreserveHeading,
bool SendPositionImmediately);
/// <summary>
/// C3-1: the public host consumption shape for one executor drain's
/// completion, reached via
/// <see cref="RuntimePlacementProjectionChannel.TryGetInitialCreateCompletion"/>
/// using the correlated <see cref="RuntimePlacementProjectionKind.ExecutorCompleted"/>
/// receipt's own Entity/Sequence identity. Built exactly once, at completion
/// time, and cached alongside the internal receipt it projects (see
/// <see cref="RuntimeInitialCreateContinuationExecutor.ProjectCompletion"/>) -
/// a host retrying <c>TryGetInitialCreateCompletion</c> across multiple polls
/// never triggers a second allocation.
/// Review addendum (2026-08-02): <see cref="PositionRouteFacts"/>'s ARRAY
/// ORDER is the authoritative ordering, not <see cref="RuntimeInitialCreatePositionRouteFact.Sequence"/>
/// alone - Position facts drained from the SAME same-incarnation envelope
/// (<see cref="RuntimeInitialCreateExecutedAction.Stage"/> distinguishes
/// them internally, a field this projection does not carry) share one
/// continuation <c>Sequence</c>, so two entries can legitimately have equal
/// <c>Sequence</c> values. <see cref="RuntimeInitialCreateContinuationExecutor.ProjectCompletion"/>
/// walks the executor's trace strictly in construction order (itself the
/// exact FIFO drain order) and appends without reordering or deduplicating,
/// so array index - never a sort or group-by on <c>Sequence</c> - is the
/// only reliable way to recover drain order from this array.
/// </summary>
public readonly record struct RuntimeInitialCreatePlacementCompletion(
RuntimeEntityKey Entity,
uint FullCellId,
RuntimeInitialCreateTeleportHookPhase TeleportHookPhase,
ImmutableArray<RuntimeInitialCreatePositionRouteFact> PositionRouteFacts,
int ReplayedDeferredChildCount);
/// <summary>
/// Applies one entity's completed initial-Create residence: adopts the
/// initial placement exactly once, emits the AfterEnterWorld teleport-hook
@ -285,10 +378,16 @@ internal sealed class RuntimeInitialCreateContinuationExecutor
/// reentrancy guard), so only the most recent completion for a key is
/// ever meaningful; the exact-sequence check in
/// <see cref="TryGetCompletionReceipt"/> rejects a stale lookup against a
/// superseded completion under a reused key.
/// superseded completion under a reused key. C3-1: the tuple's third
/// slot is the SAME receipt already projected once to the public
/// <see cref="RuntimeInitialCreatePlacementCompletion"/> shape (see
/// <see cref="ProjectCompletion"/>) - stored here, not recomputed per
/// read, so a host polling <c>TryGetInitialCreateCompletion</c> across
/// retries never allocates a second time for the same completion.
/// </summary>
private readonly Dictionary<RuntimeEntityKey,
(ulong Sequence, RuntimeInitialCreateExecutionReceipt Receipt)>
(ulong Sequence, RuntimeInitialCreateExecutionReceipt Receipt,
RuntimeInitialCreatePlacementCompletion Public)>
_completionReceipts = [];
private Func<RuntimeGenerationToken>? _generation;
private Func<bool>? _usePositionFromServer;
@ -438,7 +537,8 @@ internal sealed class RuntimeInitialCreateContinuationExecutor
{
if (_completionReceipts.TryGetValue(
token.Entity,
out (ulong Sequence, RuntimeInitialCreateExecutionReceipt Receipt) entry)
out (ulong Sequence, RuntimeInitialCreateExecutionReceipt Receipt,
RuntimeInitialCreatePlacementCompletion Public) entry)
&& entry.Sequence == token.Sequence)
{
receipt = entry.Receipt;
@ -448,6 +548,185 @@ internal sealed class RuntimeInitialCreateContinuationExecutor
return false;
}
/// <summary>
/// C3-1: the public host consumption surface for
/// <see cref="TryGetCompletionReceipt"/> - same exact-sequence
/// correlation rule, but returns the cached public projection instead of
/// the internal receipt/trace. Reached via
/// <see cref="RuntimePlacementProjectionChannel.TryGetInitialCreateCompletion"/>.
/// </summary>
internal bool TryGetCompletion(
in RuntimePlacementProjectionToken token,
out RuntimeInitialCreatePlacementCompletion completion)
{
if (_completionReceipts.TryGetValue(
token.Entity,
out (ulong Sequence, RuntimeInitialCreateExecutionReceipt Receipt,
RuntimeInitialCreatePlacementCompletion Public) entry)
&& entry.Sequence == token.Sequence)
{
completion = entry.Public;
return true;
}
completion = default;
return false;
}
/// <summary>
/// Review fix (2026-08-02, architecture pass): every arm is now listed
/// explicitly and the catch-all throws instead of silently folding an
/// unmapped future internal value into <c>None</c>. A value this method
/// cannot map must never reach a host disguised as "nothing to bind" -
/// that would silently drop presentation behavior (e.g. a real
/// teleport-hook phase host code never runs). See
/// <see cref="RuntimeInitialCreateContinuationExecutorTests"/>'s
/// reflection-based completeness test, which walks every declared
/// <see cref="RuntimeTeleportHookPhase"/> value through this exact
/// method and fails if a new internal value is ever added without a
/// matching arm here.
/// </summary>
private static RuntimeInitialCreateTeleportHookPhase MapHookPhase(
RuntimeTeleportHookPhase phase) => phase switch
{
RuntimeTeleportHookPhase.None =>
RuntimeInitialCreateTeleportHookPhase.None,
RuntimeTeleportHookPhase.BeforePositionOperation =>
RuntimeInitialCreateTeleportHookPhase.BeforePositionOperation,
RuntimeTeleportHookPhase.AfterPositionOperation =>
RuntimeInitialCreateTeleportHookPhase.AfterPositionOperation,
RuntimeTeleportHookPhase.AfterEnterWorld =>
RuntimeInitialCreateTeleportHookPhase.AfterEnterWorld,
_ => throw new ArgumentOutOfRangeException(
nameof(phase),
phase,
$"Unmapped {nameof(RuntimeTeleportHookPhase)} value - add an explicit arm to {nameof(MapHookPhase)} and to the public {nameof(RuntimeInitialCreateTeleportHookPhase)} projection."),
};
/// <summary>
/// Review fix (2026-08-02): see <see cref="MapHookPhase"/>'s remarks -
/// same explicit-arms-plus-throwing-catch-all discipline.
/// </summary>
private static RuntimeInitialCreatePositionDisposition MapDisposition(
RuntimeAuthoritativePositionDisposition disposition) => disposition switch
{
RuntimeAuthoritativePositionDisposition.RejectedAuthority =>
RuntimeInitialCreatePositionDisposition.RejectedAuthority,
RuntimeAuthoritativePositionDisposition.RejectedData =>
RuntimeInitialCreatePositionDisposition.RejectedData,
RuntimeAuthoritativePositionDisposition.AwaitFreshPosition =>
RuntimeInitialCreatePositionDisposition.AwaitFreshPosition,
RuntimeAuthoritativePositionDisposition.NoPositionOperation =>
RuntimeInitialCreatePositionDisposition.NoPositionOperation,
RuntimeAuthoritativePositionDisposition.Interpolate =>
RuntimeInitialCreatePositionDisposition.Interpolate,
RuntimeAuthoritativePositionDisposition.SetPosition =>
RuntimeInitialCreatePositionDisposition.SetPosition,
RuntimeAuthoritativePositionDisposition.SetPositionSimple =>
RuntimeInitialCreatePositionDisposition.SetPositionSimple,
_ => throw new ArgumentOutOfRangeException(
nameof(disposition),
disposition,
$"Unmapped {nameof(RuntimeAuthoritativePositionDisposition)} value - add an explicit arm to {nameof(MapDisposition)} and to the public {nameof(RuntimeInitialCreatePositionDisposition)} projection."),
};
/// <summary>
/// Review fix (2026-08-02): see <see cref="MapHookPhase"/>'s remarks -
/// same explicit-arms-plus-throwing-catch-all discipline.
/// </summary>
private static RuntimeInitialCreatePositionConstrainPhase MapConstrainPhase(
RuntimePositionConstrainPhase phase) => phase switch
{
RuntimePositionConstrainPhase.None =>
RuntimeInitialCreatePositionConstrainPhase.None,
RuntimePositionConstrainPhase.BeforePositionOperation =>
RuntimeInitialCreatePositionConstrainPhase.BeforePositionOperation,
RuntimePositionConstrainPhase.AfterPositionOperation =>
RuntimeInitialCreatePositionConstrainPhase.AfterPositionOperation,
_ => throw new ArgumentOutOfRangeException(
nameof(phase),
phase,
$"Unmapped {nameof(RuntimePositionConstrainPhase)} value - add an explicit arm to {nameof(MapConstrainPhase)} and to the public {nameof(RuntimeInitialCreatePositionConstrainPhase)} projection."),
};
/// <summary>
/// C3-1: projects an internal <see cref="RuntimeInitialCreateExecutionReceipt"/>
/// to the public <see cref="RuntimeInitialCreatePlacementCompletion"/>
/// shape exactly once, at completion time (see the
/// <c>_completionReceipts</c> assignment in <see cref="ExecuteCore"/>).
/// Only Position-kind trace entries carry route facts meaningful for
/// constrain/interpolation binding; every other action kind
/// (InitialAdoption, TeleportHookRequest, replay, envelope stages, ...)
/// is intentionally excluded from <see cref="RuntimeInitialCreatePlacementCompletion.PositionRouteFacts"/> -
/// widening this to every trace entry would require making the whole
/// internal action-kind vocabulary public, which the pinned contract
/// explicitly prefers to avoid.
/// </summary>
private static RuntimeInitialCreatePlacementCompletion ProjectCompletion(
in RuntimeInitialCreateExecutionReceipt receipt)
{
ImmutableArray<RuntimeInitialCreateExecutedAction> trace = receipt.Trace;
int positionCount = 0;
for (int i = 0; i < trace.Length; i++)
{
if (trace[i].Kind == RuntimeInitialCreateExecutedActionKind.Position)
positionCount++;
}
ImmutableArray<RuntimeInitialCreatePositionRouteFact> positionFacts;
if (positionCount == 0)
{
positionFacts = ImmutableArray<RuntimeInitialCreatePositionRouteFact>.Empty;
}
else
{
var builder = ImmutableArray.CreateBuilder<RuntimeInitialCreatePositionRouteFact>(
positionCount);
for (int i = 0; i < trace.Length; i++)
{
RuntimeInitialCreateExecutedAction action = trace[i];
if (action.Kind != RuntimeInitialCreateExecutedActionKind.Position)
continue;
// Review fix (2026-08-02): PositionDisposition is nullable
// on RuntimeInitialCreateExecutedAction because it is only
// meaningful for Position/hook-request entries in general -
// but BuildPositionTrace is the SOLE constructor of
// Kind.Position entries (grep-confirmed, 5 call sites, all
// through BuildPositionTrace) and it always passes
// route.Disposition, a non-nullable enum, into this slot.
// Null is therefore NOT a legitimate state for a Position-
// kind entry specifically - a silent `?? NoPositionOperation`
// fallback here would have hidden a real bug (a future
// Position-trace producer that forgot to set it) behind a
// plausible-looking default. Fail loudly instead.
if (action.PositionDisposition is not { } disposition)
{
throw new InvalidOperationException(
"A Position-kind executor trace entry must always " +
"carry a non-null PositionDisposition - " +
"BuildPositionTrace (the sole producer of Kind.Position " +
"entries) always supplies route.Disposition.");
}
builder.Add(new RuntimeInitialCreatePositionRouteFact(
action.Sequence,
MapDisposition(disposition),
MapHookPhase(action.HookPhase),
MapConstrainPhase(action.ConstrainPhase),
action.StopInterpolating,
action.ZeroVelocity,
action.PreserveHeading,
action.SendPositionImmediately));
}
positionFacts = builder.MoveToImmutable();
}
return new RuntimeInitialCreatePlacementCompletion(
receipt.Entity,
receipt.FullCellId,
MapHookPhase(receipt.TeleportHookPhase),
positionFacts,
receipt.ReplayedDeferredChildCount);
}
/// <summary>
/// F2: reaps exactly one completion-receipt correlation entry, bound as
/// <see cref="RuntimeSetPositionState.BindExecutorCompletionAcknowledgement"/>'s
@ -461,7 +740,8 @@ internal sealed class RuntimeInitialCreateContinuationExecutor
{
if (_completionReceipts.TryGetValue(
key,
out (ulong Sequence, RuntimeInitialCreateExecutionReceipt Receipt) entry)
out (ulong Sequence, RuntimeInitialCreateExecutionReceipt Receipt,
RuntimeInitialCreatePlacementCompletion Public) entry)
&& entry.Sequence == sequence)
{
_completionReceipts.Remove(key);
@ -787,6 +1067,13 @@ internal sealed class RuntimeInitialCreateContinuationExecutor
progress.Trace.ToImmutable(),
progress.ReplayedDeferredChildCount);
receipt = completedReceipt;
// C3-1: project to the public host-consumption shape
// exactly once here, alongside the internal receipt -
// never recomputed per host read/retry (see
// ProjectCompletion's and _completionReceipts's own doc
// comments).
RuntimeInitialCreatePlacementCompletion publicCompletion =
ProjectCompletion(completedReceipt);
_progress.Remove(key);
// C0-1: bridge the executor's own completion onto the
// SAME ordered placement receipt stream every
@ -806,7 +1093,7 @@ internal sealed class RuntimeInitialCreateContinuationExecutor
canonical,
beforePublish: token =>
_completionReceipts[key] =
(token.Sequence, completedReceipt));
(token.Sequence, completedReceipt, publicCompletion));
return RuntimeInitialCreateExecutionStatus.Completed;
}
case RuntimeInitialCreateResidenceExecutorReleaseStatus.Revised:

View file

@ -12,16 +12,20 @@ public sealed class RuntimePlacementProjectionChannel
{
private readonly RuntimeEntityObjectEventStream _events;
private readonly RuntimeSetPositionState _setPosition;
private readonly RuntimeInitialCreateContinuationExecutor _initialCreateExecution;
private Func<RuntimeGenerationToken> _generation = static () => default;
private bool _generationBound;
internal RuntimePlacementProjectionChannel(
RuntimeEntityObjectEventStream events,
RuntimeSetPositionState setPosition)
RuntimeSetPositionState setPosition,
RuntimeInitialCreateContinuationExecutor initialCreateExecution)
{
_events = events ?? throw new ArgumentNullException(nameof(events));
_setPosition = setPosition
?? throw new ArgumentNullException(nameof(setPosition));
_initialCreateExecution = initialCreateExecution
?? throw new ArgumentNullException(nameof(initialCreateExecution));
}
/// <summary>
@ -81,6 +85,32 @@ public sealed class RuntimePlacementProjectionChannel
public int PendingCount => _setPosition.PendingProjectionCount;
/// <summary>
/// C3-1: the public host consumption shape for an initial-Create
/// continuation-executor drain's completion. Reached with the exact
/// <see cref="RuntimePlacementProjectionToken"/> carried by a
/// <see cref="RuntimePlacementProjectionKind.ExecutorCompleted"/> receipt
/// observed through <see cref="Subscribe"/> - the same correlation
/// identity (Entity/Sequence) every other placement Kind uses. Exposes
/// exactly the facts a cutover host needs to bind presentation off an
/// initial placement (the teleport-hook phase, the drained Position
/// continuations' route facts for constrain/interpolation binding, and
/// the replayed-deferred-child count) without widening any internal
/// Runtime type's accessibility. Returns false for a generation
/// mismatch or a stale/superseded/unknown token, mirroring every other
/// generation-gated method on this channel.
/// </summary>
public bool TryGetInitialCreateCompletion(
RuntimeGenerationToken expectedGeneration,
in RuntimePlacementProjectionToken token,
out RuntimeInitialCreatePlacementCompletion completion)
{
if (IsCurrent(expectedGeneration))
return _initialCreateExecution.TryGetCompletion(token, out completion);
completion = default;
return false;
}
private bool IsCurrent(RuntimeGenerationToken expectedGeneration) =>
_generationBound
&& expectedGeneration.Value != 0UL

View file

@ -1,5 +1,6 @@
using System.Collections.Immutable;
using System.Numerics;
using System.Reflection;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
@ -4319,6 +4320,264 @@ public sealed class RuntimeInitialCreateContinuationExecutorTests
observed[1].Token));
}
// ---------------------------------------------------------------
// C3-1: the public RuntimePlacementProjectionChannel host consumption
// surface for an executor completion (TryGetInitialCreateCompletion).
// ---------------------------------------------------------------
[Fact]
public void PlacementChannel_TryGetInitialCreateCompletion_ProjectsHookPhaseCellAndReplayCount()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 420UL);
const uint guid = 0x70024020u;
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);
var generation = new RuntimeGenerationToken(420UL);
Assert.True(lifetime.Placements.TryGetInitialCreateCompletion(
generation,
completion.Token,
out RuntimeInitialCreatePlacementCompletion publicCompletion));
Assert.Equal(canonical.Key, publicCompletion.Entity);
Assert.Equal(receipt.Entity, publicCompletion.Entity);
Assert.Equal(receipt.FullCellId, publicCompletion.FullCellId);
Assert.Equal(receipt.ReplayedDeferredChildCount, publicCompletion.ReplayedDeferredChildCount);
// This is a local-player Create (login): retail's init_player path
// requests the AfterEnterWorld teleport hook (see RunInitialTail) -
// the exact fact route-1/8's cutover caller needs to know whether to
// run the after-enter teleport suffix. No Position continuation ran
// in this scenario, so the route-fact array projects empty.
Assert.Equal(
RuntimeInitialCreateTeleportHookPhase.AfterEnterWorld,
publicCompletion.TeleportHookPhase);
Assert.Equal(RuntimeTeleportHookPhase.AfterEnterWorld, receipt.TeleportHookPhase);
Assert.Empty(publicCompletion.PositionRouteFacts);
}
[Fact]
public void PlacementChannel_TryGetInitialCreateCompletion_ProjectsPositionRouteFactsForConstrainInterpolationBinding()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 421UL);
const uint guid = 0x70024021u;
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 and lands a Position trace entry with real
// route facts - the same scenario as
// ExecutorCompletion_ObservedOnlyAfterAnyContinuationPlacementInFifoOrder.
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);
RuntimePlacementProjectionSnapshot executorCompletion = observed[^1];
Assert.Equal(
RuntimePlacementProjectionKind.ExecutorCompleted,
executorCompletion.Kind);
RuntimeInitialCreateExecutedAction internalPositionTrace = Assert.Single(
receipt.Trace.Where(
static a => a.Kind == RuntimeInitialCreateExecutedActionKind.Position));
var generation = new RuntimeGenerationToken(421UL);
Assert.True(lifetime.Placements.TryGetInitialCreateCompletion(
generation,
executorCompletion.Token,
out RuntimeInitialCreatePlacementCompletion publicCompletion));
RuntimeInitialCreatePositionRouteFact fact = Assert.Single(
publicCompletion.PositionRouteFacts);
Assert.Equal(internalPositionTrace.Sequence, fact.Sequence);
Assert.Equal(internalPositionTrace.StopInterpolating, fact.StopInterpolating);
Assert.Equal(internalPositionTrace.ZeroVelocity, fact.ZeroVelocity);
Assert.Equal(internalPositionTrace.PreserveHeading, fact.PreserveHeading);
Assert.Equal(
internalPositionTrace.SendPositionImmediately,
fact.SendPositionImmediately);
Assert.Equal(
internalPositionTrace.PositionDisposition!.Value.ToString(),
fact.Disposition.ToString());
Assert.Equal(
internalPositionTrace.ConstrainPhase.ToString(),
fact.ConstrainPhase.ToString());
Assert.Equal(
internalPositionTrace.HookPhase.ToString(),
fact.HookPhase.ToString());
}
[Fact]
public void PlacementChannel_TryGetInitialCreateCompletion_RejectsWrongGeneration()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 422UL);
const uint guid = 0x70024022u;
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)));
RunToCompletion(lifetime, canonical, lease.Token, NoContact);
RuntimePlacementProjectionSnapshot completion = Assert.Single(observed);
Assert.False(lifetime.Placements.TryGetInitialCreateCompletion(
new RuntimeGenerationToken(999UL),
completion.Token,
out RuntimeInitialCreatePlacementCompletion stale));
Assert.Equal(default, stale);
}
[Fact]
public void PlacementChannel_TryGetInitialCreateCompletion_ReturnsFalseAfterAcknowledgeReapsTheCorrelationEntry()
{
using RuntimeEntityObjectLifetime lifetime = EngineLifetime();
Bind(lifetime, 423UL);
const uint guid = 0x70024023u;
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.True(lifetime.Physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot completion));
var generation = new RuntimeGenerationToken(423UL);
Assert.True(lifetime.Placements.TryGetInitialCreateCompletion(
generation,
completion.Token,
out _));
Assert.True(lifetime.Placements.Acknowledge(generation, completion.Token));
Assert.False(lifetime.Placements.TryGetInitialCreateCompletion(
generation,
completion.Token,
out RuntimeInitialCreatePlacementCompletion afterAck));
Assert.Equal(default, afterAck);
}
/// <summary>
/// Review fix (2026-08-02): the compiler's exhaustiveness net for the
/// three enum-mirror switches (<c>MapHookPhase</c>/<c>MapDisposition</c>/
/// <c>MapConstrainPhase</c>) is gone the moment a catch-all arm exists -
/// that is exactly why those catch-alls now throw instead of silently
/// defaulting. This reflection-based test is the runtime replacement:
/// for each (internal enum, public projection enum, private mapper
/// method) triple it asserts equal arity AND drives every declared
/// internal value through the mapper via reflection (the methods are
/// `private static`), asserting the mapped public value's NAME equals
/// the internal value's name (every mapper is a literal 1:1 name
/// mirror by design - see each public enum's own doc comment). Adding a
/// new member to either enum without updating the other and the mapper
/// fails this test immediately, mirroring
/// <c>OperationResetAllFieldsToDefaultTouchesEveryDeclaredField</c>'s
/// reflection-based completeness guard for
/// <see cref="RuntimeSetPositionState"/>'s pooled Operation fields.
/// Sabotage-verified during development: temporarily adding an extra
/// member to <c>RuntimeTeleportHookPhase</c> (with no matching arm in
/// <c>MapHookPhase</c> or the public
/// <see cref="RuntimeInitialCreateTeleportHookPhase"/> enum) failed this
/// test exactly as predicted - both the arity assertion and the
/// unhandled-value invocation threw - before the sabotage was reverted.
/// </summary>
[Fact]
public void EnumProjectionMapsHaveEqualArityAndEveryInternalValueRoundTripsByName()
{
AssertMapIsCompleteAndNamePreserving(
typeof(RuntimeTeleportHookPhase),
typeof(RuntimeInitialCreateTeleportHookPhase),
"MapHookPhase");
AssertMapIsCompleteAndNamePreserving(
typeof(RuntimeAuthoritativePositionDisposition),
typeof(RuntimeInitialCreatePositionDisposition),
"MapDisposition");
AssertMapIsCompleteAndNamePreserving(
typeof(RuntimePositionConstrainPhase),
typeof(RuntimeInitialCreatePositionConstrainPhase),
"MapConstrainPhase");
}
private static void AssertMapIsCompleteAndNamePreserving(
Type internalEnumType,
Type publicEnumType,
string mapMethodName)
{
MethodInfo? method = typeof(RuntimeInitialCreateContinuationExecutor)
.GetMethod(
mapMethodName,
BindingFlags.NonPublic | BindingFlags.Static);
Assert.True(
method is not null,
$"{nameof(RuntimeInitialCreateContinuationExecutor)} no longer " +
$"declares a private static method named {mapMethodName} - " +
"update this test's reflection lookup to match.");
Array internalValues = Enum.GetValues(internalEnumType);
Array publicValues = Enum.GetValues(publicEnumType);
// Equal arity: every internal value must have exactly one public
// counterpart and vice versa. A mismatch here is the first sign
// either enum grew without the other (or the mapper) being updated
// to match.
Assert.True(
internalValues.Length == publicValues.Length,
$"{internalEnumType.Name} has {internalValues.Length} values " +
$"but {publicEnumType.Name} has {publicValues.Length} - keep " +
"the internal/public enum pair in lockstep.");
foreach (object? internalValue in internalValues)
{
// Invoked via reflection deliberately - a value this mapper
// cannot handle now throws ArgumentOutOfRangeException (see
// the mapper's own doc comment), which TargetInvocationException
// propagates through Invoke and fails this test with a clear
// message identifying exactly which enum member is unmapped.
object? mapped = method!.Invoke(null, [internalValue]);
Assert.Equal(internalValue!.ToString(), mapped!.ToString());
}
}
[Fact]
public void ExecutorCompletion_ReceiptIsReadableFromWithinTheSameSynchronousOnPlacementDispatch()
{