feat(runtime): execute initial placement continuations

The admission checkpoint (30012361) sealed accepted updates behind a
pending initial placement; nothing could apply them, so AcknowledgeAdoption
refused any non-empty FIFO and the residence system had no path to
completion. RuntimeInitialCreateContinuationExecutor is that missing
mechanism: a synchronous, retry-idempotent Execute transaction that adopts
the acknowledged initial placement exactly once (consuming the retained
completion so later authored placements for the key can begin), emits the
AfterEnterWorld hook request for the local player, replays deferred
missing-parent raw Creates and queued parent relations by parent GUID
(retail ProcessObjectNetBlobs order: whole-bucket detach, FIFO dispatch,
cancellation-aware restore), and drains the mixed continuation FIFO
strictly by sequence with retail route decisions taken at execution time
via ClassifyAcceptedPosition on live inputs (server-asserted wire contact,
data-driven animation proxy, live distance/options).

Apply bodies are shared with the legacy fused paths through new gate-less
instance seams on InboundPhysicsStateController that keep the one snapshot
store in lockstep; SameIncarnationCreate envelopes apply atomically with
per-stage idempotency and buffered publication after the final stage;
every abandonment path retires the residence through the lifetime choke
point and converges the ownership ledger (executor progress, deferred
buckets, replay windows, placement watches all folded into IsConverged).
Position/placement side effects are exactly-once under retry, external
mutations are detected via a field-masked executor baseline, and
AwaitingContinuationPlacement yields keep the FIFO head retryable.

Production routes are deliberately untouched: graphical and headless
Create still use legacy RegisterEntity, and no host calls Execute. The
cutover is the next checkpoint; AP-1/AD-1 remain open until it lands.
Register rows AD-59/AD-60/AP-130/AP-131/AP-132/TS-62/TS-63 document the
slice's deviations in this commit.

Reviewed: retail-conformance PASS + architecture/adversarial PASS after
five implementation rounds (wire-contact source, snapshot lockstep,
WeenieDescription merge, abandonment convergence, reentrant retirement
windows, acknowledged-completion leak, baseline precision, replay
containment/restore, queue-by-parent-GUID relation deferral all fixed at
root cause). Runtime tests 903/903; complete Release solution 10,696
passed / 4 intentional skips; focused executor gate 161/161.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-02 03:49:56 +02:00
parent 4a8f74dc72
commit 5db3de3c7a
9 changed files with 8002 additions and 86 deletions

View file

@ -31,6 +31,7 @@ public readonly record struct RuntimeEntityObjectOwnershipSnapshot(
int EquipmentOwnerCount,
int PendingMoveCount,
int InitialCreateResidenceLeaseCount,
int InitialCreateExecutorProgressCount,
int StreamSubscriberCount,
int PlacementStreamSubscriberCount,
long StreamDispatchFailureCount,
@ -38,7 +39,12 @@ public readonly record struct RuntimeEntityObjectOwnershipSnapshot(
int PendingDispatchCount,
bool IsDispatching,
bool IsSessionClearInProgress,
bool IsDisposed)
bool IsDisposed,
/// <summary>Round 5 R5-1: pending queue-by-parent-GUID accepted relations (see <see cref="ParentAttachmentState.DeferredAcceptedRelationCount"/>).</summary>
int DeferredAcceptedRelationCount = 0,
/// <summary>Round 5 R5-3: mirrors StreamDispatchFailureCount/HasLastStreamDispatchFailure for the executor's contained-replay failure surface. Diagnostic only - like its stream precedent, NOT gated by <see cref="IsConverged"/>.</summary>
long ReplayFailureCount = 0,
bool HasLastReplayFailure = false)
{
public bool IsConverged =>
IsDisposed
@ -48,6 +54,7 @@ public readonly record struct RuntimeEntityObjectOwnershipSnapshot(
&& AcceptedSnapshotCount == 0
&& UnresolvedParentRelationCount == 0
&& DeferredParentCreateCount == 0
&& DeferredAcceptedRelationCount == 0
&& StagedParentRelationCount == 0
&& RecoveryParentRelationCount == 0
&& CommittedParentRelationCount == 0
@ -57,6 +64,7 @@ public readonly record struct RuntimeEntityObjectOwnershipSnapshot(
&& EquipmentOwnerCount == 0
&& PendingMoveCount == 0
&& InitialCreateResidenceLeaseCount == 0
&& InitialCreateExecutorProgressCount == 0
&& StreamSubscriberCount == 0
&& PlacementStreamSubscriberCount == 0
&& PendingDispatchCount == 0
@ -127,6 +135,23 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
InitialCreateResidences = new RuntimeInitialCreateResidenceState(
Entities,
Physics.SetPosition);
InitialCreateExecution = new RuntimeInitialCreateContinuationExecutor(
Entities,
InitialCreateResidences,
Physics,
Events,
(spawn, isLocalPlayer) =>
RegisterEntityWithInitialResidence(spawn, isLocalPlayer),
(canonical, version, spawn, replaceGeneration) =>
ApplyAcceptedSpawn(canonical, version, spawn, replaceGeneration));
// Round 3 B3: every residence retirement path - not only the
// executor's own DiscardProgress calls - must converge the
// executor's progress AND its separately-tracked pending
// continuation placement token. This class owns both sides of the
// relationship, so it binds the delegate here rather than the
// residence state referencing the executor type directly.
InitialCreateResidences.BindRetirementNotification(
key => InitialCreateExecution.DiscardProgress(key));
Placements = new RuntimePlacementProjectionChannel(
Events,
Physics.SetPosition);
@ -155,6 +180,23 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
InitialCreateResidences = new RuntimeInitialCreateResidenceState(
Entities,
Physics.SetPosition);
InitialCreateExecution = new RuntimeInitialCreateContinuationExecutor(
Entities,
InitialCreateResidences,
Physics,
Events,
(spawn, isLocalPlayer) =>
RegisterEntityWithInitialResidence(spawn, isLocalPlayer),
(canonical, version, spawn, replaceGeneration) =>
ApplyAcceptedSpawn(canonical, version, spawn, replaceGeneration));
// Round 3 B3: every residence retirement path - not only the
// executor's own DiscardProgress calls - must converge the
// executor's progress AND its separately-tracked pending
// continuation placement token. This class owns both sides of the
// relationship, so it binds the delegate here rather than the
// residence state referencing the executor type directly.
InitialCreateResidences.BindRetirementNotification(
key => InitialCreateExecution.DiscardProgress(key));
Placements = new RuntimePlacementProjectionChannel(
Events,
Physics.SetPosition);
@ -183,6 +225,23 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
InitialCreateResidences = new RuntimeInitialCreateResidenceState(
Entities,
Physics.SetPosition);
InitialCreateExecution = new RuntimeInitialCreateContinuationExecutor(
Entities,
InitialCreateResidences,
Physics,
Events,
(spawn, isLocalPlayer) =>
RegisterEntityWithInitialResidence(spawn, isLocalPlayer),
(canonical, version, spawn, replaceGeneration) =>
ApplyAcceptedSpawn(canonical, version, spawn, replaceGeneration));
// Round 3 B3: every residence retirement path - not only the
// executor's own DiscardProgress calls - must converge the
// executor's progress AND its separately-tracked pending
// continuation placement token. This class owns both sides of the
// relationship, so it binds the delegate here rather than the
// residence state referencing the executor type directly.
InitialCreateResidences.BindRetirementNotification(
key => InitialCreateExecution.DiscardProgress(key));
Placements = new RuntimePlacementProjectionChannel(
Events,
Physics.SetPosition);
@ -197,6 +256,8 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
public RuntimePlacementProjectionChannel Placements { get; }
internal RuntimeInitialCreateResidenceState InitialCreateResidences
{ get; }
internal RuntimeInitialCreateContinuationExecutor InitialCreateExecution
{ get; }
public RuntimeEntityObjectOwnershipSnapshot CaptureOwnership()
{
@ -220,6 +281,7 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
Objects.PendingMoveCount,
initialResidence.ActiveLeaseCount
+ initialResidence.PendingAdoptionCount,
InitialCreateExecution.ProgressCount,
Events.SubscriberCount,
Events.PlacementSubscriberCount,
Events.DispatchFailureCount,
@ -227,7 +289,10 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
Events.PendingDispatchCount,
Events.IsDispatching,
_sessionClearInProgress,
_disposed);
_disposed,
parents.DeferredAcceptedRelationCount,
InitialCreateExecution.ReplayFailureCount,
InitialCreateExecution.LastReplayFailure is not null);
}
public void BindEventContext(
@ -238,6 +303,7 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
Events.BindContext(generation, frameNumber);
Placements.BindGeneration(generation);
InitialCreateResidences.BindGeneration(generation);
InitialCreateExecution.BindGeneration(generation);
}
/// <summary>
@ -1515,6 +1581,7 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
_sessionClearInProgress = true;
RuntimeEntityRecord[] active = Entities.ActiveRecords.ToArray();
InitialCreateResidences.Clear();
InitialCreateExecution.DiscardAll();
Physics.CollisionReports.LeaveWorldBatch(active);
Physics.ResetSessionPhysics();
Entities.BeginSessionClear();
@ -2028,12 +2095,20 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
private RuntimePlacementCancellationReceipt ForgetInitialCreateResidence(
RuntimeEntityRecord canonical)
{
return InitialCreateResidences.Forget(
bool forgotten = InitialCreateResidences.Forget(
canonical,
out _,
out RuntimePlacementCancellationReceipt cancellation)
? cancellation
: default;
out RuntimePlacementCancellationReceipt cancellation);
// Round 3 B3: InitialCreateResidences.Forget's own retirement
// notification already routes to InitialCreateExecution.DiscardProgress
// for a successful Forget. This explicit call is defensive-in-depth
// for the (record never held a residence) case where Forget returns
// false without ever reaching the notification - DiscardProgress is
// idempotent, so a redundant call after a successful Forget is a
// guaranteed no-op, never a double-discard.
if (canonical.Key is { } key)
InitialCreateExecution.DiscardProgress(key);
return forgotten ? cancellation : default;
}
private static RuntimePlacementCancellationReceipt PreferCancellation(