fix(#171): R5-V3 — bind sticky melee (StickyManager live) + real arrival radii
Group-melee interpenetration + facing drift: the R5-V1-ported StickyManager/PositionManager were Core-only — the StickTo/Unstick seams were unbound and every arrival radius was 0, so ACE's Sticky|UseSpheres melee chases closed ~one body-radius too deep and froze at stale arrival poses until the next wire re-arm. Retires TS-39. Wiring (anchors re-verified against the named decomp this session): - EntityPhysicsHost owns a PositionManager; HandleUpdateTarget fans MoveToManager then PositionManager (CPhysicsObj::HandleUpdateTarget 0x00512bc0 order). - Seams bound remote + player: MoveToManager.StickTo (BeginNextNode sticky arrival @0x00529d3a), Unstick (PerformMovement head), and MotionInterpreter.UnstickFromObject (UM funnel head, 0x0050eaea). - AdjustOffset at the retail UpdatePositionInternal slot (@0x00512d0e): NPC branch composes pre-sweep (steer is swept by ResolveWithTransition); remote-player branch chains the combiner offset through the shared delta frame (the interp stage) so sticky OVERWRITES when armed (0x00555430 assigns m_fOrigin, not accumulates); player inside the 30 Hz physics quantum before UpdatePhysicsInternal. - UseTime (the 1 s lease watchdog) at the UpdateObjectInternal tail (@0x005159b3): unconditional per remote; player gated on the physics tick (retail's MinQuantum gate skips UseTime too). - Real setup cylsphere radii (CPartArray::GetRadius/GetHeight 0x005180a0/0x005180b0 = setup radius/height x ObjScale from the spawn record): own via EnsureRemoteMotionBindings + player wiring; target via RouteServerMoveTo AND the speculative use-walk install (retail resolves the target PartArray at EVERY MoveToObject site — ACE PhysicsObj.cs:951). - Teardown parity: exit_world (0x00514e60) UnStick + ClearTarget before the ExitWorld notify; player teleport fires teleport_hook's tail (UnStick in SetPosition + EntityPhysicsHost.NotifyTeleported = ClearTarget + NotifyVoyeurOfEvent(Teleported) @0x00514f1b) so mobs stuck to the player drop their sticks on a recall. - SERVERVEL arbitration also yields to a stuck entity (same starvation class as the #170 fix — sticky owns the between-snap translation). - StickyManager.UseTime aligned to retail's strict > deadline (0x00555626; ACE >): two V1 tests had pinned the >= edge — corrected. Register: TS-39 deleted; TS-41 narrowed (stickyArmed gate); TS-43 added (remote teleport_hook gap — self-corrects within the 1 s lease); AP-23 narrowed (real radii at the speculative site; only the use-radius buckets remain invented). Conformance: 2 new full-stack sticky scenarios in RemoteChaseEndToEndHarnessTests (arrive -> stick -> strafing-target gap+facing track -> lease expiry; unstick-on-rearm -> re-stick). Full suite 4038 green. Pre-commit adversarial diff review (3 lenses + per-finding refuters) confirmed and fixed 4 findings: ObjScale-dead radius read, player UseTime order inversion, missing teleport voyeur notify, speculative- site radius asymmetry. Awaiting the user visual gate: pack melee side-by-side vs retail (attackers reshuffle + keep facing; some overlap is ACE-server-side). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
d413ac2a29
commit
5bd2b8bc8b
10 changed files with 586 additions and 32 deletions
|
|
@ -24,9 +24,15 @@ namespace AcDream.App.Rendering;
|
|||
/// <see cref="HandleUpdateTarget"/> fan-out are injected by GameWindow so this
|
||||
/// class stays free of GameWindow's internals (code-structure rule #1).</para>
|
||||
///
|
||||
/// <para>PositionManager (sticky) is R5-V3 — this host gains a
|
||||
/// <c>PositionManager</c> and <see cref="HandleUpdateTarget"/> fans to it then;
|
||||
/// V2 delivers only to the MoveToManager.</para>
|
||||
/// <para>R5-V3: owns a <see cref="PositionManager"/> too (retail
|
||||
/// <c>CPhysicsObj::position_manager</c> — retail creates it lazily via
|
||||
/// <c>get_position_manager</c>; acdream constructs it eagerly, which is
|
||||
/// behaviorally identical because the empty facade no-ops until its first
|
||||
/// <c>StickTo</c>/<c>ConstrainTo</c>). <see cref="HandleUpdateTarget"/> fans
|
||||
/// deliveries to the injected MoveToManager fan FIRST, then the
|
||||
/// PositionManager — retail <c>CPhysicsObj::HandleUpdateTarget</c> order
|
||||
/// (0x00512bc0: MovementManager @0x00512bf0, PositionManager
|
||||
/// @0x00512c1a).</para>
|
||||
/// </summary>
|
||||
public sealed class EntityPhysicsHost : IPhysicsObjHost
|
||||
{
|
||||
|
|
@ -68,6 +74,7 @@ public sealed class EntityPhysicsHost : IPhysicsObjHost
|
|||
_interruptCurrentMovement = interruptCurrentMovement
|
||||
?? throw new ArgumentNullException(nameof(interruptCurrentMovement));
|
||||
_targetManager = new TargetManager(this);
|
||||
PositionManager = new PositionManager(this);
|
||||
}
|
||||
|
||||
// ── IPhysicsObjHost accessors ──────────────────────────────────────────
|
||||
|
|
@ -84,9 +91,24 @@ public sealed class EntityPhysicsHost : IPhysicsObjHost
|
|||
/// <c>CPhysicsObj::target_manager</c>).</summary>
|
||||
public TargetManager TargetManager => _targetManager;
|
||||
|
||||
/// <summary>R5-V3 — the owned <see cref="PositionManager"/> facade (retail
|
||||
/// <c>CPhysicsObj::position_manager</c>): sticky follow + (unarmed)
|
||||
/// constraint leash. Seam targets: <c>MoveToManager.StickTo/Unstick</c>,
|
||||
/// <c>MotionInterpreter.UnstickFromObject</c>, the per-tick
|
||||
/// <c>AdjustOffset</c>/<c>UseTime</c> drivers.</summary>
|
||||
public PositionManager PositionManager { get; }
|
||||
|
||||
// ── IPhysicsObjHost fan-out / target-tracking seams ────────────────────
|
||||
public IPhysicsObjHost? GetObjectA(uint id) => _getObjectA(id);
|
||||
public void HandleUpdateTarget(TargetInfo info) => _handleUpdateTarget(info);
|
||||
|
||||
public void HandleUpdateTarget(TargetInfo info)
|
||||
{
|
||||
// Retail CPhysicsObj::HandleUpdateTarget (0x00512bc0) fan order:
|
||||
// MovementManager (the injected MoveToManager fan) first, then
|
||||
// PositionManager (@0x00512c1a — the R5-V3 sticky consumer).
|
||||
_handleUpdateTarget(info);
|
||||
PositionManager.HandleUpdateTarget(info);
|
||||
}
|
||||
public void InterruptCurrentMovement() => _interruptCurrentMovement();
|
||||
|
||||
public void SetTarget(uint contextId, uint objectId, float radius, double quantum)
|
||||
|
|
@ -112,4 +134,16 @@ public sealed class EntityPhysicsHost : IPhysicsObjHost
|
|||
/// stick/moveto). Called on despawn before the host is removed from the
|
||||
/// registry.</summary>
|
||||
public void NotifyExitWorld() => _targetManager.NotifyVoyeurOfEvent(TargetStatus.ExitWorld);
|
||||
|
||||
/// <summary>R5-V3 (#171): retail <c>CPhysicsObj::teleport_hook</c>'s tail
|
||||
/// (0x00514ed0 @0x00514f1b-0x00514f28) — <c>TargetManager::ClearTarget</c>
|
||||
/// (drop this entity's OWN subscription) then
|
||||
/// <c>NotifyVoyeurOfEvent(Teleported)</c> (every entity watching THIS one
|
||||
/// drops its stick/moveto — <c>StickyManager::HandleUpdateTarget</c>'s
|
||||
/// non-Ok teardown path). Called after a teleport placement.</summary>
|
||||
public void NotifyTeleported()
|
||||
{
|
||||
_targetManager.ClearTarget();
|
||||
_targetManager.NotifyVoyeurOfEvent(TargetStatus.Teleported);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue