fix(world): retire motion links at hidden transitions

Port CPhysicsObj::set_hidden's PartArray HandleEnterWorld boundary on both Hidden and UnHide. This strips linked recall/casting animations and aborts their pending completions before cell visibility changes, preventing spell-recall tails after portal travel without arrival resets or action classifiers.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-16 07:39:34 +02:00
parent 75acae02d6
commit bfb4b26dff
6 changed files with 185 additions and 21 deletions

View file

@ -300,21 +300,45 @@ SmartBox.UseTime:
The exact call sites are `Client::UseTime` `0x00411C40` and
`SmartBox::UseTime` `0x00455410`, and `CommandInterpreter::UseTime`
`0x006B3BF0`. `SmartBox::DoSetState` `0x004520D0`
applies Hidden directly and does not advance or reset animation. Separately,
`0x006B3BF0`. `SmartBox::DoSetState` `0x004520D0` applies the accepted
state through `CPhysicsObj::set_state` `0x00514DD0`. Separately,
`CPhysicsObj::UpdatePositionInternal` `0x00512C30` skips `CPartArray::Update`
while Hidden. Therefore the ordering boundary is load-bearing: the current
frame's animation must advance before an inbound Hidden transition can freeze
it.
while Hidden, so ordinary animation never advances behind the portal viewport.
ACE schedules lifestone recall teleport after the MotionTable-reported action
length. With the installed human DATs, the boundary is about 15.06024 seconds;
floating-point conversion can leave acdream at frame 149.999998 of the
0..149 recall node if it dispatches Hidden before the current object tick. On
UnHide that microscopic remainder becomes a visible destination-side recall
tail. Retail's object-before-network order retires the node first. The faithful
fix is that frame barrier, never an arrival-only Ready/reset command and never
advancing PartArray while Hidden.
tail if Hidden only suppresses drawing. Retail has a second, decisive boundary:
`CPhysicsObj::set_hidden` `0x00514C60` invokes
`CPartArray::HandleEnterWorld` `0x00517D70` on **both** Hidden and UnHide.
That delegates to `MotionTableManager::HandleEnterWorld` `0x0051BDD0`, which
removes every link animation from the sequence and drains every pending
completion as aborted. On the Hidden edge it runs after the Hidden PES, child
NoDraw propagation, collision stop, and cell hide. On UnHide it runs after
collision restoration and before the cell becomes visible. A casting recoil,
recall remainder, or authored stance transition therefore cannot resume after
portal space.
ACE also has timing differences when recall starts in combat. Its
`Player_Location` handlers call `SetCombatMode(NonCombat)` and immediately send
the NonCombat/Ready/recall command state. House, Lifestone, Allegiance Hometown,
and PK Arena schedule `Teleport` using only
`MotionTable.GetAnimationLength(recall)`. Retail
`MovementManager::unpack_movement` (`0x00524440`) and
`CMotionInterp::move_to_interpreted_state` (`0x005289C0`) preserve the authored
combat-to-NonCombat link before the recall action. For a human leaving Magic
stance that link is about 0.31 seconds, but ACE does not add it to the teleport
delay. The retail Hidden boundary intentionally tolerates that: whichever
authored links remain when teleport begins are retired by `set_hidden`, not
carried through the tunnel.
Marketplace is a separate ACE residual: its handler deliberately uses a fixed
14-second delay and comments out `GetAnimationLength` because the installed
animation is approximately 18.4 seconds. That can shorten the visible source-
side action, but the client must still apply the same retail Hidden teardown;
no Marketplace-specific classifier or portal-exit behavior is required.
## acdream integration translation
@ -355,6 +379,11 @@ advancing PartArray while Hidden.
by that inbound pass is likewise projected without receiving a late physics
tick; its first normal object tick occurs on the next update.
This is update-loop ordering, not portal-specific animation logic.
- `LiveEntityPresentationController` owns the complete accepted Hidden/UnHide
side-effect order. Both edges route the current local entity to its existing
`AnimationSequencer.Manager.HandleEnterWorld`, matching retail's PartArray
boundary for player, creature, and object animations without classifying the
preceding action.
- Destination residency remains acdream's asynchronous adaptation. It supplies
the same `EndTeleportAnimation` edge retail receives when its blocking cell
load completes; it does not alter presentation ordering.