# R6 retail object-update order Date: 2026-07-19 Retail oracle: Asheron's Call v11.4186 (September 2013 EoR) ## Scope This note pins the control-flow order needed by R6. It deliberately separates two kinds of animation-hook work: - **control hooks** such as `AnimDoneHook`, whose result changes the motion queues observed later in the same object update; and - **pose-dependent presentation hooks** such as particles and sounds, whose App-layer delivery may wait until the final root/part pose has been published without changing motion state. That split is an acdream presentation boundary. It does not change retail's logical hook order. ## Named-retail sources - `CPhysicsObj::process_hooks` `0x00511550` - `CPhysicsObj::UpdatePositionInternal` `0x00512C30` - `CPhysicsObj::UpdateObjectInternal` `0x005156B0` - `CPhysicsObj::update_object` `0x00515D10` - `CPartArray::AnimationDone` `0x00517D30` - `CPartArray::HandleMovement` `0x00517D60` - `AnimDoneHook::Execute` `0x00526C20` - `CPhysicsObj::Hook_AnimDone` `0x0050FDA0` Primary text: `docs/research/named-retail/acclient_2013_pseudo_c.txt`. Addresses and names are also pinned in `docs/research/named-retail/symbols.json`. ## Corrected top-level order An older roadmap diagram put `process_hooks` after the manager tail. The named retail function body disproves that. `UpdatePositionInternal` processes hooks before it returns to `UpdateObjectInternal`; the transition/commit and every manager therefore observe completed animation state in the same object tick. ```text CPhysicsObj::update_object -> quantum gate/subdivision -> CPhysicsObj::UpdateObjectInternal -> CPhysicsObj::UpdatePositionInternal -> CPartArray::Update -> PositionManager::adjust_offset -> Frame::combine -> CPhysicsObj::UpdatePhysicsInternal -> CPhysicsObj::process_hooks <-- completion drain -> transition / SetPositionInternal -> DetectionManager::CheckDetection -> TargetManager::HandleTargetting -> MovementManager::UseTime -> CPartArray::HandleMovement -> PositionManager::UseTime -> ParticleManager::UpdateParticles -> ScriptManager::UpdateScripts ``` ## Pseudocode ### `CPhysicsObj::update_object` ```text if parent != null or cell == null or state contains Frozen: return refresh active/view-distance state elapsed = current_physics_time - last_update_time if elapsed > HugeQuantum: last_update_time = current_physics_time return while elapsed > MaxQuantum: last_update_time += MaxQuantum UpdateObjectInternal(MaxQuantum) elapsed -= MaxQuantum if elapsed > MinQuantum: last_update_time = current_physics_time UpdateObjectInternal(elapsed) ``` The comparisons are strict (`>`), not `>=`. A sub-minimum remainder stays in the clock for a later call. ### `CPhysicsObj::UpdatePositionInternal(dt, candidate)` ```text delta = identity Frame if state does not contain Hidden: if part_array != null: part_array.Update(dt, delta) if transient state contains OnWalkable: delta.origin *= object_scale else: delta.origin = zero if position_manager != null: position_manager.adjust_offset(delta, dt) candidate = combine(current_frame, delta) if state does not contain Hidden: UpdatePhysicsInternal(dt, candidate) process_hooks() ``` Hidden suppresses PartArray time advance and physics integration. It does not suppress `PositionManager::adjust_offset`, hook processing, or the later manager tail. ### `CPhysicsObj::process_hooks` ```text execute each queued physics hook in FIFO order clear physics-hook queue execute each queued animation hook in FIFO order clear animation-hook queue ``` For an `AnimDoneHook`: ```text AnimDoneHook::Execute(owner) -> owner.Hook_AnimDone() -> owner.part_array.AnimationDone(success = true) -> MotionTableManager.AnimationDone(true) -> possibly MotionDone on the matching pending motion ``` Consequently, targeting and movement managers in the same object tick must see the post-completion queue state. ### `CPhysicsObj::UpdateObjectInternal(dt)` manager tail ```text candidate = current frame UpdatePositionInternal(dt, candidate) if candidate moved: transition/sweep from current frame to candidate commit accepted position and collision response if detection_manager != null: detection_manager.CheckDetection() if target_manager != null: target_manager.HandleTargetting() if movement_manager != null: movement_manager.UseTime() if part_array != null: part_array.HandleMovement() // tailcalls MotionTableManager.UseTime / // CheckForCompletedMotions if position_manager != null: position_manager.UseTime() if particle_manager != null: particle_manager.UpdateParticles(dt) if script_manager != null: script_manager.UpdateScripts(dt) ``` The tail is reached for both visible and Hidden objects when the object quantum runs. A missing manager is simply skipped. No manager is reordered around another manager based on local/remote/player/NPC classification. ## acdream conformance boundary The first R6 cutover uses one explicit coordinator for the ordered manager tail. Local and remote owners provide callbacks for the managers they possess; unsupported `DetectionManager` is represented by an absent callback rather than an invented behavior. Animation-hook capture performs `AnimationDone` control effects immediately, at the `process_hooks` boundary. Pose-dependent routing remains deferred until the current frame's root/part poses are published. Deferred routing must never invoke `AnimationDone` a second time and must never own `MotionTableManager.UseTime`; that belongs only to the PartArray manager-tail slot. ## Conformance cases 1. Animation completion is visible to TargetManager and MovementManager in the same object tick. 2. Target runs before Movement; Movement runs before PartArray; PartArray runs before PositionManager. 3. A missing manager does not shift or duplicate another callback. 4. Hidden objects skip sequence advance but retain the same manager tail. 5. A visual hook sees a pose published after capture, while an `AnimDoneHook` changes motion state at capture time. 6. Deferred visual-hook drain never completes an animation twice and never performs a zero-tick motion sweep. 7. A sub-MinQuantum frame performs no object-tail call; accumulated time is consumed by a later quantum. ## Cross-reference notes ACE's physics/motion ports remain interpretation aids for manager ownership and state naming; the retail binary above is authoritative for order. The existing `RemoteChaseEndToEndHarnessTests` hand-codes acdream's pre-R6 order and is therefore a migration target, not an oracle.