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,5 +1,6 @@
using System.Collections.Immutable;
using System.Numerics;
using AcDream.Content;
using AcDream.Core.Items;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
@ -59,6 +60,17 @@ public enum RuntimePlacementProjectionKind
Withdraw,
Place,
Discard,
/// <summary>
/// C0-1: the initial-create continuation executor's own FIFO drain has
/// finished for this entity (not itself a SetPosition operation - the
/// residence lease is already released by the time this publishes).
/// Published on the SAME ordered stream every Place/Withdraw/Discard
/// receipt uses so a host learns "this entity's placement committed and
/// its FIFO drained" through the one already-built observer seam,
/// instead of a second stream/queue. Acknowledge-only, like Discard -
/// see AcknowledgeProjection's dedicated branch.
/// </summary>
ExecutorCompleted,
}
public readonly record struct RuntimePortalPlacementAuthority(
@ -432,6 +444,7 @@ internal sealed class RuntimeSetPositionState : IDisposable
private ulong _nextCollisionPrefixQuiescenceOperationId;
private ulong _nextLostDeadlineSequence;
private RuntimeEntityObjectEventStream? _events;
private Action<RuntimeEntityKey, ulong>? _executorCompletionAcknowledged;
private bool _disposed;
private readonly record struct LostDeadlineEntry(
@ -770,6 +783,87 @@ internal sealed class RuntimeSetPositionState : IDisposable
}
}
/// <summary>
/// C0-1: publishes the initial-create continuation executor's own
/// completion (its FIFO drain has finished and its residence lease is
/// already released) on the SAME ordered receipt stream every
/// Place/Withdraw/Discard uses - the pinned contract forbids a second
/// stream/queue. This is NOT an Operation-backed receipt (the executor's
/// own placement operation, if any, already committed and was
/// acknowledged earlier in the drain - see
/// RuntimeInitialCreateContinuationExecutor.ExecuteCore's call site) so
/// the token is built the same way PublishProjection assembles one, but
/// from the CANONICAL record's current authority/version/cell facts
/// (self-consistent: nothing else can move them synchronously between
/// the executor's release and this publish) instead of a now-gone
/// Operation. AcknowledgeProjection's dedicated ExecutorCompleted branch
/// treats it exactly like Discard - acknowledge-only, no operation to
/// resume or commit against.
/// </summary>
internal RuntimePlacementProjectionToken PublishExecutorCompletion(
RuntimeEntityRecord record,
Action<RuntimePlacementProjectionToken>? beforePublish = null,
RuntimePortalPlacementAuthority portal = default)
{
EnsureNotDisposed();
ArgumentNullException.ThrowIfNull(record);
if (record.Key is not { } key)
return default;
PhysicsBody? body = record.PhysicsBody;
ulong sequence = checked(++_nextProjectionSequence);
var token = new RuntimePlacementProjectionToken(
sequence,
Revision: 1UL,
key,
record.PositionAuthorityVersion,
record.SpatialAuthorityVersion,
record.PlacementCommitVersion,
_entities.SessionLifetimeVersion,
record.FullCellId,
_physics.ExpectedCollisionGeneration(record.FullCellId),
portal);
var snapshot = new RuntimePlacementProjectionSnapshot(
token,
RuntimePlacementProjectionKind.ExecutorCompleted,
body?.Position ?? Vector3.Zero,
body?.Orientation ?? Quaternion.Identity,
body?.CellPosition.Frame.Origin ?? Vector3.Zero,
body?.InContact ?? false,
body?.OnWalkable ?? false);
_pendingProjection.Add(sequence, snapshot);
// F2: register-before-publish - beforePublish runs while the token is
// already in _pendingProjection but before PublishPlacement's
// synchronous observer dispatch, so a subscriber reading back the
// executor's correlation entry from inside its OWN OnPlacement
// callback always finds it.
beforePublish?.Invoke(token);
PublishPlacement(snapshot);
return token;
}
/// <summary>
/// F2: binds the ONE notification fired when a Kind ExecutorCompleted
/// receipt is acknowledged (mirrors
/// RuntimeInitialCreateResidenceState.BindRetirementNotification's
/// existing one-bound-delegate shape). The executor uses this to reap
/// its own token-to-receipt correlation entry exactly when the receipt
/// it correlates is consumed - never before (a host might still be
/// mid-retry) and never left dangling after (unbounded per-completed-
/// entity retention).
/// </summary>
internal void BindExecutorCompletionAcknowledgement(
Action<RuntimeEntityKey, ulong> acknowledged)
{
ArgumentNullException.ThrowIfNull(acknowledged);
if (_executorCompletionAcknowledged is not null)
{
throw new InvalidOperationException(
"The executor-completion acknowledgement notification is already bound.");
}
_executorCompletionAcknowledged = acknowledged;
}
internal void ResetSession()
{
EnsureNotDisposed();
@ -1132,6 +1226,95 @@ internal sealed class RuntimeSetPositionState : IDisposable
return RuntimeSetPositionMoverPreparationStatus.Prepared;
}
/// <summary>
/// C0-3: chains the exact-Setup mover pipeline end-to-end for an
/// authored placement (initial-Create or any other authored-mover
/// operation) whose route performs SetPosition - PrepareMover /
/// RuntimeSetPositionMoverPreparer.TryBuild /
/// IPreparedCollisionSource.ReadSetupCollision already exist piecewise
/// (inventory gap a); this is the missing wiring, not a behavior change.
/// Reads the CANONICAL Setup table id from the record via
/// <see cref="CanonicalSetupTableId"/> - the SAME field
/// <see cref="CapturePreparationAuthority"/> already trusts - rather than
/// a caller-supplied id, so this can never be pointed at the wrong
/// Setup. A record with no authored Setup at all (id 0) takes retail's
/// genuine "no Setup" dummy-sphere path
/// (<see cref="RuntimeSetPositionMoverSetup.ResolvedAbsent"/>) instead of
/// reading anything; a record WITH an id but an unavailable/corrupt
/// asynchronous read yields <see cref="RuntimeSetPositionMoverPreparationStatus.RetrySetupUnavailable"/>
/// so the caller retries once the prepared-collision package lands,
/// mirroring <see cref="RuntimeSetPositionMoverSetup"/>'s own
/// doc-comment distinction between "not arrived yet" and "resolved
/// absent". Dormant: internal, no production caller - a residence
/// lease's own <c>Placement</c>/<c>Route.OperationKind</c>/
/// <c>Route.SetPositionFlags</c> are exactly the token/kind/flags this
/// takes.
/// </summary>
internal RuntimeSetPositionMoverPreparationStatus
TryPrepareAndSubmitAuthoredPlacement(
RuntimeEntityRecord record,
in RuntimeEntityPlacementToken token,
RuntimeSetPositionOperationKind operationKind,
PhysicsSetPositionFlags flags,
IPreparedCollisionSource collisionSource,
double gameTime,
out RuntimeSetPositionOutcome outcome,
PhysicsPlacementClass placementClass = PhysicsPlacementClass.Ordinary,
RuntimePortalPlacementAuthority portal = default,
Vector3 line = default,
float scatterRadiusX = 0f,
float scatterRadiusY = 0f,
uint scatterAttempts = 0u,
float shadowWorldOffsetX = 0f,
float shadowWorldOffsetY = 0f)
{
EnsureNotDisposed();
ArgumentNullException.ThrowIfNull(record);
ArgumentNullException.ThrowIfNull(collisionSource);
outcome = default;
uint setupTableId = CanonicalSetupTableId(record);
RuntimeSetPositionMoverSetup setup;
if (setupTableId == 0u)
{
setup = RuntimeSetPositionMoverSetup.ResolvedAbsent;
}
else
{
PreparedCollisionReadResult<FlatSetupCollision> read =
collisionSource.ReadSetupCollision(setupTableId);
if (read.Status != PreparedAssetReadStatus.Loaded
|| read.Data is null)
{
return RuntimeSetPositionMoverPreparationStatus
.RetrySetupUnavailable;
}
setup = RuntimeSetPositionMoverSetup.Resolved(
setupTableId, read.Data);
}
var preparation = new RuntimeSetPositionMoverPreparation(
setup,
operationKind,
gameTime,
placementClass,
flags,
line,
scatterRadiusX,
scatterRadiusY,
scatterAttempts,
shadowWorldOffsetX,
shadowWorldOffsetY,
portal);
RuntimeSetPositionMoverPreparationStatus status = PrepareMover(
token, preparation, out RuntimeSetPositionCommand command);
if (status != RuntimeSetPositionMoverPreparationStatus.Prepared)
return status;
outcome = SubmitPreparedPlacement(token, command);
return RuntimeSetPositionMoverPreparationStatus.Prepared;
}
internal bool IsExactPreparedPlacementCurrent(
RuntimeEntityRecord record,
in RuntimeEntityPlacementToken token,
@ -2313,10 +2496,24 @@ internal sealed class RuntimeSetPositionState : IDisposable
{
return false;
}
if (pending.Kind is RuntimePlacementProjectionKind.Discard)
if (pending.Kind is RuntimePlacementProjectionKind.Discard
or RuntimePlacementProjectionKind.ExecutorCompleted)
{
// C0-1: an ExecutorCompleted receipt is never Operation-backed
// (see PublishExecutorCompletion) - there is nothing to resume or
// commit against, exactly like Discard.
_pendingProjection.Remove(token.Sequence);
RetireQuiescenceProjectionSequence(token.Sequence);
if (pending.Kind is RuntimePlacementProjectionKind.ExecutorCompleted)
{
// F2: notify the executor so it can reap its own
// token-to-receipt correlation entry now - not before (a
// host might still be mid-retry against this exact
// unacknowledged receipt) and not left dangling after.
_executorCompletionAcknowledged?.Invoke(
token.Entity,
token.Sequence);
}
return true;
}
if (!_operations.TryGetValue(