acdream/docs/research/2026-07-31-remaining-physics-campaign-handoff.md

497 lines
20 KiB
Markdown

# Remaining physics-divergence campaign handoff — 2026-07-31
> **Checkpoint 2 update:** Slice 4B2 prerequisite A, Runtime SetPosition
> collision-report ownership, is implemented in the next checkpoint. Continue
> with the dedicated
> [`runtime SetPosition collision-reporting handoff`](2026-07-31-runtime-set-position-collision-reporting-handoff.md),
> not the prerequisite-A instructions preserved below as historical context.
## Purpose and stopping point
This is the deliberate handoff boundary requested after placement Slice 4B2
checkpoint 1. The repository is stopped before any production graphical or
headless route submits to the canonical Runtime SetPosition owner.
The completed foundation is useful and tested, but the overall campaign is
**not complete**. AP-1 and AD-1 remain narrowed/open. AP-22 and AD-10 remain
open. Do not retire those rows until their exact automated and connected gates
pass.
### Exact workspace
- Worktree: `C:\Users\erikn\.codex\worktrees\af5e\acdream`
- Branch: `codex/port-claude-agents`
- Handoff code checkpoint: `270f5154`
(`feat(runtime): expose dormant placement receipts`)
- Immediately preceding residence-owner checkpoint: `4c02ac42`
(`feat(runtime): own deferred set-position residence`)
- Pure Core SetPosition checkpoint: `e84a388e`
(`feat(physics): port canonical retail set-position core`)
- No upstream is configured for this worktree branch.
- Remotes:
- `origin`: `https://git.snakedesert.se/erik/acdream.git`
- `github`: `git@github.com:eriknihlen/acdream.git`
The handoff was written in this same worktree. The next agent should continue
there rather than creating a different worktree unless the user explicitly
requests it.
## Worktree hygiene
The worktree intentionally reports unrelated modifications. Preserve them.
Never use `git add -A`, `git reset --hard`, or checkout/revert commands against
these paths.
At the checkpoint, `AGENTS.md` has a real unrelated content diff. The following
paths report modified due to existing line-ending/stat noise but have no
content diff against the index:
- `src/AcDream.App/Input/PlayerModeController.cs`
- `src/AcDream.App/Interaction/PlayerInteractionMovementSink.cs`
- `src/AcDream.App/Rendering/LiveAnimationPresentationContext.cs`
- `src/AcDream.App/World/LiveEntityRuntime.cs`
- `src/AcDream.Core/Physics/CellArray.cs`
- `src/AcDream.Core/Physics/PhysicsBody.cs`
- `src/AcDream.Headless/Hosting/HeadlessSessionWorldProjection.cs`
- `src/AcDream.Runtime/Gameplay/PlayerMovementController.cs`
- `src/AcDream.Runtime/Physics/RuntimePhysicsState.cs`
- `src/AcDream.Runtime/Physics/RuntimeRemotePhysicsUpdater.cs`
- `tests/AcDream.App.Tests/World/LiveEntityRuntimeTests.cs`
- `tests/AcDream.Core.Tests/Physics/CellTransitTests.cs`
- `tests/AcDream.Core.Tests/Physics/Issue133DungeonTeleportPrefixTests.cs`
- `tests/AcDream.Runtime.Tests/Gameplay/PlayerMovementControllerTests.cs`
- `tools/A8CellAudit/A8CellAudit.csproj`
Before every commit, stage exact paths and inspect:
```powershell
git diff --check
git diff --cached --check
git diff --cached --stat
git status --short
```
## What is complete
### Campaign baseline and issue #273
- `c24bc571` — retail StepDown support-radius behavior for tight gaps.
- `10b55d74` — tight-gap controls and diagnostics.
### Retail retry and edge/StepDown dispatcher
- `e5f855ac` — nested per-cell collision retries.
- `67d1e9b3` — refreshed-cell retry state.
- `4ca7230b` — retained cell across inner retries.
- `c559c48d` — retail edge-response ordering.
- `4fbd93ec` — edge-slide stop semantics.
- `1fd5da67` — StepDown placement validation.
- `acec33ec` — StepDown probe state.
- `75b6f6b6` — Path-6 collision response.
- `d3c0d9ec` — TS-4 production chronology gate.
These retire AP-3, AP-4, AP-5, AD-53, AD-54, and TS-4 under the tests and
research already recorded in the divergence register.
### Exact cell availability and atomic collision generations
- `7716c2ee` — retail cell-availability semantics.
- `3e0f3b62` — containment-root validation.
- `be94bc9b` — atomic collision-generation activation.
- `d94145e6` — seal before activation.
- `6b28ff99` — starvation-free activation.
These retire AD-3, AD-4, and AD-6. The active collision world remains visible
until a complete replacement generation is atomically committed.
### Canonical SetPosition Core and Runtime residence owner
- `e84a388e` ports the pure Core SetPosition transaction.
- `4c02ac42` adds `RuntimeSetPositionState`, including:
- exact accepted operation ownership;
- canonical body/contact/cell/shadow/workset commit;
- authored mover retention;
- exact-cell and collision-generation wake;
- 25-second root/direct-child lost-cell lifetime;
- bounded indexed deadline structures;
- revisioned ordered Withdraw/Place/Discard host receipts;
- cancellation/GUID-reuse/reset/disposal convergence;
- structural cell/quaternion validation and bounded scatter work.
- `270f5154` adds `RuntimePlacementProjectionChannel`, a public,
generation-gated host seam over the one existing receipt owner:
- subscribe to ordered immutable receipts;
- peek the exact FIFO head;
- retry pending receipts without recommitting Runtime state;
- acknowledge only the exact current FIFO-head token;
- observe pending receipt debt.
The channel owns no second queue and has no App or Headless production
consumer. A source guard pins that dormancy. This is intentional.
### Validation at the stopping point
The final channel-only checkpoint passed:
- Release solution build: 0 errors, 18 existing warnings.
- Complete Release suite: 10,279 passed, 4 skipped, 0 failed.
- Runtime SetPosition focused tests: 47/47.
- App dormancy/ownership guards: 3/3.
- `git diff --check`: clean.
- Retail-conformance re-review: clean.
- Architecture/adversarial re-review: clean.
## Rejected prototype — do not resurrect it
A prototype made graphical and headless `PlayerMovementController` instances
borrow the canonical `RuntimeEntityRecord.PhysicsBody` immediately and used a
snapshot/rollback lease to recover from late construction failures. It was
fully removed before `270f5154`.
The design was rejected because it was failure-atomic only without
reentrancy. While the lease was open, a nested SetPosition, remote/projectile
bind or update, deletion/GUID replacement, object-clock epoch change, or
disposal could establish newer authority. The outer rollback could then erase
that newer commit or detach a body already used by another canonical owner.
The next implementation must use one of these complete solutions:
1. A Runtime-owned exclusive/versioned controller/body publication
transaction respected by **every** canonical body writer, binding path,
SetPosition operation, clock epoch transition, deletion, reset, and
disposal; or
2. Off-canonical preparation followed by one validated atomic Runtime commit
that publishes the prepared controller/body relationship without copying
stale state over newer authority.
Because every writer must participate, this belongs to the atomic production
route cutover. Do not reintroduce a local snapshot lease or a Commit method
that merely checks the body reference at the end.
## What remains — required execution order
### Slice 4B2 prerequisite A — real collision-report ownership
`RuntimePhysicsState.HandleSetPositionCollisions` still returns `false`.
Retail `CPhysicsObj::SetPositionInternal` (`0x00515330`) returns the real
per-object report/tracking result. Establish the Runtime owner for that result
and return it exactly. Do not restore the former environment/object-presence
guess.
Required tests:
- report/no-report objects;
- collided object set ordering and lifetime;
- reentrant deletion/reset;
- graphical/headless equality;
- no report state surviving GUID reuse.
### Slice 4B2 prerequisite B — exact authored mover preparation
Every production preparation must pass the full SetPosition request using:
- Setup's exact ordered authored spheres;
- exact scale, including valid zero/presence semantics;
- exact StepUp and StepDown heights;
- exact flags;
- exact cell-local frame and orientation;
- current position/vector/state authority versions.
Do not reconstruct a cylinder from visual radius/height, clamp a positive
scale, use the projectile mover as a generic object fallback, or pre-mutate
`FullCellId`, `PhysicsBody`, `WorldEntity`, App buckets, or shadows.
### Slice 4B2 prerequisite C — atomic local controller/body publication
Implement the complete transaction described in the rejected-prototype note.
Graphical and no-window controllers must end with the exact same Runtime body,
but construction cannot expose or mutate canonical state before the validated
atomic commit.
Adversarial gates must include:
- nested construction;
- reentrant SetPosition;
- remote and projectile binding/update;
- deletion and same-GUID new incarnation;
- projection-owner replacement;
- object-clock epoch change;
- reset and disposal;
- commit and rollback after replacement;
- late graphical camera/shadow/host failure;
- late headless prepared-collision failure.
### Slice 4B2 prerequisite D — presentation-only host projection
Add one graphical and one headless `IRuntimePlacementObserver` using
`GameRuntime.Placements`.
Host receipt rules:
- `Withdraw`: remove render/spatial presentation, picking, radar, audio, and
targeting while retaining logical Runtime ownership.
- `Place`: project only the immutable Runtime-committed frame, then
acknowledge the exact token.
- `Discard`: discard the older projection revision, then acknowledge it.
- A host exception or unavailable backend does not roll Runtime back; retry
the same FIFO head.
`LiveEntityRuntime.RebucketLiveEntity` must become presentation-only. Its
current `CommitRebucket` call is a second spatial authority and must be removed
as part of the same cutover.
### Slice 4B2 prerequisite E — collision-prefix quiescence
Before landblock collision demotion, removal, or replacement:
1. quiesce the prefix;
2. drain/ack the existing placement receipt prefix;
3. call `RuntimeSetPositionState.ParkCollisionResidents`;
4. commit/withdraw the collision generation atomically;
5. wake only exact cell+generation residents.
Cover `LandblockPhysicsPublisher.DemoteToTerrain`, `RemoveLandblock`,
replacement commit, and the headless collision-retirement path. No partially
observable collision generation is allowed.
## Production route cutover
Cut routes only after all prerequisites above are present. The canonical chain
for every route is:
```text
wire acceptance
-> BeginAcceptedPlacement exact token
-> exact DAT/Setup preparation
-> Runtime SetPosition canonical commit or deferred residence
-> immutable host projection receipt
-> exact host acknowledgement
```
### 1. Initial login and CreateObject
Current duplicate authority:
- `DatLiveEntityProjectionMaterializer.MaterializeProjection` immediately
positions/rebuckets the world entity.
- `PlayerModeController.BuildControllerAndCamera` builds its own body and runs
`Resolve`/`ResolvePlacement`.
- Headless performs its own initial resolve/placement/body construction.
Required order:
1. register identity cellless;
2. begin initial/remote-create placement before hydration;
3. load exact Setup mover;
4. prepare the atomic Runtime controller/body relationship;
5. submit canonical SetPosition;
6. publish presentation only from `Place`;
7. acknowledge, then enable player mode/simulation.
Tests: outdoor/indoor login, unavailable destination then exact-generation
wake, malformed or delayed Setup, one body identity, one enter-world clock
reset, no early visible entity, graphical/headless identical snapshots.
### 2. Local ForcePosition
Delete the placement authority in `LocalForcePositionTransaction` and the
direct `BlipPosition`/pre-commit acknowledgement in
`LiveEntityNetworkUpdateController.OnPosition`.
Required order: accept timestamp and preserve heading; begin
`LocalAuthoritative`; canonical SetPosition; host `Place` acknowledgement;
then send the outbound Position acknowledgement. A missing destination cell
must not acknowledge ACE early.
Tests: same/cross cell, preserved heading/velocity, missing-cell wake,
reentrant newer Position, stale host ack, exactly one outbound ack.
### 3. Portal transit and materialization
Remove placement authority from `LocalPlayerTeleportPlacement.Place` and its
direct resolve/controller/world-entity/rebucket/spatial mutations.
Bind a Runtime portal-placement authority to the active
`RuntimeWorldTransitState` reveal generation, teleport sequence, exact
destination cell, and placement token. Readiness permits submission only.
Materialization and simulation release happen only after canonical commit,
host projection, and exact acknowledgement. Cancellation/replacement produces
`Discard`; a stale generation/sequence/cell/token can never reveal.
Tests: `/ls`, spell recall, ordinary portal, same-location revisit, missing
destination, cancelled/replaced reveal, host throw/retry, no early world reveal
or LoginComplete.
### 4. Remote CreateObject and Position
Delete `RemoteTeleportController`, `RemoteTeleportPlacement`, their pending
dictionary/rollback/lost-cell ownership, and pre-placement
`WorldEntity.SetPosition`/rebucket calls.
Preserve retail `MoveOrTeleport` classification:
- fresh Teleport timestamp or cellless body: teleport hook then SetPosition;
- ordinary nearby grounded update: interpolation remains;
- distant update: stop interpolation then SetPosition.
Accept the timestamp, begin the exact token before hydration/body/App changes,
unparent first, run the retail teleport hook when required, submit the exact
mover, project after Runtime commit, then re-arm constraints.
Tests: visible/hidden/parented CreateObject, first Position, Teleport timestamp,
near interpolation, >96 m far placement, unloaded indoor destination, racing
velocity, delete/GUID reuse during host callback.
### 5. Projectile authoritative create/corrections
Remove authoritative placement from App `ProjectileController` and the direct
SnapToCell/cell/shadow commit in `RuntimeProjectilePhysicsUpdater`.
Use `ProjectileAuthoritative` with the same Runtime body and exact projectile
Setup sphere for initial create and authoritative corrections. Preserve
prediction/component/effect identity. Do not route ordinary per-quantum
projectile integration through SetPosition.
Tests: arrow, bolt, spell projectile, mid-flight correction, unloaded cell,
landblock crossing, delete during ack, no duplicate body/projectile/effect.
### 6. Drops and unparent-to-world
`InventoryWorldDropProjectionController.TryRecoverUnknownPosition` may create
the logical object, but it must enter the same canonical create-placement
transaction. Do not expose a stale source position or replay create-time
effects.
Tests: whole item, split stack, new GUID, second drop position, attached child
becoming a world root, unavailable destination, newer Position while waiting.
### 7. Pickup, Parent, and Delete
Runtime hooks already exist in `RuntimeEntityObjectLifetime`, but the route
cutover must ensure pickup/parent/delete cancel the exact active
placement/lost-cell family first and publish `Discard`/`Withdraw` before the
later entity/inventory delta.
Tests: pickup during preparation/deferred residence, parent during pending
withdrawal, delete during host callback, GUID reuse, reset/disposal ownership
convergence.
### 8. Headless parity
Delete the independent resolve/placement/direct SetPosition and Blip logic in
`HeadlessSessionWorldProjection`. Headless must prepare/commit/ack through the
same Runtime operations as graphical presentation. Portal completion also
waits for the exact placement acknowledgement.
Tests: byte-identical login, ForcePosition, portal, missing-cell wake,
reconnect, and teardown snapshots.
## AP-22 — retail-authored collision shapes
After AP-1/AD-1 production cutover is stable:
- Make `ShadowShapeBuilder` the single Core authority for prepared Setup
primitives.
- Preserve authored cylinder order.
- If no cylinders exist, preserve authored spheres as spheres.
- Mixed data uses retail cylinder-first precedence.
- A truly shapeless Setup emits no world shadow.
- Remove `Setup.Radius/Height` collision synthesis, `Radius * 2` height guesses,
and sphere-to-cylinder coercion.
- Cut graphical static, headless static, and live-entity publication over
together.
- Do not alter transition dummy spheres, sticky/range radius, or projectile
mover shapes.
Automated gates: raw/prepared parity, cylinder order, sphere-only, mixed,
shapeless, scale, graphical/headless equality, representative installed DATs,
and dropped/portal/sign/door behavior.
## AD-10 — canonical remote slope projection
After AP-22:
- Prove remote movement uses the full `ResolveWithTransition` sweep.
- Remove terrain-normal preprojection from `RemoteMotionCombiner`.
- Remove Runtime terrain-normal sampling calls and delete the sampler if no
longer used.
- Let `CTransition::adjust_offset` project against the retained actual contact
plane.
- Preserve interpolation queues, correction replacement, Hidden behavior,
network cadence, and graphical/headless parity.
Tests must deliberately make terrain normals disagree with BSP/prop contact
normals, then cover uphill/downhill motion, seams, stairs, jumping, landing,
queue-empty/head-reached boundaries, and two-client observation.
## Closeout gates
Do not mark the campaign complete from narrow tests alone.
Automated:
```powershell
dotnet build AcDream.slnx -c Release
dotnet test AcDream.slnx -c Release --no-build --nologo
```
Also run every focused fixture named in the campaign plan: #273 tight gap,
#271 stair side, #269 slope, #265 landing, #185 stairs, #137 sliding normal,
#116 head collision, roof/cellar wedge, missing-cell, generation replacement,
GUID reuse, graphical/headless parity, and allocation/quiescence gates.
Connected/visual:
- login and portal arrival at outdoor, indoor, dungeon, stair-lip, and world
edge locations;
- repeated `/ls`, spell recall, ordinary portals, same-location revisit, and
reconnect;
- no early world reveal, outdoor demotion, floor snap, terrain-Z lift, or void;
- tight gaps, stairs, steep roofs, ledges, doors, crowds, shallow water, and
landblock seams;
- dropped objects, portals, signs, doors, and shapeless decorations;
- two-client uphill/downhill movement and sloped props;
- headless/graphical trace equality and graceful zero-residue teardown.
Only then retire AP-1, AD-1, AP-22, and AD-10, update the architecture,
divergence register, campaign/roadmap/milestones, research notes, durable
memory, `CLAUDE.md`, and `AGENTS.md`, and record final rollback SHAs.
## Review procedure for every remaining behavior commit
1. Implement one bisectable mechanism and run focused tests.
2. Run a retail-conformance reviewer against named retail symbols/addresses.
3. Run an architecture/adversarial reviewer against reentrancy, stale
sequences, malformed data, GUID reuse, streaming replacement, host failure,
reset, and disposal.
4. Fix every confirmed finding at its root cause.
5. Re-run the same reviewers until clean.
6. Run Release build plus the complete Release test suite.
7. Update the divergence/docs in the same behavior commit.
8. Stage exact paths only and commit.
## Rollback points
Newest first:
```powershell
git revert 270f5154 # dormant public placement receipt channel
git revert 4c02ac42 # Runtime SetPosition/lost-cell residence owner
git revert e84a388e # pure Core retail SetPosition transaction
```
Earlier campaign commits are individually bisectable and listed in the
completed sections above. Revert only the responsible mechanism; do not
restore the rejected snapshot lease or revive legacy compensation elsewhere.
## First action for the next agent
1. Read this file completely.
2. Read `docs/research/2026-07-31-canonical-set-position.md` and the AP-1/AD-1
rows in `docs/architecture/retail-divergence-register.md`.
3. Confirm `HEAD` contains `270f5154` in the exact worktree above.
4. Confirm only `AGENTS.md` has a real unrelated unstaged diff.
5. Implement prerequisite A (real Runtime collision-report ownership) as its
own reviewed commit.
6. Then design prerequisites B/C together so exact mover preparation and the
atomic controller/body transaction cannot create another partial ownership
state.