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
|
|
@ -286,6 +286,21 @@ public sealed class PlayerMovementController
|
|||
/// </summary>
|
||||
public AcDream.Core.Physics.Motion.MoveToManager? MoveTo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// R5-V3 (#171): the player's <c>PositionManager</c> facade (retail
|
||||
/// <c>CPhysicsObj::position_manager</c> — owned by the player's
|
||||
/// <c>EntityPhysicsHost</c>, handed here by <c>EnterPlayerModeNow</c>).
|
||||
/// <see cref="Update"/> drives it at the two retail per-tick points:
|
||||
/// <c>AdjustOffset</c> inside the physics-tick block (retail
|
||||
/// <c>UpdatePositionInternal</c> @0x00512d0e, BEFORE
|
||||
/// <c>UpdatePhysicsInternal</c> so the sticky steer is part of the swept
|
||||
/// motion) and <c>UseTime</c> after the completed-motions sweep (retail
|
||||
/// <c>UpdateObjectInternal</c> tail @0x005159b3 — the sticky 1 s lease
|
||||
/// watchdog). <see cref="SetPosition(Vector3, uint, Vector3)"/> tears any
|
||||
/// stick down (retail <c>teleport_hook</c> @0x00514eee).
|
||||
/// </summary>
|
||||
public AcDream.Core.Physics.Motion.PositionManager? PositionManager { get; set; }
|
||||
|
||||
public PlayerMovementController(PhysicsEngine physics)
|
||||
{
|
||||
_physics = physics;
|
||||
|
|
@ -468,6 +483,12 @@ public sealed class PlayerMovementController
|
|||
// 0x00527e40: resets fwd/sidestep/turn COMMANDS, zeroes velocity,
|
||||
// enqueues the A9 jump-snapshot node) — not a bare DoMotion(Ready).
|
||||
_motion.StopCompletely();
|
||||
// R5-V3 (#171): retail teleport_hook (0x00514ed0) — PositionManager::
|
||||
// UnStick (@0x00514eee) right after the moveto cancel: a teleport
|
||||
// tears down any active stick. (StopInterpolating/UnConstrain have no
|
||||
// armed acdream counterparts — no local-player InterpolationManager,
|
||||
// constraint leash unarmed per #167.)
|
||||
PositionManager?.UnStick();
|
||||
// Reset the edge tracker: the stop wiped the motion state, so keys
|
||||
// still physically held must re-fire as press edges on the next
|
||||
// Update (matches the pre-W6 level-triggered behavior of walking
|
||||
|
|
@ -793,6 +814,29 @@ public sealed class PlayerMovementController
|
|||
// Integrate accumulated dt, clamped to MaxQuantum so a long
|
||||
// pause doesn't produce one giant integration step.
|
||||
float tickDt = MathF.Min(_physicsAccum, PhysicsBody.MaxQuantum);
|
||||
|
||||
// R5-V3 (#171): retail UpdatePositionInternal (0x00512c30) —
|
||||
// PositionManager::adjust_offset (@0x00512d0e) composes the
|
||||
// sticky steer into this quantum's motion BEFORE
|
||||
// UpdatePhysicsInternal, so the transition sweep below resolves
|
||||
// it like any other movement (preIntegratePos was captured
|
||||
// above). The delta's Origin is mover-LOCAL (rotate out by the
|
||||
// body orientation); the heading is RELATIVE and writes Yaw —
|
||||
// the authoritative facing the body quaternion is re-derived
|
||||
// from every frame (a quaternion-only write would be clobbered).
|
||||
// No-op while nothing is stuck (untouched delta frame).
|
||||
if (PositionManager is { } ppm)
|
||||
{
|
||||
var pmDelta = new AcDream.Core.Physics.Motion.MotionDeltaFrame();
|
||||
ppm.AdjustOffset(pmDelta, tickDt);
|
||||
if (pmDelta.Origin != Vector3.Zero)
|
||||
_body.Position += Vector3.Transform(pmDelta.Origin, _body.Orientation);
|
||||
if (!pmDelta.Orientation.IsIdentity)
|
||||
Yaw = AcDream.Core.Physics.Motion.MoveToMath.YawFromHeading(
|
||||
AcDream.Core.Physics.Motion.MoveToMath.HeadingFromYaw(Yaw)
|
||||
+ pmDelta.GetHeading());
|
||||
}
|
||||
|
||||
_body.calc_acceleration();
|
||||
_body.UpdatePhysicsInternal(tickDt);
|
||||
_physicsAccum -= tickDt;
|
||||
|
|
@ -853,6 +897,16 @@ public sealed class PlayerMovementController
|
|||
{
|
||||
_prevPhysicsPos = oldTickEndPos;
|
||||
_currPhysicsPos = _body.Position;
|
||||
|
||||
// R5-V3 (#171): retail UpdateObjectInternal TAIL — PositionManager::
|
||||
// UseTime (@0x005159b3) runs AFTER the quantum's integration
|
||||
// (whose head hosts adjust_offset) and only on frames where a
|
||||
// physics quantum executed (retail's update_object MinQuantum
|
||||
// gate skips the whole UpdateObjectInternal). Diff-review find:
|
||||
// a head-of-frame placement tore an expiring stick down BEFORE
|
||||
// the crossing quantum's final steer/turn — retail applies that
|
||||
// last adjust_offset, THEN the tail watchdog tears down.
|
||||
PositionManager?.UseTime();
|
||||
}
|
||||
|
||||
// L.3a (2026-04-30): retail wall-bounce / velocity reflection.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue