feat(physics): R5-V5 — MovementManager facade owns each entity's interp+moveto pair
Structural capstone of the R5 movement-manager arc; zero behavior change. Retail MovementManager (acclient.h /* 3463 */, 16 bytes / four pointers) gives every CPhysicsObj ONE owner for its motion_interpreter + moveto_manager. acdream carried them as loose per-entity objects wired by hand at three sites. This slice: - New src/AcDream.Core/Physics/Motion/MovementManager.cs — owns MotionInterpreter + lazy MoveToManager (MakeMoveToManager 0x00524000 via a MoveToFactory closure, the acdream stand-in for the physics_obj/ weenie_obj backpointers) + the relays with retail call shapes: PerformMovement 0x005240d0 (types 1-5 -> minterp, 6-9 -> MakeMoveToManager + moveto, (type-1)>8 -> 0x47), UseTime 0x005242f0 (moveto only), HitGround 0x00524300 (minterp FIRST then moveto), HandleExitWorld 0x00524350 (minterp only), CancelMoveTo 0x005241b0, HandleUpdateTarget 0x00524790, IsMovingTo 0x00524260. - RemoteMotion.Movement + PlayerMovementController.Movement hold the ONE facade; Motion/MoveTo become child views so the comment-dense call sites read unchanged. The three wiring sites (EnsureRemoteMotionBindings, EnterPlayerModeNow, the chase harness — same commit per the mirror rule) construct through MoveToFactory + MakeMoveToManager(), preserving the pre-facade eager timing (side-effect-free ctor = unobservable either way). - Relay call sites repointed: both remote landing HitGround pairs + the player landing pair, despawn HandleExitWorld, TickRemoteMoveTo + the player Update UseTime, RouteServerMoveTo (takes the facade; routes via the retail PerformMovement dispatch), InstallSpeculativeTurnToTarget, host HandleUpdateTarget/InterruptCurrentMovement closures (retail CPhysicsObj::HandleUpdateTarget @0x00512bf0 fan head + the TS-36 interrupt chain, now the literal facade relays). - NOT absorbed per the slice spec: unpack_movement stays App (RouteServerMoveTo + UM heads; Core.Net types stay out of Core.Physics); TS-42 per-tick order untouched (R6); #170/#171 gate-passed machinery untouched. PerformMovement's set_active(1) head not re-asserted (spawn asserts Active; status quo — no new register row). - Register: TS-41/TS-42 source wording freshened to the facade shape; AD-36 retire note corrected (facade half closed; residue = entities that never get a RemoteMotion). No new rows. - Conformance: 15 new MovementManagerTests pin the dispatch table, lazy create, relay targets/order, null tolerance. Suite 4052 green; the 183-case/funnel/moveto/chase/sticky suites UNMODIFIED (harness construction mirrors production, test bodies untouched). Decomp: docs/research/2026-07-03-r5-managers/r5-movementmanager-decomp.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
6feaab91e3
commit
dccd700991
6 changed files with 833 additions and 200 deletions
|
|
@ -268,23 +268,46 @@ public sealed class PlayerMovementController
|
|||
// MoveToManager remotes use (R4-V4), bound below by GameWindow's
|
||||
// EnterPlayerModeNow beside the R3-W6 DefaultSink bind.
|
||||
|
||||
/// <summary>
|
||||
/// R5-V5: retail <c>CPhysicsObj::movement_manager</c> (acclient.h
|
||||
/// <c>/* 3463 */</c>) — the ONE owner of this controller's
|
||||
/// <see cref="Motion"/> + <see cref="MoveTo"/> pair. Constructed in the
|
||||
/// ctor around the interp; the MoveToManager side arrives via
|
||||
/// <c>MoveToFactory</c> + <c>MakeMoveToManager()</c> in
|
||||
/// <c>GameWindow.EnterPlayerModeNow</c> (the same facade shape
|
||||
/// <c>EnsureRemoteMotionBindings</c> gives remotes). <see cref="Update"/>
|
||||
/// ticks <c>UseTime()</c> (0x005242f0) at the slot the deleted
|
||||
/// <c>DriveServerAutoWalk</c> occupied and relays <c>HitGround()</c>
|
||||
/// (0x00524300, minterp first then moveto).
|
||||
/// </summary>
|
||||
public AcDream.Core.Physics.Motion.MovementManager Movement { get; }
|
||||
|
||||
/// <summary>
|
||||
/// R4-V5: the local player's verbatim retail <c>MoveToManager</c>
|
||||
/// (decomp 0x00529010-0x0052a987), constructed + seam-bound by
|
||||
/// <c>GameWindow.EnterPlayerModeNow</c> against this controller's
|
||||
/// <see cref="Motion"/>/body/Yaw (the same wiring shape
|
||||
/// <c>EnsureRemoteMotionBindings</c> uses for remotes). GameWindow
|
||||
/// routes inbound mt 6-9 movement events to
|
||||
/// <see cref="AcDream.Core.Physics.Motion.MoveToManager.PerformMovement"/>;
|
||||
/// <see cref="Update"/> ticks <c>UseTime()</c> at the slot the deleted
|
||||
/// <c>DriveServerAutoWalk</c> occupied and relays <c>HitGround()</c>
|
||||
/// (retail order: minterp first, then moveto — MovementManager::HitGround
|
||||
/// 0x00524300). User input cancels a moveto through the retail chain:
|
||||
/// key edge → DoMotion (ctor-default params, CancelMoveTo bit set) →
|
||||
/// routes inbound mt 6-9 movement events through
|
||||
/// <see cref="AcDream.Core.Physics.Motion.MovementManager.PerformMovement"/>.
|
||||
/// User input cancels a moveto through the retail chain: key edge →
|
||||
/// DoMotion (ctor-default params, CancelMoveTo bit set) →
|
||||
/// <see cref="MotionInterpreter.InterruptCurrentMovement"/> →
|
||||
/// <c>CancelMoveTo(ActionCancelled)</c> (register TS-36 retired).
|
||||
/// R5-V5: a view of <see cref="Movement"/>'s moveto child; the setter is
|
||||
/// sugar over the facade's factory path (kept for the
|
||||
/// PlayerMoveToCutoverTests rig and any pre-facade bind shape).
|
||||
/// </summary>
|
||||
public AcDream.Core.Physics.Motion.MoveToManager? MoveTo { get; set; }
|
||||
public AcDream.Core.Physics.Motion.MoveToManager? MoveTo
|
||||
{
|
||||
get => Movement.MoveTo;
|
||||
set
|
||||
{
|
||||
var mtm = value ?? throw new ArgumentNullException(nameof(value));
|
||||
Movement.MoveToFactory = () => mtm;
|
||||
Movement.MakeMoveToManager();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// R5-V3 (#171): the player's <c>PositionManager</c> facade (retail
|
||||
|
|
@ -327,6 +350,10 @@ public sealed class PlayerMovementController
|
|||
int jumpSkill = int.TryParse(Environment.GetEnvironmentVariable("ACDREAM_JUMP_SKILL"), out var jsv) ? jsv : 300;
|
||||
_weenie = new PlayerWeenie(runSkill: runSkill, jumpSkill: jumpSkill);
|
||||
_motion = new MotionInterpreter(_body, _weenie);
|
||||
// R5-V5: the MovementManager facade owns the interp from birth
|
||||
// (retail CPhysicsObj::movement_manager); the moveto child binds
|
||||
// later via MoveToFactory (EnterPlayerModeNow / the test rigs).
|
||||
Movement = new AcDream.Core.Physics.Motion.MovementManager(_motion);
|
||||
// R3-W4 (A3): the local player's movement is input-driven —
|
||||
// movement_is_autonomous true so apply_current_movement's dual
|
||||
// dispatch routes apply_raw_movement (IsThePlayer && autonomous).
|
||||
|
|
@ -528,8 +555,9 @@ public sealed class PlayerMovementController
|
|||
// user motion and never builds a MoveToState mid-moveto (the #75
|
||||
// invariant, now by construction). Provisional tick placement until
|
||||
// R6 ports retail's UpdateObjectInternal ordering (r4-port-plan.md
|
||||
// §3 placement decision).
|
||||
MoveTo?.UseTime();
|
||||
// §3 placement decision). R5-V5: the facade relay
|
||||
// (MovementManager::UseTime 0x005242f0 — moveto side only).
|
||||
Movement.UseTime();
|
||||
|
||||
// R4-V5 wedge fix (2026-07-03 live door bug), retail's per-tick
|
||||
// completion slot: CPartArray::HandleMovement — a tailcall chain to
|
||||
|
|
@ -1033,13 +1061,13 @@ public sealed class PlayerMovementController
|
|||
|
||||
if (wasAirborne)
|
||||
{
|
||||
_motion.HitGround();
|
||||
// R4-V5: retail order — minterp first, then moveto
|
||||
// (MovementManager::HitGround 0x00524300, decomp §2d);
|
||||
// re-arms a moveto suspended by the airborne UseTime
|
||||
// contact gate. LeaveGround has NO moveto side (COMDAT
|
||||
// no-op, §2e) — do not add one.
|
||||
MoveTo?.HitGround();
|
||||
// R4-V5 → R5-V5: retail order — minterp first, then
|
||||
// moveto (MovementManager::HitGround 0x00524300, decomp
|
||||
// §2d — now the literal facade relay); re-arms a moveto
|
||||
// suspended by the airborne UseTime contact gate.
|
||||
// LeaveGround has NO moveto side (COMDAT no-op, §2e) —
|
||||
// do not add one.
|
||||
Movement.HitGround();
|
||||
justLanded = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue