diff --git a/src/AcDream.App/UI/Layout/CharacterStatController.cs b/src/AcDream.App/UI/Layout/CharacterStatController.cs
index 7b7e6a6f..282d94d6 100644
--- a/src/AcDream.App/UI/Layout/CharacterStatController.cs
+++ b/src/AcDream.App/UI/Layout/CharacterStatController.cs
@@ -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)
diff --git a/src/AcDream.App/UI/Layout/LayoutImporter.cs b/src/AcDream.App/UI/Layout/LayoutImporter.cs
index 49130206..24fb30f0 100644
--- a/src/AcDream.App/UI/Layout/LayoutImporter.cs
+++ b/src/AcDream.App/UI/Layout/LayoutImporter.cs
@@ -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.
+ ///
+ ///
+ /// Dat UIState visibility model (2026-06-26 audit):
+ /// The dat's ElementDesc.DefaultState field specifies which SPRITE/MEDIA
+ /// state an element starts in (e.g., Normal, Minimized). It does
+ /// NOT encode visibility of sibling Group containers. The StateDesc's
+ /// contains X/Y/Width/Height/
+ /// ZLevel/PassToChildren — there is no Visible flag.
+ ///
+ ///
+ ///
+ /// 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
+ /// UIElement::SetState(stateId) 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
+ /// () perform the initial show/hide.
+ ///
///
/// Optional per-element font resolver (see
/// for details). Null = original single-font behavior.