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>
91 lines
4.7 KiB
C#
91 lines
4.7 KiB
C#
using AcDream.Runtime.Session;
|
|
|
|
namespace AcDream.App.Streaming;
|
|
|
|
/// <summary>
|
|
/// C4 route 4b-1: the graphical host's
|
|
/// <see cref="IRuntimeRemotePlacementServiceWindow"/> — headless already had
|
|
/// one (<c>HeadlessCollisionNeighborhood.IsWithinServiceWindow</c>,
|
|
/// <c>HeadlessSessionWorldProjection.cs:27</c>/<c>:245-257</c>); the
|
|
/// graphical host had none.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// <b>Predicate choice — <see cref="GpuWorldState.IsNearTier"/>, not
|
|
/// <see cref="GpuWorldState.IsNearTierOrPending"/>.</b> The contract asks
|
|
/// whether near-tier residency is exactly co-extensive with collision
|
|
/// publication and, if not, to propose the correct predicate rather than
|
|
/// ship the assumption. It is not — for the "pending" half specifically:
|
|
/// <c>IsNearTierOrPending</c> is true for a landblock that has only been
|
|
/// PARKED as a pending near-tier entity bucket
|
|
/// (<c>GpuWorldState.CommitEntitiesToExistingLandblockSpatialCore</c>'s
|
|
/// <c>parkIfMissing</c> branch, <c>:1919-1952</c>) — i.e. queued, not yet
|
|
/// collision-published. Using it here would let this guard say "go" for a
|
|
/// destination whose collision generation has not committed, reopening
|
|
/// exactly the DeferredCell park this route exists to prevent.
|
|
/// <see cref="GpuWorldState.IsNearTier"/> alone is the correct predicate:
|
|
/// </para>
|
|
/// <para>
|
|
/// <b>Promotion direction (verified by reading, not assumed):</b> the only
|
|
/// two writers of <c>_tierByLandblock[...] = LandblockStreamTier.Near</c> are
|
|
/// <c>GpuWorldState.CommitLandblockSpatialCore</c> (:969) and
|
|
/// <c>CommitEntitiesToExistingLandblockSpatialCore</c> (:1972), both called
|
|
/// from <c>LandblockPresentationPipeline.Advance</c>'s
|
|
/// <c>SpatialPresentationCommitted</c> stage — which runs strictly AFTER the
|
|
/// preceding <c>PresentationCommitted</c> stage, the one that drives
|
|
/// <c>LandblockPhysicsPublisher</c>'s staged collision/EnvCell advance to
|
|
/// completion (<c>LandblockPresentationPipeline.cs</c> ~826-935). A
|
|
/// landblock's tier cannot read Near before its collision has committed.
|
|
/// </para>
|
|
/// <para>
|
|
/// <b>Retirement direction (verified by reading, not assumed):</b>
|
|
/// <c>GpuWorldState.DetachNearLayer</c> (:1755-1834) flips the tier to Far as
|
|
/// the FIRST, synchronous step of a "detach-first" landblock retirement —
|
|
/// <c>LandblockRetirementCoordinator.AdoptDetachedFull</c>'s own doc comment
|
|
/// states spatial detachment has already committed by the time its retirement
|
|
/// ticket is constructed, and that ticket's <c>LandblockRetirementStage.Physics</c>
|
|
/// step (<c>_physics.AdvanceRemoval</c> → <c>RuntimeSetPositionState
|
|
/// .WithdrawCollision</c> → the collision-side retirement
|
|
/// <c>ParkCollisionResidents</c> can reach) runs strictly AFTER. A
|
|
/// landblock's tier cannot still read Near once its collision has begun
|
|
/// retiring.
|
|
/// </para>
|
|
/// <para>
|
|
/// <b>Residual, not closed:</b> a landblock-prefix collision MUTATION that is
|
|
/// not a full retirement (a live in-place quiescence/refresh while the tier
|
|
/// stays Near) is not ruled out by this reading and is not exercised by any
|
|
/// gate this route adds. <c>RuntimeSetPositionState</c>'s private
|
|
/// <c>TryGetBlockingQuiescence</c> (:3820-3863) is Core's own check for
|
|
/// exactly this case — an active <c>CollisionPrefixQuiescence</c> entry for
|
|
/// the destination's landblock prefix, independent of tier/residency — and
|
|
/// it is what a placement can still hit even after this guard passes,
|
|
/// producing the narrow <c>DeferredCell</c> residual
|
|
/// <see cref="AcDream.Runtime.Session.RuntimeRemotePlacementDriveController"/>'s
|
|
/// class doc describes.
|
|
/// </para>
|
|
/// </remarks>
|
|
internal sealed class GraphicalRemotePlacementServiceWindow
|
|
: IRuntimeRemotePlacementServiceWindow
|
|
{
|
|
private readonly GpuWorldState _state;
|
|
|
|
internal GraphicalRemotePlacementServiceWindow(GpuWorldState state)
|
|
{
|
|
_state = state ?? throw new ArgumentNullException(nameof(state));
|
|
}
|
|
|
|
/// <summary>
|
|
/// <paramref name="landblockId"/> is a full ACE cell id (landblock high
|
|
/// word + cell low word — <c>CreateObject.ServerPosition.LandblockId</c>
|
|
/// carries the full id despite its name; see
|
|
/// <c>RuntimeAcceptedPositionDriveControllerTests</c>'s own
|
|
/// <c>SourceCell = SourceLandblock | 0x0001u</c> fixture shape).
|
|
/// Canonicalized to <c>0xAAAAFFFF</c> the same way every
|
|
/// <see cref="GpuWorldState"/> tier writer/reader does
|
|
/// (<c>DetachNearLayer</c>, <c>CommitLandblockSpatialCore</c>) before the
|
|
/// dictionary lookup — <see cref="GpuWorldState.IsNearTier"/> itself does
|
|
/// not canonicalize its argument.
|
|
/// </summary>
|
|
public bool IsWithinServiceWindow(uint landblockId) =>
|
|
_state.IsNearTier((landblockId & 0xFFFF0000u) | 0xFFFFu);
|
|
}
|