diff --git a/docs/research/2026-07-31-remaining-physics-campaign-handoff.md b/docs/research/2026-07-31-remaining-physics-campaign-handoff.md new file mode 100644 index 00000000..2593ce45 --- /dev/null +++ b/docs/research/2026-07-31-remaining-physics-campaign-handoff.md @@ -0,0 +1,491 @@ +# Remaining physics-divergence campaign handoff — 2026-07-31 + +## 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.