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
|
|
@ -393,16 +393,56 @@ public sealed class HeadlessSessionHostTests
|
|||
acknowledgeProjection: null,
|
||||
out PositionTimestampDisposition disposition,
|
||||
out _,
|
||||
out _));
|
||||
out AcceptedPhysicsTimestamps timestamps));
|
||||
Assert.Equal(
|
||||
PositionTimestampDisposition.ForcePosition,
|
||||
disposition);
|
||||
projection.ProjectPosition(
|
||||
record,
|
||||
isLocalPlayer: true,
|
||||
disposition);
|
||||
|
||||
Assert.Equal(new Vector3(72f, 73f, 50f), controller.Position);
|
||||
// C4 route 2 (2026-08-03): a ForcePosition on the local player no
|
||||
// longer routes through HeadlessSessionWorldProjection.ProjectPosition
|
||||
// at all — RuntimeLiveEntitySessionController.OnPositionUpdated
|
||||
// dispatches it directly to RuntimeAcceptedPositionDriveController
|
||||
// instead (the deleted BlipLocalPlayer's replacement), but R2 review
|
||||
// fix (2026-08-03): it re-centers the collision neighborhood on the
|
||||
// destination FIRST — the deleted BlipLocalPlayer's own CenterOn
|
||||
// side effect, restored via CenterOnAcceptedForcePosition, because a
|
||||
// DeferredCell park this neighborhood's window can never publish is
|
||||
// a dead end, not a real park
|
||||
// (RuntimeAcceptedPositionDriveController.Advance's R1 doc comment).
|
||||
projection.CenterOnAcceptedForcePosition(record);
|
||||
RuntimeAcceptedPositionDriveController acceptedPositionDrive =
|
||||
CreateAcceptedPositionDrive(runtime);
|
||||
RuntimeAcceptedPositionExecutionStatus forceStatus =
|
||||
acceptedPositionDrive.TryExecuteAcceptedLocalPosition(
|
||||
record,
|
||||
force,
|
||||
disposition,
|
||||
timestamps,
|
||||
timestamps.PreviousTeleport);
|
||||
|
||||
Assert.Equal(
|
||||
RuntimeAcceptedPositionExecutionStatus.Committed,
|
||||
forceStatus);
|
||||
// R7 review fix (2026-08-03): Z is 50.005f — within 5 mm of the
|
||||
// wire's bare 50f — NOT 50.48f. The dat-exact human Setup's foot
|
||||
// sphere is (0,0,0.475) r=.48 (LoadedSetupCollisionSource above);
|
||||
// its bottom sits at origin + 0.475 − 0.48 = origin − 0.005, so a
|
||||
// settled origin lands 0.005 m ABOVE the floor it rests on (measured
|
||||
// empirically against this exact fixture), not a full sphere RADIUS
|
||||
// above it. Retail's BlipPlayer (CPhysicsObj::SetPositionSimple
|
||||
// @0x005162B0, called from SmartBox::BlipPlayer @0x00453940) has
|
||||
// never lifted the origin by a sphere radius — the C4-route-2 FIRST
|
||||
// implementation pass (uncommitted; this file asserts a bare 50f at
|
||||
// HEAD, never 50.48f) had fitted a 50.48f assertion to a dummy
|
||||
// fixture sphere whose offset happened to equal its own radius, not
|
||||
// to retail behavior. The comment it carried described the sphere's
|
||||
// CENTRE, then wrongly asserted that description about
|
||||
// controller.Position, which is the body's ORIGIN
|
||||
// (PlayerMovementController.cs -> PhysicsBody.cs Position), not the
|
||||
// sphere centre.
|
||||
Assert.Equal(new Vector3(72f, 73f, 50.005f), controller.Position);
|
||||
// CenterCount is 2: ProjectSpawn's initial centering plus the
|
||||
// ForcePosition's own re-centering above (R2's restored mechanism).
|
||||
Assert.Equal(2, collision.CenterCount);
|
||||
}
|
||||
|
||||
|
|
@ -1296,6 +1336,25 @@ public sealed class HeadlessSessionHostTests
|
|||
Height: 1.835f,
|
||||
RuntimeLocalPlayerShadowDisposition.ProvenShapeless));
|
||||
|
||||
/// <summary>
|
||||
/// C4 route 2 (2026-08-03): mirrors <see cref="CreateFirstEntryDrive"/>'s
|
||||
/// construction pattern for the accepted-Position drive controller. No
|
||||
/// real WorldSession is needed for these fixture tests —
|
||||
/// LocalPlayerOutboundController.SendImmediatePosition no-ops on a null
|
||||
/// session.
|
||||
/// </summary>
|
||||
private static RuntimeAcceptedPositionDriveController
|
||||
CreateAcceptedPositionDrive(GameRuntime runtime) => new(
|
||||
runtime.EntityObjects,
|
||||
runtime.Clock,
|
||||
new LoadedSetupCollisionSource(),
|
||||
new LocalPlayerOutboundController((_, _, _, _, _, _) => { }),
|
||||
() => runtime.Generation,
|
||||
() => runtime.PlayerIdentity.ServerGuid,
|
||||
() => runtime.MovementOwner.Controller,
|
||||
() => runtime.CharacterOwner.UsePositionFromServer,
|
||||
() => null);
|
||||
|
||||
private sealed class LoadedSetupCollisionSource
|
||||
: AcDream.Content.IPreparedCollisionSource
|
||||
{
|
||||
|
|
@ -1312,7 +1371,23 @@ public sealed class HeadlessSessionHostTests
|
|||
.Loaded(new FlatSetupCollision(
|
||||
System.Collections.Immutable.ImmutableArray<
|
||||
FlatCollisionCylinder>.Empty,
|
||||
[new FlatCollisionSphere(Vector3.Zero, 0.48f)],
|
||||
// R7 review fix (2026-08-03): the dat-exact human Setup
|
||||
// 0x02000001 spheres (Ts46SphereListConformanceTests.cs
|
||||
// :35-39) — foot (0,0,0.475) r=.48, head/torso
|
||||
// (0,0,1.350) r=.48. The PREVIOUS single dummy sphere at
|
||||
// (0,0,0) r=.48 (offset == radius) made a settled origin
|
||||
// rest a FULL radius above the floor; the real foot
|
||||
// sphere's bottom is origin + 0.475 − 0.48 = origin −
|
||||
// 0.005, so a settled origin lands ON the floor within
|
||||
// 5 mm. Retail's BlipPlayer has never lifted the origin
|
||||
// by a sphere radius — that was a fixture artifact, not
|
||||
// a retail-fidelity gain (docs/ISSUES.md #285 correction).
|
||||
[
|
||||
new FlatCollisionSphere(
|
||||
new Vector3(0f, 0f, 0.475f), 0.48f),
|
||||
new FlatCollisionSphere(
|
||||
new Vector3(0f, 0f, 1.350f), 0.48f),
|
||||
],
|
||||
height: 0f,
|
||||
radius: 0f,
|
||||
stepUpHeight: 0.4f,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue