# Character window — faithful element spec (LayoutDesc 0x2100002E) Decomp-grounded blueprint for the Character window, produced to REDO the guessed pilot. Sources: `docs/research/named-retail/acclient_2013_pseudo_c.txt` (PDB-named decomp), the user UI dump `docs/research/2026-06-25-retail-ui-layout-dump.json`, and acdream's own importer-resolved tree (dumped live in the studio). **No guessing** — every row cites a decomp address or a confirmed `FindElement` result. ## What 0x2100002E actually is A **tabbed Attributes / Skills / Titles window** (300×600, root panel `0x10000227`, Type 8). It is NOT a text report. Three tab-content slots each mount a sub-layout via `BaseElement`+`BaseLayoutId` inheritance (acdream's `ShouldMountBaseChildren` path): | Slot | BaseElement | BaseLayoutId | gm*UI | Register | |---|---|---|---|---| | `0x1000022B` (Attributes) | `0x10000225` | `0x2100002C` | gmAttributeUI (root type 0x1000002A) | 0x0049db50 | | `0x1000022C` (Skills) | `0x1000022E` | `0x2100002D` | gmSkillUI | 0x0049adb0 | | `0x10000539` (Titles) | `0x1000052D` | `0x2100005E` | gmCharacterTitleUI | 0x0049aba0 | Both Attributes + Skills tabs share a header strip filled by the base class **`gmStatManagementUI`** (`UpdateCharacterInfo` 0x004f0770, `UpdateExperience` 0x004f0a70, `UpdatePKStatus` 0x004f00a0). The tab sub-layout `0x2100002C` nests `0x10000226` which chains again into `0x21000045` — the real header/list content is **two inheritance levels deep**. The **text report** ("birth/age/deaths, innate attributes, augmentations, load") the earlier pilot guessed is a SEPARATE sub-panel: LayoutDesc `0x2100001A`, `gmCharacterInfoUI` (register 0x004b8c90), whose `m_pMainText` (`0x1000011d`) is created at RUNTIME. That window is handled by `CharacterController` (create-the-element path) — different layout, not 0x2100002E. ## Importer reality (dumped live, 2026-06-25) - `dump-vitals-layout` prints the RAW dat (no inheritance) → the content elements look "missing". That is an artifact. The **importer** mounts them. Confirmed by `FindElement` on the studio's imported 0x2100002E: - `0x10000231` name → `UiText` ✓ - `0x10000232` heritage → `UiText` ✓ - `0x10000233` PK status → `UiText` ✓ - `0x1000023B` level → `UiText` ✓ - `0x10000235` total XP → `UiText` ✓ - `0x10000236` XP-to-level meter → `UiMeter` ✓ - `0x1000023D` list box → `UiDatElement` (generic container) ✓ - `0x10000238` XP-to-level text → **NULL** (nested variant; not mounted — minor) - `UiElement.EventId` is NOT the dat element id (root shows 0x10000001, not 0x10000227). The dat id lives in `ImportedLayout._byId` (set from `info.Id`, LayoutImporter:107). Bind via `FindElement(id)`, never by reading `widget.EventId`. ## Header element map (gmStatManagementUI) | Id | Field | Content source | Rect (in sub-layout) | |---|---|---|---| | `0x10000231` | m_pNameText | player full name | 0,25,230,20 | | `0x10000232` | m_pHeritageText | gender+heritage display (InqGenderHeritageDisplay) | 0,45,230,15 | | `0x10000233` | m_pPKStatusText | PK status string (StringTable) | 0,60,230,15 | | `0x1000023B` | m_pLevelText | PropertyInt 0x19 (level) | 235,60,65,50 | | `0x10000234` | (label) | "Total Experience:" | 0,95,130,18 | | `0x10000235` | m_pTotalXPText | PropertyInt64 1 | 130,95,100,18 | | `0x10000236` | m_pXPToLevelMeter | (cur−base)/(cap−base), img 0x060011A6 | 0,113,230,17 | | `0x10000237` | (label) | "To next level:" | 0,113,130,17 | | `0x10000238` | m_pXPToLevelText | XP remaining | 130,113,100,17 | | `0x1000023D` | m_pListBox | attribute/skill rows (runtime) | 0,137,300,398 | Attribute list rows: gmAttributeUI builds `AttributeInfoRegion` rows (name + base/buffed value + raise button). Canonical AC display order: Strength, Endurance, Coordination, Quickness, Focus, Self. ## StringTable `DatReaderWriter` CAN read `StringTable` (CLI uses `GetAllIdsOfType`). The decomp's `StringInfo::SetStringIDandTableEnum(…, stringId, 0x10000001)` references table enum 0x10000001 → the game string table dat (0x31000001 family); StringId=0 is a placeholder overwritten by a preceding `compute_str_hash("ID_…")`. To reproduce exact wording: read the StringTable + a StringInfo helper (hash → id → lookup, `AddVariable_*` substitution). NOT yet ported — current controller uses canonical AC labels. ## Status (this session) - **DONE (commit 0e644b5):** `CharacterStatController` binds name/heritage/PK/level/total-XP + the XP meter fill + the six attributes into the real elements. Studio renders the real panel. - **Follow-ups (known sources):** (1) tab-button active/inactive STATE so the 3 tabs draw their sprites (0x06005D92-97); (2) StringTable wording; (3) full AttributeInfoRegion row template (column-aligned values + per-attribute raise buttons); (4) wire `0x10000234`/`0x10000237` labels + the right-side level area placement.