feat(physics): R5-V1 — port PositionManager/Sticky/Constraint + TargetManager (Core, unwired)

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>
This commit is contained in:
Erik 2026-07-03 19:34:49 +02:00
parent 517bbfdae4
commit 3d89446d98
25 changed files with 7279 additions and 12 deletions

View file

@ -0,0 +1,36 @@
namespace AcDream.Core.Physics.Motion;
/// <summary>
/// R5 — port of retail's <c>TargettedVoyeurInfo</c> (acclient.h:52807,
/// struct #5801). One entry in a <see cref="TargetManager"/>'s voyeur table:
/// a subscriber watching THIS object, the send-on-move <see cref="Radius"/>
/// threshold it registered, its dead-reckoning <see cref="Quantum"/>, and the
/// <see cref="LastSentPosition"/> already delivered to it (the delta baseline
/// <c>CheckAndUpdateVoyeur</c> compares against). Mutable class (retail heap
/// record updated in place by <c>AddVoyeur</c>/<c>SendVoyeurUpdate</c>).
/// </summary>
public sealed class TargettedVoyeurInfo
{
/// <summary>+0x00 retail <c>object_id</c> — the subscriber's guid.</summary>
public uint ObjectId { get; }
/// <summary>+0x04 retail <c>quantum</c> — the subscriber's dead-reckoning
/// lookahead horizon (seconds).</summary>
public double Quantum { get; set; }
/// <summary>+0x10 retail <c>radius</c> — the send-on-move threshold: an
/// update is pushed only when the tracked object drifts more than this from
/// <see cref="LastSentPosition"/>.</summary>
public float Radius { get; set; }
/// <summary>+0x14 retail <c>last_sent_position</c> — the position last
/// delivered to this subscriber (updated by <c>SendVoyeurUpdate</c>).</summary>
public Position LastSentPosition { get; set; }
public TargettedVoyeurInfo(uint objectId, float radius, double quantum)
{
ObjectId = objectId;
Radius = radius;
Quantum = quantum;
}
}