Element map for LayoutDesc 0x2100002E (the tabbed Attributes/Skills/Titles window) +
the importer-mount reality + the StringTable verdict + the follow-up list. Grounds the
CharacterStatController work in 0e644b5 and the remaining refinements.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4.8 KiB
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-layoutprints the RAW dat (no inheritance) → the content elements look "missing". That is an artifact. The importer mounts them. Confirmed byFindElementon the studio's imported 0x2100002E:0x10000231name →UiText✓0x10000232heritage →UiText✓0x10000233PK status →UiText✓0x1000023Blevel →UiText✓0x10000235total XP →UiText✓0x10000236XP-to-level meter →UiMeter✓0x1000023Dlist box →UiDatElement(generic container) ✓0x10000238XP-to-level text → NULL (nested variant; not mounted — minor)
UiElement.EventIdis NOT the dat element id (root shows 0x10000001, not 0x10000227). The dat id lives inImportedLayout._byId(set frominfo.Id, LayoutImporter:107). Bind viaFindElement(id), never by readingwidget.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<StringTable>). 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):CharacterStatControllerbinds 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/0x10000237labels + the right-side level area placement.