# Retail Use busy-reference ownership Date: 2026-07-23 Scope: the retained UI busy cursor and the `You can only move or use one item at a time` gate from an accepted object/NPC Use through its authoritative completion. ## Retail oracle Named Sept-2013 retail symbols: - `ItemHolder::UseObject @ 0x00588A80` - `ClientUISystem::Handle_Item__UseDone @ 0x00564900` - `CM_Item::DispatchUI_UseDone @ 0x006A8510` - `ClientUISystem::UsageCallback @ 0x00565B20` `ItemHolder::UseObject` checks the 200 ms throttle and `IsPlayerReadyToMakeInventoryRequest`, emits `CM_Inventory::Event_UseEvent`, increments `ClientUISystem::m_cBusy`, and then calls `CPlayerSystem::UsingItem`. The confirmation callback has the same order. `CM_Item::DispatchUI_UseDone` routes event `0x01C7` to `Handle_Item__UseDone`, which decrements exactly one busy reference and updates the cursor when the count reaches zero. Therefore the invariant is: ```text one accepted Use request that reached the wire owns one busy reference until one authoritative UseDone arrives ``` There is no retail timeout and movement cancellation is not a substitute for `UseDone` after dispatch. ## ACE and protocol cross-reference - ACE `GameActionUseItem` and `Player_Use.TryUseItem` confirm that the Use request owns the server-side operation and that every completed/rejected operation returns `UseDone`. - ACE `Player_Move.CreateMoveToChain` confirms that the server may approach a distant target before executing that operation. - holtburger `inventory/actions.rs` and `inventory/events.rs` independently confirm the one Use action / one UseDone event protocol pair. The connected 2026-07-25 regression trace proved why the old AD-27 Use branch was incorrect: a pending local Ready animation could starve the speculative TurnToObject, so no Use packet ever reached ACE. Retail has no such dependency. `ItemHolder::UseObject` emits `CM_Inventory::Event_UseEvent` first; the event packs and sends opcode `0x36` in `CM_Inventory::Event_UseEvent @ 0x006AC3B0`. Only then does retail call `CPlayerSystem::UsingItem`. ACE owns any required authoritative `CreateMoveToChain`. The Use half of AD-27 is therefore retired; its remaining client completion adaptation applies only to pickup. The immediate-send rerun then proved a distinct presentation-owner defect: ACE sent the correct MoveTo and acdream resolved its target, but a login Ready node remained at the head of the local `MotionInterpreter`. During login hydration the player's logical animation runtime remained current while its spatial projection was temporarily withdrawn. The presenter consumed the zero-tick PartArray motion but its spatial-only guard withheld the `MotionDoneTarget`, so the interpreter never popped that node. The correction binds completion to the exact logical live-record animation owner and drains the pre-controller PartArray queue before publishing a candidate interpreter. The connected distant-NPC gate then turned, walked, sent no duplicate Use, and completed through authoritative UseDone. ## Ported ownership pseudocode ```text BeginAcceptedUse(object): reservation = BusyOwner.Reserve() busyCount += 1 RequestUse(object, reservation) RequestUse(object, reservation): if session is invalid: reservation.CancelBeforeDispatch() return if a non-owned world object is not usable: reservation.CancelBeforeDispatch() return if SendUse(object) succeeds: reservation.MarkDispatched() else: reservation.CancelBeforeDispatch() reservation.MarkDispatched(): // Ownership transfers to the server completion. Keep busyCount unchanged. make later local cancellation inert reservation.CancelBeforeDispatch(): // No packet exists, therefore no UseDone can arrive for this reservation. busyCount -= 1 make duplicate cancellation inert OnUseDone(error): busyCount -= 1 if busyCount == 0: update cursor OnSessionReset(): invalidate the reservation generation busyCount = 0 ``` The reservation is idempotent and generation-bound. A late callback from a retired session cannot decrement a new session's busy reference. ## Regression evidence The 2026-07-25 Alcott trace captured the failure before correction: ```text [input] UseSelected Press [autowalk-pending] count=1 nodes=[0x41000003/ctx=0/jump=0x0] [autowalk-target] object=0x8000D085 status=Ok ... ``` There was no `[B.4b] use`, no `UseDone`, and therefore no possible authoritative release of the busy reference. Focused tests now pin that close and far world Use both dispatch immediately, never install speculative local movement, never duplicate on a later movement callback, and retain busy ownership until exactly one `UseDone`.