diff --git a/CLAUDE.md b/CLAUDE.md
index 0e02b6d..469b95c 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -552,17 +552,14 @@ via `PlayerMovementController.ApplyServerRunRate`) or from
diagnostics (`[UM_RAW]`, `[SCFAST]`, `[SCFULL]`, `[SETCYCLE]`,
`[FWD_WIRE]`, `[OMEGA_DIAG]`, `[SEQSTATE]`, `[PARTSDIAG]`,
`[VEL_DIAG]`, `[UPCYCLE]`). Heavy.
-- ⚠️ `ACDREAM_INTERP_MANAGER=1` — **DO NOT ENABLE.** This was an
- experimental rewrite (e94e791) of the per-tick remote motion path.
- It's regressed: the env-var path drops the per-tick collision sweep
- (`ResolveWithTransition`) that the default path retains, causing a
- visible "staircase" pattern when remotes run up/down slopes (body
- Z stays flat between UPs, snaps at each one) plus position blips
- during steady-state motion. Default (env-var unset) uses the
- working retail-port chain. The PositionManager class itself is
- fine and retail-faithful; only the integration into per-tick was
- wrong. To be re-done in a future L.3 follow-up phase as additive
- refinement on top of the working chain.
+- *(retired 2026-05-05 by L.3 M2/M3)* `ACDREAM_INTERP_MANAGER` was an
+ env-var gate on an experimental per-tick remote motion path. L.3 M2
+ (commit 40d88b9) replaced both gates (`OnLivePositionUpdated` +
+ `TickAnimations`) with `IsPlayerGuid(...)` so player remotes use the
+ retail-faithful queue routing (InterpolationManager queue catch-up +
+ PositionManager combiner) unconditionally. NPCs and airborne player
+ remotes still flow through the legacy `apply_current_movement` +
+ `ResolveWithTransition` path. The env-var no longer toggles anything.
### Outbound motion wire format (acdream → ACE)
diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs
index 0c1c15e..08a8a05 100644
--- a/src/AcDream.App/Rendering/GameWindow.cs
+++ b/src/AcDream.App/Rendering/GameWindow.cs
@@ -295,11 +295,6 @@ public sealed class GameWindow : IDisposable
///
public double LastMoveToPacketTime;
///
- /// Legacy field — no longer used for slerp (retail hard-snaps
- /// per FUN_00514b90 set_frame). Kept to avoid churn.
- ///
- public System.Numerics.Quaternion TargetOrientation = System.Numerics.Quaternion.Identity;
- ///
/// Angular velocity seeded from UpdateMotion TurnCommand/TurnSpeed
/// (π/2 × turnSpeed, signed). Applied per tick to body orientation
/// via manual integration (bypassing PhysicsBody.update_object's
@@ -334,34 +329,21 @@ public sealed class GameWindow : IDisposable
///
/// Per-remote position-waypoint queue + catch-up math (retail's
/// CPhysicsObj::InterpolateTo + InterpolationManager::adjust_offset).
- /// Replaces the hard-snap-then-Euler-extrapolate path when
- /// ACDREAM_INTERP_MANAGER=1 — see Phase L.3.1 spec at
- /// docs/superpowers/specs/2026-05-02-l3-remote-entity-motion-design.md.
- /// Field exists from Task 3 onwards; consumed in Tasks 4 + 5.
+ /// Drives per-tick body translation for grounded player remotes
+ /// via .
///
public AcDream.Core.Physics.InterpolationManager Interp { get; } =
new AcDream.Core.Physics.InterpolationManager();
///
/// Per-frame combiner for animation root motion + InterpolationManager
- /// correction (Phase L.3.2). Consumed in TickAnimations to compute the
- /// per-frame body.Position delta.
+ /// correction. Mirrors retail UpdatePositionInternal @ 0x00512c30:
+ /// queue catch-up REPLACES anim when active; anim stands when queue
+ /// is idle.
///
public AcDream.Core.Physics.PositionManager Position { get; } =
new AcDream.Core.Physics.PositionManager();
- ///
- /// Most recent server-broadcast Z coordinate from any UpdatePosition
- /// (including mid-arc airborne UPs). Used by the
- /// ACDREAM_INTERP_MANAGER=1 per-tick path as a landing-fallback
- /// floor: if gravity drags the body's Z below this value while
- /// is still set, force-land locally because
- /// the server has effectively told us where the ground is even if
- /// it never sent an IsGrounded=true UP. Initialized to NaN so the
- /// fallback is a no-op until the first UP arrives.
- ///
- public float LastServerZ = float.NaN;
-
///
/// Diagnostic-only (gated on ACDREAM_REMOTE_VEL_DIAG=1): the
/// previous UpdatePosition's world position + timestamp. The per-tick
@@ -3444,30 +3426,12 @@ public sealed class GameWindow : IDisposable
// from ServerVelocity / ServerMoveTo which the legacy path
// already handles correctly.
//
- // Was previously gated on ACDREAM_INTERP_MANAGER=1; the env-var
- // path's per-tick TickAnimations counterpart is regressed
- // (issue #40). M2 keeps the OnLivePositionUpdated half (which
- // is correct) and rewrites the per-tick half — see TickAnimations.
if (IsPlayerGuid(update.Guid))
{
// Orientation always snaps on receipt — InterpolationManager walks
// position only; heading would otherwise lag the queue.
rmState.Body.Orientation = rot;
- // Track the most recent GROUNDED server-broadcast Z. Read by
- // the per-tick landing-fallback in TickAnimations: if gravity
- // drags the body more than 0.5 m below this floor while still
- // airborne, we force-land locally even when the server never
- // sent an IsGrounded=true UP for the actual landing frame.
- //
- // Only updated for grounded UPs — mid-arc airborne UPs would
- // raise this value to the player's peak Z, then the body's
- // descent would cross (peak - 0.5) and trigger a force-land
- // mid-air, producing the user-reported "small landing in the
- // air before landing on the ground" when jumping while moving.
- if (update.IsGrounded)
- rmState.LastServerZ = worldPos.Z;
-
// Diagnostic (ACDREAM_REMOTE_VEL_DIAG=1): roll the previous
// server-pos snapshot forward AND print the per-UP comparison
// between the max sequencer speed observed since last UP and
@@ -3643,7 +3607,6 @@ public sealed class GameWindow : IDisposable
// a halved "observed" rate → visible slow-start. Formula-only
// is stable and simple; hard-snap fixes any drift.
rmState.Body.Orientation = rot;
- rmState.TargetOrientation = rot;
rmState.LastServerPos = worldPos;
rmState.LastServerPosTime = nowSec;
// Align the body's physics clock with our clock so update_object