diff --git a/docs/superpowers/plans/2026-05-02-l3-positionmanager-jump.md b/docs/superpowers/plans/2026-05-02-l3-positionmanager-jump.md
new file mode 100644
index 0000000..2091058
--- /dev/null
+++ b/docs/superpowers/plans/2026-05-02-l3-positionmanager-jump.md
@@ -0,0 +1,785 @@
+# Phase L.3.1+L.3.2 Combined — PositionManager + Retail-Faithful Remote Tick
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Add the PositionManager combiner (animation root motion + InterpolationManager corrections) that was originally deferred to L.3.2, plumb `IsGrounded` through `EntityPositionUpdate`, and rewrite the per-frame remote tick + `OnLivePositionUpdated` env-var-on branches to match retail's `MoveOrTeleport` semantics. This eliminates the 1-Hz chop and endless-jump bugs surfaced during Task 7 visual verification.
+
+**Architecture:** Pure-data `PositionManager.ComputeOffset(dt, body.Position, seqVel, ori, interp, maxSpeed) → Vector3` returns the per-frame world-space delta to add to body.Position. Combines (a) animation root motion = `seqVel * dt` rotated by body orientation with (b) `InterpolationManager.AdjustOffset` correction. Per-frame tick always runs all steps (matches retail `UpdateObjectInternal`). `OnLivePositionUpdated` routes per `MoveOrTeleport`: airborne → no-op; landing transition → snap + clear flags; grounded → enqueue or slide-snap. Server is authoritative for airborne arcs (no local prediction fights gravity).
+
+**Tech Stack:** C# / .NET 10 / xUnit. No new NuGet deps. Tests at `tests/AcDream.Core.Tests/Physics/*Tests.cs`.
+
+**Spec:** [`docs/superpowers/specs/2026-05-02-l3-remote-entity-motion-design.md`](../specs/2026-05-02-l3-remote-entity-motion-design.md) (committed `c4446e7`).
+
+**Already shipped (do NOT rebuild):**
+- `f43f168` + `927636e` Task 1 — InterpolationManager
+- `9c5634a` + `5b26d28` Task 2 — MotionInterpreter.GetMaxSpeed
+- `517a3ce` Task 3 — RemoteMotion.Interp field
+- `062e19f` Task 4 — OnLivePositionUpdated env-var routing v1
+- `ae79e34` Task 5 — Per-frame Interp.AdjustOffset v1
+- `e08accf` Task 6 — VectorUpdate.Omega
+- `1641d6e` revert of band-aids
+- `c4446e7` spec revision
+
+---
+
+## File Structure
+
+| File | Action | Responsibility |
+|---|---|---|
+| `src/AcDream.Core/Physics/PositionManager.cs` | **CREATE** | Pure-function combiner: animation root motion + Interp correction. ~50 lines including XML docs. |
+| `tests/AcDream.Core.Tests/Physics/PositionManagerTests.cs` | **CREATE** | 6 unit tests against pure `ComputeOffset`. |
+| `src/AcDream.Core.Net/Messages/UpdatePosition.cs` | **MODIFY** | Add `IsGrounded` to `Parsed` record, populate from `flags & PositionFlags.IsGrounded`. ~3 lines. |
+| `src/AcDream.Core.Net/WorldSession.cs` | **MODIFY** | Add `IsGrounded` to `EntityPositionUpdate` record, pass through in PositionUpdated invoke. ~2 lines. |
+| `src/AcDream.App/Rendering/GameWindow.cs` | **MODIFY** | (a) `RemoteMotion` gains `Position` field; (b) rewrite `OnLivePositionUpdated` env-var-on branch (airborne no-op + landing transition + grounded routing); (c) rewrite `TickAnimations` env-var-on branch (`PositionManager.ComputeOffset` + `UpdatePhysicsInternal`). |
+| (cleanup commit) `src/AcDream.App/Rendering/GameWindow.cs` | **MODIFY** | Delete env-var dual paths; delete `RemoteMotion` soft-snap residual fields. |
+| `docs/plans/2026-04-11-roadmap.md` | **MODIFY** (cleanup phase) | Update Phase L.3 entry to reflect L.3.1+L.3.2 combined. |
+| `docs/superpowers/specs/2026-05-02-l3-remote-entity-motion-design.md` | **MODIFY** (cleanup phase) | Mark L.3.1+L.3.2 as SHIPPED. |
+
+---
+
+## Task Decomposition Overview
+
+```
+ Task 1 — PositionManager class + 6 tests (subagent)
+ ↓
+ Task 2 — Plumb IsGrounded through EntityPositionUpdate (parent, 2 files, ~5 lines)
+ ↓
+ Task 3 — Retail-faithful per-frame remote tick (subagent — biggest change)
+ ↓
+ Task 4 — USER GATE: visual verification with retail observer
+ ↓ (after sign-off)
+ ┌─ DISPATCH IN PARALLEL ──────────────────┐
+ │ Task 5: Cleanup commit (subagent) │
+ │ Task 6: Roadmap + spec status (parent) │
+ └──────────────────────────────────────────┘
+```
+
+---
+
+## Task 1 — PositionManager class + 6 unit tests
+
+**Owner:** Sonnet subagent (general-purpose).
+
+**Files:**
+- Create: `src/AcDream.Core/Physics/PositionManager.cs`
+- Create: `tests/AcDream.Core.Tests/Physics/PositionManagerTests.cs`
+
+**Subagent dispatch prompt** (use `general-purpose` agent type, Sonnet):
+
+> You are implementing Task 1 of Phase L.3.1+L.3.2 in the acdream codebase. Read the spec at `docs/superpowers/specs/2026-05-02-l3-remote-entity-motion-design.md` section "L.3.2 architecture" → "New file — `src/AcDream.Core/Physics/PositionManager.cs`".
+>
+> **What to build:**
+>
+> Create `src/AcDream.Core/Physics/PositionManager.cs`:
+>
+> ```csharp
+> using System.Numerics;
+>
+> namespace AcDream.Core.Physics;
+>
+> ///
+> /// Per-frame combiner for remote-entity motion: animation root motion
+> /// + InterpolationManager catch-up correction. Pure function — no
+> /// side effects, no hidden state.
+> ///
+> /// Mirrors retail CPhysicsObj::UpdateObjectInternal (acclient @ 0x00513730):
+> /// rootOffset = CPartArray::Update(dt) // animation
+> /// PositionManager::adjust_offset(rootOffset) // adds correction
+> /// frame.origin += rootOffset
+> ///
+> /// In acdream the animation root motion is sourced from
+> /// AnimationSequencer.CurrentVelocity (body-local velocity from the
+> /// active locomotion cycle). We rotate that by the body's orientation
+> /// to get a world-space delta, then add the InterpolationManager's
+> /// world-space correction.
+> ///
+> public sealed class PositionManager
+> {
+> ///
+> /// Compute the per-frame world-space delta to add to body.Position.
+> ///
+> /// Per-frame delta time, seconds.
+> /// Body's current world-space position.
+> ///
+> /// Body-local velocity from the active animation cycle
+> /// (from AnimationSequencer.CurrentVelocity); pass
+> /// Vector3.Zero if the entity has no sequencer or is on a
+> /// non-locomotion cycle.
+> ///
+> /// Body orientation; used to rotate seqVel from body-local to world.
+> /// The remote's InterpolationManager (for AdjustOffset call).
+> /// From MotionInterpreter.GetMaxSpeed() — passed to AdjustOffset for the catch-up clamp.
+> public Vector3 ComputeOffset(
+> double dt,
+> Vector3 currentBodyPosition,
+> Vector3 seqVel,
+> Quaternion ori,
+> InterpolationManager interp,
+> float maxSpeed)
+> {
+> // Step 1: animation root motion (body-local → world).
+> Vector3 rootMotionLocal = seqVel * (float)dt;
+> Vector3 rootMotionWorld = Vector3.Transform(rootMotionLocal, ori);
+>
+> // Step 2: interpolation correction (world-space already).
+> Vector3 correction = interp.AdjustOffset(dt, currentBodyPosition, maxSpeed);
+>
+> // Step 3: combined delta.
+> return rootMotionWorld + correction;
+> }
+> }
+> ```
+>
+> Create `tests/AcDream.Core.Tests/Physics/PositionManagerTests.cs` with EXACTLY these 6 test names (these are the contract):
+>
+> 1. `ComputeOffset_StationaryRemote_BothSourcesZero_NoMotion`
+> - seqVel = Vector3.Zero, no enqueued nodes in interp
+> - Assert: returned offset == Vector3.Zero
+>
+> 2. `ComputeOffset_AnimationOnly_Forward_BodyAdvances`
+> - seqVel = (0, 4, 0) (4 m/s forward), ori = Quaternion.Identity, dt = 0.1
+> - Assert: returned offset == (0, 0.4, 0) (forward 0.4m)
+>
+> 3. `ComputeOffset_AnimationOnly_OrientedSouth_BodyMovesSouth`
+> - seqVel = (0, 4, 0), ori = quaternion rotating +Y → -Y (180° around Z), dt = 0.1
+> - Assert: returned offset.Y ≈ -0.4 (south)
+>
+> 4. `ComputeOffset_InterpOnly_NoAnimation_BodyChasesQueue`
+> - seqVel = Vector3.Zero, interp has 1 enqueued node 1m ahead, dt = 0.1, maxSpeed = 4f
+> - Expected: AdjustOffset returns the catch-up step (≤ 1m, clamped); ComputeOffset returns same
+>
+> 5. `ComputeOffset_BothActive_Combined`
+> - seqVel = (0, 4, 0) — root motion (0, 0.4, 0)
+> - interp has node 1m ahead — AdjustOffset returns ~Vector3.UnitY * step
+> - Assert: returned offset == rootMotion + correction
+>
+> 6. `ComputeOffset_LocalToWorldRotation_Yaw90`
+> - seqVel = (0, 1, 0) (forward 1 m/s in body frame)
+> - ori = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, MathF.PI / 2f) (yaw +90°)
+> - dt = 1
+> - Verify the rotation is applied correctly. With yaw +90° around Z, body-local +Y rotates to world... compute the expected and assert with precision: 4.
+>
+> Use xUnit, `namespace AcDream.Core.Tests.Physics;`, file-private fakes via `file sealed class` if needed. Read `tests/AcDream.Core.Tests/Physics/MotionInterpreterTests.cs` for the existing pattern.
+>
+> Note: Tests #4 and #5 need a real `InterpolationManager` (not a fake) because PositionManager calls AdjustOffset directly. Construct one inline in each test, Enqueue what you need, and call ComputeOffset.
+>
+> **Build + test:**
+>
+> ```bash
+> cd C:/Users/erikn/source/repos/acdream
+> dotnet build src/AcDream.Core/AcDream.Core.csproj -c Debug --nologo
+> dotnet test tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj --no-build --nologo --filter "FullyQualifiedName~PositionManager"
+> ```
+>
+> Both green. 6 tests pass.
+>
+> **Commit:**
+>
+> ```bash
+> git add src/AcDream.Core/Physics/PositionManager.cs tests/AcDream.Core.Tests/Physics/PositionManagerTests.cs
+> git commit -m "$(cat <<'EOF'
+> feat(physics): PositionManager combiner class + 6 unit tests (L.3.2)
+>
+> Pure-function ComputeOffset(dt, pos, seqVel, ori, interp, maxSpeed) →
+> Vector3. Combines animation root motion (seqVel × dt rotated by body
+> orientation) with InterpolationManager.AdjustOffset world-space
+> correction. Mirrors retail CPhysicsObj::UpdateObjectInternal
+> (acclient @ 0x00513730).
+>
+> Composed into RemoteMotion in subsequent task (L.3.1+L.3.2 Task 3);
+> not yet consumed.
+>
+> Co-Authored-By: Claude Opus 4.7
+> EOF
+> )"
+> ```
+>
+> **Self-review checklist:**
+> - [ ] `PositionManager` is public sealed class
+> - [ ] `ComputeOffset` is the only public method (no other API)
+> - [ ] All 6 tests have the exact names listed
+> - [ ] Tests #4 and #5 use a real `InterpolationManager`
+> - [ ] No game/window/sequencer dependencies — only `System.Numerics` + `AcDream.Core.Physics.InterpolationManager`
+> - [ ] Build clean, all 6 tests pass
+> - [ ] Commit references "L.3.2"
+>
+> **Report:**
+> - Status: DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT
+> - What you built (1-2 sentences)
+> - Test results (count, any deviations)
+> - Files changed
+> - Concerns (if any)
+
+**Steps for the parent (controller):**
+
+- [ ] **Step 1.1: Dispatch the implementer subagent** using the prompt above.
+- [ ] **Step 1.2: Verify the commit landed**
+ ```bash
+ cd C:/Users/erikn/source/repos/acdream && git log -1 --stat src/AcDream.Core/Physics/PositionManager.cs
+ ```
+ Expected: commit message starts with `feat(physics): PositionManager combiner class`.
+- [ ] **Step 1.3: Re-run tests in parent**
+ ```bash
+ cd C:/Users/erikn/source/repos/acdream && dotnet test tests/AcDream.Core.Tests/AcDream.Core.Tests.csproj --no-build --nologo --filter "FullyQualifiedName~PositionManager"
+ ```
+ Expected: 6 tests pass.
+- [ ] **Step 1.4: Dispatch spec compliance reviewer** (use `general-purpose`, Sonnet). Verify the 6 tests have the EXACT names listed and verify `ComputeOffset` algorithm matches the spec's pseudocode.
+- [ ] **Step 1.5: Dispatch code quality reviewer** (use `superpowers:code-reviewer`). Check for: API surface (only ComputeOffset public), test quality, no superfluous deps.
+- [ ] **Step 1.6: Address review issues if any.** If issues found, dispatch fix subagent. Re-review.
+
+---
+
+## Task 2 — Plumb `IsGrounded` through `EntityPositionUpdate`
+
+**Owner:** Parent. Mechanical edit, ~5 lines across 2 files.
+
+**Files:**
+- Modify: `src/AcDream.Core.Net/Messages/UpdatePosition.cs:62-69` (add `IsGrounded` to `Parsed` record)
+- Modify: `src/AcDream.Core.Net/Messages/UpdatePosition.cs:166` (populate `IsGrounded` in the constructor call)
+- Modify: `src/AcDream.Core.Net/WorldSession.cs:110-113` (add `IsGrounded` to `EntityPositionUpdate` record)
+- Modify: `src/AcDream.Core.Net/WorldSession.cs:711-714` (pass `posUpdate.Value.IsGrounded` through)
+
+**Steps:**
+
+- [ ] **Step 2.1: Read existing `UpdatePosition.Parsed` record + TryParse return**
+ ```bash
+ grep -n "public readonly record struct Parsed\|return new Parsed" "C:/Users/erikn/source/repos/acdream/src/AcDream.Core.Net/Messages/UpdatePosition.cs"
+ ```
+
+- [ ] **Step 2.2: Add `IsGrounded` field to `UpdatePosition.Parsed`**
+
+ Edit `src/AcDream.Core.Net/Messages/UpdatePosition.cs` (~line 62):
+
+ Change:
+ ```csharp
+ public readonly record struct Parsed(
+ uint Guid,
+ CreateObject.ServerPosition Position,
+ System.Numerics.Vector3? Velocity,
+ uint? PlacementId,
+ ushort InstanceSequence = 0,
+ ushort TeleportSequence = 0,
+ ushort ForcePositionSequence = 0);
+ ```
+ To:
+ ```csharp
+ public readonly record struct Parsed(
+ uint Guid,
+ CreateObject.ServerPosition Position,
+ System.Numerics.Vector3? Velocity,
+ uint? PlacementId,
+ bool IsGrounded,
+ ushort InstanceSequence = 0,
+ ushort TeleportSequence = 0,
+ ushort ForcePositionSequence = 0);
+ ```
+
+- [ ] **Step 2.3: Populate `IsGrounded` in the `Parsed` constructor call (~line 166)**
+
+ Find the line `return new Parsed(guid, serverPos, velocity, placementId,` (~line 166) and change to pass `(flags & PositionFlags.IsGrounded) != 0` as the new IsGrounded argument. Looks roughly like:
+
+ ```csharp
+ return new Parsed(guid, serverPos, velocity, placementId,
+ (flags & PositionFlags.IsGrounded) != 0,
+ instSeq, teleSeq, forceSeq);
+ ```
+
+ (Verify the trailing-arg layout against what's actually there; preserve any existing trailing arguments.)
+
+- [ ] **Step 2.4: Add `IsGrounded` field to `WorldSession.EntityPositionUpdate`**
+
+ Edit `src/AcDream.Core.Net/WorldSession.cs:110`:
+
+ Change:
+ ```csharp
+ public readonly record struct EntityPositionUpdate(
+ uint Guid,
+ CreateObject.ServerPosition Position,
+ System.Numerics.Vector3? Velocity);
+ ```
+ To:
+ ```csharp
+ public readonly record struct EntityPositionUpdate(
+ uint Guid,
+ CreateObject.ServerPosition Position,
+ System.Numerics.Vector3? Velocity,
+ bool IsGrounded);
+ ```
+
+- [ ] **Step 2.5: Pass `IsGrounded` through in PositionUpdated invoke (~line 711)**
+
+ Change:
+ ```csharp
+ PositionUpdated?.Invoke(new EntityPositionUpdate(
+ posUpdate.Value.Guid,
+ posUpdate.Value.Position,
+ posUpdate.Value.Velocity));
+ ```
+ To:
+ ```csharp
+ PositionUpdated?.Invoke(new EntityPositionUpdate(
+ posUpdate.Value.Guid,
+ posUpdate.Value.Position,
+ posUpdate.Value.Velocity,
+ posUpdate.Value.IsGrounded));
+ ```
+
+- [ ] **Step 2.6: Build + test**
+ ```bash
+ cd C:/Users/erikn/source/repos/acdream && dotnet build src/AcDream.App/AcDream.App.csproj -c Debug --nologo
+ dotnet test --no-build --nologo 2>&1 | tail -6
+ ```
+ Expected: 0 build errors. Same 4 pre-existing test failures, no new failures.
+
+- [ ] **Step 2.7: Commit**
+ ```bash
+ git add src/AcDream.Core.Net/Messages/UpdatePosition.cs src/AcDream.Core.Net/WorldSession.cs
+ git commit -m "$(cat <<'EOF'
+ feat(net): plumb IsGrounded through EntityPositionUpdate (L.3.2)
+
+ PositionFlags.IsGrounded (0x04) was already parsed by UpdatePosition
+ but not exposed through Parsed record or EntityPositionUpdate.
+ Adds the bool field to both records so OnLivePositionUpdated can
+ consume it for retail-faithful MoveOrTeleport routing
+ (acclient @ 0x00516330: has_contact=false → no-op during airborne arc).
+
+ Consumed in subsequent task (L.3.1+L.3.2 Task 3).
+
+ Co-Authored-By: Claude Opus 4.7
+ EOF
+ )"
+ ```
+
+---
+
+## Task 3 — Retail-faithful per-frame remote tick
+
+**Owner:** Sonnet subagent (general-purpose). Largest task — touches 3 distinct sites in `GameWindow.cs`.
+
+**Files:**
+- Modify: `src/AcDream.App/Rendering/GameWindow.cs` (RemoteMotion class line ~224 + OnLivePositionUpdated env-var branch + TickAnimations env-var branch)
+
+**Subagent dispatch prompt:**
+
+> You are implementing Task 3 of Phase L.3.1+L.3.2 in the acdream codebase. This task rewrites two env-var-gated branches in `src/AcDream.App/Rendering/GameWindow.cs` to consume the new PositionManager (Task 1) and IsGrounded plumbing (Task 2).
+>
+> **Repo:** `C:/Users/erikn/source/repos/acdream` — main branch — direct-to-main per CLAUDE.md.
+>
+> **Spec:** `docs/superpowers/specs/2026-05-02-l3-remote-entity-motion-design.md` "L.3.2 architecture" sections.
+>
+> **Three changes in `GameWindow.cs`:**
+>
+> ### Change 1: `RemoteMotion` class gains `Position` field
+>
+> Find the existing `Interp` field (added in commit `517a3ce`). Right after it, add:
+>
+> ```csharp
+> ///
+> /// Per-frame combiner for animation root motion + InterpolationManager
+> /// correction (Phase L.3.2). Consumed in TickAnimations to compute the
+> /// per-frame body.Position delta.
+> ///
+> public AcDream.Core.Physics.PositionManager Position { get; } =
+> new AcDream.Core.Physics.PositionManager();
+> ```
+>
+> ### Change 2: Rewrite `OnLivePositionUpdated` env-var-on branch
+>
+> Find the existing env-var-on block in `OnLivePositionUpdated` (was added at commit `062e19f`). It currently looks roughly like:
+> ```csharp
+> if (Environment.GetEnvironmentVariable("ACDREAM_INTERP_MANAGER") == "1")
+> {
+> rmState.Body.Orientation = rot;
+> // teleport check, dist check, etc.
+> return;
+> }
+> ```
+>
+> Replace the env-var-on body with this new logic:
+>
+> ```csharp
+> if (Environment.GetEnvironmentVariable("ACDREAM_INTERP_MANAGER") == "1")
+> {
+> // Orientation always snaps on receipt — the InterpolationManager
+> // walks position only; heading would otherwise lag the queue.
+> rmState.Body.Orientation = rot;
+>
+> // ── AIRBORNE NO-OP ────────────────────────────────────────────
+> // Mirrors retail CPhysicsObj::MoveOrTeleport (acclient @ 0x00516330):
+> // when has_contact==0, return false (don't touch body, don't queue).
+> // body.Velocity (set once by OnLiveVectorUpdated at jump start) keeps
+> // integrating gravity via per-frame UpdatePhysicsInternal. Server is
+> // authoritative for the arc; we don't predict it locally.
+> if (!update.IsGrounded)
+> return;
+>
+> // ── LANDING TRANSITION ─────────────────────────────────────────
+> // First IsGrounded=true UP after rmState.Airborne signals landed.
+> // Clear airborne flags, hard-snap to authoritative landing position,
+> // clear interpolation queue (any pre-jump waypoints are stale).
+> if (rmState.Airborne)
+> {
+> rmState.Airborne = false;
+> rmState.Body.Velocity = System.Numerics.Vector3.Zero;
+> rmState.Body.State &= ~AcDream.Core.Physics.PhysicsStateFlags.Gravity;
+> rmState.Body.TransientState |= AcDream.Core.Physics.TransientStateFlags.Contact
+> | AcDream.Core.Physics.TransientStateFlags.OnWalkable;
+> rmState.Interp.Clear();
+> rmState.Body.Position = worldPos;
+> return;
+> }
+>
+> // ── GROUNDED ROUTING (CPhysicsObj::MoveOrTeleport) ────────────
+> const float MaxPhysicsDistance = 96f;
+> var localPlayerPos = _playerController?.Position ?? System.Numerics.Vector3.Zero;
+> float dist = System.Numerics.Vector3.Distance(worldPos, localPlayerPos);
+>
+> if (dist > MaxPhysicsDistance)
+> {
+> // Beyond view bubble: SetPositionSimple slide-snap. Clear queue.
+> rmState.Interp.Clear();
+> rmState.Body.Position = worldPos;
+> }
+> else
+> {
+> // Within view bubble: enqueue waypoint for adjust_offset to walk to.
+> // PositionManager (called per-frame in TickAnimations) handles the
+> // actual body advancement — mix of animation root motion + queue
+> // correction.
+> float headingFromQuat = ExtractYawFromQuaternion(rot);
+> rmState.Interp.Enqueue(worldPos, headingFromQuat, isMovingTo: false);
+> }
+> return;
+> }
+> ```
+>
+> The legacy `else` branch (env-var unset) STAYS UNCHANGED.
+>
+> If `ExtractYawFromQuaternion` doesn't exist anymore (it might have been removed in the revert), re-add it near the original location (search for it in commit `062e19f`'s diff). The body is:
+> ```csharp
+> private static float ExtractYawFromQuaternion(System.Numerics.Quaternion q)
+> {
+> // Standard z-up yaw extraction: atan2(2(wz + xy), 1 - 2(y² + z²))
+> return MathF.Atan2(2f * (q.W * q.Z + q.X * q.Y),
+> 1f - 2f * (q.Y * q.Y + q.Z * q.Z));
+> }
+> ```
+>
+> ### Change 3: Rewrite `TickAnimations` env-var-on branch
+>
+> Find the existing env-var-on block in the per-frame remote tick (added at commit `ae79e34`). It currently looks roughly like:
+> ```csharp
+> if (Environment.GetEnvironmentVariable("ACDREAM_INTERP_MANAGER") == "1")
+> {
+> if (rm.Interp.IsActive) {
+> float maxSpeed = rm.Motion.GetMaxSpeed();
+> Vector3 delta = rm.Interp.AdjustOffset((double)dt, rm.Body.Position, maxSpeed);
+> rm.Body.Position += delta;
+> }
+> rm.Body.UpdatePhysicsInternal(dt);
+> // entity write-back
+> }
+> ```
+>
+> Replace with PositionManager call:
+>
+> ```csharp
+> if (Environment.GetEnvironmentVariable("ACDREAM_INTERP_MANAGER") == "1")
+> {
+> // Always-run-all-steps per retail CPhysicsObj::UpdateObjectInternal
+> // (acclient @ 0x00513730):
+> // 1+2. animation root motion + interpolation correction (combined)
+> // 3. physics integration (gravity for airborne; no-op for grounded)
+> System.Numerics.Vector3 seqVel = ae.Sequencer?.CurrentVelocity
+> ?? System.Numerics.Vector3.Zero;
+> float maxSpeed = rm.Motion.GetMaxSpeed();
+> System.Numerics.Vector3 offset = rm.Position.ComputeOffset(
+> dt: (double)dt,
+> currentBodyPosition: rm.Body.Position,
+> seqVel: seqVel,
+> ori: rm.Body.Orientation,
+> interp: rm.Interp,
+> maxSpeed: maxSpeed);
+> rm.Body.Position += offset;
+> rm.Body.UpdatePhysicsInternal(dt);
+> // KEEP whatever entity write-back lines were here (ae.Entity.Position = ..., etc.)
+> }
+> else
+> {
+> // EXISTING legacy path UNCHANGED
+> }
+> ```
+>
+> The `else` branch (legacy path) stays UNCHANGED.
+>
+> **Build + test:**
+>
+> ```bash
+> cd C:/Users/erikn/source/repos/acdream
+> dotnet build src/AcDream.App/AcDream.App.csproj -c Debug --nologo
+> dotnet test --no-build --nologo 2>&1 | tail -6
+> ```
+>
+> Expected: 0 build errors. Same 4 pre-existing failures (`DispatcherToMovementIntegrationTests` + `BSPStepUpTests` — these are not related to L.3 work). No NEW failures.
+>
+> **Commit:**
+>
+> ```bash
+> git add src/AcDream.App/Rendering/GameWindow.cs
+> git commit -m "$(cat <<'EOF'
+> feat(motion): retail-faithful per-frame remote tick (L.3.1+L.3.2)
+>
+> Combines PositionManager (Task 1) + IsGrounded plumbing (Task 2) into
+> the per-frame remote motion path. Three changes in GameWindow.cs,
+> all gated behind ACDREAM_INTERP_MANAGER=1:
+>
+> 1. RemoteMotion gains Position field (PositionManager instance).
+>
+> 2. OnLivePositionUpdated env-var branch rewritten to mirror retail
+> CPhysicsObj::MoveOrTeleport (acclient @ 0x00516330):
+> - orientation snap-on-receipt (PositionManager handles position only)
+> - airborne (!IsGrounded) → no-op (server is authoritative for arc;
+> body.Velocity from VectorUpdate integrates gravity locally)
+> - landing transition (first IsGrounded=true after Airborne) →
+> clear airborne flags, hard-snap to landing pos, clear queue
+> - grounded routing: dist > 96m → slide-snap; dist ≤ 96m → enqueue
+>
+> 3. TickAnimations env-var branch rewritten to use PositionManager:
+> body.Position += PositionManager.ComputeOffset(dt, pos, seqVel,
+> ori, interp, maxSpeed); body.UpdatePhysicsInternal(dt) for gravity.
+>
+> Replaces the L.3.1-only AdjustOffset-only path. Legacy (env-var off)
+> path unchanged.
+>
+> Cleanup commit (next sub-task) deletes the env-var dual paths after
+> visual verification.
+>
+> Co-Authored-By: Claude Opus 4.7
+> EOF
+> )"
+> ```
+>
+> **Self-review checklist:**
+> - [ ] `RemoteMotion.Position` field added (alongside existing `Interp`)
+> - [ ] `OnLivePositionUpdated` env-var branch has 3 sub-branches: airborne return, landing transition, grounded routing (snap or enqueue)
+> - [ ] `OnLivePositionUpdated` legacy `else` branch UNCHANGED
+> - [ ] `TickAnimations` env-var branch uses `PositionManager.ComputeOffset` exclusively (no direct `AdjustOffset` call)
+> - [ ] `TickAnimations` legacy `else` branch UNCHANGED
+> - [ ] `ExtractYawFromQuaternion` helper present (re-add if missing)
+> - [ ] `OnLiveVectorUpdated` UNTOUCHED (it already does the right thing)
+> - [ ] Build clean, same 4 pre-existing failures
+>
+> **Report:**
+> - Status: DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT
+> - Lines changed (with file:line refs)
+> - Test count
+> - Concerns (if any)
+>
+> If the existing legacy `else` path is so tangled that you can't safely rewrite the env-var branch without disturbing it, REPORT BLOCKED with specifics.
+
+**Steps for the parent:**
+
+- [ ] **Step 3.1: Dispatch the implementer subagent** using the prompt above.
+- [ ] **Step 3.2: Verify the commit landed**
+ ```bash
+ cd C:/Users/erikn/source/repos/acdream && git log -1 --stat src/AcDream.App/Rendering/GameWindow.cs
+ ```
+- [ ] **Step 3.3: Build + test in parent**
+ ```bash
+ cd C:/Users/erikn/source/repos/acdream && dotnet build src/AcDream.App/AcDream.App.csproj -c Debug --nologo && dotnet test --no-build --nologo 2>&1 | tail -6
+ ```
+ Expected: 0 build errors. Same 4 pre-existing failures.
+- [ ] **Step 3.4: Spec compliance review** (general-purpose subagent). Verify the rewrite matches the spec's pseudocode exactly. Verify legacy `else` paths are byte-for-byte unchanged.
+- [ ] **Step 3.5: Code quality review** (`superpowers:code-reviewer`). Specifically check: orientation snap is in ALL routing paths; airborne no-op is the FIRST gate; landing transition resets all the right flags; ExtractYawFromQuaternion is correct.
+- [ ] **Step 3.6: Address review issues if any.** Fix subagent + re-review.
+
+---
+
+## Task 4 — USER GATE: visual verification
+
+**Owner:** User. Cannot be automated.
+
+**Steps:**
+
+- [ ] **Step 4.1: Kill any running acdream**
+ ```powershell
+ Get-Process -Name AcDream.App -ErrorAction SilentlyContinue | Stop-Process -Force
+ Start-Sleep -Seconds 8
+ ```
+
+- [ ] **Step 4.2: Launch acdream with `ACDREAM_INTERP_MANAGER=1`**
+ ```powershell
+ $env:ACDREAM_DAT_DIR = "$env:USERPROFILE\Documents\Asheron's Call"
+ $env:ACDREAM_LIVE = "1"
+ $env:ACDREAM_TEST_HOST = "127.0.0.1"
+ $env:ACDREAM_TEST_PORT = "9000"
+ $env:ACDREAM_TEST_USER = "testaccount"
+ $env:ACDREAM_TEST_PASS = "testpassword"
+ $env:ACDREAM_INTERP_MANAGER = "1"
+ dotnet run --project C:\Users\erikn\source\repos\acdream\src\AcDream.App\AcDream.App.csproj --no-build -c Debug 2>&1 | Tee-Object -FilePath "C:\Users\erikn\source\repos\acdream\.claude\worktrees\jovial-blackburn-773942\launch.log"
+ ```
+
+- [ ] **Step 4.3: Visual test matrix** with parallel retail observer of `+Acdream`. On the retail side, walk + run + jump + turn the toon and verify:
+
+ | Scenario | Expected |
+ |---|---|
+ | Walk forward 5 sec | acdream observer sees smooth glide, NO 1-Hz popping |
+ | Walk backward 5 sec | smooth glide backward (regression check vs commit `17a9ff1`) |
+ | Strafe left/right 5 sec each | smooth glide sideways |
+ | Stop, then run forward 5 sec | smooth glide at run speed |
+ | Jump from standstill 2-3× | curved arc, lands cleanly, NO endless rise |
+ | Jump while running 2-3× | arc preserves forward motion, lands cleanly |
+ | Turn quickly while running | heading tracks smoothly (not stuck at login direction) |
+
+- [ ] **Step 4.4: User signs off OR files a regression**
+ - If smooth + jumps land + turning works → proceed to Tasks 5+6.
+ - If anything regresses → describe the symptom; parent dispatches a fix subagent or unsets the env-var for instant rollback.
+
+---
+
+## Task 5 — Cleanup commit (parallel with Task 6)
+
+**Owner:** Sonnet subagent (general-purpose). Independent of Task 6.
+
+**Files:**
+- Modify: `src/AcDream.App/Rendering/GameWindow.cs` (delete env-var dual paths + soft-snap fields)
+
+**Subagent dispatch prompt:**
+
+> You are implementing Task 5 of Phase L.3.1+L.3.2: cleanup. The user has visually verified that `ACDREAM_INTERP_MANAGER=1` works correctly. Now collapse the dual-path scaffolding.
+>
+> **Repo:** `C:/Users/erikn/source/repos/acdream` — main — direct-to-main per CLAUDE.md.
+>
+> **What to do in `src/AcDream.App/Rendering/GameWindow.cs`:**
+>
+> 1. **In `OnLivePositionUpdated`**: delete the `if (Environment.GetEnvironmentVariable("ACDREAM_INTERP_MANAGER") == "1") { ... return; }` wrapper. Keep ONLY the new logic inside it. Delete the legacy hard-snap path that came after.
+>
+> 2. **In `TickAnimations` (per-frame remote tick)**: delete the `if/else` env-var gate. Keep ONLY the new path (`PositionManager.ComputeOffset` + `UpdatePhysicsInternal`). Delete the legacy `apply_current_movement` + `force-OnWalkable` + Euler-extrapolate code in the `else` branch.
+>
+> 3. **In the `RemoteMotion` class** (~line 224): delete `SnapResidualDecayRate` and any soft-snap residual fields. Search for `_snapResidual`, `SnapResidualDecayRate`, `SoftSnap`. Also delete any related code in the call sites.
+>
+> 4. **Search for any remaining `ACDREAM_INTERP_MANAGER` references** in the codebase and confirm zero remain:
+> ```bash
+> grep -rn "ACDREAM_INTERP_MANAGER" "C:/Users/erikn/source/repos/acdream/src/" 2>&1
+> ```
+> Expected: no output.
+>
+> **Build + test:**
+> ```bash
+> cd C:/Users/erikn/source/repos/acdream
+> dotnet build src/AcDream.App/AcDream.App.csproj -c Debug --nologo
+> dotnet test --no-build --nologo 2>&1 | tail -6
+> ```
+> Expected: 0 build errors. Same 4 pre-existing failures, no new ones.
+>
+> **Commit:**
+> ```bash
+> git add src/AcDream.App/Rendering/GameWindow.cs
+> git commit -m "$(cat <<'EOF'
+> chore(motion): remove ACDREAM_INTERP_MANAGER flag + dead legacy paths (L.3.1+L.3.2 cleanup)
+>
+> User has visually verified the new PositionManager + IsGrounded
+> routing path works correctly. Collapses the env-var dual-path:
+> deletes legacy hard-snap + apply_current_movement + Euler-extrapolate
+> code from OnLivePositionUpdated and the per-frame remote tick.
+> Deletes SnapResidualDecayRate + soft-snap residual fields from
+> RemoteMotion.
+>
+> Single retail-faithful path remains. ~80 lines net deletion.
+>
+> Co-Authored-By: Claude Opus 4.7
+> EOF
+> )"
+> ```
+>
+> **Report:**
+> - Status, line counts deleted, files touched, test results.
+
+**Steps for the parent:**
+
+- [ ] **Step 5.1: Dispatch the cleanup subagent in parallel with Task 6** (one message, two Agent tool calls).
+- [ ] **Step 5.2: Verify the commit landed**
+ ```bash
+ cd C:/Users/erikn/source/repos/acdream && git log -1 --stat
+ ```
+- [ ] **Step 5.3: Confirm zero env-var references remain**
+ ```bash
+ grep -rn "ACDREAM_INTERP_MANAGER" "C:/Users/erikn/source/repos/acdream/src/" 2>&1
+ ```
+ Expected: no output.
+- [ ] **Step 5.4: Re-run all tests in parent**
+ ```bash
+ cd C:/Users/erikn/source/repos/acdream && dotnet test --no-build --nologo 2>&1 | tail -6
+ ```
+ Expected: same baseline.
+
+---
+
+## Task 6 — Roadmap + spec status update (parallel with Task 5)
+
+**Owner:** Parent. Mechanical doc updates.
+
+**Files:**
+- Modify: `docs/plans/2026-04-11-roadmap.md` (Phase L.3 entry — mark L.3.1+L.3.2 SHIPPED)
+- Modify: `docs/superpowers/specs/2026-05-02-l3-remote-entity-motion-design.md` (add SHIPPED status banner)
+
+**Steps:**
+
+- [ ] **Step 6.1: Find the Phase L.3 entry in the roadmap**
+ ```bash
+ grep -n "Phase L.3\|L.3.1\|L.3.2\|L.3.3" "C:/Users/erikn/source/repos/acdream/docs/plans/2026-04-11-roadmap.md"
+ ```
+ If the roadmap doesn't yet have a Phase L.3 entry, add one between L.2 and M with the L.3.1+L.3.2 combined status = SHIPPED, L.3.3 status = PLANNED.
+
+- [ ] **Step 6.2: Update the spec doc's status**
+
+ In `docs/superpowers/specs/2026-05-02-l3-remote-entity-motion-design.md`, near the top (after the title / methodology), add or update a status line:
+
+ ```markdown
+ **Status:** L.3.1+L.3.2 SHIPPED 2026-05-02. L.3.3 PLANNED.
+ ```
+
+- [ ] **Step 6.3: Commit (combined doc update)**
+ ```bash
+ git add docs/plans/2026-04-11-roadmap.md docs/superpowers/specs/2026-05-02-l3-remote-entity-motion-design.md
+ git commit -m "$(cat <<'EOF'
+ docs(roadmap+spec): Phase L.3.1+L.3.2 shipped (L.3.3 pending)
+
+ Roadmap Phase L.3 entry updated. Spec status banner reflects the
+ combined L.3.1+L.3.2 deliverable as shipped after visual verification.
+ L.3.3 (MoveToManager) remains a separate sub-lane to be specced and
+ scheduled.
+
+ Co-Authored-By: Claude Opus 4.7
+ EOF
+ )"
+ ```
+
+---
+
+## Verification Plan
+
+End-to-end smoke test after Task 6:
+
+```bash
+cd C:/Users/erikn/source/repos/acdream
+dotnet build src/AcDream.App/AcDream.App.csproj -c Debug --nologo # green
+dotnet test --no-build --nologo # 4 pre-existing failures only
+git log --oneline -10 # see commits in order
+grep -rn "ACDREAM_INTERP_MANAGER" src/ # zero hits (cleanup confirmed)
+grep -rn "SnapResidualDecayRate" src/ # zero hits (deleted)
+```
+
+User can re-run the visual test matrix WITHOUT setting `ACDREAM_INTERP_MANAGER` (default behavior is now the new path) and confirm parity.
+
+If everything's green → Phase L.3.1+L.3.2 done; brainstorm L.3.3 (MoveToManager) as the next sub-lane.
+
+---
+
+## Self-Review Notes
+
+- **Spec coverage:** every section of the spec maps to a task here. PositionManager → Task 1; IsGrounded plumbing → Task 2; per-frame tick rewrite + RemoteMotion field + OnLivePositionUpdated rewrite → Task 3; cleanup → Task 5; doc updates → Task 6.
+- **Already-shipped commits NOT rebuilt.** L.3.1's first 6 commits (f43f168 → e08accf) already provide InterpolationManager + GetMaxSpeed + Interp field + v1 routing + v1 tick + Omega.
+- **Reverted commits** (5154a3e + f199a6a) were band-aids; their replacements live in Task 3.
+- **Subagent failure handling:** if a subagent reports BLOCKED on Task 3 (the largest), break it into smaller pieces (3a: RemoteMotion field; 3b: OnLivePositionUpdated rewrite; 3c: TickAnimations rewrite) and dispatch sequentially. Don't let a confused subagent leave broken code in main.
+- **Task 4's visual verification is the gate.** Tasks 5+6 only fire after user sign-off. If visual fails, dispatch a fix subagent before Tasks 5+6.