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

@ -2379,6 +2379,82 @@ public sealed class RuntimeInitialCreateResidenceStateTests
Assert.Single(retained.Continuations).Kind);
}
// ---------------------------------------------------------------
// C0-4: TryCommitParent/CommitWithdrawal cancellation asymmetries
// ---------------------------------------------------------------
[Fact]
public void TryCommitParent_CancelsActiveInitialResidenceAndItsPendingPlacement()
{
var engine = new PhysicsEngine { DataCache = new PhysicsDataCache() };
engine.AddLandblock(
Landblock,
new TerrainSurface(new byte[81], new float[256]),
Array.Empty<CellSurface>(),
Array.Empty<PortalPlane>(),
worldOffsetX: 0f,
worldOffsetY: 0f);
using var lifetime = new RuntimeEntityObjectLifetime(engine);
Bind(lifetime, 200UL);
const uint guid = 0x70004001u;
RuntimeEntityRecord canonical = lifetime
.RegisterEntityWithInitialResidence(
Spawn(guid, 1, setupId: null),
isLocalPlayer: false)
.Canonical!;
Assert.True(lifetime.TryGetInitialCreateResidence(
canonical,
out RuntimeInitialCreateResidenceLease lease));
// Unparented -> the classifier's route performs SetPosition, so
// Own() has already begun the lease's own placement operation.
// Submit it (a live Place receipt now sits unacknowledged) so
// cancellation has an ACTUAL outstanding receipt to discard, not
// merely an unpublished AwaitingPreparation operation.
Assert.True(lease.Placement.IsValid);
AttachDormantBody(lifetime, canonical);
RuntimeSetPositionOutcome outcome = lifetime.Physics.SetPosition
.SubmitPreparedPlacement(
lease.Placement,
Prepare(lifetime, lease, RuntimeSetPositionMoverSetup.ResolvedAbsent));
Assert.Equal(
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
outcome.Status);
Assert.Equal(1, lifetime.Physics.SetPosition.PendingProjectionCount);
var discards = new List<RuntimePlacementProjectionSnapshot>();
using IDisposable subscription = lifetime.Events.SubscribePlacement(
new PlacementObserver(delta =>
{
if (delta.Placement.Kind is RuntimePlacementProjectionKind.Discard)
discards.Add(delta.Placement);
}));
var relation = new ParentAttachmentRelation(
ParentGuid: 0x70004100u,
ChildGuid: guid,
ParentLocation: 1u,
PlacementId: 1u,
ParentInstanceSequence: 1,
ChildPositionSequence: 1);
Assert.True(lifetime.TryCommitParent(relation, null, out _));
Assert.Equal(0, lifetime.CaptureOwnership()
.InitialCreateResidenceLeaseCount);
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership()
.ActiveOperationCount);
// The old Place receipt is REPLACED by a Discard at the same
// sequence, not removed outright - still awaiting host ack.
Assert.Equal(1, lifetime.Physics.SetPosition.PendingProjectionCount);
Assert.False(lifetime.Physics.SetPosition.IsPlacementCurrent(
lease.Placement));
RuntimePlacementProjectionSnapshot discard = Assert.Single(discards);
Assert.Equal(canonical.Key, discard.Token.Entity);
Assert.Equal(outcome.Projection.Sequence, discard.Token.Sequence);
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
discard.Token));
Assert.Equal(0, lifetime.Physics.SetPosition.PendingProjectionCount);
}
private static RuntimeSetPositionCommand Prepare(
RuntimeEntityObjectLifetime lifetime,
in RuntimeInitialCreateResidenceLease lease,