acdream/docs/research/2026-07-17-retail-shared-main-panel-pseudocode.md
Erik 3f3cfdac30 fix(ui): share primary panel placement
Port gmPanelUI's persistent parent placement semantics across Inventory, Character, and Magic while preserving each imported child's content and resize policy. Synchronize typed retained-window handles so drag, switching, close/reopen, and layout persistence all observe one canonical location; keep effect panels independent.

User-verified in normal Release. Release build and all 5,770 tests passed with five intentional skips.

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-17 09:13:31 +02:00

3.8 KiB

Retail shared main-panel placement — pseudocode

Date: 2026-07-17 Symptom: moving Inventory and then opening Character/Skills or Magic opens the new panel at its own stale saved position instead of the position just used.

Retail oracle

Named Sept-2013 retail symbols:

  • gmPanelUI::SetupChildren @ 0x004BC9E0
  • gmPanelUI::RecvNotice_SetPanelVisibility @ 0x004BC6F0
  • gmPanelUI::PostInit @ 0x004BD010
  • gmPanelUI::ResizeTo @ 0x004BC6E0

Verbatim acclient.h ownership:

gmPanelUI : UIElement_Field
    SmartArray<PanelChildInfo> m_childrenInfoArray
    UIElement* m_currentlyShownPanel
    UIElement* m_previouslyShownPanel
    UIElement* m_pPanelPagesFrame
    bool m_bStretchMode

PanelChildInfo
    UIElement* child
    uint panelID

SetupChildren resolves each known panel element recursively, stores the child pointer together with its property-0x10000029 panel ID, and initially hides every child. It does not create independent top-level windows.

Retail behavior

SetPanelVisibility(panelId, show):
    requested = find m_childrenInfoArray entry by panelId
    if requested is missing:
        return

    if show:
        if requested.child == m_currentlyShownPanel:
            requested.child.SetVisible(true)
            parent.SetVisible(true)
            return

        previous = m_currentlyShownPanel
        m_currentlyShownPanel = requested.child

        if previous is visible:
            if requested restores previous and previous does not:
                m_previouslyShownPanel = previous
            else:
                m_previouslyShownPanel = null
            send SetPanelVisibility(previous.panelID, false)

        requested.child.SetVisible(true)
        parent.SetVisible(true)
        return

    if requested.child == m_currentlyShownPanel:
        requested.child.SetVisible(false)
        if m_previouslyShownPanel exists:
            send SetPanelVisibility(previous.panelID, true)
            m_previouslyShownPanel = null
        else:
            m_currentlyShownPanel = null
            parent.SetVisible(false)
        return

    requested.child.SetVisible(false)

The decisive placement fact is structural: Inventory, Character/Skills, and Magic are children of one gmPanelUI. The switch changes child visibility; there is no child move and no per-panel top-level position. Dragging the parent therefore moves the one position subsequently used by every child.

acdream port shape

acdream imports these child LayoutDesc roots independently, so each currently has its own retained wrapper. RetailPanelUiController already ports retail's one-active-child and restore-previous lifecycle. Complete that logical-parent ownership by adding one canonical placement for the primary toolbar children (Inventory 7, Character 11, Magic 13):

on primary child frame moved:
    canonicalPosition = moved frame position
    move every other primary child frame to canonicalPosition

before showing a primary child:
    if canonicalPosition exists:
        move requested child frame to canonicalPosition
    else:
        canonicalPosition = requested child frame position

before hiding the active primary child:
    capture its current position as canonicalPosition

The controller owns this synchronization and subscribes to typed RetailWindowHandle.Moved events. Toolbar commands, keyboard commands, restore-previous effects, persistence restore, and direct window dragging all therefore pass through the same owner. Panel-authored size/resize policy stays with the active child; only the retail parent placement is shared.

ACE contains no client UI implementation, and the in-tree WorldBuilder reference contains no gmPanelUI port, so neither supplies a competing behavioral interpretation. The named retail client and verbatim header are the oracle for this UI ownership rule.