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

20 KiB

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, 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:

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:

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:

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:

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.