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:
Erik 2026-07-19 21:40:14 +02:00
parent 0f996db747
commit c5ab99081c
17 changed files with 493 additions and 98 deletions

View file

@ -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