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
|
|
@ -19,6 +19,24 @@ public interface IRuntimeDirectWorldProjection
|
|||
bool isLocalPlayer,
|
||||
PositionTimestampDisposition disposition);
|
||||
|
||||
/// <summary>
|
||||
/// R2 review fix (2026-08-03): a ForcePosition on the local player is
|
||||
/// dispatched directly to <see cref="RuntimeAcceptedPositionDriveController"/>
|
||||
/// and never reaches <see cref="ProjectPosition"/> at all, so THIS is
|
||||
/// where a host that keeps a narrow collision/streaming window (the
|
||||
/// deleted <c>HeadlessSessionWorldProjection.BlipLocalPlayer</c>'s own
|
||||
/// <c>_collision.CenterOn</c> call) re-centers on the destination BEFORE
|
||||
/// the drive controller submits — establishing that the destination's
|
||||
/// collision generation is one this host's window can ever publish is a
|
||||
/// precondition for a <c>DeferredCell</c> park to be a real park rather
|
||||
/// than a dead end (see <c>RuntimeAcceptedPositionDriveController.Advance</c>'s
|
||||
/// R1 doc comment). A host with no narrow window (the graphical host,
|
||||
/// whose landblock streaming already follows the accepted position via
|
||||
/// <c>LiveEntityInboundAuthorityGate.ObserveAcceptedLocalPosition</c>) is
|
||||
/// a no-op here.
|
||||
/// </summary>
|
||||
void CenterOnAcceptedForcePosition(RuntimeEntityRecord record);
|
||||
|
||||
void BeginTeleport();
|
||||
|
||||
RuntimeDestinationReadiness PrepareDestination(
|
||||
|
|
@ -38,20 +56,28 @@ public sealed class RuntimeLiveEntitySessionController
|
|||
private readonly WorldSession _session;
|
||||
private readonly Action<string> _log;
|
||||
private readonly IRuntimeDirectWorldProjection? _worldProjection;
|
||||
private readonly LocalPlayerOutboundController _localPlayerOutbound =
|
||||
new((_, _, _, _, _, _) => { });
|
||||
/// <summary>
|
||||
/// C4 route 2 (2026-08-03): the headless accepted-Position drive
|
||||
/// controller. Owns its own outbound-ack collaborator internally; the
|
||||
/// ForcePosition + manual <c>LocalPlayerOutboundController.SendImmediatePosition</c>
|
||||
/// pair this class used to drive directly is retired (the deleted
|
||||
/// <c>HeadlessSessionWorldProjection.BlipLocalPlayer</c>).
|
||||
/// </summary>
|
||||
private readonly RuntimeAcceptedPositionDriveController? _acceptedPositionDrive;
|
||||
private bool _initialLoginCompleteSent;
|
||||
|
||||
public RuntimeLiveEntitySessionController(
|
||||
GameRuntime runtime,
|
||||
WorldSession session,
|
||||
Action<string>? log = null,
|
||||
IRuntimeDirectWorldProjection? worldProjection = null)
|
||||
IRuntimeDirectWorldProjection? worldProjection = null,
|
||||
RuntimeAcceptedPositionDriveController? acceptedPositionDrive = null)
|
||||
{
|
||||
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
|
||||
_session = session ?? throw new ArgumentNullException(nameof(session));
|
||||
_log = log ?? (_ => { });
|
||||
_worldProjection = worldProjection;
|
||||
_acceptedPositionDrive = acceptedPositionDrive;
|
||||
}
|
||||
|
||||
public LiveEntitySessionSink CreateSink() => new(
|
||||
|
|
@ -218,16 +244,55 @@ public sealed class RuntimeLiveEntitySessionController
|
|||
update.Guid,
|
||||
out RuntimeEntityRecord record))
|
||||
{
|
||||
_worldProjection?.ProjectPosition(
|
||||
record,
|
||||
isLocalPlayer: true,
|
||||
disposition);
|
||||
}
|
||||
if (disposition is PositionTimestampDisposition.ForcePosition)
|
||||
{
|
||||
_localPlayerOutbound.SendImmediatePosition(
|
||||
_session,
|
||||
_runtime.MovementOwner.Controller);
|
||||
if (disposition is PositionTimestampDisposition.ForcePosition)
|
||||
{
|
||||
// R2 review fix (2026-08-03): re-center BEFORE submitting —
|
||||
// see IRuntimeDirectWorldProjection.CenterOnAcceptedForcePosition's
|
||||
// doc comment. This is what the deleted BlipLocalPlayer's own
|
||||
// _collision.CenterOn call used to guarantee.
|
||||
_worldProjection?.CenterOnAcceptedForcePosition(record);
|
||||
|
||||
// C4 route 2 (2026-08-03): the Runtime-owned accepted-
|
||||
// Position execution seam replaces the deleted
|
||||
// HeadlessSessionWorldProjection.BlipLocalPlayer + manual
|
||||
// SendImmediatePosition pair. Canonical commit, controller
|
||||
// reconciliation, and the outbound ack (an OUTPUT of the
|
||||
// committed route, not a step alongside it) all run inside
|
||||
// the call below.
|
||||
RuntimeAcceptedPositionExecutionStatus forceStatus =
|
||||
_acceptedPositionDrive?.TryExecuteAcceptedLocalPosition(
|
||||
record,
|
||||
update,
|
||||
disposition,
|
||||
timestamps,
|
||||
timestamps.PreviousTeleport)
|
||||
?? RuntimeAcceptedPositionExecutionStatus.NotApplicable;
|
||||
if (forceStatus is RuntimeAcceptedPositionExecutionStatus
|
||||
.NotApplicable)
|
||||
{
|
||||
// R3 review fix (2026-08-03): NotApplicable (e.g. an
|
||||
// initial-Create residence still owns this record —
|
||||
// route 1's job, or the login-window controller-null
|
||||
// branch before route 1 has even published a
|
||||
// controller) is NOT the terminal case the previous
|
||||
// comment here claimed. ProjectPosition's own
|
||||
// controller-null branch is the pre-existing legacy
|
||||
// fallback this disposition always had — it must still
|
||||
// run, exactly as every other disposition's fallback
|
||||
// does below.
|
||||
_worldProjection?.ProjectPosition(
|
||||
record,
|
||||
isLocalPlayer: true,
|
||||
disposition);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_worldProjection?.ProjectPosition(
|
||||
record,
|
||||
isLocalPlayer: true,
|
||||
disposition);
|
||||
}
|
||||
}
|
||||
TryCompletePortal();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue