diff --git a/src/AcDream.App/Physics/ProjectileController.cs b/src/AcDream.App/Physics/ProjectileController.cs
index 7e68faef..7e4ad350 100644
--- a/src/AcDream.App/Physics/ProjectileController.cs
+++ b/src/AcDream.App/Physics/ProjectileController.cs
@@ -5,7 +5,7 @@ using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Core.World;
using DatReaderWriter.DBObjs;
-using RemoteMotion = AcDream.App.Rendering.GameWindow.RemoteMotion;
+using RemoteMotion = AcDream.App.Physics.RemoteMotion;
namespace AcDream.App.Physics;
diff --git a/src/AcDream.App/Physics/RemoteMotion.cs b/src/AcDream.App/Physics/RemoteMotion.cs
new file mode 100644
index 00000000..7b35d0bd
--- /dev/null
+++ b/src/AcDream.App/Physics/RemoteMotion.cs
@@ -0,0 +1,307 @@
+using AcDream.App.Rendering;
+using AcDream.App.World;
+
+namespace AcDream.App.Physics;
+
+///
+/// Per-remote-entity physics + motion stack — verbatim application of
+/// retail's client-side motion pipeline to every remote. Mirrors retail
+/// CPhysicsObj::update_object (0x00515D10) →
+/// CPhysicsObj::UpdateObjectInternal (0x005156B0) →
+/// CPhysicsObj::UpdatePositionInternal (0x00512C30) →
+/// CPhysicsObj::UpdatePhysicsInternal (0x00510700), and ACE's
+/// PhysicsObj.cs port.
+///
+///
+/// Retail has NO special "interpolator" for remote entities — it runs
+/// the full motion state machine on every entity, local or remote,
+/// and reconciles via hard-snap on UpdatePosition. This class simply
+/// pairs a with its
+/// so each
+/// remote gets the same treatment as the local player.
+///
+///
+internal sealed class RemoteMotion :
+ AcDream.App.World.ILiveEntityRemotePlacementRuntime
+{
+ public AcDream.Core.Physics.PhysicsBody Body { get; }
+ AcDream.Core.Physics.PhysicsBody AcDream.App.World.ILiveEntityRemoteMotionRuntime.Body => Body;
+
+ /// R5-V5: retail CPhysicsObj::movement_manager — the
+ /// ONE per-entity owner of the interp + moveto pair (acclient.h
+ /// /* 3463 */). Constructed here with the interp; the
+ /// MoveToManager side arrives via MoveToFactory +
+ /// MakeMoveToManager() in EnsureRemoteMotionBindings.
+ /// / below are views of its
+ /// children (retail get_minterp-style access), kept so the
+ /// dozens of existing call sites read unchanged.
+ public AcDream.Core.Physics.Motion.MovementManager Movement;
+
+ public AcDream.Core.Physics.MotionInterpreter Motion => Movement.Minterp;
+ /// R3-W4: the persistent per-remote dispatch sink (created
+ /// once by EnsureRemoteMotionBindings; also bound as
+ /// Motion.DefaultSink so LeaveGround/HitGround re-applies dispatch
+ /// cycles through the motion-table backend).
+ public AcDream.Core.Physics.Motion.MotionTableDispatchSink? Sink;
+ /// Last UpdatePosition timestamp — drives body.update_object sub-stepping.
+ public double LastServerPosTime;
+ /// Last known server position — kept for diagnostics / HUD.
+ public System.Numerics.Vector3 LastServerPos;
+ ///
+ /// #184: the body position at which this remote's collision SHADOW was
+ /// last (re-)registered. The per-tick shadow-follows-resolved sync
+ /// (SyncRemoteShadowToBody) skips re-flooding when the body has
+ /// not moved > ε since this — a resting/idle-town crowd doesn't churn
+ /// the registry, while a moving/de-overlapping crowd re-registers every
+ /// tick. Sentinel Zero forces the first sync.
+ ///
+ public System.Numerics.Vector3 LastShadowSyncPos;
+ ///
+ /// Complete root orientation paired with .
+ /// Multipart and off-centre Setup collision geometry must be re-flooded
+ /// when the object turns in place, not only when its origin translates.
+ /// The default zero quaternion is the first-sync sentinel.
+ ///
+ public System.Numerics.Quaternion LastShadowSyncOrientation;
+ ///
+ /// Latest server-authoritative velocity for NPC/monster smoothing.
+ /// Prefer the HasVelocity vector from UpdatePosition; when ACE omits
+ /// it for a server-controlled creature, derive it from consecutive
+ /// authoritative positions instead of guessing from player RUM state.
+ ///
+ public System.Numerics.Vector3 ServerVelocity;
+ public bool HasServerVelocity;
+
+ /// R4-V4: the entity's verbatim retail MoveToManager
+ /// (server-directed movement), constructed + seam-bound by
+ /// EnsureRemoteMotionBindings beside the R2-Q5/R3 binds. R5-V5:
+ /// owned by ; this is the child view.
+ public AcDream.Core.Physics.Motion.MoveToManager? MoveTo => Movement.MoveTo;
+
+ // R5-V2: the entity's CPhysicsObj stand-in — owns the TargetManager
+ // voyeur system (retail CPhysicsObj::target_manager). Replaces the
+ // AP-79 TrackedTarget* poll fields: the MoveToManager's set_target/
+ // clear_target/quantum seams route here, HandleTargetting ticks per
+ // frame, and OTHER entities resolve this one via GameWindow._physicsHosts.
+ public EntityPhysicsHost? Host;
+ ///
+ /// True while a server MoveToObject/MoveToPosition packet is the
+ /// active locomotion source. Retail runs these through MoveToManager
+ /// and CMotionInterp; the per-tick remote driver consults this to
+ /// decide whether to feed body steering through
+ /// instead of
+ /// the InterpretedMotionState path.
+ ///
+ public bool ServerMoveToActive;
+
+ ///
+ /// True once a MoveTo packet's full path payload (Origin + thresholds)
+ /// has been parsed and the world-converted destination is stored on
+ /// . Cleared on arrival or when
+ /// the next non-MoveTo UpdateMotion replaces the locomotion source.
+ /// Phase L.1c (2026-04-28).
+ ///
+ public bool HasMoveToDestination;
+
+ ///
+ /// World-space destination from the most recent MoveTo packet's
+ /// Origin field, converted via the same landblock-grid
+ /// arithmetic OnLivePositionUpdated uses.
+ ///
+ public System.Numerics.Vector3 MoveToDestinationWorld;
+
+ ///
+ /// min_distance from the MoveTo packet's MovementParameters.
+ /// Used by as
+ /// the chase-arrival threshold per retail
+ /// MoveToManager::HandleMoveToPosition.
+ ///
+ public float MoveToMinDistance;
+
+ ///
+ /// distance_to_object from the MoveTo packet. Reserved for
+ /// the flee branch (move_away); chase uses
+ /// .
+ ///
+ public float MoveToDistanceToObject;
+
+ ///
+ /// True if MovementParameters bit 9 (move_towards, mask
+ /// 0x200) is set on the active packet — i.e. this is a
+ /// chase. False = flee (move_away) or static target.
+ ///
+ public bool MoveToMoveTowards;
+
+ ///
+ /// Seconds-since-epoch timestamp of the most recent MoveTo packet
+ /// for this entity. Used by the per-tick driver to give up
+ /// steering when no refresh has arrived for
+ ///
+ /// — typically because the entity left our streaming view and
+ /// the server stopped broadcasting its MoveTo updates.
+ ///
+ public double LastMoveToPacketTime;
+ ///
+ /// Full 32-bit cell ID from the latest UpdatePosition. High 16 bits
+ /// = landblock (LBx,LBy), low 16 bits = cell index (outdoor 0x0001-
+ /// 0x0040, indoor 0x0100+). Fed into
+ ///
+ /// so the retail sphere-sweep can look up the right terrain/EnvCell
+ /// polygons for each remote's per-frame motion. Zero until the first
+ /// UP lands, which disables the transition step for that frame
+ /// (Euler alone, matching pre-wire behavior).
+ ///
+ private uint _cellId;
+ private Func? _canonicalCellReader;
+ private Action? _canonicalCellWriter;
+
+ ///
+ /// Canonical full cell for this one live CPhysicsObj. Ordinary remotes
+ /// retain the local backing value. A MovementManager attached to an
+ /// existing projectile delegates reads and writes to
+ /// , so projectile prediction,
+ /// Movement packets, and spatial rebucketing cannot develop competing
+ /// cell identities.
+ ///
+ public uint CellId
+ {
+ get => _canonicalCellReader?.Invoke() ?? _cellId;
+ set
+ {
+ _cellId = value;
+ _canonicalCellWriter?.Invoke(value);
+ }
+ }
+
+ System.Numerics.Vector3 AcDream.App.World.ILiveEntityRemotePlacementRuntime.LastServerPosition
+ {
+ get => LastServerPos;
+ set => LastServerPos = value;
+ }
+
+ double AcDream.App.World.ILiveEntityRemotePlacementRuntime.LastServerPositionTime
+ {
+ get => LastServerPosTime;
+ set => LastServerPosTime = value;
+ }
+
+ System.Numerics.Vector3 AcDream.App.World.ILiveEntityRemotePlacementRuntime.LastShadowSyncPosition
+ {
+ get => LastShadowSyncPos;
+ set => LastShadowSyncPos = value;
+ }
+
+ System.Numerics.Quaternion AcDream.App.World.ILiveEntityRemotePlacementRuntime.LastShadowSyncOrientation
+ {
+ get => LastShadowSyncOrientation;
+ set => LastShadowSyncOrientation = value;
+ }
+
+ bool AcDream.App.World.ILiveEntityRemotePlacementRuntime.Airborne
+ {
+ get => Airborne;
+ set => Airborne = value;
+ }
+
+ void AcDream.App.World.ILiveEntityRemotePlacementRuntime.HitGround() =>
+ Movement.HitGround();
+
+ void AcDream.App.World.ILiveEntityRemotePlacementRuntime.LeaveGround() =>
+ Motion.LeaveGround();
+
+ ///
+ /// K-fix9 (2026-04-26): true while the remote is airborne (jump
+ /// arc in flight). Set when a 0xF74E VectorUpdate arrives with
+ /// non-trivial +Z velocity; cleared when the next UpdatePosition
+ /// snaps to a new ground location. While true, the per-tick
+ /// remote update SKIPS the "force OnWalkable + apply_current_movement"
+ /// step that would otherwise stomp the body's Z velocity each
+ /// frame, AND enables gravity so the parabolic arc actually plays
+ /// out between server snaps.
+ ///
+ public bool Airborne;
+
+ ///
+ /// Per-remote position-waypoint queue + catch-up math (retail's
+ /// CPhysicsObj::InterpolateTo + InterpolationManager::adjust_offset).
+ /// 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. Mirrors retail UpdatePositionInternal @ 0x00512c30:
+ /// queue catch-up REPLACES anim when active; anim stands when queue
+ /// is idle.
+ ///
+ public AcDream.Core.Physics.RemoteMotionCombiner Position { get; } =
+ new AcDream.Core.Physics.RemoteMotionCombiner();
+
+ /// Reusable complete delta frame shared by interpolation,
+ /// Sticky/Constraint, and the final body composition.
+ public AcDream.Core.Physics.Motion.MotionDeltaFrame PositionManagerDeltaScratch { get; } =
+ new AcDream.Core.Physics.Motion.MotionDeltaFrame();
+
+ ///
+ /// Diagnostic-only (gated on ACDREAM_REMOTE_VEL_DIAG=1): the
+ /// previous UpdatePosition's world position + timestamp. The per-tick
+ /// path compares the server's broadcast pace with the literal root
+ /// displacement emitted by CSequence::update. The old inferred
+ /// walk/run velocity was the source of the periodic overshoot this
+ /// diagnostic was introduced to find.
+ ///
+ public System.Numerics.Vector3 PrevServerPos;
+ public double PrevServerPosTime;
+ public double LastOmegaDiagLogTime;
+
+ ///
+ /// Diagnostic-only: maximum scaled CSequence root-motion speed
+ /// observed since the last UpdatePosition arrival. The next UP
+ /// compares it with the server's observed pace. Reset on each UP.
+ ///
+ public float MaxRootMotionSpeedSinceLastUP;
+
+ public RemoteMotion(AcDream.Core.Physics.PhysicsBody? sharedBody = null)
+ {
+ Body = sharedBody ?? new AcDream.Core.Physics.PhysicsBody
+ {
+ // Remotes don't simulate gravity — server owns Z. Force
+ // Contact + OnWalkable + Active so apply_current_movement
+ // writes velocity through every tick (the gate in
+ // MotionInterpreter.apply_current_movement is
+ // PhysicsObj.OnWalkable).
+ State = AcDream.Core.Physics.PhysicsStateFlags.ReportCollisions,
+ TransientState = AcDream.Core.Physics.TransientStateFlags.Contact
+ | AcDream.Core.Physics.TransientStateFlags.OnWalkable
+ | AcDream.Core.Physics.TransientStateFlags.Active,
+ // R4-V5 door-swing fix: a RemoteMotion exists only for a
+ // WORLD entity — retail's physics_obj->cell is non-null the
+ // moment the object is placed. Without this, the interp's
+ // detached-object guard stripped every dispatched
+ // transition link (see PhysicsBody.InWorld).
+ InWorld = true,
+ };
+ // R5-V5: the interp is owned by the MovementManager facade
+ // (retail CPhysicsObj::movement_manager -> motion_interpreter).
+ // acdream constructs it eagerly here — retail's lazy-create
+ // window is never observable (see MovementManager's class doc).
+ Movement = new AcDream.Core.Physics.Motion.MovementManager(
+ new AcDream.Core.Physics.MotionInterpreter(Body)
+ {
+ // R4-V5 #160 fix: retail remotes carry a weenie whose
+ // InqRunRate FAILS, landing apply_run_to_command on
+ // my_run_rate (the M13 wire feed). A null weenie took the
+ // degenerate 1.0 branch — observer-side run movetos played
+ // and moved in slow motion. See RemoteWeenie.
+ WeenieObj = new AcDream.Core.Physics.RemoteWeenie(),
+ });
+ }
+
+ public void BindCanonicalCell(Func read, Action write)
+ {
+ _canonicalCellReader = read ?? throw new ArgumentNullException(nameof(read));
+ _canonicalCellWriter = write ?? throw new ArgumentNullException(nameof(write));
+ }
+}
diff --git a/src/AcDream.App/Physics/RemotePhysicsUpdater.cs b/src/AcDream.App/Physics/RemotePhysicsUpdater.cs
index 4c6b039b..412ef6e2 100644
--- a/src/AcDream.App/Physics/RemotePhysicsUpdater.cs
+++ b/src/AcDream.App/Physics/RemotePhysicsUpdater.cs
@@ -1,4 +1,4 @@
-using RemoteMotion = AcDream.App.Rendering.GameWindow.RemoteMotion;
+using RemoteMotion = AcDream.App.Physics.RemoteMotion;
using LiveEntityAnimationState = AcDream.App.Rendering.LiveEntityAnimationState;
namespace AcDream.App.Physics;
diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs
index fb194182..dcd6ba55 100644
--- a/src/AcDream.App/Rendering/GameWindow.cs
+++ b/src/AcDream.App/Rendering/GameWindow.cs
@@ -1,4 +1,5 @@
using AcDream.Core.Plugins;
+using AcDream.App.Physics;
using AcDream.App.World;
using AcDream.Content;
using DatReaderWriter;
@@ -421,307 +422,6 @@ public sealed class GameWindow : IDisposable, ILiveAnimationPresentationContext
private AcDream.App.World.LiveEntityRuntime? _liveEntities;
private AcDream.App.World.LiveEntityLivenessController? _liveEntityLiveness;
- ///
- /// Per-remote-entity physics + motion stack — verbatim application of
- /// retail's client-side motion pipeline to every remote. Mirrors
- /// retail FUN_00515020 update_object → FUN_00513730
- /// UpdatePositionInternal → FUN_005111D0
- /// UpdatePhysicsInternal, and ACE's PhysicsObj.cs port.
- ///
- ///
- /// Retail has NO special "interpolator" for remote entities — it runs
- /// the full motion state machine on every entity, local or remote,
- /// and reconciles via hard-snap on UpdatePosition. This class simply
- /// pairs a with its
- /// so each
- /// remote gets the same treatment as the local player.
- ///
- ///
- internal sealed class RemoteMotion :
- AcDream.App.World.ILiveEntityRemotePlacementRuntime
- {
- public AcDream.Core.Physics.PhysicsBody Body { get; }
- AcDream.Core.Physics.PhysicsBody AcDream.App.World.ILiveEntityRemoteMotionRuntime.Body => Body;
-
- /// R5-V5: retail CPhysicsObj::movement_manager — the
- /// ONE per-entity owner of the interp + moveto pair (acclient.h
- /// /* 3463 */). Constructed here with the interp; the
- /// MoveToManager side arrives via MoveToFactory +
- /// MakeMoveToManager() in EnsureRemoteMotionBindings.
- /// / below are views of its
- /// children (retail get_minterp-style access), kept so the
- /// dozens of existing call sites read unchanged.
- public AcDream.Core.Physics.Motion.MovementManager Movement;
-
- public AcDream.Core.Physics.MotionInterpreter Motion => Movement.Minterp;
- /// R3-W4: the persistent per-remote dispatch sink (created
- /// once by EnsureRemoteMotionBindings; also bound as
- /// Motion.DefaultSink so LeaveGround/HitGround re-applies dispatch
- /// cycles through the motion-table backend).
- public AcDream.Core.Physics.Motion.MotionTableDispatchSink? Sink;
- /// Last UpdatePosition timestamp — drives body.update_object sub-stepping.
- public double LastServerPosTime;
- /// Last known server position — kept for diagnostics / HUD.
- public System.Numerics.Vector3 LastServerPos;
- ///
- /// #184: the body position at which this remote's collision SHADOW was
- /// last (re-)registered. The per-tick shadow-follows-resolved sync
- /// (SyncRemoteShadowToBody) skips re-flooding when the body has
- /// not moved > ε since this — a resting/idle-town crowd doesn't churn
- /// the registry, while a moving/de-overlapping crowd re-registers every
- /// tick. Sentinel Zero forces the first sync.
- ///
- public System.Numerics.Vector3 LastShadowSyncPos;
- ///
- /// Complete root orientation paired with .
- /// Multipart and off-centre Setup collision geometry must be re-flooded
- /// when the object turns in place, not only when its origin translates.
- /// The default zero quaternion is the first-sync sentinel.
- ///
- public System.Numerics.Quaternion LastShadowSyncOrientation;
- ///
- /// Latest server-authoritative velocity for NPC/monster smoothing.
- /// Prefer the HasVelocity vector from UpdatePosition; when ACE omits
- /// it for a server-controlled creature, derive it from consecutive
- /// authoritative positions instead of guessing from player RUM state.
- ///
- public System.Numerics.Vector3 ServerVelocity;
- public bool HasServerVelocity;
-
- /// R4-V4: the entity's verbatim retail MoveToManager
- /// (server-directed movement), constructed + seam-bound by
- /// EnsureRemoteMotionBindings beside the R2-Q5/R3 binds. R5-V5:
- /// owned by ; this is the child view.
- public AcDream.Core.Physics.Motion.MoveToManager? MoveTo => Movement.MoveTo;
-
- // R5-V2: the entity's CPhysicsObj stand-in — owns the TargetManager
- // voyeur system (retail CPhysicsObj::target_manager). Replaces the
- // AP-79 TrackedTarget* poll fields: the MoveToManager's set_target/
- // clear_target/quantum seams route here, HandleTargetting ticks per
- // frame, and OTHER entities resolve this one via GameWindow._physicsHosts.
- public EntityPhysicsHost? Host;
- ///
- /// True while a server MoveToObject/MoveToPosition packet is the
- /// active locomotion source. Retail runs these through MoveToManager
- /// and CMotionInterp; the per-tick remote driver consults this to
- /// decide whether to feed body steering through
- /// instead of
- /// the InterpretedMotionState path.
- ///
- public bool ServerMoveToActive;
-
- ///
- /// True once a MoveTo packet's full path payload (Origin + thresholds)
- /// has been parsed and the world-converted destination is stored on
- /// . Cleared on arrival or when
- /// the next non-MoveTo UpdateMotion replaces the locomotion source.
- /// Phase L.1c (2026-04-28).
- ///
- public bool HasMoveToDestination;
-
- ///
- /// World-space destination from the most recent MoveTo packet's
- /// Origin field, converted via the same landblock-grid
- /// arithmetic OnLivePositionUpdated uses.
- ///
- public System.Numerics.Vector3 MoveToDestinationWorld;
-
- ///
- /// min_distance from the MoveTo packet's MovementParameters.
- /// Used by as
- /// the chase-arrival threshold per retail
- /// MoveToManager::HandleMoveToPosition.
- ///
- public float MoveToMinDistance;
-
- ///
- /// distance_to_object from the MoveTo packet. Reserved for
- /// the flee branch (move_away); chase uses
- /// .
- ///
- public float MoveToDistanceToObject;
-
- ///
- /// True if MovementParameters bit 9 (move_towards, mask
- /// 0x200) is set on the active packet — i.e. this is a
- /// chase. False = flee (move_away) or static target.
- ///
- public bool MoveToMoveTowards;
-
- ///
- /// Seconds-since-epoch timestamp of the most recent MoveTo packet
- /// for this entity. Used by the per-tick driver to give up
- /// steering when no refresh has arrived for
- ///
- /// — typically because the entity left our streaming view and
- /// the server stopped broadcasting its MoveTo updates.
- ///
- public double LastMoveToPacketTime;
- ///
- /// Full 32-bit cell ID from the latest UpdatePosition. High 16 bits
- /// = landblock (LBx,LBy), low 16 bits = cell index (outdoor 0x0001-
- /// 0x0040, indoor 0x0100+). Fed into
- ///
- /// so the retail sphere-sweep can look up the right terrain/EnvCell
- /// polygons for each remote's per-frame motion. Zero until the first
- /// UP lands, which disables the transition step for that frame
- /// (Euler alone, matching pre-wire behavior).
- ///
- private uint _cellId;
- private Func? _canonicalCellReader;
- private Action? _canonicalCellWriter;
-
- ///
- /// Canonical full cell for this one live CPhysicsObj. Ordinary remotes
- /// retain the local backing value. A MovementManager attached to an
- /// existing projectile delegates reads and writes to
- /// , so projectile prediction,
- /// Movement packets, and spatial rebucketing cannot develop competing
- /// cell identities.
- ///
- public uint CellId
- {
- get => _canonicalCellReader?.Invoke() ?? _cellId;
- set
- {
- _cellId = value;
- _canonicalCellWriter?.Invoke(value);
- }
- }
-
- System.Numerics.Vector3 AcDream.App.World.ILiveEntityRemotePlacementRuntime.LastServerPosition
- {
- get => LastServerPos;
- set => LastServerPos = value;
- }
-
- double AcDream.App.World.ILiveEntityRemotePlacementRuntime.LastServerPositionTime
- {
- get => LastServerPosTime;
- set => LastServerPosTime = value;
- }
-
- System.Numerics.Vector3 AcDream.App.World.ILiveEntityRemotePlacementRuntime.LastShadowSyncPosition
- {
- get => LastShadowSyncPos;
- set => LastShadowSyncPos = value;
- }
-
- System.Numerics.Quaternion AcDream.App.World.ILiveEntityRemotePlacementRuntime.LastShadowSyncOrientation
- {
- get => LastShadowSyncOrientation;
- set => LastShadowSyncOrientation = value;
- }
-
- bool AcDream.App.World.ILiveEntityRemotePlacementRuntime.Airborne
- {
- get => Airborne;
- set => Airborne = value;
- }
-
- void AcDream.App.World.ILiveEntityRemotePlacementRuntime.HitGround() =>
- Movement.HitGround();
-
- void AcDream.App.World.ILiveEntityRemotePlacementRuntime.LeaveGround() =>
- Motion.LeaveGround();
-
- ///
- /// K-fix9 (2026-04-26): true while the remote is airborne (jump
- /// arc in flight). Set when a 0xF74E VectorUpdate arrives with
- /// non-trivial +Z velocity; cleared when the next UpdatePosition
- /// snaps to a new ground location. While true, the per-tick
- /// remote update SKIPS the "force OnWalkable + apply_current_movement"
- /// step that would otherwise stomp the body's Z velocity each
- /// frame, AND enables gravity so the parabolic arc actually plays
- /// out between server snaps.
- ///
- public bool Airborne;
-
- ///
- /// Per-remote position-waypoint queue + catch-up math (retail's
- /// CPhysicsObj::InterpolateTo + InterpolationManager::adjust_offset).
- /// 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. Mirrors retail UpdatePositionInternal @ 0x00512c30:
- /// queue catch-up REPLACES anim when active; anim stands when queue
- /// is idle.
- ///
- public AcDream.Core.Physics.RemoteMotionCombiner Position { get; } =
- new AcDream.Core.Physics.RemoteMotionCombiner();
-
- /// Reusable complete delta frame shared by interpolation,
- /// Sticky/Constraint, and the final body composition.
- public AcDream.Core.Physics.Motion.MotionDeltaFrame PositionManagerDeltaScratch { get; } =
- new AcDream.Core.Physics.Motion.MotionDeltaFrame();
-
- ///
- /// Diagnostic-only (gated on ACDREAM_REMOTE_VEL_DIAG=1): the
- /// previous UpdatePosition's world position + timestamp. The per-tick
- /// path compares the server's broadcast pace with the literal root
- /// displacement emitted by CSequence::update. The old inferred
- /// walk/run velocity was the source of the periodic overshoot this
- /// diagnostic was introduced to find.
- ///
- public System.Numerics.Vector3 PrevServerPos;
- public double PrevServerPosTime;
- public double LastOmegaDiagLogTime;
-
- ///
- /// Diagnostic-only: maximum scaled CSequence root-motion speed
- /// observed since the last UpdatePosition arrival. The next UP
- /// compares it with the server's observed pace. Reset on each UP.
- ///
- public float MaxRootMotionSpeedSinceLastUP;
-
- public RemoteMotion(AcDream.Core.Physics.PhysicsBody? sharedBody = null)
- {
- Body = sharedBody ?? new AcDream.Core.Physics.PhysicsBody
- {
- // Remotes don't simulate gravity — server owns Z. Force
- // Contact + OnWalkable + Active so apply_current_movement
- // writes velocity through every tick (the gate in
- // MotionInterpreter.apply_current_movement is
- // PhysicsObj.OnWalkable).
- State = AcDream.Core.Physics.PhysicsStateFlags.ReportCollisions,
- TransientState = AcDream.Core.Physics.TransientStateFlags.Contact
- | AcDream.Core.Physics.TransientStateFlags.OnWalkable
- | AcDream.Core.Physics.TransientStateFlags.Active,
- // R4-V5 door-swing fix: a RemoteMotion exists only for a
- // WORLD entity — retail's physics_obj->cell is non-null the
- // moment the object is placed. Without this, the interp's
- // detached-object guard stripped every dispatched
- // transition link (see PhysicsBody.InWorld).
- InWorld = true,
- };
- // R5-V5: the interp is owned by the MovementManager facade
- // (retail CPhysicsObj::movement_manager -> motion_interpreter).
- // acdream constructs it eagerly here — retail's lazy-create
- // window is never observable (see MovementManager's class doc).
- Movement = new AcDream.Core.Physics.Motion.MovementManager(
- new AcDream.Core.Physics.MotionInterpreter(Body)
- {
- // R4-V5 #160 fix: retail remotes carry a weenie whose
- // InqRunRate FAILS, landing apply_run_to_command on
- // my_run_rate (the M13 wire feed). A null weenie took the
- // degenerate 1.0 branch — observer-side run movetos played
- // and moved in slow motion. See RemoteWeenie.
- WeenieObj = new AcDream.Core.Physics.RemoteWeenie(),
- });
- }
-
- public void BindCanonicalCell(Func read, Action write)
- {
- _canonicalCellReader = read ?? throw new ArgumentNullException(nameof(read));
- _canonicalCellWriter = write ?? throw new ArgumentNullException(nameof(write));
- }
- }
-
/// Soft-snap decay rate (1/sec). At this rate the residual
/// halves every 1/rate seconds. 8.0 → ~100ms half-life, so even a
/// 2m residual fades within ~300ms without visible snap.
diff --git a/src/AcDream.App/Rendering/LiveEntityAnimationScheduler.cs b/src/AcDream.App/Rendering/LiveEntityAnimationScheduler.cs
index 5e156244..b7b275cf 100644
--- a/src/AcDream.App/Rendering/LiveEntityAnimationScheduler.cs
+++ b/src/AcDream.App/Rendering/LiveEntityAnimationScheduler.cs
@@ -5,7 +5,7 @@ using AcDream.Core.Physics;
using AcDream.Core.Physics.Motion;
using AcDream.Core.World;
using DatReaderWriter.Types;
-using RemoteMotion = AcDream.App.Rendering.GameWindow.RemoteMotion;
+using RemoteMotion = AcDream.App.Physics.RemoteMotion;
namespace AcDream.App.Rendering;
diff --git a/tests/AcDream.App.Tests/Physics/LiveEntityOrdinaryPhysicsUpdaterTests.cs b/tests/AcDream.App.Tests/Physics/LiveEntityOrdinaryPhysicsUpdaterTests.cs
index 5d50faa0..040c23c9 100644
--- a/tests/AcDream.App.Tests/Physics/LiveEntityOrdinaryPhysicsUpdaterTests.cs
+++ b/tests/AcDream.App.Tests/Physics/LiveEntityOrdinaryPhysicsUpdaterTests.cs
@@ -41,7 +41,7 @@ public sealed class LiveEntityOrdinaryPhysicsUpdaterTests
ParentCellId = SourceCell,
})!;
- var remote = new AcDream.App.Rendering.GameWindow.RemoteMotion
+ var remote = new AcDream.App.Physics.RemoteMotion
{
CellId = SourceCell,
};
@@ -108,7 +108,7 @@ public sealed class LiveEntityOrdinaryPhysicsUpdaterTests
MeshRefs = Array.Empty(),
ParentCellId = SourceCell,
})!;
- var remote = new AcDream.App.Rendering.GameWindow.RemoteMotion
+ var remote = new AcDream.App.Physics.RemoteMotion
{
CellId = SourceCell,
};
diff --git a/tests/AcDream.App.Tests/Physics/ProjectileControllerTests.cs b/tests/AcDream.App.Tests/Physics/ProjectileControllerTests.cs
index aefa2284..2bc55d79 100644
--- a/tests/AcDream.App.Tests/Physics/ProjectileControllerTests.cs
+++ b/tests/AcDream.App.Tests/Physics/ProjectileControllerTests.cs
@@ -104,7 +104,7 @@ public sealed class ProjectileControllerTests
LiveEntityRecord record = fixture.Spawn(instance: 1);
WorldEntity entity = record.WorldEntity!;
Assert.Null(record.AnimationRuntime);
- var remote = new GameWindow.RemoteMotion();
+ var remote = new AcDream.App.Physics.RemoteMotion();
RemotePhysicsBodyInitializer.Initialize(remote.Body, record);
remote.Body.Position = entity.Position;
remote.Body.Orientation = entity.Rotation;
@@ -683,7 +683,7 @@ public sealed class ProjectileControllerTests
LiveEntityRecord record = fixture.Spawn(instance: 1);
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0));
PhysicsBody body = record.PhysicsBody!;
- fixture.Live.SetRemoteMotionRuntime(Guid, new GameWindow.RemoteMotion(body));
+ fixture.Live.SetRemoteMotionRuntime(Guid, new AcDream.App.Physics.RemoteMotion(body));
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
Assert.True(fixture.Controller.ApplyAuthoritativeState(
record, record.FinalPhysicsState, 2.0, 1, 1));
@@ -707,7 +707,7 @@ public sealed class ProjectileControllerTests
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0));
PhysicsBody body = record.PhysicsBody!;
Vector3 position = body.Position;
- fixture.Live.SetRemoteMotionRuntime(Guid, new GameWindow.RemoteMotion(body));
+ fixture.Live.SetRemoteMotionRuntime(Guid, new AcDream.App.Physics.RemoteMotion(body));
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
Assert.True(fixture.Controller.ApplyAuthoritativeState(
@@ -946,7 +946,7 @@ public sealed class ProjectileControllerTests
var fixture = new Fixture();
LiveEntityRecord record = fixture.Spawn(instance: 1);
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
- var remote = new GameWindow.RemoteMotion();
+ var remote = new AcDream.App.Physics.RemoteMotion();
RemotePhysicsBodyInitializer.Initialize(remote.Body, record);
fixture.Live.SetRemoteMotionRuntime(Guid, remote);
@@ -998,7 +998,7 @@ public sealed class ProjectileControllerTests
var fixture = new Fixture();
LiveEntityRecord record = fixture.Spawn(instance: 1);
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
- var remote = new GameWindow.RemoteMotion();
+ var remote = new AcDream.App.Physics.RemoteMotion();
RemotePhysicsBodyInitializer.Initialize(remote.Body, record);
fixture.Live.SetRemoteMotionRuntime(Guid, remote);
@@ -1070,7 +1070,7 @@ public sealed class ProjectileControllerTests
seedCellId: startCell,
isStatic: false);
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
- var remote = new GameWindow.RemoteMotion();
+ var remote = new AcDream.App.Physics.RemoteMotion();
RemotePhysicsBodyInitializer.Initialize(remote.Body, record);
Assert.Equal(new Vector3(40f, 0f, 0f), remote.Body.Velocity);
Assert.Equal(new Vector3(0f, 0f, 2f), remote.Body.Omega);
@@ -1136,7 +1136,7 @@ public sealed class ProjectileControllerTests
velocity: new Vector3(float.NaN, 0f, 0f));
WorldEntity entity = record.WorldEntity!;
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
- var remote = new GameWindow.RemoteMotion();
+ var remote = new AcDream.App.Physics.RemoteMotion();
RemotePhysicsBodyInitializer.Initialize(remote.Body, record);
remote.Body.Position = entity.Position;
remote.Body.Orientation = entity.Rotation;
@@ -1162,7 +1162,7 @@ public sealed class ProjectileControllerTests
LiveEntityRecord record = fixture.Spawn(instance: 1);
WorldEntity entity = record.WorldEntity!;
record.FinalPhysicsState = PhysicsStateFlags.ReportCollisions;
- var remote = new GameWindow.RemoteMotion();
+ var remote = new AcDream.App.Physics.RemoteMotion();
RemotePhysicsBodyInitializer.Initialize(remote.Body, record);
remote.Body.Position = entity.Position;
remote.Body.Orientation = entity.Rotation;
@@ -1186,7 +1186,7 @@ public sealed class ProjectileControllerTests
LiveEntityRecord record = fixture.Spawn(instance: 1);
Assert.True(fixture.Controller.TryBind(record, ProjectileSetup(), 1.0));
PhysicsBody body = record.PhysicsBody!;
- var remote = new GameWindow.RemoteMotion(body);
+ var remote = new AcDream.App.Physics.RemoteMotion(body);
fixture.Live.SetRemoteMotionRuntime(Guid, remote);
fixture.Live.RebucketLiveEntity(Guid, CellB);
@@ -1198,7 +1198,7 @@ public sealed class ProjectileControllerTests
Assert.Same(body, record.RemoteMotionRuntime!.Body);
Assert.Same(body, record.ProjectileRuntime!.Body);
- var replacement = new GameWindow.RemoteMotion(body);
+ var replacement = new AcDream.App.Physics.RemoteMotion(body);
fixture.Live.SetRemoteMotionRuntime(Guid, replacement);
remote.CellId = CellB;
Assert.Equal(CellA, record.FullCellId);
diff --git a/tests/AcDream.App.Tests/Physics/RemotePhysicsUpdaterTests.cs b/tests/AcDream.App.Tests/Physics/RemotePhysicsUpdaterTests.cs
index afc96a3d..02b2e6a1 100644
--- a/tests/AcDream.App.Tests/Physics/RemotePhysicsUpdaterTests.cs
+++ b/tests/AcDream.App.Tests/Physics/RemotePhysicsUpdaterTests.cs
@@ -68,7 +68,7 @@ public sealed class RemotePhysicsUpdaterTests
MeshRefs = Array.Empty(),
ParentCellId = cellId,
};
- var motion = new GameWindow.RemoteMotion
+ var motion = new AcDream.App.Physics.RemoteMotion
{
LastShadowSyncPos = Vector3.Zero,
LastShadowSyncOrientation = Quaternion.Identity,
@@ -151,7 +151,7 @@ public sealed class RemotePhysicsUpdaterTests
PartTemplate = Array.Empty(),
PartAvailability = Array.Empty(),
};
- var motion = new GameWindow.RemoteMotion();
+ var motion = new AcDream.App.Physics.RemoteMotion();
motion.Body.Position = entity.Position;
motion.Body.Orientation = entity.Rotation;
motion.Body.TransientState = TransientStateFlags.Contact
@@ -217,7 +217,7 @@ public sealed class RemotePhysicsUpdaterTests
PartTemplate = Array.Empty(),
PartAvailability = Array.Empty(),
};
- var motion = new GameWindow.RemoteMotion
+ var motion = new AcDream.App.Physics.RemoteMotion
{
Airborne = true,
};
@@ -272,7 +272,7 @@ public sealed class RemotePhysicsUpdaterTests
PartTemplate = Array.Empty(),
PartAvailability = Array.Empty(),
};
- var motion = new GameWindow.RemoteMotion();
+ var motion = new AcDream.App.Physics.RemoteMotion();
motion.Body.Position = entity.Position;
motion.Body.Orientation = initial;
motion.Interp.Enqueue(
@@ -308,7 +308,7 @@ public sealed class RemotePhysicsUpdaterTests
[Fact]
public void TickHidden_AppliesPositionManagerOffsetWithoutAdvancingPhysics()
{
- var motion = new GameWindow.RemoteMotion();
+ var motion = new AcDream.App.Physics.RemoteMotion();
motion.Body.Position = Vector3.Zero;
motion.Body.Orientation = Quaternion.Identity;
motion.Body.Velocity = new Vector3(4f, 0f, 5f);
@@ -354,7 +354,7 @@ public sealed class RemotePhysicsUpdaterTests
new PhysicsEngine(),
(_, _) => (0.48f, 1.835f),
(_, _, _, _) => { });
- var motion = new GameWindow.RemoteMotion();
+ var motion = new AcDream.App.Physics.RemoteMotion();
motion.Body.Orientation = Quaternion.Identity;
var entity = new WorldEntity
{
@@ -384,7 +384,7 @@ public sealed class RemotePhysicsUpdaterTests
new PhysicsEngine(),
(_, _) => (0.48f, 1.835f),
(_, _, _, _) => { });
- var motion = new GameWindow.RemoteMotion();
+ var motion = new AcDream.App.Physics.RemoteMotion();
motion.Body.Orientation = Quaternion.Identity;
var entity = new WorldEntity
{
@@ -424,7 +424,7 @@ public sealed class RemotePhysicsUpdaterTests
engine,
(_, _) => (0.48f, 1.835f),
(_, _, _, _) => { });
- var motion = new GameWindow.RemoteMotion();
+ var motion = new AcDream.App.Physics.RemoteMotion();
motion.Body.Position = new Vector3(191f, 10f, 50f);
motion.Body.Orientation = Quaternion.Identity;
motion.Body.TransientState = TransientStateFlags.Contact
@@ -471,7 +471,7 @@ public sealed class RemotePhysicsUpdaterTests
engine,
(_, _) => (0.48f, 1.835f),
(_, _, _, _) => { });
- var motion = new GameWindow.RemoteMotion
+ var motion = new AcDream.App.Physics.RemoteMotion
{
Airborne = true,
};
@@ -591,7 +591,7 @@ public sealed class RemotePhysicsUpdaterTests
using var fixture = new BoundaryRemoteFixture();
LiveEntityRecord oldRecord = fixture.Record;
WorldEntity oldEntity = fixture.Entity;
- GameWindow.RemoteMotion oldRemote = fixture.Remote;
+ AcDream.App.Physics.RemoteMotion oldRemote = fixture.Remote;
ulong oldAuthority = oldRecord.PositionAuthorityVersion;
int publications = 0;
@@ -628,9 +628,9 @@ public sealed class RemotePhysicsUpdaterTests
using var fixture = new BoundaryRemoteFixture();
LiveEntityRecord oldRecord = fixture.Record;
WorldEntity oldEntity = fixture.Entity;
- GameWindow.RemoteMotion oldRemote = fixture.Remote;
+ AcDream.App.Physics.RemoteMotion oldRemote = fixture.Remote;
WorldEntity? replacementEntity = null;
- GameWindow.RemoteMotion? replacementRemote = null;
+ AcDream.App.Physics.RemoteMotion? replacementRemote = null;
ulong clockEpoch = oldRecord.ObjectClockEpoch;
fixture.Live.ProjectionVisibilityChanged += (record, visible) =>
@@ -813,7 +813,7 @@ public sealed class RemotePhysicsUpdaterTests
MeshRefs = Array.Empty(),
ParentCellId = cellId,
})!;
- var remote = new GameWindow.RemoteMotion();
+ var remote = new AcDream.App.Physics.RemoteMotion();
remote.Body.Position = entity.Position;
remote.Body.Orientation = entity.Rotation;
remote.CellId = cellId;
@@ -872,7 +872,7 @@ public sealed class RemotePhysicsUpdaterTests
public RemotePhysicsUpdater Updater { get; }
public LiveEntityRecord Record { get; }
public WorldEntity Entity { get; }
- public GameWindow.RemoteMotion Remote { get; }
+ public AcDream.App.Physics.RemoteMotion Remote { get; }
public void HydrateDestination() =>
Spatial.AddLandblock(new LoadedLandblock(
@@ -881,7 +881,7 @@ public sealed class RemotePhysicsUpdaterTests
Array.Empty()));
public (LiveEntityRecord Record, WorldEntity Entity,
- GameWindow.RemoteMotion Remote) ReplaceAtSource()
+ AcDream.App.Physics.RemoteMotion Remote) ReplaceAtSource()
{
Assert.True(Live.UnregisterLiveEntity(
new DeleteObject.Parsed(Guid, InstanceSequence: 1),
@@ -902,7 +902,7 @@ public sealed class RemotePhysicsUpdaterTests
}
private (LiveEntityRecord Record, WorldEntity Entity,
- GameWindow.RemoteMotion Remote) SpawnAndBind(
+ AcDream.App.Physics.RemoteMotion Remote) SpawnAndBind(
ushort instanceSequence,
Vector3 position,
bool enqueueDestination)
@@ -925,7 +925,7 @@ public sealed class RemotePhysicsUpdaterTests
MeshRefs = Array.Empty(),
ParentCellId = SourceCell,
}));
- var remote = new GameWindow.RemoteMotion();
+ var remote = new AcDream.App.Physics.RemoteMotion();
remote.Body.Position = position;
remote.Body.Orientation = Quaternion.Identity;
remote.CellId = SourceCell;
@@ -945,7 +945,7 @@ public sealed class RemotePhysicsUpdaterTests
private void RegisterShadow(
WorldEntity entity,
- GameWindow.RemoteMotion remote)
+ AcDream.App.Physics.RemoteMotion remote)
{
Engine.ShadowObjects.Register(
entity.Id,
diff --git a/tests/AcDream.App.Tests/Physics/RemoteTeleportControllerTests.cs b/tests/AcDream.App.Tests/Physics/RemoteTeleportControllerTests.cs
index 7a261071..441b7780 100644
--- a/tests/AcDream.App.Tests/Physics/RemoteTeleportControllerTests.cs
+++ b/tests/AcDream.App.Tests/Physics/RemoteTeleportControllerTests.cs
@@ -101,7 +101,7 @@ public sealed class RemoteTeleportControllerTests
Rotation = Quaternion.Identity,
MeshRefs = Array.Empty(),
})!;
- var remote = new GameWindow.RemoteMotion();
+ var remote = new AcDream.App.Physics.RemoteMotion();
remote.Body.SnapToCell(
sourceCell,
entity.Position,
@@ -242,7 +242,7 @@ public sealed class RemoteTeleportControllerTests
Rotation = Quaternion.Identity,
MeshRefs = Array.Empty(),
})!;
- var remote = new GameWindow.RemoteMotion();
+ var remote = new AcDream.App.Physics.RemoteMotion();
remote.Body.SnapToCell(sourceCell, entity.Position, entity.Position);
live.SetRemoteMotionRuntime(guid, remote);
var engine = new PhysicsEngine { DataCache = new PhysicsDataCache() };
@@ -333,7 +333,7 @@ public sealed class RemoteTeleportControllerTests
Rotation = Quaternion.Identity,
MeshRefs = Array.Empty(),
})!;
- var remote = new GameWindow.RemoteMotion();
+ var remote = new AcDream.App.Physics.RemoteMotion();
remote.Body.SnapToCell(sourceCell, sourcePosition, sourcePosition);
remote.Body.TransientState = TransientStateFlags.Contact
| TransientStateFlags.OnWalkable;
@@ -774,13 +774,13 @@ public sealed class RemoteTeleportControllerTests
Assert.Throws(() =>
fixture.Live.SetRemoteMotionRuntime(
RollbackFixture.Guid,
- new GameWindow.RemoteMotion()));
+ new AcDream.App.Physics.RemoteMotion()));
Assert.Throws(() =>
fixture.Live.SetRemoteMotionRuntime(
RollbackFixture.Guid,
new BodyOnlyRemote(fixture.Remote.Body)));
- var replacement = new GameWindow.RemoteMotion(fixture.Remote.Body);
+ var replacement = new AcDream.App.Physics.RemoteMotion(fixture.Remote.Body);
fixture.Live.SetRemoteMotionRuntime(RollbackFixture.Guid, replacement);
fixture.Spatial.AddLandblock(EmptyLandblock(RollbackFixture.DestinationLandblock));
@@ -811,7 +811,7 @@ public sealed class RemoteTeleportControllerTests
Assert.Throws(() =>
fixture.Live.SetRemoteMotionRuntime(
RollbackFixture.Guid,
- new GameWindow.RemoteMotion()));
+ new AcDream.App.Physics.RemoteMotion()));
Assert.Throws(() =>
fixture.Live.SetRemoteMotionRuntime(
RollbackFixture.Guid,
@@ -1114,7 +1114,7 @@ public sealed class RemoteTeleportControllerTests
Rotation = Quaternion.Identity,
MeshRefs = Array.Empty(),
})!;
- var remote = new GameWindow.RemoteMotion();
+ var remote = new AcDream.App.Physics.RemoteMotion();
remote.Body.SnapToCell(sourceCell, entity.Position, entity.Position);
live.SetRemoteMotionRuntime(guid, remote);
controller = new RemoteTeleportController(
@@ -1191,7 +1191,7 @@ public sealed class RemoteTeleportControllerTests
Rotation = Quaternion.Identity,
MeshRefs = Array.Empty(),
})!;
- var remote = new GameWindow.RemoteMotion();
+ var remote = new AcDream.App.Physics.RemoteMotion();
remote.Body.SnapToCell(sourceCell, entity.Position, entity.Position);
live.SetRemoteMotionRuntime(guid, remote);
return new LoadedRemoteFixture(
@@ -1236,7 +1236,7 @@ public sealed class RemoteTeleportControllerTests
private readonly record struct LoadedRemoteFixture(
LiveEntityRuntime Live,
WorldEntity Entity,
- GameWindow.RemoteMotion Remote,
+ AcDream.App.Physics.RemoteMotion Remote,
PhysicsEngine Engine);
private static void AddDestination(PhysicsEngine engine) =>
@@ -1395,7 +1395,7 @@ public sealed class RemoteTeleportControllerTests
Rotation = Quaternion.Identity,
MeshRefs = Array.Empty(),
})!;
- Remote = new GameWindow.RemoteMotion();
+ Remote = new AcDream.App.Physics.RemoteMotion();
Remote.Body.SnapToCell(
celllessSource ? 0u : SourceCell,
SourcePosition,
@@ -1476,7 +1476,7 @@ public sealed class RemoteTeleportControllerTests
internal GpuWorldState Spatial { get; } = new();
internal LiveEntityRuntime Live { get; }
internal WorldEntity Entity { get; }
- internal GameWindow.RemoteMotion Remote { get; }
+ internal AcDream.App.Physics.RemoteMotion Remote { get; }
internal PhysicsEngine Engine { get; } = new() { DataCache = new PhysicsDataCache() };
internal RemoteTeleportController Controller { get; }
internal LiveEntityPresentationController Presentation { get; }
diff --git a/tests/AcDream.App.Tests/Physics/RemoteTeleportPlacementTests.cs b/tests/AcDream.App.Tests/Physics/RemoteTeleportPlacementTests.cs
index a754d92d..6caac319 100644
--- a/tests/AcDream.App.Tests/Physics/RemoteTeleportPlacementTests.cs
+++ b/tests/AcDream.App.Tests/Physics/RemoteTeleportPlacementTests.cs
@@ -19,7 +19,7 @@ public sealed class RemoteTeleportPlacementTests
0x01010001u,
new Vector3(3f, 4f, 5f),
new Vector3(3f, 4f, 5f));
- var remote = new AcDream.App.Rendering.GameWindow.RemoteMotion(body);
+ var remote = new AcDream.App.Physics.RemoteMotion(body);
Quaternion orientation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, 0.75f);
RemoteTeleportPlacement.Apply(
@@ -54,7 +54,7 @@ public sealed class RemoteTeleportPlacementTests
State = PhysicsStateFlags.Gravity | PhysicsStateFlags.ReportCollisions,
};
var contactPlane = new Plane(Vector3.UnitZ, 0f);
- var remote = new AcDream.App.Rendering.GameWindow.RemoteMotion(body)
+ var remote = new AcDream.App.Physics.RemoteMotion(body)
{
Airborne = true,
};
@@ -97,7 +97,7 @@ public sealed class RemoteTeleportPlacementTests
// landblock is absent. SetPositionInternal must still receive the
// grounded source edge captured before parking.
body.TransientState &= ~(TransientStateFlags.Contact | TransientStateFlags.OnWalkable);
- var remote = new AcDream.App.Rendering.GameWindow.RemoteMotion(body)
+ var remote = new AcDream.App.Physics.RemoteMotion(body)
{
Airborne = true,
};
@@ -141,7 +141,7 @@ public sealed class RemoteTeleportPlacementTests
// calc_acceleration, which clears grounded angular drift, before
// set_on_walkable installs the steep destination's false value.
body.TransientState &= ~(TransientStateFlags.Contact | TransientStateFlags.OnWalkable);
- var remote = new AcDream.App.Rendering.GameWindow.RemoteMotion(body);
+ var remote = new AcDream.App.Physics.RemoteMotion(body);
RemoteTeleportPlacement.Apply(
remote,
@@ -171,7 +171,7 @@ public sealed class RemoteTeleportPlacementTests
{
var original = new Vector3(7f, 8f, 9f);
var body = new PhysicsBody { Position = original };
- var remote = new AcDream.App.Rendering.GameWindow.RemoteMotion(body);
+ var remote = new AcDream.App.Physics.RemoteMotion(body);
Assert.Throws(() =>
RemoteTeleportPlacement.Apply(
diff --git a/tests/AcDream.App.Tests/Rendering/LiveEntityAnimationSchedulerTests.cs b/tests/AcDream.App.Tests/Rendering/LiveEntityAnimationSchedulerTests.cs
index ca14c642..c81d5a2b 100644
--- a/tests/AcDream.App.Tests/Rendering/LiveEntityAnimationSchedulerTests.cs
+++ b/tests/AcDream.App.Tests/Rendering/LiveEntityAnimationSchedulerTests.cs
@@ -52,7 +52,7 @@ public sealed class LiveEntityAnimationSchedulerTests
public void HiddenRemotePose_IsSampledOnceAfterAdmittedHiddenQuantum()
{
var (live, record, animation) = BuildHiddenAnimated(RemoteGuid);
- var remote = new GameWindow.RemoteMotion
+ var remote = new AcDream.App.Physics.RemoteMotion
{
CellId = Cell,
};
@@ -169,7 +169,7 @@ public sealed class LiveEntityAnimationSchedulerTests
var (live, record, entity) = BuildMaterialized(
RemoteGuid,
PhysicsStateFlags.Gravity);
- var remote = new GameWindow.RemoteMotion
+ var remote = new AcDream.App.Physics.RemoteMotion
{
CellId = Cell,
Airborne = true,
@@ -286,7 +286,7 @@ public sealed class LiveEntityAnimationSchedulerTests
var physics = new PhysicsEngine();
var projectiles = new ProjectileController(live, physics);
record.FinalPhysicsState = ProjectileState;
- var remote = new GameWindow.RemoteMotion
+ var remote = new AcDream.App.Physics.RemoteMotion
{
CellId = Cell,
Airborne = false,
@@ -598,9 +598,9 @@ public sealed class LiveEntityAnimationSchedulerTests
Assert.Equal(1, rootPublishes);
}
- private static GameWindow.RemoteMotion BuildRemote(WorldEntity entity)
+ private static AcDream.App.Physics.RemoteMotion BuildRemote(WorldEntity entity)
{
- var remote = new GameWindow.RemoteMotion
+ var remote = new AcDream.App.Physics.RemoteMotion
{
CellId = Cell,
};
diff --git a/tests/AcDream.Core.Tests/Physics/Motion/RemoteChaseEndToEndHarnessTests.cs b/tests/AcDream.Core.Tests/Physics/Motion/RemoteChaseEndToEndHarnessTests.cs
index d4b59bff..cfbfb27e 100644
--- a/tests/AcDream.Core.Tests/Physics/Motion/RemoteChaseEndToEndHarnessTests.cs
+++ b/tests/AcDream.Core.Tests/Physics/Motion/RemoteChaseEndToEndHarnessTests.cs
@@ -30,7 +30,7 @@ namespace AcDream.Core.Tests.Physics.Motion;
//
// This harness wires a real MotionInterpreter + AnimationSequencer +
// MotionTableDispatchSink + MoveToManager EXACTLY like GameWindow's
-// EnsureRemoteMotionBindings (GameWindow.cs:4251) and ticks them in
+// EnsureRemoteMotionBindings and ticks them in
// the retail per-object order: CSequence update → PositionManager adjust →
// complete Frame combine → hook processing → Target/Movement/PartArray/
// Position manager tail. Wire events (aggro stance UM, mt-6 arms, attack
@@ -81,8 +81,8 @@ internal sealed class RemoteChaseHarness
public const float OwnRadius = 0.3f;
public const float StickyTargetRadius = 0.5f;
- // Field-for-field mirror of GameWindow's RemoteMotion construction
- // (GameWindow.cs:592-618): Contact+OnWalkable+Active, InWorld=true (the
+ // Field-for-field mirror of AcDream.App.Physics.RemoteMotion construction:
+ // Contact+OnWalkable+Active, InWorld=true (the
// R4-V5 door fix — without it the interp's detached-object guard strips
// every dispatched transition link), and the R4-V5 #160 RemoteWeenie
// (null weenie degrades run-rate to 1.0).
@@ -98,7 +98,7 @@ internal sealed class RemoteChaseHarness
public readonly AnimationSequencer Seq;
public readonly MotionTableDispatchSink Sink;
- /// R5-V5: GameWindow's RemoteMotion.Movement twin — the ONE
+ /// R5-V5: AcDream.App.Physics.RemoteMotion.Movement twin — the ONE
/// per-entity MovementManager facade owning Interp + the MoveToManager
/// (retail CPhysicsObj::movement_manager).
public readonly MovementManager Movement;
@@ -156,7 +156,7 @@ internal sealed class RemoteChaseHarness
WeenieObj = new RemoteWeenie(),
};
- // ── EnsureRemoteMotionBindings (GameWindow.cs:4251) verbatim ──
+ // ── EnsureRemoteMotionBindings verbatim ──
Sink = new MotionTableDispatchSink(Seq);
Interp.DefaultSink = Sink;
// #174: production binds the seam to Manager.HandleEnterWorld
@@ -167,7 +167,7 @@ internal sealed class RemoteChaseHarness
Interp.CheckForCompletedMotions = Seq.Manager.CheckForCompletedMotions;
// ── R5-V5: the MovementManager facade owns Interp + the moveto —
- // GameWindow's RemoteMotion ctor + EnsureRemoteMotionBindings
+ // AcDream.App.Physics.RemoteMotion ctor + EnsureRemoteMotionBindings
// factory shape verbatim (sticky binds inside the factory;
// MakeMoveToManager after the host/Pm exist). ──
Movement = new MovementManager(Interp)