acdream/docs/research/2026-07-26-slice-j4-5-retail-local-command-order.md
Erik 89e6b207f8 refactor(runtime): close canonical gameplay ownership
Unify the toolbar shortcut manager with Runtime inventory state, route retail-ordered shortcut and spellbook command effects through the canonical owners, and make retained controllers borrow those exact instances. Remove the item-interaction transaction fallback and add graphical/no-window parity plus failure-safe terminal ownership-ledger coverage.

Co-authored-by: Codex <codex@openai.com>
2026-07-26 09:48:51 +02:00

154 lines
4.1 KiB
Markdown

# Slice J4.5 — retail local gameplay-command ordering
## Scope
This note fixes the command/local-state ordering used while J4.5 removes the
last graphical-only shortcut and spellbook mirrors. It does not add a new
gameplay algorithm. It preserves the existing packet builders and moves the
retail `PlayerModule` mutation onto the one Runtime-owned state object.
Oracle:
- `docs/research/named-retail/acclient_2013_pseudo_c.txt`
- `docs/research/named-retail/acclient.h`
The existing wire ports remain:
- `AcDream.Core.Net.Messages.ClientCommandRequests`
- `AcDream.Core.Net.WorldSession`
- `AcDream.App.Net.LiveSessionCommandRouter`
## Retail pseudocode
### Toolbar shortcut add
Retail anchors:
- `gmToolbarUI::AddShortcut @ 0x004BD9A0`
- `PlayerModule::AddShortCut @ 0x005D29A0`
- `ShortCutManager::AddShortCut @ 0x005D5790`
```text
if slot is valid and the toolbar accepts the item:
create ShortCutData(slot, objectId, spellId)
send Event_AddShortCut(data)
playerModule.AddShortCut(data)
ShortCutManager.AddShortCut(data):
if data.index is outside [0, 18):
return false
if the slot already owns a record:
replace all three record fields in place
else:
allocate and install one record
return true
```
### Toolbar shortcut remove
Retail anchors:
- `gmToolbarUI::RemoveShortcut @ 0x004BD450`
- `PlayerModule::RemoveShortCut @ 0x005D29E0`
- `ShortCutManager::RemoveShortCut @ 0x005D5690`
```text
find the slot containing the selected shortcut
flush the visible slot
if this is a persisted removal:
send Event_RemoveShortCut(slot)
playerModule.RemoveShortCut(slot)
ShortCutManager.RemoveShortCut(slot):
if slot is outside [0, 18):
return
destroy the record if present
set the slot to empty
```
### Favorite spell add/remove
Retail anchors:
- spell-menu handler at `0x004C64C0`
- `PlayerModule::AddSpellFavorite @ 0x005D43E0`
- `PlayerModule::RemoveSpellFavorite @ 0x005D4910`
```text
add:
if tab is in [0, 8):
insert spellId at position in that tab
send Event_AddSpellFavorite(spellId, position, tab)
remove:
if tab is in [0, 8):
remove spellId from that tab
send Event_RemoveSpellFavorite(spellId, tab)
```
Unlike toolbar shortcuts, favorite-spell state changes locally before the
outbound event.
### Spellbook filter
Retail anchors:
- `gmSpellbookUI::UpdateFilter @ 0x0048B5E0`
```text
derive the changed school/level bit
derive the next filter bitfield from the button state
if next differs from current:
playerModule.SetSpellbookFilter(next)
send Event_SpellbookFilterEvent(next)
rebuild the visible spell list
scroll to row zero
```
### Desired component level
Retail anchors:
- `gmSpellComponentUI::ListenToElementMessage @ 0x0048A420`
- `PlayerModule::SetDesiredCompLevel @ 0x005D4940`
```text
parse the edited amount
if amount is in [0, 5000]:
send Event_SetDesiredComponentLevel(componentId, amount)
playerModule.SetDesiredCompLevel(componentId, amount)
else:
restore the current value in the editor
PlayerModule.SetDesiredCompLevel(componentId, amount):
if amount is outside [0, 5000]:
return false
lazily create the desired-component table
replace an existing value or add a new value
return true
```
Retail retains an explicit zero-valued entry. J4.5 preserves that distinction;
only the clear-list command destroys the complete desired-component table.
### Clear desired components
Retail anchors:
- chat-command handler at `0x0056FE4A`
- `PlayerModule::ClearDesiredCompList @ 0x005D2A00`
```text
send Event_SetDesiredComponentLevel(INVALID_DID, -1)
destroy the complete desired-component table
set the table pointer to null
publish the spell-component UI update notice
```
## J4.5 integration consequence
The graphical toolbar, retained spell UI, plugins, and future no-window host
must all mutate the same Runtime-owned `ShortcutStore` and `Spellbook`.
Graphical controllers may initiate the operation, but they may not retain or
reconstruct another mutable slot/favorite/component collection. Commands stay
synchronous; no additional frame or background queue is introduced.