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

@ -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()
{