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,7 @@
using System.Collections.Immutable;
using System.Numerics;
using AcDream.Content;
using AcDream.Content.Pak;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
@ -2247,6 +2249,263 @@ public sealed class RuntimeSetPositionStateTests
placed.Token));
}
// ---------------------------------------------------------------
// C0-3: exact-Setup mover chain end-to-end
// ---------------------------------------------------------------
[Fact]
public void TryPrepareAndSubmitAuthoredPlacement_ChainsSetupReadThroughPrepareMoverToSubmit()
{
PhysicsEngine engine = FlatEngine(SourceLandblock, 0f);
using var lifetime = new RuntimeEntityObjectLifetime(engine);
RuntimeEntityRecord record = CreateRecord(lifetime, 0x70001101u, 1);
AttachBody(lifetime, record, SourceCell);
RuntimeEntityPlacementToken token = lifetime.Physics.SetPosition
.BeginAuthoredPlacement(
record,
record.PositionAuthorityVersion,
RuntimeSetPositionOperationKind.RemoteAuthoritative);
Assert.True(token.IsValid);
// Spawn's own default SetupTableId (0x02000001u) - the exact
// CanonicalSetupTableId CapturePreparationAuthority already trusts;
// the chain must read THIS id, not a caller-supplied one.
ImmutableArray<FlatCollisionSphere> spheres =
[
new FlatCollisionSphere(new Vector3(0f, 0f, 0.5f), 0.4f),
new FlatCollisionSphere(new Vector3(0f, 0f, 1.2f), 0.4f),
];
var source = new FakeCollisionSource(
0x02000001u,
new FlatSetupCollision(
ImmutableArray<FlatCollisionCylinder>.Empty,
spheres,
height: 0f,
radius: 0f,
stepUpHeight: 0.4f,
stepDownHeight: 0.35f));
Assert.Equal(
RuntimeSetPositionMoverPreparationStatus.Prepared,
lifetime.Physics.SetPosition.TryPrepareAndSubmitAuthoredPlacement(
record,
token,
RuntimeSetPositionOperationKind.RemoteAuthoritative,
PhysicsSetPositionFlags.Placement
| PhysicsSetPositionFlags.Slide,
source,
gameTime: 10d,
out RuntimeSetPositionOutcome outcome));
Assert.Equal(
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
outcome.Status);
Assert.Equal(1, source.ReadCount);
Assert.True(lifetime.Physics.SetPosition.TryGetPreparedMoverSphereCount(
record,
out int sphereCount));
Assert.Equal(2, sphereCount);
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
outcome.Projection));
}
[Fact]
public void TryPrepareAndSubmitAuthoredPlacement_YieldsRetryOnAMissingSetupReadWithoutMutatingStage()
{
PhysicsEngine engine = FlatEngine(SourceLandblock, 0f);
using var lifetime = new RuntimeEntityObjectLifetime(engine);
RuntimeEntityRecord record = CreateRecord(lifetime, 0x70001102u, 1);
AttachBody(lifetime, record, SourceCell);
RuntimeEntityPlacementToken token = lifetime.Physics.SetPosition
.BeginAuthoredPlacement(
record,
record.PositionAuthorityVersion,
RuntimeSetPositionOperationKind.RemoteAuthoritative);
Assert.True(token.IsValid);
var source = new FakeCollisionSource(
0x02000001u,
setup: null,
status: PreparedAssetReadStatus.Missing);
Assert.Equal(
RuntimeSetPositionMoverPreparationStatus.RetrySetupUnavailable,
lifetime.Physics.SetPosition.TryPrepareAndSubmitAuthoredPlacement(
record,
token,
RuntimeSetPositionOperationKind.RemoteAuthoritative,
PhysicsSetPositionFlags.Placement
| PhysicsSetPositionFlags.Slide,
source,
gameTime: 10d,
out RuntimeSetPositionOutcome outcome));
Assert.Equal(default, outcome);
Assert.Equal(1, source.ReadCount);
// Never manufactured a fallback while the read is in flight - the
// token can still be prepared once the asset lands.
Assert.True(lifetime.Physics.SetPosition.IsPlacementCurrent(token));
Assert.False(lifetime.Physics.SetPosition.TryGetPreparedMoverSphereCount(
record,
out _));
}
// ---------------------------------------------------------------
// C0-1: executor completion on the SAME ordered placement stream
// ---------------------------------------------------------------
[Fact]
public void PublishExecutorCompletion_PublishesAcknowledgeOnlyReceiptAndConverges()
{
PhysicsEngine engine = FlatEngine(SourceLandblock, 0f);
using var lifetime = new RuntimeEntityObjectLifetime(engine);
RuntimeEntityRecord record = CreateRecord(lifetime, 0x70001201u, 1);
PhysicsBody body = AttachBody(lifetime, record, SourceCell);
var observer = new PlacementObserver();
using IDisposable subscription =
lifetime.Events.SubscribePlacement(observer);
RuntimePlacementProjectionToken token =
lifetime.Physics.SetPosition.PublishExecutorCompletion(record);
Assert.True(token.IsValid);
Assert.Equal(record.Key, token.Entity);
RuntimePlacementProjectionSnapshot published =
Assert.Single(observer.Deltas).Placement;
Assert.Equal(RuntimePlacementProjectionKind.ExecutorCompleted,
published.Kind);
Assert.Equal(token, published.Token);
Assert.Equal(body.Position, published.WorldPosition);
Assert.Equal(1, lifetime.Physics.SetPosition.PendingProjectionCount);
// Acknowledge-only, exactly like Discard - no operation to resume.
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(token));
Assert.Equal(0, lifetime.Physics.SetPosition.PendingProjectionCount);
// A stale re-acknowledge of the already-consumed receipt fails.
Assert.False(lifetime.Physics.SetPosition.AcknowledgeProjection(token));
}
[Fact]
public void PublishExecutorCompletion_RespectsExactHeadOrderingAcrossEntities()
{
PhysicsEngine engine = FlatEngine(SourceLandblock, 0f);
using var lifetime = new RuntimeEntityObjectLifetime(engine);
RuntimeEntityRecord first = CreateRecord(lifetime, 0x70001202u, 1);
AttachBody(lifetime, first, SourceCell);
RuntimeEntityRecord second = CreateRecord(lifetime, 0x70001203u, 1);
AttachBody(lifetime, second, SourceCell);
RuntimePlacementProjectionToken firstToken =
lifetime.Physics.SetPosition.PublishExecutorCompletion(first);
RuntimePlacementProjectionToken secondToken =
lifetime.Physics.SetPosition.PublishExecutorCompletion(second);
Assert.Equal(2, lifetime.Physics.SetPosition.PendingProjectionCount);
// The exact head (first) must acknowledge before the second, matching
// the ordered-stream contract every other Kind already honors.
Assert.False(lifetime.Physics.SetPosition.AcknowledgeProjection(
secondToken));
Assert.Equal(2, lifetime.Physics.SetPosition.PendingProjectionCount);
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
firstToken));
Assert.True(lifetime.Physics.SetPosition.AcknowledgeProjection(
secondToken));
Assert.Equal(0, lifetime.Physics.SetPosition.PendingProjectionCount);
}
// ---------------------------------------------------------------
// C0-4: TryCommitParent/CommitWithdrawal cancellation asymmetries
// (the ordinary Physics.SetPosition.Forget half, isolated from any
// initial-create residence)
// ---------------------------------------------------------------
[Fact]
public void TryCommitParent_CancelsASeparateActiveOrdinaryPendingPlacement()
{
PhysicsEngine engine = FlatEngine(SourceLandblock, 0f);
using var lifetime = new RuntimeEntityObjectLifetime(engine);
const uint guid = 0x70001301u;
RuntimeEntityRecord record = CreateRecord(lifetime, guid, 1);
AttachBody(lifetime, record, SourceCell);
RuntimeSetPositionOutcome outcome = lifetime.Physics.SetPosition.Apply(
record,
record.PositionAuthorityVersion,
Command(Request(SourceCell, new Vector3(12f, 18f, 7f))));
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: 0x70001400u,
ChildGuid: guid,
ParentLocation: 1u,
PlacementId: 1u,
ParentInstanceSequence: 1,
ChildPositionSequence: 1);
Assert.True(lifetime.TryCommitParent(relation, null, out _));
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership()
.ActiveOperationCount);
// The old Place receipt is REPLACED by a Discard at the same
// sequence, not removed outright - it still awaits host
// acknowledgement, exactly like every other cancelled-in-flight
// receipt.
Assert.Equal(1, lifetime.Physics.SetPosition.PendingProjectionCount);
RuntimePlacementProjectionSnapshot discard = Assert.Single(discards);
Assert.Equal(record.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);
}
[Fact]
public void CommitWithdrawal_CancelsAnActiveOrdinaryPendingPlacementSymmetricallyWithPickup()
{
PhysicsEngine engine = FlatEngine(SourceLandblock, 0f);
using var lifetime = new RuntimeEntityObjectLifetime(engine);
const uint guid = 0x70001302u;
RuntimeEntityRecord record = CreateRecord(lifetime, guid, 1);
AttachBody(lifetime, record, SourceCell);
RuntimeSetPositionOutcome outcome = lifetime.Physics.SetPosition.Apply(
record,
record.PositionAuthorityVersion,
Command(Request(SourceCell, new Vector3(12f, 18f, 7f))));
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);
}));
Assert.True(lifetime.CommitWithdrawal(record));
Assert.Equal(0, lifetime.Physics.SetPosition.CaptureOwnership()
.ActiveOperationCount);
Assert.Equal(1, lifetime.Physics.SetPosition.PendingProjectionCount);
RuntimePlacementProjectionSnapshot discard = Assert.Single(discards);
Assert.Equal(record.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 void VerifyPositionChannelCancellation(
CancellationChannel channel)
{
@ -2618,6 +2877,73 @@ public sealed class RuntimeSetPositionStateTests
}
}
/// <summary>
/// C0-3 test double: a minimal <see cref="IPreparedCollisionSource"/>
/// serving exactly one Setup id (matching <c>CanonicalSetupTableId</c>'s
/// field), so <see cref="TryPrepareAndSubmitAuthoredPlacement_ChainsSetupReadThroughPrepareMoverToSubmit"/>
/// can prove <c>ReadSetupCollision</c> -&gt; <c>PrepareMover</c> -&gt;
/// <c>SubmitPreparedPlacement</c> actually chains, not merely that each
/// step works in isolation (the existing preparer tests' coverage).
/// </summary>
private sealed class FakeCollisionSource(
uint expectedSetupTableId,
FlatSetupCollision? setup,
PreparedAssetReadStatus status = PreparedAssetReadStatus.Loaded)
: IPreparedCollisionSource
{
internal int ReadCount { get; private set; }
public PreparedAssetPresence ProbeCollision(
PakAssetType type, uint sourceFileId) =>
PreparedAssetPresence.Available;
public PreparedCollisionReadResult<FlatGfxObjCollisionAsset>
ReadGfxObjCollision(
uint sourceFileId,
CancellationToken cancellationToken = default) =>
throw new NotSupportedException(
"Only ReadSetupCollision is exercised by C0-3.");
public PreparedCollisionReadResult<FlatSetupCollision>
ReadSetupCollision(
uint sourceFileId,
CancellationToken cancellationToken = default)
{
ReadCount++;
Assert.Equal(expectedSetupTableId, sourceFileId);
return status switch
{
PreparedAssetReadStatus.Loaded when setup is not null =>
PreparedCollisionReadResult<FlatSetupCollision>.Loaded(
setup),
PreparedAssetReadStatus.Corrupt =>
PreparedCollisionReadResult<FlatSetupCollision>.Corrupt,
_ => PreparedCollisionReadResult<FlatSetupCollision>.Missing,
};
}
public PreparedCollisionReadResult<FlatCellStructureCollisionAsset>
ReadCellStructureCollision(
uint sourceFileId,
CancellationToken cancellationToken = default) =>
throw new NotSupportedException(
"Only ReadSetupCollision is exercised by C0-3.");
public PreparedCollisionReadResult<FlatEnvCellTopology>
ReadEnvCellTopology(
uint sourceFileId,
CancellationToken cancellationToken = default) =>
throw new NotSupportedException(
"Only ReadSetupCollision is exercised by C0-3.");
public PreparedCollisionSourceStats CollisionStats =>
new(ReadCount, ReadCount, ReadCount, 0, 0);
public void Dispose()
{
}
}
private sealed class CollisionReportObserver
: IRuntimeCollisionReportObserver
{