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

4.1 KiB

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
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
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
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
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
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
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.