fix(items): finish corpse pickup and combat switching

Port retail's first-slot ShowPendingInPlayer path for double-click loot and carry the current owned-container destination through deferred pickup. Retire the previous ground-container view as soon as a replacement is requested so its range close cannot cancel ACE's active MoveTo chain.

Preserve active-combat weapon intent across ACE's authoritative wand-to-missile stance tail while leaving peace-mode switches unchanged.

Release build succeeds and all 5,885 tests pass with five intentional skips.

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-17 18:35:29 +02:00
parent d51a0fc825
commit 0392c6d721
17 changed files with 631 additions and 81 deletions

View file

@ -111,6 +111,17 @@ on server-confirmed WieldObject(item):
and pending.blockingItem is none:
clear pending
if the transaction began in an active combat mode:
// ACE compatibility boundary; see below.
retain the ready mode implied by the confirmed equip location
on combat-mode notices after confirmed WieldObject:
if the first notice is the retained ready mode:
clear the retained mode; the server already settled correctly
else if notices form NonCombat -> retained ready mode -> NonCombat:
clear the retained mode
request that ready mode from ACE's trailing NonCombat notice
on PrivateUpdatePropertyInt(property = CombatMode, value):
if value is exactly NonCombat, Melee, Missile, or Magic:
CombatState.SetCombatMode(value)
@ -119,11 +130,28 @@ on PrivateUpdatePropertyInt(property = CombatMode, value):
## Animation ownership
The client must not synthesize a local bow-to-sword animation. In peace mode,
ACE leaves `CombatMode` as `NonCombat`. In combat mode, ACE selects the new
weapon's stance and sends the authoritative motion stream plus PropertyInt 40.
The inventory controller only sequences the two wire requests and waits for
the old weapon's confirmed move; the existing motion and equipped-child
render paths consume the server events.
ACE leaves `CombatMode` as `NonCombat`. The inventory controller sequences the
two wire requests and waits for the old weapon's confirmed move; the existing
motion and equipped-child render paths consume the server events.
### ACE active-combat compatibility boundary
A connected 2026-07-17 trace of Magic wand -> missile weapon showed ACE emit
`Magic -> Melee -> NonCombat -> Missile -> NonCombat`. This is a consequence of
ACE `Player_Inventory.TryShuffleStance` plus its queued dequip callbacks: the
server does select Missile for the new weapon, but a later queued callback
publishes NonCombat after the authoritative `WieldObject`. Retail's client does
not need a second request against the retail server.
acdream therefore preserves the user's desired ready mode only when AutoWield
begins in active combat. Authoritative `WieldObject` arms settlement. A server
whose first subsequent notice is already the desired mode needs no request.
Local ACE instead emits `NonCombat -> desired mode -> NonCombat`; the client
sends one normal `ChangeCombatMode` request from that trailing notice,
necessarily after the queued callback that caused it. All resulting state and animation remain server-
authoritative. No update is ignored, delayed, or locally fabricated, and a
transaction begun in peace sends no mode request. This narrow server-
compatibility adaptation is recorded as AP-118.
The request is not complete when `GetAndWieldItem` is merely sent. Retail
`UIAttemptWield` records `prevRequest = IR_WIELD` and leaves the item waiting