acdream/docs/research/2026-07-17-retail-shared-main-panel-pseudocode.md
Erik d1d603105f fix(ui): complete retail indicator detail panels
Port the authored effect row template, remaining-time and selection details, synchronize the full gmPanelUI child geometry, and route the burden indicator to Character Information panel 3.

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-17 11:36:43 +02:00

5.6 KiB

Retail shared main-panel geometry — 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 geometry fact is structural: Inventory, Character Information, Character/Skills, Magic, Effects, and the other detail pages are children of one gmPanelUI. The switch changes child visibility; there is no child move, resize, or per-panel top-level rectangle. Moving or resizing the parent therefore changes the one rectangle subsequently used by every child.

Resize follow-up

The production end-of-retail LayoutDesc supplies the missing geometry oracle:

LayoutDesc 0x2100006E
    gmPanelUI host 0x100005FE            310 x 372
    content parent 0x10000180            300 x 362, anchored on all edges
    Character Information 0x10000183     panel 3,  300 x 362
    Helpful Effects 0x10000184           panel 4,  300 x 362
    Harmful Effects 0x10000185           panel 5,  300 x 362
    Inventory child 0x1000018B           panel 7,  300 x 362
    Character/Skills child 0x1000018E    panel 11, 300 x 362
    Magic child 0x10000190               panel 13, 300 x 362

    top-center 0x1000065C         Type 2 Dragbar, cursor 0x06006119
    bottom-center 0x10000660      Type 9 Resizebar, cursor 0x06005E66

Every primary child therefore participates in the shared host's vertical resize; Magic is not a fixed-height exception. Named gmPanelUI::ResizeTo @ 0x004BC6E0 delegates directly to UIElement::ResizeTo @ 0x00463C30, whose only size limits are effective DAT properties 0x3C..0x3F. There is no retail 760-pixel Character limit and no maximum captured from the host's original screen position.

acdream retains independent wrappers under IA-12, so the faithful adaptation is to give each registered child wrapper the same bottom-only vertical resize policy, leave its maximum unbounded by an authored substitute, and calculate the available desktop extent from its current position during the resize. The viewport remains the physical limit; an old static maximum must not become a smaller limit after the shared panel moves.

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 rectangle for all registered main-panel children, including Character Information 3, Helpful/Harmful 4/5, Inventory 7, Character/Skills 11, and Magic 13:

on registered child frame moved or resized:
    canonicalGeometry = frame left, top, width, height
    apply canonicalGeometry to every hidden sibling

before showing a registered child:
    if canonicalGeometry exists:
        apply canonicalGeometry to requested child frame
    else:
        canonicalGeometry = requested child frame geometry

before hiding the active child:
    capture its current geometry as canonicalGeometry

The controller owns this synchronization and subscribes to typed RetailWindowHandle.Moved and Resized events. Toolbar, indicator, and keyboard commands, restore-previous effects, persistence restore, and direct window dragging all therefore pass through the same owner. Each wrapper retains its own imported content tree, but its outer rectangle and bottom-only resize contract mirror the shared retail host.

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.