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
|
|
@ -569,7 +569,7 @@ public class PlayerMovementControllerTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void BlipPosition_ResnapsPoseWithoutStoppingActiveMotion()
|
||||
public void CommitCanonicalForcePositionFrame_ReconcilesPoseWithoutStoppingActiveMotion()
|
||||
{
|
||||
var controller = new PlayerMovementController(MakeFlatEngine());
|
||||
controller.SetPosition(new Vector3(96f, 96f, 50f), 0x0001);
|
||||
|
|
@ -579,7 +579,14 @@ public class PlayerMovementControllerTests
|
|||
Assert.True(velocity.LengthSquared() > 0f);
|
||||
|
||||
var corrected = new Vector3(100f, 98f, 50f);
|
||||
controller.BlipPosition(corrected, 0x0001, corrected);
|
||||
// C4 route 2: simulates Runtime's canonical SetPosition commit
|
||||
// (RuntimeSetPositionState.CommitCanonical:4459-4462), which snaps
|
||||
// the SAME PhysicsBody directly BEFORE the controller reconciles
|
||||
// its render-lerp/cell state. The deleted BlipPosition used to do
|
||||
// both steps itself; CommitCanonicalForcePositionFrame only does the
|
||||
// second.
|
||||
controller.PhysicsBody.SnapToCell(0x0001, corrected, corrected);
|
||||
controller.CommitCanonicalForcePositionFrame();
|
||||
|
||||
Assert.Equal(corrected, controller.Position);
|
||||
Assert.Equal(corrected, controller.RenderPosition);
|
||||
|
|
@ -587,13 +594,14 @@ public class PlayerMovementControllerTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void BlipPosition_PublishesCanonicalOutdoorCellAndLocalFrame()
|
||||
public void CommitCanonicalForcePositionFrame_PublishesCanonicalOutdoorCellAndLocalFrame()
|
||||
{
|
||||
var controller = new PlayerMovementController(MakeFlatEngine());
|
||||
var world = new Vector3(150f, 193f, 50f);
|
||||
var wireLocal = new Vector3(150f, 193f, 50f);
|
||||
|
||||
controller.BlipPosition(world, 0xA9B30038u, wireLocal);
|
||||
controller.PhysicsBody.SnapToCell(0xA9B30038u, world, wireLocal);
|
||||
controller.CommitCanonicalForcePositionFrame();
|
||||
|
||||
Assert.Equal(0xA9B40031u, controller.CellId);
|
||||
Assert.Equal(controller.CellId, controller.CellPosition.ObjCellId);
|
||||
|
|
@ -959,27 +967,35 @@ public class PlayerMovementControllerTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void BlipPosition_ArmsConstraintButDoesNotTearDownOrZeroVelocity()
|
||||
public void CommitCanonicalForcePositionFrame_DoesNotRearmConstraintLeashOrTouchVelocity()
|
||||
{
|
||||
var (controller, _) = MakeControllerWithHost();
|
||||
controller.SetPosition(new Vector3(96f, 96f, 50f), 0x0001);
|
||||
var initial = new Vector3(96f, 96f, 50f);
|
||||
controller.SetPosition(initial, 0x0001);
|
||||
controller.Update(ObjectTick, new MovementInput(Forward: true));
|
||||
Vector3 velocityBeforeBlip = controller.BodyVelocity;
|
||||
Assert.NotEqual(Vector3.Zero, velocityBeforeBlip); // sanity: actually moving
|
||||
|
||||
controller.BlipPosition(
|
||||
new Vector3(150f, 150f, 50f),
|
||||
0x0001,
|
||||
new Vector3(150f, 150f, 50f));
|
||||
|
||||
// BlipPlayer (retail 0x00453940) survives motion/velocity/stick — the
|
||||
// leash is no different: ConstrainTo runs with NO preceding UnConstrain
|
||||
// and no StopCompletely.
|
||||
Assert.Equal(velocityBeforeBlip, controller.BodyVelocity);
|
||||
Vector3 velocityBeforeCommit = controller.BodyVelocity;
|
||||
Assert.NotEqual(Vector3.Zero, velocityBeforeCommit); // sanity: actually moving
|
||||
ConstraintManager cm = controller.PositionManager!.Constraint!;
|
||||
Assert.True(cm.IsConstrained);
|
||||
Assert.Equal(controller.Position, cm.ConstraintPos.Frame.Origin);
|
||||
Assert.Equal(0f, cm.ConstraintPosOffset, 3);
|
||||
Vector3 leashAnchorBeforeCommit = cm.ConstraintPos.Frame.Origin;
|
||||
|
||||
var corrected = new Vector3(150f, 150f, 50f);
|
||||
// Simulates Runtime's canonical SetPosition commit writing the SAME
|
||||
// PhysicsBody directly, exactly as CommitCanonicalForcePositionFrame
|
||||
// expects to find it.
|
||||
controller.PhysicsBody.SnapToCell(0x0001, corrected, corrected);
|
||||
controller.CommitCanonicalForcePositionFrame();
|
||||
|
||||
// C4 route 2 (2026-08-03): retail's FORCE_POSITION branch of
|
||||
// SmartBox::HandleReceivedPosition (0x00453FD0) returns at
|
||||
// 0x0045409D, before every CPhysicsObj::ConstrainTo call
|
||||
// (0x00454272/0x0045418A/0x004541EC) — the branch BlipPlayer runs
|
||||
// on is not one of them. Unlike the deleted BlipPosition (which
|
||||
// re-armed the leash to the corrected position — an unbacked
|
||||
// deviation), this method must leave the leash anchored exactly
|
||||
// where it already was. Motion/velocity are untouched either way.
|
||||
Assert.Equal(velocityBeforeCommit, controller.BodyVelocity);
|
||||
Assert.Equal(leashAnchorBeforeCommit, cm.ConstraintPos.Frame.Origin);
|
||||
Assert.NotEqual(corrected, cm.ConstraintPos.Frame.Origin);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -1133,10 +1149,8 @@ public class PlayerMovementControllerTests
|
|||
Vector3.One,
|
||||
0xA9B40021u,
|
||||
Vector3.One));
|
||||
Assert.Throws<InvalidOperationException>(() => candidate.BlipPosition(
|
||||
Vector3.One,
|
||||
0xA9B40021u,
|
||||
Vector3.One));
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
candidate.CommitCanonicalForcePositionFrame());
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
candidate.CaptureMovementResult(mouseLookEvent: false));
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue