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:
parent
3281e0acb4
commit
dc1649c493
19 changed files with 479 additions and 28 deletions
|
|
@ -489,6 +489,7 @@ behavior. Estimated 17–26 days focused work, 3–5 weeks calendar.
|
|||
- **✓ SHIPPED — Wave 4.2 exact shortcut records.** One Core-owned `ShortcutEntry` preserves retail's signed `index_`, unsigned `objectID_`, and raw 32-bit `spellID_` through PlayerDescription parse, the nullable 18-slot session store, drag snapshots/reindexing, WorldSession, and exact 12-byte AddShortcut packing. The visible `gmToolbarUI` remains object-only; `favorite_spells_[8]` remains a separate future spell-bar source. AP-103 retired. Full automated conformance and live mutate→clean-relog persistence gate passed 2026-07-11.
|
||||
- **✓ SHIPPED — Wave 4.3a shortcut-alias boundary.** Drag feedback now preserves retail's neutral/accept/reject distinction. Inventory and paperdoll apply the `(DropItemFlags & 0xE) == 0` physical-item gate, so a toolbar alias can reorder within the toolbar but can never move, unwield, wield, or drop the object it references. Live equipped-helmet gate passed 2026-07-11: inventory drop removed only the shortcut.
|
||||
- **✓ SHIPPED — Wave 4.3b source-aware toolbar transactions.** Pure Core `ShortcutDropPlanner` ports `gmToolbarUI::HandleDropRelease @ 0x004BE7C0`: fresh inventory drops displace cyclically right (with wrap), shortcut aliases restore to their vacated source, duplicate objects are removed before placement, full bars intentionally lose the displaced entry when retail finds no empty slot, and every transaction is planned before local mutation then emitted in exact Remove/Add order. Full-stack rekey planning preserves the raw spell word. AP-102 retired; 4,646 automated tests pass; live inventory-to-occupied displacement and toolbar reorder gate passed 2026-07-11.
|
||||
- **✓ SHIPPED — Wave 4.3c remaining drop branches + stack merge.** `UiButton` now has a reusable retained item-drop-target seam; gmToolbarUI's inventory/backpack button accepts only fresh physical items, draws the exact authored green-arrow overlay `0x060011F7`, and sends them to the player container while shortcut aliases remain neutral. Pure Core `StackMergePlanner` ports `IsMergeAttemptLegal @ 0x00586F30` and `AttemptMerge @ 0x005878F0`; inventory drops try legal same-WCID stack merges before normal insertion, clamp `0x0054` amount to destination capacity, immediately broadcast retail's misleadingly named `FullMergingItem` notice to rekey the toolbar, and select the destination. Existing destination shortcuts are removed before rekey. Warning-free Release build and 4,660 tests pass; live stack-merge gate passed 2026-07-11. Backpack-button drop gate remains pending.
|
||||
- **✓ SHIPPED — Character window** (`LayoutDesc 0x2100002E`, `CharacterStatController`, 2026-06-26, same branch). **Visually user-confirmed 2026-06-26 — Attributes tab reads as retail.** Three tabs, header (name/heritage/PK), large-gold level number (dat font, `largeDatFont` 18px), "Total Experience (XP):" + "XP for next level:" captions, 9-row attribute list (icons + right-aligned values + Health/Stamina/Mana vitals), click-to-select (top/bottom selection bars + footer State-B "{Attr}: {value}" / "Experience To Raise: Infinity!" + affordability-gated raise triangles), centered footer. User noted "still needs some polish for later" — deferred to Issue #158.
|
||||
- **✓ SHIPPED — D.5.4 — Client object/item data model (foundation).** Shipped 2026-06-18 (`b506f53`..`a33e897`, 11 commits). Renamed `ItemRepository`→`ClientObjectTable` / `ItemInstance`→`ClientObject`; broadened the table to hold EVERY server object (retail `weenie_object_table` shape). `CreateObject` is now the canonical merge-upsert (`ClientObjectTable.Ingest`, retail `SetWeenieDesc` semantics) via a new Core.Net `ObjectTableWiring` (off GameWindow); `DeleteObject` evicts; `PlayerDescription` is a membership manifest (`RecordMembership`); live container-membership index (`GetContents`, retail `object_inventory_table`). `_liveEntityInfoByGuid` retired (selection/describe resolve from the one table). Root fix: the old enrich-existing-only `EnrichItem` dropped `CreateObject`s for items with no `PlayerDescription` stub — live-Coldeve 4/6 hotbar slots blank; items are now created, not dropped. **Crux resolved:** retail is TWO tables (`object_table` + `weenie_object_table`), NOT one — acdream's `WorldEntity` (3D system) + `ClientObjectTable` (data/UI) split was already architecturally faithful; the fix was the ingestion path, not a table unification. 2671 tests green.
|
||||
- **Roadmap correction (2026-07-10):** the completion order is now the architecture-first campaign in `docs/superpowers/plans/2026-07-10-retail-ui-fidelity-completion.md`. Retail `gmToolbarUI` is object-only: preserve `ShortCutData.index_`, `objectID_`, and `spellID_`, but do not invent spell glyphs on this bar. `PlayerModule::favorite_spells_[8]` feeds separate spell bars.
|
||||
|
|
|
|||
|
|
@ -401,7 +401,7 @@ All rows are `outbound` direction (GameActions are client→server only).
|
|||
| 0x0048 | outbound | CastUntargetedSpell | `CM_Magic::Event_CastUntargetedSpell` | W | H | B | B+W | CastSpellRequest builder dead [^a-1] |
|
||||
| 0x004A | outbound | CastTargetedSpell | `CM_Magic::Event_CastTargetedSpell` | W | H | B | B+W | CastSpellRequest builder dead [^a-1] |
|
||||
| 0x0053 | outbound | ChangeCombatMode | `CM_Combat::Event_ChangeCombatMode` | W | H | W | B+W | Wired in WorldSession.SendChangeCombatMode |
|
||||
| 0x0054 | outbound | StackableMerge | `CM_Inventory::Event_StackableMerge` | W | H | B | B+W | InventoryActions builder dead [^a-1] |
|
||||
| 0x0054 | outbound | StackableMerge | `CM_Inventory::Event_StackableMerge` | W | H | W | B+W | Wired through `StackMergePlanner` → inventory drop → `WorldSession.SendStackableMerge` |
|
||||
| 0x0055 | outbound | StackableSplitToContainer | `CM_Inventory::Event_StackableSplitToContainer` | W | H | B | B+W | InventoryActions builder dead [^a-1] |
|
||||
| 0x0056 | outbound | StackableSplitTo3D | `CM_Inventory::Event_StackableSplitTo3D` | – | H | B | B+W | InventoryActions builder dead [^a-1] |
|
||||
| 0x0058 | outbound | ModifyCharacterSquelch | `CM_Communication::Event_ModifyCharacterSquelch` | – | H | – | B | Mute one player |
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ where I cite holtburger it is `inventory/actions.rs` or `inventory/events.rs`
|
|||
| `0x001B` | DropItem | C→S | drop an item on the ground | `GameActionDropItem.Handle` | `u32 itemGuid` (holtburger `actions.rs:140`) | **MISSING** (no builder; acdream reuses 0x0019 for moves only) |
|
||||
| `0x0035` | UseWithTarget | C→S | use src item on target (key→door) | (Interact) | `u32 sourceGuid, u32 targetGuid` | **parsed** — `InteractRequests.BuildUseWithTarget` |
|
||||
| `0x0036` | UseItem | C→S | use/equip-by-doubleclick a single item | `GameActionUseItem` | `u32 targetGuid` | **parsed** — `InteractRequests.BuildUse` |
|
||||
| `0x0054` | StackableMerge | C→S | drop stack A onto compatible stack B | `GameActionStackableMerge.Handle` | `u32 mergeFromGuid, u32 mergeToGuid, i32 amount` | **parsed** — `InventoryActions.BuildStackableMerge` |
|
||||
| `0x0054` | StackableMerge | C→S | drop stack A onto compatible stack B | `GameActionStackableMerge.Handle` | `u32 mergeFromGuid, u32 mergeToGuid, i32 amount` | **wired** — `StackMergePlanner` → `InventoryController` → `WorldSession.SendStackableMerge` |
|
||||
| `0x0055` | StackableSplitToContainer | C→S | split N off a stack into a pack slot | `GameActionStackableSplitToContainer.Handle` | `u32 stackGuid, u32 containerGuid, i32 place, i32 amount` | **parsed** — `InventoryActions.BuildStackableSplitToContainer` |
|
||||
| `0x0056` | StackableSplitTo3D | C→S | split N off a stack onto the ground | `GameActionStackableSplitTo3D.Handle` | `u32 stackGuid, i32 amount` | **parsed** — `InventoryActions.BuildStackableSplitTo3D` |
|
||||
| `0x019B` | StackableSplitToWield | C→S | split N off a stack into an equip slot (e.g. arrows) | `GameActionStackableSplitToWield` | `u32 stackGuid, u32 equipMask, i32 amount` | **parsed** — `InventoryActions.BuildStackableSplitToWield` |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -471,7 +471,7 @@ same slot after a clean reconnect.
|
|||
|
||||
### 4.3 Port every drag/drop branch
|
||||
|
||||
- [ ] Add a pure `ShortcutDropPlanner` keyed by source kind: toolbar reorder,
|
||||
- [x] Add a pure `ShortcutDropPlanner` keyed by source kind: toolbar reorder,
|
||||
inventory fresh add, occupied target, inventory-button target,
|
||||
off-bar removal, and full-stack merge replacement.
|
||||
- [x] For an inventory-to-occupied-slot drop, place the displaced shortcut at
|
||||
|
|
@ -479,7 +479,7 @@ same slot after a clean reconnect.
|
|||
- [x] Validate the complete mutation before changing state, apply it atomically, and
|
||||
emit exact Remove/Add wire ordering. Use pending/confirm/rollback only where the
|
||||
protocol actually exposes rejection; never leave a partially mutated bar.
|
||||
- [ ] On full-stack merge, rekey every matching object shortcut and preserve the
|
||||
- [x] On full-stack merge, rekey the matching object shortcut and preserve the
|
||||
non-object fields of its raw `ShortCutData` entry.
|
||||
|
||||
**Wave 4.3 partial implementation (2026-07-11):** drag-over feedback is now an
|
||||
|
|
@ -499,9 +499,18 @@ records, exact wire order, and raw full-stack rekeying have conformance fixtures
|
|||
`ToolbarController` applies the validated local transaction atomically, then emits
|
||||
its ordered Remove/Add events. AP-102 is retired; 4,646 tests pass. The live
|
||||
inventory-to-occupied displacement plus toolbar-reorder gate passed 2026-07-11.
|
||||
Inventory-button placement and
|
||||
the upstream full-stack-merge notice owner remain part of the unchecked umbrella
|
||||
items above rather than being implied complete by this slice.
|
||||
Wave 4.3c completes the umbrella: retained buttons can own item-drop callbacks;
|
||||
the toolbar inventory button uses retail's fresh-item gate and exact green-arrow
|
||||
overlay, while aliases stay neutral. Inventory now tries the pure named-retail
|
||||
stack-merge legality/amount planner before ordinary insertion, sends `0x0054`,
|
||||
broadcasts `FullMergingItem` immediately from the attempt owner, rekeys the toolbar
|
||||
with existing-destination removal, and selects the target. The notice is not a
|
||||
server confirmation despite its name. AP-101 still covers the absent shared split
|
||||
entry/slider; until it lands, merge requests use the whole source stack before the
|
||||
retail destination-capacity clamp. Release build is warning-free and 4,660 tests
|
||||
pass. The compatible-stack live gate passed 2026-07-11; it exposed the expected
|
||||
Wave 4.4/AP-101 gap that the selected strip still displays one instead of the
|
||||
stack count and has no split slider. The inventory-button live gate remains pending.
|
||||
|
||||
### 4.4 Finish toolbar controls
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue