fix(R4-V5): remote-player movetos never ticked (the glide) + door UMs dropped since the funnel cutover
Two more live findings from the user's verify pass (the walk-up itself
works since 24569fd2):
GLIDE (a retail player using an object slides to it with no legs):
remote PLAYERS' MoveToManagers were armed by mt-6 but never TICKED -
the V4 UseTime slot lived only in the NPC/legacy per-tick branch,
gated !IsPlayerGuid (inherited from the deleted RemoteMoveToDriver's
NPC-only scope). Retail's UpdateObjectInternal has no entity-class
fork. The P4 tracker feed + UseTime is extracted into a shared
TickRemoteMoveTo(rm) and now also runs in the grounded player-remote
pipeline (L.3 M2): the manager's dispatches produce the locomotion
cycle through the funnel sink (the legs) while position stays
queue-chased per the M2 spec - the same composition an NPC gets.
DOORS (used doors flip ETHEREAL but never animate; a door opened by a
retail client does not animate either): static animated objects never
receive an UpdatePosition, so they never got a RemoteMotion (created
only in the UP handler) - and since the L.2g S2b funnel cutover the
UM motion apply is rm-only (the old direct SetCycle became the
funnel), so their On/Off forward-commands were parsed and dropped.
Confirmed live: door UMs arriving with cmd=0x000B/0x000C while the
door sequencer stayed frozen. Fix: create the rm on first UM (the
retail one-pipeline-for-every-entity-class shape), with a
LastServerPosTime>0 gate keeping UP-less entities OUT of the
dead-reckoning tick block (it force-asserts ground contact and
ground-resolves the body - position machinery for entities the server
moves; a wall-mounted door must not be terrain-snapped). Their cycles
play via the sequencer's own TickAnimations advance.
Remote jump-landing pose (third user report) not addressed here -
instrumentation next if it survives this build's retest.
Full suite green: 3,958.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
24569fd2a1
commit
006cf6597a
1 changed files with 95 additions and 36 deletions
|
|
@ -4442,6 +4442,54 @@ public sealed class GameWindow : IDisposable
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// R4-V5 (extracted from the V4 NPC tick block, now shared with the
|
||||||
|
/// grounded player-remote pipeline — the glide fix): the P4
|
||||||
|
/// TargetTracker delivery + the per-tick MoveToManager drive. Feeds
|
||||||
|
/// <c>HandleUpdateTarget(Ok)</c> from the live entity table when the
|
||||||
|
/// tracked target moved beyond the voyeur radius (ExitWorld on
|
||||||
|
/// despawn), then runs <c>UseTime()</c> — steering, arrival,
|
||||||
|
/// fail-distance. Safe for any remote: a manager with no armed moveto
|
||||||
|
/// no-ops, and airborne bodies are held by UseTime's contact gate.
|
||||||
|
/// </summary>
|
||||||
|
private void TickRemoteMoveTo(RemoteMotion rm)
|
||||||
|
{
|
||||||
|
if (rm.MoveTo is not { } mtm)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (rm.TrackedTargetGuid != 0)
|
||||||
|
{
|
||||||
|
if (_entitiesByServerGuid.TryGetValue(rm.TrackedTargetGuid, out var trackedEnt))
|
||||||
|
{
|
||||||
|
var tpos = trackedEnt.Position;
|
||||||
|
if (!rm.TrackedTargetFedOnce
|
||||||
|
|| System.Numerics.Vector3.Distance(tpos, rm.TrackedTargetLastFedPos)
|
||||||
|
> rm.TrackedTargetRadius)
|
||||||
|
{
|
||||||
|
rm.TrackedTargetFedOnce = true;
|
||||||
|
rm.TrackedTargetLastFedPos = tpos;
|
||||||
|
var tp = new AcDream.Core.Physics.Position(
|
||||||
|
rm.CellId, tpos, System.Numerics.Quaternion.Identity);
|
||||||
|
mtm.HandleUpdateTarget(new AcDream.Core.Physics.Motion.TargetInfo(
|
||||||
|
rm.TrackedTargetGuid,
|
||||||
|
AcDream.Core.Physics.Motion.TargetStatus.Ok,
|
||||||
|
tp, tp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var lp = new AcDream.Core.Physics.Position(
|
||||||
|
rm.CellId, rm.TrackedTargetLastFedPos,
|
||||||
|
System.Numerics.Quaternion.Identity);
|
||||||
|
mtm.HandleUpdateTarget(new AcDream.Core.Physics.Motion.TargetInfo(
|
||||||
|
rm.TrackedTargetGuid,
|
||||||
|
AcDream.Core.Physics.Motion.TargetStatus.ExitWorld,
|
||||||
|
lp, lp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mtm.UseTime();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Phase 6.6: the server says an entity's motion has changed. Look up
|
/// Phase 6.6: the server says an entity's motion has changed. Look up
|
||||||
/// the AnimatedEntity for that guid, re-resolve the idle cycle with the
|
/// the AnimatedEntity for that guid, re-resolve the idle cycle with the
|
||||||
|
|
@ -4741,7 +4789,24 @@ public sealed class GameWindow : IDisposable
|
||||||
// 6/7/8/9 route to MoveToManager.PerformMovement (which
|
// 6/7/8/9 route to MoveToManager.PerformMovement (which
|
||||||
// cancels again itself — retail does both) while ONLY type 0
|
// cancels again itself — retail does both) while ONLY type 0
|
||||||
// flows through the interpreted-state funnel below.
|
// flows through the interpreted-state funnel below.
|
||||||
if (_remoteDeadReckon.TryGetValue(update.Guid, out var remoteMot))
|
//
|
||||||
|
// R4-V5 door fix (2026-07-03 user report: doors stopped
|
||||||
|
// animating): entities that never receive an UpdatePosition
|
||||||
|
// (doors, levers — static animated objects) never got a
|
||||||
|
// RemoteMotion (created only in the UP handler), so since
|
||||||
|
// the L.2g S2b funnel cutover NOTHING applied their UM
|
||||||
|
// forward-command (the old direct SetCycle became the
|
||||||
|
// rm-only funnel) — a used door flipped ETHEREAL but never
|
||||||
|
// played its On/Off cycle. Retail runs the SAME unpack →
|
||||||
|
// CMotionInterp pipeline for every entity class; create the
|
||||||
|
// rm on first UM exactly like the UP handler does.
|
||||||
|
if (!_remoteDeadReckon.TryGetValue(update.Guid, out var remoteMot))
|
||||||
|
{
|
||||||
|
remoteMot = new RemoteMotion();
|
||||||
|
remoteMot.Body.Orientation = entity.Rotation;
|
||||||
|
remoteMot.Body.Position = entity.Position;
|
||||||
|
_remoteDeadReckon[update.Guid] = remoteMot;
|
||||||
|
}
|
||||||
{
|
{
|
||||||
var sink = EnsureRemoteMotionBindings(remoteMot, ae, update.Guid);
|
var sink = EnsureRemoteMotionBindings(remoteMot, ae, update.Guid);
|
||||||
|
|
||||||
|
|
@ -9525,7 +9590,19 @@ public sealed class GameWindow : IDisposable
|
||||||
if (ae.Sequencer is not null
|
if (ae.Sequencer is not null
|
||||||
&& serverGuid != 0
|
&& serverGuid != 0
|
||||||
&& serverGuid != _playerServerGuid
|
&& serverGuid != _playerServerGuid
|
||||||
&& _remoteDeadReckon.TryGetValue(serverGuid, out var rm))
|
&& _remoteDeadReckon.TryGetValue(serverGuid, out var rm)
|
||||||
|
// R4-V5 door fix companion: rm entries now also exist for
|
||||||
|
// static animated objects (doors/levers — created on first
|
||||||
|
// UM so the funnel can play their On/Off cycles). Those
|
||||||
|
// must NOT enter this dead-reckoning block: it force-
|
||||||
|
// asserts ground contact, zeroes/integrates velocity, and
|
||||||
|
// ground-resolves the body — position machinery for
|
||||||
|
// entities the server MOVES. "Has ever received an
|
||||||
|
// UpdatePosition" is exactly that distinction (there is
|
||||||
|
// nothing to dead-reckon between UPs that don't exist);
|
||||||
|
// their animation still plays via the sequencer's own
|
||||||
|
// TickAnimations advance.
|
||||||
|
&& rm.LastServerPosTime > 0)
|
||||||
{
|
{
|
||||||
if (IsPlayerGuid(serverGuid) && !rm.Airborne)
|
if (IsPlayerGuid(serverGuid) && !rm.Airborne)
|
||||||
{
|
{
|
||||||
|
|
@ -9583,6 +9660,21 @@ public sealed class GameWindow : IDisposable
|
||||||
rm.Body.TransientState |= AcDream.Core.Physics.TransientStateFlags.Active;
|
rm.Body.TransientState |= AcDream.Core.Physics.TransientStateFlags.Active;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// R4-V5 glide fix (2026-07-03 user report: a retail
|
||||||
|
// player using an object GLIDES to it — position moves
|
||||||
|
// via the UP queue but no walk/run legs): remote
|
||||||
|
// PLAYERS' MoveToManagers were armed by mt-6 but never
|
||||||
|
// TICKED — the V4 UseTime slot lived only in the
|
||||||
|
// NPC/legacy branch below, gated !IsPlayerGuid
|
||||||
|
// (inherited from the deleted RemoteMoveToDriver's
|
||||||
|
// NPC-only scope). Retail ticks every entity's
|
||||||
|
// MovementManager (UpdateObjectInternal has no entity-
|
||||||
|
// class fork). The manager's dispatches produce the
|
||||||
|
// locomotion cycle through the funnel sink (the LEGS);
|
||||||
|
// position stays queue-chased per the L.3 M2 spec, so
|
||||||
|
// the two compose exactly like an NPC's tick.
|
||||||
|
TickRemoteMoveTo(rm);
|
||||||
|
|
||||||
// Step 2 (M3): queue + anim translation via PositionManager.
|
// Step 2 (M3): queue + anim translation via PositionManager.
|
||||||
// ComputeOffset returns:
|
// ComputeOffset returns:
|
||||||
// - Vector3.Zero when queue is empty AND seqVel is zero
|
// - Vector3.Zero when queue is empty AND seqVel is zero
|
||||||
|
|
@ -9804,40 +9896,7 @@ public sealed class GameWindow : IDisposable
|
||||||
// apply_current_movement recomputes velocity from
|
// apply_current_movement recomputes velocity from
|
||||||
// whatever the manager dispatched — the same
|
// whatever the manager dispatched — the same
|
||||||
// per-tick shape ordinary locomotion uses.
|
// per-tick shape ordinary locomotion uses.
|
||||||
if (!IsPlayerGuid(serverGuid) && rm.MoveTo is { } mtm)
|
TickRemoteMoveTo(rm);
|
||||||
{
|
|
||||||
if (rm.TrackedTargetGuid != 0)
|
|
||||||
{
|
|
||||||
if (_entitiesByServerGuid.TryGetValue(rm.TrackedTargetGuid, out var trackedEnt))
|
|
||||||
{
|
|
||||||
var tpos = trackedEnt.Position;
|
|
||||||
if (!rm.TrackedTargetFedOnce
|
|
||||||
|| System.Numerics.Vector3.Distance(tpos, rm.TrackedTargetLastFedPos)
|
|
||||||
> rm.TrackedTargetRadius)
|
|
||||||
{
|
|
||||||
rm.TrackedTargetFedOnce = true;
|
|
||||||
rm.TrackedTargetLastFedPos = tpos;
|
|
||||||
var tp = new AcDream.Core.Physics.Position(
|
|
||||||
rm.CellId, tpos, System.Numerics.Quaternion.Identity);
|
|
||||||
mtm.HandleUpdateTarget(new AcDream.Core.Physics.Motion.TargetInfo(
|
|
||||||
rm.TrackedTargetGuid,
|
|
||||||
AcDream.Core.Physics.Motion.TargetStatus.Ok,
|
|
||||||
tp, tp));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var lp = new AcDream.Core.Physics.Position(
|
|
||||||
rm.CellId, rm.TrackedTargetLastFedPos,
|
|
||||||
System.Numerics.Quaternion.Identity);
|
|
||||||
mtm.HandleUpdateTarget(new AcDream.Core.Physics.Motion.TargetInfo(
|
|
||||||
rm.TrackedTargetGuid,
|
|
||||||
AcDream.Core.Physics.Motion.TargetStatus.ExitWorld,
|
|
||||||
lp, lp));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mtm.UseTime();
|
|
||||||
}
|
|
||||||
rm.Motion.apply_current_movement(cancelMoveTo: false, allowJump: false);
|
rm.Motion.apply_current_movement(cancelMoveTo: false, allowJump: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue