feat(physics): C4 route 2 — ForcePosition through the canonical placement

A local-player ForcePosition had TWO independent writers for one accepted
packet: LocalForcePositionTransaction snapped the physics body
(PlayerMovementController.BlipPosition, a raw SnapToCell with no collision
resolve), while LiveEntityNetworkUpdateController's generic tail separately
wrote position/cell/rotation to the render WorldEntity from the raw wire and
rebucketed it. Two stores, one packet — the divergence class 670f307c fixed on
the remote path. The outbound AutonomousPosition ack also fired BEFORE any
canonical commit existed: we told ACE "got it, I'm here" before deciding where
"here" was, and the trailing isCurrent() could only suppress the continuation,
never recall the packet.

RuntimeAcceptedPositionDriveController is now the one Runtime-owned seam. Both
hosts call the identical TryExecuteAcceptedLocalPosition; App and headless
project the committed result through the existing placement projection sink
(LiveEntityRuntime.TryApplyRuntimePlacementPlace already performed the same
four writes, from committed state rather than a wire guess).

Retail: SmartBox::HandleReceivedPosition @0x00453FD0's FORCE_POSITION branch is
get_heading -> Frame::set_heading -> SmartBox::BlipPlayer @0x00453940 -> stamp
POSITION_TS -> SendPositionEvent @0x00454091 -> return @0x0045409D. BlipPlayer
is CPhysicsObj::SetPositionSimple @0x005162B0 with flags 0x1012
(Teleport|Slide|SendPositionEvent) — a real collision-resolving SetPosition,
not a snap. The pinned classifier already encoded this exactly.

Named behaviour changes:

* The ack is now an OUTPUT of the committed route, fired strictly after the
  canonical commit and exactly once per accepted force packet.
* The ForcePosition route no longer re-arms the constraint leash. The force
  branch returns at 0x0045409D, ahead of all three ConstrainTo sites
  (0x00454272, 0x0045418A, 0x004541EC); the old re-arm cited retail's "Player,
  normal" branch, which BlipPlayer is not on. The teleport, CommitPreparedPosition
  and first-entry callers legitimately still constrain and are untouched.
* A force correction that terminates WITHOUT committing still sends its
  position event and is not retried — retail's BlipPlayer discards
  SetPositionSimple's SetPositionError return and acks unconditionally.

A single _pending funnel owns the in-flight placement, deciding on the token's
PositionAuthorityVersion against the record's: equal -> clear; advanced with the
newest accepted event still a force -> re-issue, re-classified; advanced to an
ordinary Apply -> clear, since newer server truth owns that pose. This closes a
double-apply/double-ack and a silently-dropped correction that two earlier
iterations of this slice each introduced.

AD-62 records the residual: a ForcePosition our async collision publication
cannot carry to a committed placement is not re-applied. Retail has no park —
its world is fully resident and its placement synchronous — so the state is
unreachable there. AP-131 is NOT retired; its legacy Position caller is route 4.

Deleted: LocalForcePositionTransaction, PlayerMovementController.BlipPosition,
HeadlessSessionWorldProjection.BlipLocalPlayer.

Gates: complete Release solution 10,858 passed / 4 skipped / 0 failed (baseline
10,844/4/0). Two independent Opus reviews (retail-conformance and
architecture/adversarial) PASS on the final diff after three FAIL rounds; every
intermediate state was fully green, so the suite caught none of the four real
defects. Connected acceptance is NOT run: nothing a user can do makes ACE emit
a ForcePosition without retail's @pklite, which acdream does not implement — see
docs/research/2026-08-03-c4-route-2-visual-gate.md.

Known gap, recorded not claimed: the plan's acceptance item 2 is unmet. The App
double-write check is a source pin, and "the committed projection moves the
render entity" is uncovered at any layer (#292). Filed alongside: #286-#291,
#293-#296.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-03 18:46:36 +02:00
parent 22a5c95400
commit 9966b53174
25 changed files with 4292 additions and 195 deletions

View file

@ -17,6 +17,7 @@ internal sealed class GraphicalSessionEventRoute : ILiveSessionEventRouting
private readonly Func<RuntimeGenerationToken> _generation;
private readonly RuntimePlacementProjectionRetrySlot _retries;
private readonly RuntimeFirstEntryDriveController? _firstEntry;
private readonly RuntimeAcceptedPositionDriveController? _acceptedPositionDrive;
private readonly Action<RuntimeEntityRecord>? _localPlayerCompleted;
private RuntimePlacementProjectionSubscription? _subscription;
private IDisposable? _retryLease;
@ -30,7 +31,8 @@ internal sealed class GraphicalSessionEventRoute : ILiveSessionEventRouting
IRuntimePlacementProjectionSink placements,
RuntimePlacementProjectionRetrySlot retries,
RuntimeFirstEntryDriveController? firstEntry = null,
Action<RuntimeEntityRecord>? localPlayerCompleted = null)
Action<RuntimeEntityRecord>? localPlayerCompleted = null,
RuntimeAcceptedPositionDriveController? acceptedPositionDrive = null)
: this(
events,
() => new RuntimePlacementProjectionSubscription(
@ -40,7 +42,8 @@ internal sealed class GraphicalSessionEventRoute : ILiveSessionEventRouting
() => runtime.Generation,
retries,
firstEntry,
localPlayerCompleted)
localPlayerCompleted,
acceptedPositionDrive)
{
ArgumentNullException.ThrowIfNull(runtime);
ArgumentNullException.ThrowIfNull(placements);
@ -52,7 +55,8 @@ internal sealed class GraphicalSessionEventRoute : ILiveSessionEventRouting
Func<RuntimeGenerationToken> generation,
RuntimePlacementProjectionRetrySlot retries,
RuntimeFirstEntryDriveController? firstEntry = null,
Action<RuntimeEntityRecord>? localPlayerCompleted = null)
Action<RuntimeEntityRecord>? localPlayerCompleted = null,
RuntimeAcceptedPositionDriveController? acceptedPositionDrive = null)
{
_events = events ?? throw new ArgumentNullException(nameof(events));
_createSubscription = createSubscription
@ -62,6 +66,7 @@ internal sealed class GraphicalSessionEventRoute : ILiveSessionEventRouting
_retries = retries ?? throw new ArgumentNullException(nameof(retries));
_firstEntry = firstEntry;
_localPlayerCompleted = localPlayerCompleted;
_acceptedPositionDrive = acceptedPositionDrive;
}
public void Attach()
@ -75,6 +80,10 @@ internal sealed class GraphicalSessionEventRoute : ILiveSessionEventRouting
// detached — session reset precedes a new route — before this route
// takes ownership of the shared drive controller's tracked entries.
_firstEntry?.AttachRoute(this, _localPlayerCompleted);
// C4 route 2: same one-route-at-a-time latch for the accepted-
// Position drive controller (RuntimeAcceptedPositionDriveController
// .AttachRoute's doc comment).
_acceptedPositionDrive?.AttachRoute(this);
_events.Attach();
RuntimePlacementProjectionSubscription? subscription = null;
@ -90,10 +99,13 @@ internal sealed class GraphicalSessionEventRoute : ILiveSessionEventRouting
// republishing the pending FIFO head — a conductor's own
// Advance is what consumes conductor-owned receipts, and the
// subsequent RetryPending lets the presentation sink apply
// whatever new head the drive surfaced.
// whatever new head the drive surfaced. C4 route 2: the
// accepted-Position drive's own Advance resolves a parked
// DeferredCell ForcePosition the same way.
() =>
{
_firstEntry?.DriveAll();
_acceptedPositionDrive?.Advance();
return boundSubscription.RetryPending();
});
_subscription = subscription;
@ -123,6 +135,7 @@ internal sealed class GraphicalSessionEventRoute : ILiveSessionEventRouting
// route-scoped — a route that never attached cannot clear a live
// route's entries.
_firstEntry?.DetachRoute(this);
_acceptedPositionDrive?.DetachRoute(this);
if (!_eventsDisposed)
{
_events.Dispose();

View file

@ -87,7 +87,8 @@ internal sealed record LiveSessionWorldRuntime(
RenderSceneShadowRuntime? RenderSceneShadow,
RuntimePlacementPresentationSink PlacementProjection,
RuntimePlacementProjectionRetrySlot PlacementRetries,
RuntimeFirstEntryDriveController FirstEntryDrive);
RuntimeFirstEntryDriveController FirstEntryDrive,
RuntimeAcceptedPositionDriveController AcceptedPositionDrive);
/// <summary>
/// Builds the exact per-generation route/reset graph for the canonical live
@ -264,7 +265,8 @@ internal sealed class LiveSessionRuntimeFactory
_world.PlacementProjection,
_world.PlacementRetries,
_world.FirstEntryDrive,
_ => session.SendGameAction(GameActionLoginComplete.Build()));
_ => session.SendGameAction(GameActionLoginComplete.Build()),
_world.AcceptedPositionDrive);
}
private LiveInventorySessionBindings CreateInventoryBindings() => new(