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>
This commit is contained in:
parent
ce6fae7b38
commit
89e6b207f8
36 changed files with 1433 additions and 251 deletions
|
|
@ -0,0 +1,154 @@
|
|||
# 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue