fix(motion): restore retail object manager order
Process animation completion at the retail process_hooks boundary, then run targeting, movement, PartArray completion, and PositionManager in the named UpdateObjectInternal order for local, remote, hidden, and position-less animated objects. Retire TS-42 with deterministic conformance coverage.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
0f996db747
commit
c5ab99081c
17 changed files with 493 additions and 98 deletions
|
|
@ -11113,6 +11113,7 @@ public sealed class GameWindow : IDisposable
|
|||
var ae = kv.Value;
|
||||
bool sequenceAdvancedBeforeAnimationPass =
|
||||
ae.SequenceAdvancedBeforeAnimationPass;
|
||||
bool managerTailHandledForObject = ae.Entity.ServerGuid == _playerServerGuid;
|
||||
IReadOnlyList<AcDream.Core.Physics.PartTransform>? preparedSequenceFrames =
|
||||
ae.PreparedSequenceFrames;
|
||||
ae.SequenceAdvancedBeforeAnimationPass = false;
|
||||
|
|
@ -11208,6 +11209,7 @@ public sealed class GameWindow : IDisposable
|
|||
remoteRootMotionFrame.Origin,
|
||||
_liveCenterX,
|
||||
_liveCenterY);
|
||||
managerTailHandledForObject = true;
|
||||
}
|
||||
|
||||
// ── Get per-part (origin, orientation) from either sequencer or legacy ──
|
||||
|
|
@ -11264,7 +11266,9 @@ public sealed class GameWindow : IDisposable
|
|||
// This matches CPhysicsObj::UpdateObjectInternal followed by
|
||||
// CPhysicsObj::UpdateChild (0x00512D50): a hand/weapon effect
|
||||
// reads this frame's pose, never the previous frame's pose.
|
||||
// AnimationDone/UseTime remain paired with the deferred drain.
|
||||
// AnimationDone is processed semantically by Capture at
|
||||
// retail's process_hooks slot. Pose-dependent delivery is
|
||||
// deferred; the later manager-tail block owns UseTime.
|
||||
if (!hidden)
|
||||
_animationHookFrames?.Capture(ae.Entity.Id, ae.Sequencer);
|
||||
}
|
||||
|
|
@ -11287,6 +11291,41 @@ public sealed class GameWindow : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
// R6: objects which did not enter the local or moving-remote
|
||||
// physics owner still need their CPartArray manager tail. Doors
|
||||
// and levers commonly receive UpdateMotion without ever receiving
|
||||
// UpdatePosition; classifying the tail by LastServerPosTime made
|
||||
// their zero-tick completions depend on the old global hook drain.
|
||||
// Hidden remotes were already handled by TickHiddenEntities above.
|
||||
bool hiddenRemoteManagerTailHandled = hidden
|
||||
&& _remoteDeadReckon.TryGetValue(serverGuid, out _);
|
||||
AcDream.Core.Physics.AnimationSequencer? tailSequencer = ae.Sequencer;
|
||||
if (!managerTailHandledForObject
|
||||
&& !hiddenRemoteManagerTailHandled
|
||||
&& tailSequencer is not null)
|
||||
{
|
||||
if (_remoteDeadReckon.TryGetValue(serverGuid, out var tailRemote)
|
||||
&& _projectileController?.HandlesMovement(serverGuid) != true)
|
||||
{
|
||||
System.Action? handleTargeting = tailRemote.Host is { } targetHost
|
||||
? targetHost.HandleTargetting
|
||||
: null;
|
||||
System.Action? positionUseTime = tailRemote.Host is { } positionHost
|
||||
? positionHost.PositionManager.UseTime
|
||||
: null;
|
||||
AcDream.Core.Physics.RetailObjectManagerTail.Run(
|
||||
checkDetection: null,
|
||||
handleTargeting,
|
||||
movementUseTime: tailRemote.Movement.UseTime,
|
||||
partArrayHandleMovement: tailSequencer.Manager.UseTime,
|
||||
positionUseTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
tailSequencer.Manager.UseTime();
|
||||
}
|
||||
}
|
||||
|
||||
int partCount = ae.PartTemplate.Count;
|
||||
|
||||
// D5 (Commit A 2026-05-03): PARTSDIAG — proves whether
|
||||
|
|
|
|||
|
|
@ -45,9 +45,22 @@ public sealed class AnimationHookFrameQueue
|
|||
{
|
||||
ArgumentNullException.ThrowIfNull(sequencer);
|
||||
ArgumentNullException.ThrowIfNull(hooks);
|
||||
|
||||
// Retail CPhysicsObj::process_hooks (0x00511550) executes AnimDone
|
||||
// inside UpdatePositionInternal, before the transition and every
|
||||
// UpdateObjectInternal manager. Complete the semantic motion state at
|
||||
// capture time so that this object's later Target/Movement/PartArray
|
||||
// tail sees the new queue in the same quantum. The hook remains in the
|
||||
// deferred entry so presentation sinks still observe the complete
|
||||
// authored hook stream after the final part poses are published.
|
||||
for (int i = 0; i < hooks.Count; i++)
|
||||
{
|
||||
if (hooks[i] is AnimationDoneHook)
|
||||
sequencer.Manager.AnimationDone(success: true);
|
||||
}
|
||||
|
||||
_entries.Add(new Entry(
|
||||
ownerLocalId,
|
||||
sequencer,
|
||||
hooks));
|
||||
}
|
||||
|
||||
|
|
@ -69,13 +82,7 @@ public sealed class AnimationHookFrameQueue
|
|||
continue;
|
||||
if (hasRootPose)
|
||||
_router.OnHook(entry.OwnerLocalId, worldPosition, hook);
|
||||
if (hook is AnimationDoneHook)
|
||||
entry.Sequencer.Manager.AnimationDone(success: true);
|
||||
}
|
||||
|
||||
// Retail sweeps zero-tick motion entries even on frames without a
|
||||
// hook. It remains paired with every sequencer advanced this frame.
|
||||
entry.Sequencer.Manager.UseTime();
|
||||
}
|
||||
_entries.Clear();
|
||||
}
|
||||
|
|
@ -84,6 +91,5 @@ public sealed class AnimationHookFrameQueue
|
|||
|
||||
private readonly record struct Entry(
|
||||
uint OwnerLocalId,
|
||||
AnimationSequencer Sequencer,
|
||||
IReadOnlyList<AnimationHook> Hooks);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue