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

@ -1,4 +1,5 @@
using System.Collections.Immutable;
using System.Numerics;
using AcDream.Core.Items;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
@ -44,7 +45,18 @@ public readonly record struct RuntimeEntityObjectOwnershipSnapshot(
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)
bool HasLastReplayFailure = false,
/// <summary>
/// F2: outstanding <see cref="RuntimeInitialCreateContinuationExecutor"/>
/// token-to-receipt correlation entries - one per completed drain whose
/// ExecutorCompleted receipt a host has not yet acknowledged. Gated by
/// <see cref="IsConverged"/>, mirroring
/// <see cref="RuntimeSetPositionOwnershipSnapshot.PendingProjectionAcknowledgementCount"/>'s
/// existing "unacknowledged receipt is outstanding debt" shape for the
/// SAME underlying receipt stream - unlike ReplayFailureCount above,
/// this is NOT a diagnostic-only counter.
/// </summary>
int PendingCompletionReceiptCount = 0)
{
public bool IsConverged =>
IsDisposed
@ -65,6 +77,7 @@ public readonly record struct RuntimeEntityObjectOwnershipSnapshot(
&& PendingMoveCount == 0
&& InitialCreateResidenceLeaseCount == 0
&& InitialCreateExecutorProgressCount == 0
&& PendingCompletionReceiptCount == 0
&& StreamSubscriberCount == 0
&& PlacementStreamSubscriberCount == 0
&& PendingDispatchCount == 0
@ -152,6 +165,13 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
// residence state referencing the executor type directly.
InitialCreateResidences.BindRetirementNotification(
key => InitialCreateExecution.DiscardProgress(key));
// F2: reaps the executor's completion-receipt correlation entry
// exactly when a host acknowledges the ExecutorCompleted receipt it
// correlates - mirrors the residence-retirement binding immediately
// above.
Physics.SetPosition.BindExecutorCompletionAcknowledgement(
(key, sequence) =>
InitialCreateExecution.ForgetCompletionReceipt(key, sequence));
Placements = new RuntimePlacementProjectionChannel(
Events,
Physics.SetPosition);
@ -197,6 +217,13 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
// residence state referencing the executor type directly.
InitialCreateResidences.BindRetirementNotification(
key => InitialCreateExecution.DiscardProgress(key));
// F2: reaps the executor's completion-receipt correlation entry
// exactly when a host acknowledges the ExecutorCompleted receipt it
// correlates - mirrors the residence-retirement binding immediately
// above.
Physics.SetPosition.BindExecutorCompletionAcknowledgement(
(key, sequence) =>
InitialCreateExecution.ForgetCompletionReceipt(key, sequence));
Placements = new RuntimePlacementProjectionChannel(
Events,
Physics.SetPosition);
@ -242,6 +269,13 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
// residence state referencing the executor type directly.
InitialCreateResidences.BindRetirementNotification(
key => InitialCreateExecution.DiscardProgress(key));
// F2: reaps the executor's completion-receipt correlation entry
// exactly when a host acknowledges the ExecutorCompleted receipt it
// correlates - mirrors the residence-retirement binding immediately
// above.
Physics.SetPosition.BindExecutorCompletionAcknowledgement(
(key, sequence) =>
InitialCreateExecution.ForgetCompletionReceipt(key, sequence));
Placements = new RuntimePlacementProjectionChannel(
Events,
Physics.SetPosition);
@ -292,7 +326,8 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
_disposed,
parents.DeferredAcceptedRelationCount,
InitialCreateExecution.ReplayFailureCount,
InitialCreateExecution.LastReplayFailure is not null);
InitialCreateExecution.LastReplayFailure is not null,
InitialCreateExecution.PendingCompletionReceiptCount);
}
public void BindEventContext(
@ -306,6 +341,25 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
InitialCreateExecution.BindGeneration(generation);
}
/// <summary>
/// C0-2: forwards to <see cref="RuntimeInitialCreateContinuationExecutor.BindLiveInputs"/>,
/// the same fan-out shape <see cref="BindEventContext"/> already uses for
/// generation binding. Separate from <see cref="BindEventContext"/>
/// because <c>GameRuntime</c> constructs <c>RuntimeCharacterState</c>/
/// <c>RuntimeLocalPlayerMovementState</c> (the real source owners) AFTER
/// this lifetime, so the live-input bind necessarily happens at a later
/// point in <c>GameRuntime</c>'s construction sequence than the
/// generation bind.
/// </summary>
public void BindLiveInputs(
Func<bool> usePositionFromServer,
Func<Vector3?> localPlayerPosition)
{
EnsureNotDisposed();
InitialCreateExecution.BindLiveInputs(
usePositionFromServer, localPlayerPosition);
}
/// <summary>
/// Owns the presentation-free half of retail's CreateObject lifetime
/// transaction. An attached graphical host may synchronously retire the
@ -963,6 +1017,28 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
}
Entities.RefreshSnapshot(canonical, accepted);
// C0-4(a): this method had NO cancellation choke-point at all,
// leaving a residence lease or ordinary SetPosition operation
// dangling once ordinary placement traffic goes live - fixed with
// the SAME exactly-once ForgetInitialCreateResidence ->
// Physics.SetPosition.Forget -> PreferCancellation sequence every
// other commit in this family (CommitPositionChannelUpdate,
// TryApplyPickup, CommitAcceptedParentCellless, TryAcceptDelete)
// uses for THIS part of the job.
// F4 (deliberate, NOT an oversight): unlike CommitPositionChannelUpdate,
// this method does NOT also call Physics.CollisionReports.LeaveWorld.
// Retail set_parent (0x00515A90, lines 283832-283833) performs its
// single leave_world call gated behind the SAME add_child branch this
// method's staged/deferred-replay commit represents (App's
// EquippedChildRenderController realize sequence, the executor's own
// ParentRelationReplay) - a second LeaveWorld here would double-leave-
// world with no retail counterpart.
RuntimePlacementCancellationReceipt initialCancellation =
ForgetInitialCreateResidence(canonical);
RuntimePlacementCancellationReceipt ordinaryCancellation =
Physics.SetPosition.Forget(canonical);
RuntimePlacementCancellationReceipt cancellation =
PreferCancellation(initialCancellation, ordinaryCancellation);
Entities.AdvanceParentCommit(canonical);
ulong parentCommitVersion = canonical.ParentCommitVersion;
return AcknowledgeProjectionAndPublish(
@ -970,7 +1046,8 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
() => acknowledgeProjection?.Invoke(canonical),
RuntimeEntityChange.Updated,
() => canonical.ParentCommitVersion
== parentCommitVersion);
== parentCommitVersion,
cancellation);
}
public bool CommitAcceptedParentCellless(
@ -1407,9 +1484,21 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
if (!Entities.IsCurrent(canonical))
return false;
RuntimePlacementCancellationReceipt cancellation =
// C0-4(b): this cancelled the initial-create residence but never the
// ORDINARY placement family (Physics.SetPosition.Forget), unlike
// TryApplyPickup/CommitAcceptedParentCellless/TryAcceptDelete, which
// all cancel both. WithdrawLiveEntityProjectionToCellless routes
// through here, so a live ordinary SetPosition/lost-cell watch could
// dangle across a withdrawal-to-cellless once ordinary placement
// traffic goes live. Fixed symmetrically with the same
// Forget/PreferCancellation pair every sibling withdrawal uses.
RuntimePlacementCancellationReceipt initialCancellation =
ForgetInitialCreateResidence(canonical);
Physics.CollisionReports.LeaveWorld(canonical);
RuntimePlacementCancellationReceipt ordinaryCancellation =
Physics.SetPosition.Forget(canonical);
RuntimePlacementCancellationReceipt cancellation =
PreferCancellation(initialCancellation, ordinaryCancellation);
Entities.SuspendObjectClock(canonical);
Entities.SetFullCell(canonical, 0u, 0u);
ulong spatialVersion = canonical.SpatialAuthorityVersion;