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;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// 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
|
||||
|
|
@ -4741,7 +4789,24 @@ public sealed class GameWindow : IDisposable
|
|||
// 6/7/8/9 route to MoveToManager.PerformMovement (which
|
||||
// cancels again itself — retail does both) while ONLY type 0
|
||||
// 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);
|
||||
|
||||
|
|
@ -9525,7 +9590,19 @@ public sealed class GameWindow : IDisposable
|
|||
if (ae.Sequencer is not null
|
||||
&& serverGuid != 0
|
||||
&& 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)
|
||||
{
|
||||
|
|
@ -9583,6 +9660,21 @@ public sealed class GameWindow : IDisposable
|
|||
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.
|
||||
// ComputeOffset returns:
|
||||
// - 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
|
||||
// whatever the manager dispatched — the same
|
||||
// per-tick shape ordinary locomotion uses.
|
||||
if (!IsPlayerGuid(serverGuid) && rm.MoveTo is { } mtm)
|
||||
{
|
||||
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();
|
||||
}
|
||||
TickRemoteMoveTo(rm);
|
||||
rm.Motion.apply_current_movement(cancelMoveTo: false, allowJump: false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue