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

@ -252,9 +252,70 @@ same commit) → docs/handoff commit. No workarounds; no fused slices.
4 (remote Create/Position; delete `RemoteTeleportController`/`Placement`
and the inline MoveOrTeleport duplicate), 5 (projectile authoritative),
6 (drops + split-recovery marking), 7 (residual pickup/parent/delete
polish). — OPEN at the 2026-08-03 handoff.** May land as more than one
commit if a route proves large;
each sub-landing keeps the full review discipline.
polish). — route 2 implementation COMPLETE, pending review, at the
2026-08-03 handoff; routes 3/4/5/6/7 remain OPEN.**
**Route 2 (ForcePosition) — implemented 2026-08-03, contract:**
[`2026-08-03-c4-route-2-contract.md`](../research/2026-08-03-c4-route-2-contract.md),
**plan:** [`2026-08-03-c4-route-2-implementation-plan.md`](../research/2026-08-03-c4-route-2-implementation-plan.md).
`RuntimeAcceptedPositionDriveController`
(`src/AcDream.Runtime/Session/RuntimeAcceptedPositionDriveController.cs`)
is the single accepted-Position execution seam for a ForcePosition on the
already-live local player; `LocalForcePositionTransaction` and
`HeadlessSessionWorldProjection.BlipLocalPlayer` are deleted, and the
generic App render-tail is skipped for the local player's ForcePosition.
Named behaviour changes (both retail-exact, ISSUES #285): the outbound
ack now fires strictly after the canonical commit, and the constraint
leash is no longer re-armed on this route (retail's FORCE_POSITION branch
never reaches `ConstrainTo`).
**Fix round (2026-08-03):** both independent dual reviews (retail-
conformance + architecture/adversarial) FAILed the first pass — see
[`2026-08-03-c4-route-2-review-findings.md`](../research/2026-08-03-c4-route-2-review-findings.md)
for the full R1-R9 list. The critical finding (R1) was that the
DeferredCell park could not survive a single ACE broadcast interval in
production (`RuntimeEntityObjectLifetime.TryApplyPosition`'s unconditional
`Forget` on every accepted Position cancelled it before its collision
generation could commit), silently dropping the correction forever;
`RuntimeAcceptedPositionDriveController.Advance` now detects the dead
watch and re-issues from the entity's current canonical snapshot. R2/R3
restored headless's collision re-centering and login-window fallback; R4
stopped the force-ack from stealing a receipt the presentation sink had
legitimately declined; R5/R6/R9 corrected false doc claims, closed a
`_pending`-leak/overwrite gap, and fixed streaming-observer/pose-dirty
side effects firing on a declined placement. R7 corrected a fixture bug
(a dummy Setup sphere with its centre at the origin) that had been
written up as a retail fidelity gain; R8 added App-layer double-write
source pins and corrected an overclaimed single-ack test. Full detail:
[`2026-08-03-c4-route-2-review-findings.md`](../research/2026-08-03-c4-route-2-review-findings.md).
Complete Release solution after the fix round: **10,853 passed / 4
skipped / 0 failed** (baseline 10,844/4/0; first pass 10,848/4/0).
**Acceptance item 2 is NOT met — recorded gap, B2 (2026-08-03 round 2).**
An earlier revision of this paragraph claimed R8 "added the App-layer
double-write source pins the plan's own acceptance item required". That was
a claim of coverage this changeset does not have, and it is corrected here
rather than left as the citation a future session trusts (same rule that
produced R7). The truth, per the adversarial review:
- *First half — "the generic tail no longer double-writes the local
player":* **source-pinned, not proven.** The pin is a regex/`Assert.Single`
over `LiveEntityNetworkUpdateController`'s source text, so it would still
pass if a second write were spelled differently, and **no test exercises
the branch** at runtime.
- *Second half — "the committed projection is what moves the render
entity":* **uncovered at any layer.** No test drives a route-2
ForcePosition through `RuntimePlacementPresentationSink` /
`TryApplyRuntimePlacementPlace` and asserts the `WorldEntity` actually
moved. Given R4 (the force-ack no longer consumes a declined `Place`),
this is precisely the seam whose failure mode is silent: the canonical
body moves and the render entity stays put.
Closing this gap needs an App-layer test that runs the accepted
ForcePosition end to end and asserts the render entity's position/cell came
from the committed placement receipt — carry it into C5's parity tests or
file it before this sub-landing closes.
**Not yet done:** both reviews must be RE-RUN on this fixed diff, and the
connected (user-gated) acceptance gate this campaign's standing
discipline requires, before this sub-landing is considered closed — those,
and the commit itself, are next. May land as more than one commit if a
route proves large; each sub-landing keeps the full review discipline.
- **C5 — legacy deletion + closeout gates — OPEN.** Delete every superseded legacy
path; parity tests; exact lifecycle/reconnect + canonical nine-stop
connected routes; two-client observation; **user visual matrix** (the