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:
parent
634bc5513a
commit
2e8e09acd0
14 changed files with 3151 additions and 6 deletions
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue