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

113 lines
3.8 KiB
Markdown

# 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:
```text
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
```text
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`):
```text
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.