feat(physics): C4 route 4b-1 — remote placement infrastructure (dormant)

Builds the machinery route 4b-2 and 4b-3 will flip on, and changes no remote
behaviour: it has no production caller, so RemotePlacementDrivePendingCount is
provably 0 and IsConverged is unchanged.

Five pieces: a per-entity remote placement owner (RuntimeRemotePlacementDriveController),
a Position-time service-window guard with a Runtime interface plus BOTH host
implementations, N3's headless RetryPending pump, parked-count observability in
the ownership ledger, and the service-window optimisation that avoids parks we
can cheaply predict.

Landed alone because it is where the park-withdraws-the-entity failure was
decided; that decision is fixed at the source in the preceding commit and must
not share a review signal with a behaviour flip.

Two parts of route 2's controller are deliberately NOT ported, both verified
against retail rather than assumed. There is no ack: SendPositionEvent is called
only inside HandleReceivedPosition's local-player FORCE_POSITION gate
@0x0045400C-@0x00454091, and the remote arm @0x0045414D has no equivalent. There
is no re-issue funnel: retail never re-attempts a position it could not apply —
stale timestamps merely bump error_count @0x004542AC — and re-issuing packet N
after N+1 has merged would apply a pose the newer packet already superseded,
which is correct for a one-shot ForcePosition and wrong for a 5-10 Hz stream.

The service-window guard is an OPTIMISATION, not the correctness mechanism. The
original contract had it the other way round, justified by a claim that retail
cannot represent "arrived but not placeable" — false, and corrected in the
review findings: retail's GotoLostCell/reenter_visibility path represents it
exactly. A pre-flight guard also cannot be complete, because Core defers on the
entity's CURRENT cell, on the swept QueriedCellIds footprint spanning
neighbouring landblocks, and on residency evaluated after AdjustToOutside —
conditions only Core can see.

Review found and this commit fixes: DetachRoute cleared two maps of LIVE Core
operations without cancelling them (route 2's AbandonPending is the correct
mirror, not the first-entry controller) and its test asserted that blindness as
convergence; the headless predicate answered "can ever publish" rather than "is
published", and after the first fix still matched only 1 of the 9 landblocks
this host publishes; OwnsPlacement admitted remote top-level Creates until
gated on the Teleport flag as well as the disposition; Advance re-submitted
without re-checking the window; and four comments cited a report that did not
exist.

Contract item 6 is met by the structural proof, not the earlier test:
HasOldPrefixPlacementDebt refuses collision-prefix mutation permission before
ParkCollisionResidents is ever entered, so its overlap throw is unreachable.
That same mechanism is the unbounded stall filed as #310, which 4b-1 does not
bound — it only avoids widening it.

#311 files the remaining per-tick allocation in RetryPendingProjections; the
early-out for the empty-FIFO case landed via a new HasPendingReceipts accessor
so hosts still never touch .Placements. directly.

Gates: complete Release solution 10,973 passed / 4 skipped / 0 failed (baseline
10,938). Four review rounds; every fix discrimination-verified by revert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-04 04:08:19 +02:00
parent 634bc5513a
commit 2e8e09acd0
14 changed files with 3151 additions and 6 deletions

View file

@ -225,6 +225,125 @@ public sealed class HeadlessSessionHostTests
Assert.True(host.Runtime.CaptureOwnership().IsConverged);
}
/// <summary>
/// B5(a) review fix: <see cref="HeadlessSessionEventRouteRetryPendingTests"/>
/// proved the underlying re-offer MECHANISM works, but hand-constructed
/// <see cref="HeadlessSessionEventRoute"/> directly and called
/// <c>route.RetryPending()</c> itself — it never touches
/// <see cref="HeadlessSessionHost.Tick"/>'s own
/// <c>_eventRoute?.RetryPending()</c> call. This test drives <c>Tick</c>
/// itself (via the <c>placementSinkOverride</c> test seam added for this
/// fix, mirroring the existing <c>policyOverride</c> parameter) so a
/// regression that deletes or reorders that exact line would fail HERE,
/// not just in the lower-level subscription test.
/// </summary>
[Fact]
public void TickRetriesAPreviouslyDeclinedPlacementThroughTheRealEventRoute()
{
const uint remote = 0x70004301u;
const uint landblock = 0xA9B40000u;
const uint cell = landblock | 0x0001u;
const float height = 6f;
var operations = new FixtureSessionOperations();
using var credential = new HeadlessCredentialSecret(
"fixture",
"password");
var sink = new DecliningThenAcceptingPlacementSink();
using var host = new HeadlessSessionHost(
Descriptor(),
credential,
new HeadlessDiagnosticWriter(TextWriter.Null),
operations,
placementSinkOverride: sink);
GameRuntime runtime = host.Runtime;
Assert.Equal(
RuntimeSessionStartStatus.Connected,
host.Start().Status);
runtime.EntityObjects.Physics.ObserveLocalWorldFrame(
cell, teleportAdvanced: false);
runtime.EntityObjects.Physics.SetPosition.BeginCollisionGeneration(
landblock, 1UL);
AddFlatLandblock(runtime.EntityObjects.Physics.Engine);
runtime.EntityObjects.Physics.SetPosition.CommitCollisionGeneration(
landblock, 1UL, ready: true);
RuntimeEntityRecord record = runtime.EntityObjects
.RegisterEntity(Spawn(remote, cell))
.Canonical!;
runtime.EntityObjects.Entities.SetFinalPhysicsState(
record, PhysicsStateFlags.Gravity);
runtime.EntityObjects.Entities.SetFullCell(
record, cell, landblock);
var body = new PhysicsBody
{
Position = new Vector3(10f, 10f, height),
Orientation = Quaternion.Identity,
LastUpdateTime = 1d,
State = PhysicsStateFlags.Gravity,
TransientState = TransientStateFlags.Active,
};
body.SnapToCell(cell, body.Position, body.Position);
runtime.EntityObjects.Entities.SetPhysicsBody(record, body);
record.ObjectClock.Activate();
runtime.EntityObjects.Physics.AcknowledgeSpatialProjection(
record, spatial: true);
RuntimeEntityPlacementToken token = runtime.EntityObjects.Physics
.SetPosition.TryBeginExclusiveAuthoredPlacement(
record,
record.PositionAuthorityVersion,
RuntimeSetPositionOperationKind.RemoteAuthoritative);
Assert.True(token.IsValid);
RuntimeSetPositionMoverPreparationStatus status = runtime.EntityObjects
.Physics.SetPosition.TryPrepareAndSubmitAuthoredPlacement(
record,
token,
RuntimeSetPositionOperationKind.RemoteAuthoritative,
PhysicsSetPositionFlags.Teleport | PhysicsSetPositionFlags.Slide,
new LoadedSetupCollisionSource(),
gameTime: runtime.Clock.SimulationTimeSeconds,
out RuntimeSetPositionOutcome outcome,
resolveWorldOffsetFromRuntimeFrame: true);
Assert.Equal(RuntimeSetPositionMoverPreparationStatus.Prepared, status);
Assert.Equal(
RuntimeSetPositionStatus.CommittedHostAcknowledgementPending,
outcome.Status);
// The production HeadlessSessionEventRoute's subscription attached
// during host.Start() already observed this Place synchronously —
// the fake sink is still declining, so it must remain unacknowledged.
Assert.Equal(1, sink.CallCount);
Assert.True(
runtime.EntityObjects.Physics.SetPosition.TryPeekProjection(
out _));
// The sink starts accepting (mirrors a landblock finishing streaming
// in) — driving ONE real host tick is what must re-offer the head,
// through Tick's own wiring, not a hand-built route.
sink.Accept = true;
host.Tick(0.015d);
Assert.Equal(2, sink.CallCount);
Assert.False(
runtime.EntityObjects.Physics.SetPosition.TryPeekProjection(
out _));
}
private sealed class DecliningThenAcceptingPlacementSink
: IRuntimePlacementProjectionSink
{
internal int CallCount { get; private set; }
internal bool Accept { get; set; }
public bool TryApply(in RuntimePlacementProjectionSnapshot projection)
{
CallCount++;
return Accept;
}
}
[Fact]
public void WorldProjectionHydratesCanonicalMovementAndTeleportState()
{