The retail movement-manager family the R4 MoveToManager port left as do-not-invent seams (decomp §9f/§9g). Faithful C# ports of retail's PositionManager facade + StickyManager + ConstraintManager + the TargetManager voyeur system, with full conformance tests. NO wiring yet — purely additive, no behavior change. Wiring (retiring TS-39 sticky + AP-79 target adapter) is R5-V2/V3. New Core classes (src/AcDream.Core/Physics/Motion/): - StickyManager (0x00555400): follow-a-target steering. adjust_offset's dense x87 mush decoded via ACE (StickyRadius 0.3, StickyTime 1.0, follow speed ×5 / fallback 15) — speed-clamped signed-distance steer + bounded turn-to-face; 1 s watchdog; Ok→initialized / non-Ok→teardown. - ConstraintManager (0x00556090): the server-position rubber-band leash. 90% IsFullyConstrained jump gate + grounded linear brake taper. Structural only — acdream never ARMS it (retail arms from SmartBox::HandleReceivedPosition, which acdream lacks, with two x87 constants BN elided). IsFullyConstrained stays false = TS-35 behavior; leash-arming + the unknown constants are a deferred issue. - PositionManager facade (0x00555160): lazy Sticky/Constraint + fan-out. - TargetManager (0x0051a370) + TargettedVoyeurInfo: the peer-to-peer voyeur subscription system (0.5 s throttle, 10 s staleness, send-on-drift-past-radius, dead-reckon GetInterpolatedPosition). A faithful superset of the AP-79 adapter — SetTarget subscribes ON the target; the target's HandleTargetting pushes updates back. - IPhysicsObjHost: the CPhysicsObj back-pointer seam (position/velocity/ radius/contact/GetObjectA + target-tracking fan-out) the App wires per entity in V2/V3. MotionDeltaFrame: mutable retail-Frame delta accumulator. Supporting: - TargetInfo extended to the full retail 10-field struct (additive defaults keep the R4 4-arg call sites compiling). - MoveToMath: signed CylinderDistanceNoZ, NormalizeCheckSmall, GlobalToLocalVec. - Rename: the misnamed AcDream.Core.Physics.PositionManager (a remote anim+interp per-frame combiner, NOT the retail facade) → RemoteMotion Combiner, freeing the name and removing the ambiguity that breaks every file importing both Physics + Physics.Motion (GameWindow will in V2/V3). Tests: 42 new conformance cases (Sticky/Constraint/Position facade + TargetManager incl. the full cross-entity voyeur round-trip). Full suite 4006 green (+2 skipped), no regressions. Decomp + ACE cross-ref + port plan: docs/research/2026-07-03-r5-managers/. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
102 lines
4.6 KiB
C#
102 lines
4.6 KiB
C#
using System;
|
|
|
|
namespace AcDream.Core.Physics.Motion;
|
|
|
|
/// <summary>
|
|
/// R5 — port of retail's <c>PositionManager</c> facade (acclient.h:30952,
|
|
/// struct #3468; decomp 0x00555160-0x005553d0,
|
|
/// <c>r5-positionmanager-sticky-decomp.md</c>). A thin fan-out over three
|
|
/// sub-managers: Interpolation, Sticky, Constraint. Owned 1:1 by the entity's
|
|
/// <see cref="IPhysicsObjHost"/> (retail <c>CPhysicsObj::position_manager</c>,
|
|
/// lazily created).
|
|
///
|
|
/// <para><b>Interpolation note:</b> retail's <c>adjust_offset</c> chains
|
|
/// Interpolation → Sticky → Constraint. acdream's interpolation stage lives in
|
|
/// <see cref="RemoteMotionCombiner"/> (the R5-renamed remote-motion combiner,
|
|
/// formerly the misnamed <c>Physics.PositionManager</c>) and is NOT chained
|
|
/// here in V1 — this facade owns only the two R5 targets (Sticky retires TS-39;
|
|
/// Constraint is structural — see <see cref="ConstraintManager"/>). Folding the
|
|
/// combiner in as the interp stage is a wiring-slice cleanup.</para>
|
|
/// </summary>
|
|
public sealed class PositionManager
|
|
{
|
|
private readonly IPhysicsObjHost _host;
|
|
|
|
// Lazily created (retail: sticky on first StickTo, constraint on first
|
|
// ConstrainTo — 0x00555230 / 0x00555280).
|
|
private StickyManager? _sticky;
|
|
private ConstraintManager? _constraint;
|
|
|
|
public PositionManager(IPhysicsObjHost host)
|
|
=> _host = host ?? throw new ArgumentNullException(nameof(host));
|
|
|
|
/// <summary>Exposed for wiring/tests — the lazily-created sub-managers
|
|
/// (null until first use).</summary>
|
|
public StickyManager? Sticky => _sticky;
|
|
public ConstraintManager? Constraint => _constraint;
|
|
|
|
/// <summary>Retail <c>PositionManager::StickTo</c> (0x00555230) — lazily
|
|
/// create the <see cref="StickyManager"/> and begin following
|
|
/// <paramref name="objectId"/>.</summary>
|
|
public void StickTo(uint objectId, float radius, float height)
|
|
{
|
|
_sticky ??= new StickyManager(_host);
|
|
_sticky.StickTo(objectId, radius, height);
|
|
}
|
|
|
|
/// <summary>Retail <c>PositionManager::UnStick</c> (0x005551e0) — forward
|
|
/// to the sticky sub-manager if it exists.</summary>
|
|
public void UnStick() => _sticky?.UnStick();
|
|
|
|
/// <summary>Retail <c>PositionManager::GetStickyObjectID</c>
|
|
/// (0x00555270).</summary>
|
|
public uint GetStickyObjectId() => _sticky?.TargetId ?? 0u;
|
|
|
|
/// <summary>Retail <c>PositionManager::ConstrainTo</c> (0x00555280) —
|
|
/// lazily create the <see cref="ConstraintManager"/> and arm the leash.
|
|
/// (Unused in acdream — no arming call site; see
|
|
/// <see cref="ConstraintManager"/>.)</summary>
|
|
public void ConstrainTo(Position anchor, float startDistance, float maxDistance)
|
|
{
|
|
_constraint ??= new ConstraintManager(_host);
|
|
_constraint.ConstrainTo(anchor, startDistance, maxDistance);
|
|
}
|
|
|
|
/// <summary>Retail <c>PositionManager::UnConstrain</c>
|
|
/// (0x005552b0).</summary>
|
|
public void UnConstrain() => _constraint?.UnConstrain();
|
|
|
|
/// <summary>Retail <c>PositionManager::IsFullyConstrained</c>
|
|
/// (0x005552c0) — false when no constraint sub-manager exists.</summary>
|
|
public bool IsFullyConstrained() => _constraint?.IsFullyConstrained() ?? false;
|
|
|
|
/// <summary>
|
|
/// Retail <c>PositionManager::HandleUpdateTarget</c> (0x005553d0) — only
|
|
/// the sticky sub-manager cares about live target positions (interpolation
|
|
/// and constraint don't). Fanned out from
|
|
/// <c>CPhysicsObj::HandleUpdateTarget</c>.
|
|
/// </summary>
|
|
public void HandleUpdateTarget(TargetInfo info) => _sticky?.HandleUpdateTarget(info);
|
|
|
|
/// <summary>
|
|
/// Retail <c>PositionManager::adjust_offset</c> (0x00555190) — chains the
|
|
/// sub-managers' contributions into the SAME <paramref name="offset"/>
|
|
/// accumulator, in retail order. Retail runs Interpolation → Sticky →
|
|
/// Constraint; acdream's interpolation stays in the separate remote-motion
|
|
/// combiner (see class note), so this chains Sticky → Constraint only.
|
|
/// Constraint is LAST because it clamps the already-composed displacement.
|
|
/// </summary>
|
|
public void AdjustOffset(MotionDeltaFrame offset, double quantum)
|
|
{
|
|
_sticky?.AdjustOffset(offset, quantum);
|
|
_constraint?.AdjustOffset(offset, quantum);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retail <c>PositionManager::UseTime</c> (0x00555160) — per-tick pump.
|
|
/// Retail order Interpolation → Constraint → Sticky; acdream runs the two
|
|
/// owned managers (constraint's UseTime is a retail no-op, so effectively
|
|
/// just the sticky 1 s watchdog).
|
|
/// </summary>
|
|
public void UseTime() => _sticky?.UseTime();
|
|
}
|