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:
parent
27e05b99e4
commit
67f63e85e5
14 changed files with 1639 additions and 11 deletions
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue