docs(ui): Fix 4 — dat UIState model has no Group visibility encoding; CharacterStatController hacks are correct
Investigation (2026-06-26): the character footer three sibling groups (0x10000240/241/247)
all carry DefaultState=0x10000011 (StatManagement_Footer_Default) — the dat does NOT
differentiate them by visibility. Parent 0x1000022F has States {Default, Text, Meter}
with PassToChildren=true, but each child group registers all three states with
IncorporationFlags=None and Media=0, so state-propagation produces no visual change
on any group. Retail gmStatManagementUI uses hardcoded element-id dispatch for
0x10000240/241/247 — the groups are never hidden/shown via the dat state mechanism
(decomp gmStatManagementUI::GetFooterTitleLabel @0x004f0170).
Tab-page containers (0x1000022B/22C/10000539) have DefaultState=Undef — no visibility
encoding. Tab visibility is managed purely at runtime by gmTabUI.
IncorporationFlags (DatReaderWriter): {None, PassToChildren, X, Y, Width, Height,
ZLevel} — no Visible flag. The importer cannot set initial Group visibility from
the dat; the controller is the correct and only place. CharacterStatController
HideAllById footer pass and ContainsWidget page-visibility walk are retail-faithful
and must be kept.
No logic changes — comment-only documentation. Build green, 710 App tests green.
Studio screenshots: character footer shows State-A by default with no overlap;
vitals and toolbar unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ed626f4250
commit
1b9dd6c7a8
2 changed files with 38 additions and 0 deletions
|
|
@ -333,6 +333,19 @@ public static class CharacterStatController
|
|||
// the LAST copy of each id; the others live in the VISIBLE Attributes page and must
|
||||
// also be hidden.
|
||||
//
|
||||
// WHY this cannot be done in the importer (dat state-model audit 2026-06-26):
|
||||
// All three group elements have DefaultState = StatManagement_Footer_Default
|
||||
// (0x10000011) — the dat does NOT differentiate them by visibility. The parent
|
||||
// element (0x1000022F) has a States map {Default, Text, Meter} with PassToChildren=
|
||||
// true, but each child group also registers all three states (IncFlags=None,
|
||||
// Media=0) — meaning the state-propagation produces no media change on any group.
|
||||
// Retail's gmStatManagementUI uses hardcoded element-id dispatch
|
||||
// (GetChildRecursive(this, 0x10000240) for Default, 0x10000241 for Text, 0x10000247
|
||||
// for Meter) to access the right group's children at runtime — the groups themselves
|
||||
// are never hidden/shown via the dat state mechanism. The controller is the correct
|
||||
// and only place for this visibility management. See retail decomp
|
||||
// gmStatManagementUI::GetFooterTitleLabel @0x004f0170.
|
||||
//
|
||||
// Strategy: walk the Attributes page (the root child that contains the NameId anchor)
|
||||
// and hide every UiDatElement whose ElementId matches B or C. The page-visibility
|
||||
// pass hides everything in the Skills/Titles pages, so we only need to act on the
|
||||
|
|
@ -368,6 +381,12 @@ public static class CharacterStatController
|
|||
}
|
||||
|
||||
// ── Active-page selection (fixes the dark-overlay) ─────────────────────
|
||||
// WHY this cannot be done in the importer (dat state-model audit 2026-06-26):
|
||||
// The three tab-page content areas (0x1000022B Attributes, 0x1000022C Skills,
|
||||
// 0x10000539 Titles) all have DefaultState = Undef (0) — the dat carries no
|
||||
// visibility encoding for tabs. Tab visibility is managed at runtime by gmTabUI
|
||||
// via SetVisible(bool) on the page containers. The controller is the correct
|
||||
// and only place for initial tab-page selection.
|
||||
if (layout.FindElement(NameId) is { } anchor && layout.Root is { } root)
|
||||
{
|
||||
foreach (var page in root.Children)
|
||||
|
|
|
|||
|
|
@ -186,6 +186,25 @@ public static class LayoutImporter
|
|||
/// Dat shell: load the LayoutDesc, resolve inheritance for every top-level
|
||||
/// element, and build the widget tree. Returns null if the layout is absent
|
||||
/// from the dats.
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Dat UIState visibility model (2026-06-26 audit):</b>
|
||||
/// The dat's <c>ElementDesc.DefaultState</c> field specifies which SPRITE/MEDIA
|
||||
/// state an element starts in (e.g., <c>Normal</c>, <c>Minimized</c>). It does
|
||||
/// NOT encode visibility of sibling Group containers. The <c>StateDesc</c>'s
|
||||
/// <see cref="DatReaderWriter.Enums.IncorporationFlags"/> contains X/Y/Width/Height/
|
||||
/// ZLevel/PassToChildren — there is no Visible flag.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Windows that display multiple sibling Group containers at the same position
|
||||
/// (the character footer's three state-groups; the tab-page content areas) manage
|
||||
/// visibility purely at runtime via C++ controller code. Retail uses
|
||||
/// <c>UIElement::SetState(stateId)</c> on the parent to propagate state, then
|
||||
/// C++ getters access the right sub-group by element id. All groups are shipped
|
||||
/// as visible in the imported widget tree; the relevant controllers
|
||||
/// (<see cref="CharacterStatController"/>) perform the initial show/hide.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="fontResolve">Optional per-element font resolver (see
|
||||
/// <see cref="Build"/> for details). Null = original single-font behavior.</param>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue