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:
parent
22a5c95400
commit
9966b53174
25 changed files with 4292 additions and 195 deletions
|
|
@ -573,6 +573,21 @@ internal sealed class SessionPlayerCompositionPhase
|
|||
.RegisteredAuthoredPayload
|
||||
: RuntimeLocalPlayerShadowDisposition.ProvenShapeless);
|
||||
});
|
||||
// C4 route 2 (2026-08-03): the graphical accepted-Position drive
|
||||
// controller — the Runtime-owned execution seam for a ForcePosition
|
||||
// on the already-live local player. Constructed once per session
|
||||
// route alongside firstEntryDrive; both share the SAME entity
|
||||
// lifetime, collision source, and clock.
|
||||
var acceptedPositionDrive = new RuntimeAcceptedPositionDriveController(
|
||||
d.EntityObjects,
|
||||
d.Runtime.Clock,
|
||||
firstEntryCollision,
|
||||
d.PlayerOutbound,
|
||||
() => d.Runtime.Generation,
|
||||
() => d.PlayerIdentity.ServerGuid,
|
||||
() => d.PlayerController.Controller,
|
||||
() => d.Character.UsePositionFromServer,
|
||||
() => liveSessionSource.CurrentSession);
|
||||
var hydration = new LiveEntityHydrationController(
|
||||
live.LiveEntities,
|
||||
d.EntityObjects,
|
||||
|
|
@ -591,7 +606,8 @@ internal sealed class SessionPlayerCompositionPhase
|
|||
deletion,
|
||||
dormantLiveEntities,
|
||||
d.Options.DumpLiveSpawns ? d.Log : null,
|
||||
firstEntryDrive);
|
||||
firstEntryDrive,
|
||||
acceptedPositionDrive);
|
||||
bindings.Adopt(
|
||||
"landblock-loaded hydration",
|
||||
live.LandblockLoaded.Bind(hydration));
|
||||
|
|
@ -637,6 +653,7 @@ internal sealed class SessionPlayerCompositionPhase
|
|||
liveSessionSource,
|
||||
localPhysicsTimestamps.Publish,
|
||||
d.MovementDiagnostics,
|
||||
acceptedPositionDrive,
|
||||
worldDropProjection);
|
||||
var liveness = new LiveEntityLivenessController(
|
||||
live.LiveEntities,
|
||||
|
|
@ -959,7 +976,8 @@ internal sealed class SessionPlayerCompositionPhase
|
|||
live.RenderSceneShadow,
|
||||
live.PlacementProjection,
|
||||
placementProjectionRetry,
|
||||
firstEntryDrive),
|
||||
firstEntryDrive,
|
||||
acceptedPositionDrive),
|
||||
liveSessionCommands,
|
||||
d.Log);
|
||||
LiveSessionHost sessionHost = sessionRuntimeFactory.Create(
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ using AcDream.Core.Items;
|
|||
using AcDream.Core.Physics;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
using AcDream.Runtime.Session;
|
||||
using AcDream.Core.Selection;
|
||||
using AcDream.Core.World;
|
||||
using DatReaderWriter;
|
||||
|
|
@ -63,6 +64,7 @@ internal sealed class LiveEntityNetworkUpdateController
|
|||
private readonly IMovementTruthDiagnosticSink _movementTruthDiagnostics;
|
||||
private readonly InventoryWorldDropProjectionController?
|
||||
_worldDropProjection;
|
||||
private readonly RuntimeAcceptedPositionDriveController _acceptedPositionDrive;
|
||||
|
||||
private PlayerMovementController? _playerController => _playerControllerSource.Controller;
|
||||
private EntityPhysicsHost? _playerHost => _playerHostSource.Host;
|
||||
|
|
@ -103,6 +105,7 @@ internal sealed class LiveEntityNetworkUpdateController
|
|||
ILiveWorldSessionSource session,
|
||||
Action<uint, AcceptedPhysicsTimestamps> publishTimestamps,
|
||||
IMovementTruthDiagnosticSink movementTruthDiagnostics,
|
||||
RuntimeAcceptedPositionDriveController acceptedPositionDrive,
|
||||
InventoryWorldDropProjectionController? worldDropProjection = null)
|
||||
{
|
||||
_liveEntities = liveEntities ?? throw new ArgumentNullException(nameof(liveEntities));
|
||||
|
|
@ -138,6 +141,8 @@ internal sealed class LiveEntityNetworkUpdateController
|
|||
publishTimestamps);
|
||||
_movementTruthDiagnostics = movementTruthDiagnostics
|
||||
?? throw new ArgumentNullException(nameof(movementTruthDiagnostics));
|
||||
_acceptedPositionDrive = acceptedPositionDrive
|
||||
?? throw new ArgumentNullException(nameof(acceptedPositionDrive));
|
||||
_worldDropProjection = worldDropProjection;
|
||||
}
|
||||
|
||||
|
|
@ -1113,20 +1118,61 @@ internal sealed class LiveEntityNetworkUpdateController
|
|||
bool forceLocal = timestampDisposition is AcDream.Core.Physics.PositionTimestampDisposition.ForcePosition
|
||||
&& update.Guid == _playerServerGuid
|
||||
&& _playerController is not null;
|
||||
if (!LocalForcePositionTransaction.Apply(
|
||||
forceLocal,
|
||||
() => IsCurrentPositionOwner(),
|
||||
() => _playerController!.BlipPosition(
|
||||
worldPos,
|
||||
p.LandblockId,
|
||||
new System.Numerics.Vector3(
|
||||
p.PositionX,
|
||||
p.PositionY,
|
||||
p.PositionZ)),
|
||||
() => _localPlayerOutbound.SendImmediatePosition(
|
||||
_session.CurrentSession,
|
||||
_playerController)))
|
||||
return;
|
||||
if (forceLocal)
|
||||
{
|
||||
if (!IsCurrentPositionOwner())
|
||||
return;
|
||||
|
||||
// C4 route 2 (2026-08-03): the Runtime-owned accepted-Position
|
||||
// execution seam replaces the deleted LocalForcePositionTransaction.
|
||||
// Ownership validation is the operation's own currency check,
|
||||
// the body commit is Runtime's canonical SetPosition transaction
|
||||
// (retail CPhysicsObj::SetPositionSimple @0x005162B0, called from
|
||||
// SmartBox::BlipPlayer @0x00453940), and the outbound ack is an
|
||||
// OUTPUT of that committed route, fired strictly after it.
|
||||
RuntimeAcceptedPositionExecutionStatus forceStatus =
|
||||
_acceptedPositionDrive.TryExecuteAcceptedLocalPosition(
|
||||
acceptedPositionCanonical,
|
||||
update,
|
||||
timestampDisposition,
|
||||
timestamps,
|
||||
timestamps.PreviousTeleport);
|
||||
if (forceStatus is RuntimeAcceptedPositionExecutionStatus.Committed
|
||||
or RuntimeAcceptedPositionExecutionStatus.DeferredCell)
|
||||
{
|
||||
// App projects the committed (or parked-toward) result
|
||||
// through the existing Runtime placement sink
|
||||
// (RuntimePlacementPresentationSink), which observes the
|
||||
// SAME Runtime SetPosition FIFO every other placement uses —
|
||||
// it must not ALSO independently mutate the render-facing
|
||||
// WorldEntity here (the retired duplicate-write authority:
|
||||
// the generic tail below). The two local-player side effects
|
||||
// this neighbourhood still owns independently of WorldEntity
|
||||
// position (owned-VFX pose-dirty tracking and the
|
||||
// pre-player-mode streaming landblock tracker) are
|
||||
// preserved — DeferredCell included, since following the
|
||||
// destination is what lets its streaming/collision window
|
||||
// eventually publish the generation the park is waiting on.
|
||||
_entityEffects?.MarkLiveOwnerPoseDirty(update.Guid);
|
||||
_authorityGate.ObserveAcceptedLocalPosition(
|
||||
update.Position.LandblockId);
|
||||
return;
|
||||
}
|
||||
if (forceStatus is not RuntimeAcceptedPositionExecutionStatus.NotApplicable)
|
||||
{
|
||||
// R9 review fix (2026-08-03): Rejected/Contention — no
|
||||
// correction was applied or parked for this exact packet.
|
||||
// Do NOT move the streaming observer or mark the render pose
|
||||
// dirty for a landblock this route explicitly declined to
|
||||
// place into, and do not fall through to the generic tail
|
||||
// below either (that would resurrect the retired duplicate-
|
||||
// write authority this whole route exists to remove).
|
||||
return;
|
||||
}
|
||||
// NotApplicable — e.g. an initial-Create residence still owns
|
||||
// this record (route 1's job). Fall through to the pre-existing
|
||||
// path unchanged, exactly as every other disposition does.
|
||||
}
|
||||
|
||||
// A leave-world transition deliberately retains WorldEntity as the
|
||||
// logical/render-resource owner while IsSpatiallyProjected is false.
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
namespace AcDream.App.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// Preserves retail ForcePosition's atomic local order: validate the accepted
|
||||
/// Position authority, blip once, acknowledge once, then stop if the
|
||||
/// acknowledgement synchronously displaced that authority.
|
||||
/// </summary>
|
||||
internal static class LocalForcePositionTransaction
|
||||
{
|
||||
internal static bool Apply(
|
||||
bool isForcePosition,
|
||||
Func<bool> isCurrent,
|
||||
Action blip,
|
||||
Action acknowledge)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(isCurrent);
|
||||
ArgumentNullException.ThrowIfNull(blip);
|
||||
ArgumentNullException.ThrowIfNull(acknowledge);
|
||||
|
||||
if (!isForcePosition)
|
||||
return true;
|
||||
if (!isCurrent())
|
||||
return false;
|
||||
|
||||
blip();
|
||||
acknowledge();
|
||||
return isCurrent();
|
||||
}
|
||||
}
|
||||
|
|
@ -189,6 +189,13 @@ internal sealed class LiveEntityHydrationController : ILiveEntityLandblockLoaded
|
|||
/// controller without one.
|
||||
/// </summary>
|
||||
private readonly RuntimeFirstEntryDriveController? _firstEntry;
|
||||
/// <summary>
|
||||
/// C4 route 2: the graphical accepted-Position drive controller — pumped
|
||||
/// alongside <see cref="_firstEntry"/> so a ForcePosition parked awaiting
|
||||
/// its destination collision generation resolves promptly. Optional for
|
||||
/// the same reason <see cref="_firstEntry"/> is.
|
||||
/// </summary>
|
||||
private readonly RuntimeAcceptedPositionDriveController? _acceptedPositionDrive;
|
||||
private readonly Dictionary<RuntimeEntityRecord, CanonicalProjectionOperation>
|
||||
_projectionOperations =
|
||||
new(ReferenceEqualityComparer.Instance);
|
||||
|
|
@ -213,7 +220,8 @@ internal sealed class LiveEntityHydrationController : ILiveEntityLandblockLoaded
|
|||
LiveEntityDeletionController deletion,
|
||||
DormantLiveEntityStore? dormant = null,
|
||||
Action<string>? diagnostic = null,
|
||||
RuntimeFirstEntryDriveController? firstEntry = null)
|
||||
RuntimeFirstEntryDriveController? firstEntry = null,
|
||||
RuntimeAcceptedPositionDriveController? acceptedPositionDrive = null)
|
||||
{
|
||||
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
|
||||
_entityObjects = entityObjects
|
||||
|
|
@ -230,6 +238,7 @@ internal sealed class LiveEntityHydrationController : ILiveEntityLandblockLoaded
|
|||
_dormant = dormant ?? new DormantLiveEntityStore();
|
||||
_diagnostic = diagnostic;
|
||||
_firstEntry = firstEntry;
|
||||
_acceptedPositionDrive = acceptedPositionDrive;
|
||||
}
|
||||
|
||||
internal event Action<uint>? AppearanceApplied;
|
||||
|
|
@ -403,6 +412,8 @@ AppearanceSynchronization:
|
|||
// destination cell, FIFO ahead of us) is retried by the
|
||||
// per-frame placement retry phase.
|
||||
_firstEntry?.DriveAll();
|
||||
// C4 route 2: same pump for a parked ForcePosition.
|
||||
_acceptedPositionDrive?.Advance();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue