feat(ui): complete retail item drop branches

Give retained buttons a reusable item-drop seam, wire the toolbar backpack target, and port retail stack-merge legality, capacity clamping, wire dispatch, destination selection, and immediate shortcut rekey notice. Record the live ACE merge gate and keep split quantity under AP-101.

Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-11 09:46:32 +02:00
parent 3281e0acb4
commit dc1649c493
19 changed files with 479 additions and 28 deletions

View file

@ -422,7 +422,7 @@ an occupied slot sends the displaced item to the first empty slot cyclically to
the right of the target. Only a dragged shortcut alias attempts to restore the
displaced item to `m_lastShortcutNumDragged`.
### 4.6 Full-stack merge rekey
### 4.6 Stack-merge attempt rekey
```text
RecvNotice_FullMergingItem(oldId, newId): // 0x004BE9B0
@ -435,6 +435,39 @@ This body updates the first matching shortcut returned by `RemoveShortcut`.
Whether duplicate object shortcuts can exist through external player-description
data is unresolved; `CreateShortcutToItem` normally prevents duplicates.
The notice name is misleading: `ItemHolder::AttemptMerge @ 0x005878F0` broadcasts
`SendNotice_FullMergingItem(oldId, newId)` immediately after `UIAttemptMerge`, for
every legal merge request. It is not an ACE confirmation and is not conditional on
the source stack being exhausted. `CreateShortcutToItem` also removes any existing
`newId` shortcut before adding it at the old source shortcut's slot.
```text
IsMergeAttemptLegal(sourceId, targetId, quiet): // 0x00586F30
if player not ready for inventory request: return false
if sourceId == targetId: return false
source = lookup sourceId; target = lookup targetId
if either missing: return false
if source.maxStackSize <= 1 or target.maxStackSize <= 1: return false
if source.tradeState == 1 or target.tradeState == 1: return false
if source.wcid != target.wcid: return false
return target.stackSize < target.maxStackSize
AttemptMerge(sourceId, targetId, quiet): // 0x005878F0
if not IsMergeAttemptLegal(...): return false
requested = sourceId == selectedId ? splitSize : max(1, source.stackSize)
available = target.maxStackSize - max(1, target.stackSize)
amount = min(requested, available)
source.UIAttemptMerge(targetId, amount) // sends 0x0054
SendNotice_FullMergingItem(sourceId, targetId) // immediate local notice
Select(targetId)
return true
```
acdream's selected-object stack entry/slider is still absent under AP-101, so the
current inventory merge path requests the whole source stack and lets the same
retail capacity clamp limit the transfer. Once the shared split-quantity owner
lands, it supplies `requested` without changing legality or wire ordering.
---
## 5. Selected-object health, mana, and stack behavior