ToAnchors was inverted vs retail UIElement::UpdateForParentSizeChange @0x00462640: stretch is RightEdge==1 (not ==2/==4), LeftEdge==2 = track-right. Verified against all 19 vitals fixture pieces. Enables Resizable/ResizeX on the importer vitals root (the prior 'dat is fixed-size' conclusion was wrong). At-rest render unchanged (anchors only fire on resize). Added a 160->200 resize conformance test. Also fixed DatWidgetFactoryTests.RectAndAnchors_SetFromElementInfo which encoded the old inverted model (Right=2 expecting Right anchor; corrected to Right=1). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
29 KiB
LayoutDesc Format Enumeration Reference
Date: 2026-06-15
Author: Task 1 of the LayoutDesc Importer plan (docs/superpowers/plans/2026-06-15-layoutdesc-importer.md)
Sources:
- Dat dumps:
dump-vitals-layouton0x2100006C,0x21000014,0x21000075,0x2100003F - Retail decomp:
docs/research/named-retail/acclient_2013_pseudo_c.txt(Sept 2013 EoR PDB) - DatReaderWriter 2.1.7 reflection probe (deleted after use)
This doc is the ground-truth API table for Tasks 2–6. Where it corrects a plan assumption, the correction is called out in § Corrections to plan assumptions at the end.
1. ElementDesc — exact API
All members are public fields (not properties), except ElementId, Type, BaseElement, BaseLayoutId, DefaultState, ReadOrder which are also fields. There are no ElementDesc properties used by the importer.
| Member | Kind | Type | Notes |
|---|---|---|---|
ElementId |
field | uint |
unique element id (e.g. 0x100000E6) |
Type |
field | uint |
element class id — not an enum in DRW; raw uint |
BaseElement |
field | uint |
base element id in base layout (0 = no base) |
BaseLayoutId |
field | uint |
layout id where base element lives (0 = no base) |
DefaultState |
field | UIStateId (enum) |
the element's initial active state |
ReadOrder |
field | uint |
draw order within parent |
X |
field | uint |
left position within parent, in pixels |
Y |
field | uint |
top position within parent, in pixels |
Width |
field | uint |
pixel width |
Height |
field | uint |
pixel height |
ZLevel |
field | uint |
z-order (0 in all vitals elements) |
LeftEdge |
field | uint |
left anchor flag (see §4) |
TopEdge |
field | uint |
top anchor flag (see §4) |
RightEdge |
field | uint |
right anchor flag (see §4) |
BottomEdge |
field | uint |
bottom anchor flag (see §4) |
StateDesc |
field | StateDesc? |
the element's "DirectState" (no name); null if absent |
States |
field | Dictionary<UIStateId, StateDesc> |
named states (e.g. HideDetail, ShowDetail) |
Children |
field | Dictionary<uint, ElementDesc> |
child elements keyed by their ElementId |
Important: X, Y, Width, Height, LeftEdge, etc. are all uint, not int or float. Cast to float/int when constructing ElementInfo.
The dump tool iterates both properties and fields; the scalars (X, Y, etc.) are found as fields.
2. StateDesc — exact API
| Member | Kind | Type | Notes |
|---|---|---|---|
StateId |
field | uint |
redundant with the dict key |
PassToChildren |
field | bool |
|
IncorporationFlags |
field | IncorporationFlags |
|
Properties |
field | Dictionary<uint, BaseProperty> |
keyed by property-id (uint); see §3 |
Media |
field | List<MediaDesc> |
polymorphic list of media items |
States dictionary key type
ElementDesc.States is Dictionary<UIStateId, StateDesc>. The dump shows string names like "HideDetail" and "ShowDetail" because the dump tool calls .Key.ToString() on the UIStateId enum values. The actual key is a UIStateId enum:
// Key: UIStateId.HideDetail = 268435462 (0x10000006)
// Key: UIStateId.ShowDetail = 268435463 (0x10000007)
See §6 for the full UIStateId enum.
Iterating in code:
foreach (var s in d.States)
ReadState(s.Value, s.Key.ToString(), info); // s.Key is UIStateId; .ToString() gives "HideDetail" etc.
3. Properties (StateDesc.Properties) — how font DID and fill are stored
StateDesc.Properties is Dictionary<uint, BaseProperty>. The BaseProperty base class has:
BasePropertyType PropertyType(enum)uint MasterPropertyIdbool ShouldPackMasterPropertyId
Concrete subclasses (DatReaderWriter.Types.*):
| Subclass | Field | Type | Notes |
|---|---|---|---|
BoolBaseProperty |
Value |
bool |
|
IntegerBaseProperty |
Value |
int |
|
FloatBaseProperty |
Value |
float |
|
EnumBaseProperty |
Value |
uint |
|
DataIdBaseProperty |
Value |
uint |
a dat object DID |
ArrayBaseProperty |
Value |
List<BaseProperty> |
array of sub-properties |
ColorBaseProperty |
Value |
ColorARGB |
struct { byte Blue, Green, Red, Alpha } |
StringInfoBaseProperty |
Value |
StringInfo |
|
VectorBaseProperty |
Value |
Vector3 |
|
Bitfield32BaseProperty |
Value |
uint |
|
Bitfield64BaseProperty |
Value |
ulong |
|
InstanceIdBaseProperty |
Value |
uint |
|
StructBaseProperty |
Value |
Dictionary<uint, BaseProperty> |
Property key meanings (confirmed from decomp + dat inspection)
| Key | Type found in dat | Meaning | Decomp ref |
|---|---|---|---|
0x1A |
ArrayBaseProperty (contains DataIdBaseProperty) |
Font DID — array with one item; the inner DataIdBaseProperty.Value is the font dat object id |
UIElement_Text::SetFontDIDHelper(this, 0x1a, ...) @0x46829e |
0x1B |
ArrayBaseProperty (contains ColorBaseProperty) |
Font color — array with one item; ColorARGB {R,G,B,A} |
UIElement_Text::SetFontColorHelper(this, 0x1b, ...) @0x4682c2 |
0x14 |
EnumBaseProperty |
Horizontal justification | UIElement_Text::SetHorizontalJustification @0x467200 |
0x15 |
EnumBaseProperty |
Vertical justification | UIElement_Text::SetVerticalJustification @0x467230 |
0x1C / 0x1D |
ArrayBaseProperty |
Tag font color / tag font | (secondary font style for in-text tags) |
0x16 |
BoolBaseProperty |
Some text flag | |
0x21 |
BoolBaseProperty |
One-line mode | |
0x23 |
IntegerBaseProperty |
Left margin | |
0x24 |
IntegerBaseProperty |
Top margin | |
0x25 |
IntegerBaseProperty |
Right margin | |
0x26 |
IntegerBaseProperty |
Bottom margin | |
0x27 |
BoolBaseProperty |
Some text option | |
0x20 |
BoolBaseProperty |
Some text option | |
0x69 |
— (NOT in dat) | Fill percent — set at runtime via UIElement::SetAttribute_Float(meter, 0x69, fillRatio) |
gmVitalsUI::Update @0x4bff2a |
0xCB |
BoolBaseProperty |
Some text option |
Critical point for font DID extraction:
Property 0x1A is an ArrayBaseProperty containing ONE DataIdBaseProperty. To read the font DID:
if (sd.Properties.TryGetValue(0x1Au, out var raw) && raw is ArrayBaseProperty arr && arr.Value.Count > 0)
if (arr.Value[0] is DataIdBaseProperty did)
fontDid = did.Value; // e.g. 0x40000000
Confirmed for element 0x10000376 (the vitals text prototype):
- Property
0x1A→DataIdBaseProperty.Value = 0x40000000(font DID) - Property
0x1B→ColorBaseProperty.Value = {B=255,G=255,R=255,A=255}(white)
The fill (0x69) is NOT in the dat. It is pushed at runtime by gmVitalsUI::Update calling UIElement::SetAttribute_Float(meter, 0x69, ratio). The importer does not read this from the dat — the VitalsController sets it via UiMeter.Fill after binding.
4. Edge-anchor flags (LeftEdge/TopEdge/RightEdge/BottomEdge)
These are uint fields on ElementDesc. The values found across all four vitals layouts are:
| Value | Meaning | Where observed |
|---|---|---|
0 |
Not present / no constraint | Base layout 0x2100003F (zero-size elements) |
1 |
Stretch / track-far — for LeftEdge: pin left (near); for RightEdge: stretch (track parent's right edge); for TopEdge: pin top; for BottomEdge: stretch (track parent's bottom) | Most vitals pieces |
2 |
Track-right (for LeftEdge) / fixed-far (for RightEdge) — LeftEdge=2 means the element's LEFT side tracks the parent's RIGHT edge (fixed-width piece that moves right); RightEdge=2 means the right edge is fixed relative to the parent right (no stretch) | Corners/right-side pieces |
3 |
Centered / floating — contributes no anchor on that axis | The expand-detail overlay child 0x100004A9 |
4 |
Both-sides — both near AND far edges fire simultaneously | Seen in child layout meter elements |
Anchor logic (retail-faithful, per UIElement::UpdateForParentSizeChange @0x00462640)
The far-axis fields (RightEdge, BottomEdge) drive stretch:
- RightEdge==1 ⇒ the right edge tracks the parent's right edge (STRETCH; designRight+delta)
- RightEdge==2 ⇒ designRight is fixed (no stretch)
- LeftEdge==2 ⇒ a fixed-width piece's left side tracks the parent's right edge (it moves right)
- LeftEdge==1 ⇒ pin left at designX (near-pin)
- value==4 ⇒ both near AND far fire simultaneously (stretch + keep near)
- value==3 ⇒ centered / floating (no anchor on that axis)
- value==0 ⇒ no anchor (prototype-only)
This is the INVERSE of the earlier §Corrections reading ("1=near, 2=far"), which was wrong. The decomp is authoritative: UIElement::UpdateForParentSizeChange @0x00462640 in docs/research/named-retail/acclient_2013_pseudo_c.txt lines 108459–108668.
Correct ToAnchors logic (as implemented in ElementReader.cs):
// Per UIElement::UpdateForParentSizeChange @0x00462640
public static AnchorEdges ToAnchors(uint left, uint top, uint right, uint bottom)
{
var a = AnchorEdges.None;
if (left == 1 || left == 4) a |= AnchorEdges.Left;
if (right == 1 || right == 4 || left == 2) a |= AnchorEdges.Right;
if (top == 1 || top == 4) a |= AnchorEdges.Top;
if (bottom == 1 || bottom == 4 || top == 2) a |= AnchorEdges.Bottom;
if (a == AnchorEdges.None) a = AnchorEdges.Left | AnchorEdges.Top; // default: pin top-left
return a;
}
Verified against all 19 vitals pieces (format doc §11). At-rest render (no resize) is pixel-identical — anchors only fire on resize. Value 3 contributes no anchor on its axis and falls through to the Left|Top default only when all four values are 3 or 0.
5. MediaDesc kinds
StateDesc.Media is List<MediaDesc>. The concrete types found across the vitals layouts:
| Subclass | Fields | Used in vitals? | Notes |
|---|---|---|---|
MediaDescImage |
uint File, DrawModeType DrawMode, MediaType Type |
YES — all sprite images | The primary media type |
MediaDescCursor |
uint File, uint XHotspot, uint YHotspot, MediaType Type |
YES — grip/dragbar cursor | Sets the mouse cursor when hovering the element |
MediaDescAnimation |
float Duration, DrawModeType DrawMode, List<BaseProperty> Frames, MediaType Type |
not in vitals | Animated sprite |
MediaDescAlpha |
uint File, MediaType Type |
not in vitals | Alpha overlay |
MediaDescFade |
float StartAlpha, EndAlpha, Duration, MediaType Type |
not in vitals | Fade transition |
MediaDescSound |
uint File, ... |
not in vitals | |
MediaDescState |
UIStateId StateId, ... |
not in vitals | State transition |
MediaDescJump |
uint JumpItemIndex, ... |
not in vitals | |
MediaDescMessage |
uint Id, ... |
not in vitals | |
MediaDescPause |
float MinDuration, MaxDuration, ... |
not in vitals | |
MediaDescMovie |
PStringBase<char> FileName, ... |
not in vitals |
Elements can have multiple media items in the same StateDesc.Media list — e.g. a grip element has both a MediaDescImage (the sprite) and a MediaDescCursor (the cursor shape). Iterate all items; for rendering pick the MediaDescImage; for cursor behavior pick MediaDescCursor.
6. DrawModeType enum (confirmed from reflection)
DatReaderWriter.Enums.DrawModeType (the type on MediaDescImage.DrawMode):
| Name | Value | Behavior | Used in vitals? |
|---|---|---|---|
Undefined |
0 | (not used) | no |
Normal |
1 | Tile at native width (UV-repeat; matches ImgTex::TileCSI @0x53e740) |
YES — all bar sprites, chrome |
Overlay |
2 | Blended overlay (not observed in vitals) | no |
Alphablend |
3 | Blended overlay — used for the "ShowDetail" expand panels | YES — ShowDetail state sprites |
The vitals window uses only Normal (1) and Alphablend (3). No Stretch value exists in DrawModeType — the plan's mention of a "Stretch" draw-mode is NOT a value in this enum. There is a MediaType.Stretch = 12 in a separate enum but that refers to a different concept (animation sequence? not a blit mode). Do not branch on Stretch in UiDatElement.
7. UIStateId enum (key type for ElementDesc.States)
DatReaderWriter.Enums.UIStateId. Key values relevant to the vitals window:
| Name | Value |
|---|---|
Undef |
0 |
Normal |
1 |
HideDetail |
268435462 (= 0x10000006) |
ShowDetail |
268435463 (= 0x10000007) |
IsCharacter |
268435542 (= 0x10000056) |
IsAccount |
268435543 (= 0x10000057) |
The dump prints these as strings ("HideDetail", "ShowDetail") via UIStateId.ToString(). When iterating d.States, s.Key.ToString() gives the readable name.
8. Type → meaning → render method → widget bucket
From UIElement::RegisterElementClass calls in the decomp. The mapping is CONFIRMED by retail:
| Type (uint) | Class registered | Render method | Widget bucket | Vitals? |
|---|---|---|---|---|
| 0 | — (no registration) | text label; inherits from UIElement_Text behavior via UIElement_Scrollable |
behavioral → dat-font label widget | YES — the text overlay (e.g. 0x100000EB/ED/EF) |
| 1 | UIElement_Button::Register() |
UIRegion::DrawHere (vtable) |
behavioral → button widget | no |
| 2 | UIElement_Dragbar::Register() |
UIRegion::DrawHere |
generic → UiDatElement (drag region) |
YES — top/bottom drag bars |
| 3 | UIElement_Field::Register() |
UIRegion::DrawHere |
generic → UiDatElement |
YES — container/group elements, chrome corners/edges |
| 4 | (unregistered in stdlib; may be custom) | — | generic fallback | no |
| 5 | UIElement_ListBox::Register() |
UIRegion::DrawHere |
behavioral → list widget | no |
| 6 | UIElement_Menu::Register() |
UIRegion::DrawHere |
behavioral → menu widget | no |
| 7 | UIElement_Meter::Register() |
UIElement_Meter::DrawChildren @0x46fbd0 |
behavioral → UiMeter |
YES — the three vitals bars |
| 8 | UIElement_Panel::Register() |
UIRegion::DrawHere |
generic → UiDatElement |
no |
| 9 | UIElement_Resizebar::Register() |
UIRegion::DrawHere |
generic → UiDatElement (grip) |
YES — resize grips (corners + edges) |
| 0xB | UIElement_Scrollbar::Register() |
UIRegion::DrawHere |
behavioral → scrollbar | no |
| 0xC | UIElement_Text::Register() |
UIElement_Text::DrawSelf @0x467aa0 |
behavioral → dat-font label | YES — Type=0 elements have BaseElement which resolves to a Type=0x0C in the base |
| 0xD | UIElement_Viewport::Register() |
— | behavioral → 3D viewport | no |
| 0xE | UIElement_Browser::Register() |
— | behavioral → browser | no |
| 0x10 | UIElement_ColorPicker::Register() |
— | behavioral → color picker | no |
| 0x11 | UIElement_GroupBox::Register() |
— | behavioral → group box | no |
| 0x12 | — (Type=12 in base layout) | No render method registered — these are style prototypes (zero-size elements used as BaseElement sources, never instantiated directly) |
skip/omit | YES — 0x2100003F is full of Type=12 elements |
| 0x13–0x19 | ConfirmationDialog* / MessageDialog* / etc. |
dialog widgets | behavioral → dialog | no |
| 0x1000xxxx | gmVitalsUI, gmAttributeUI, etc. |
game-specific custom classes | custom widget (registered with high ids) | YES — the stacked vitals window root 0x100005F9 has Type=268435533=0x10000009; the floaty row root has Type=268435465=0x10000009… actually see below |
Root element types in the vitals layouts
0x2100006Croot element0x100005F9:Type = 268435533 = 0x10000009→gmVitalsUI::Registerregisters type0x100000090x21000014root element0x100000E5:Type = 268435465 = 0x10000009— wait,268435465 = 0x10000009✓
Actually: 268435533 = 0x1000000D (not 9). Let me recompute:
268435533 decimal:268435456 + 77 = 0x10000000 + 0x4D = 0x1000004D— that'sgmVitalsUI-ish but a different id.268435465:268435456 + 9 = 0x10000009— confirmedgmVitalsUItype.
The correct decomp cross-check: UIElement::RegisterElementClass(0x10000009, gmVitalsUI::Create) @0x4bfe1a. The stacked vitals window root 0x100005F9 has Type=268435533. 268435533 = 0x1000004D which would be a different registered type. The floaty row root 0x100000E5 has Type=268435465 = 0x10000009 = confirmed gmVitalsUI.
The key observation: the root element's Type selects the gmVitalsUI C++ class, which is the window-level controller. In our importer, we don't need to match this: the LayoutImporter walks children, and the VitalsController binds the meter elements by id directly — the root type is irrelevant to Plan 1.
Plan 1 relevant types (vitals window only):
| Type | Role | Bucket |
|---|---|---|
| 0 | text overlay label (BaseElement → Type 12 for font, but the element itself renders as text) | behavioral → dat-font label |
| 2 | drag bar (top/bottom) | generic |
| 3 | container / chrome edge / corner (no children hierarchy in vitals) | generic |
| 7 | meter | behavioral → UiMeter |
| 9 | resize grip (corners + edges) | generic |
| 12 | style prototype — zero-size, never directly rendered | skip |
| 0x10000009 | gmVitalsUI root — the window itself |
behavioral → window root (use as container) |
| 0x1000004D | the stacked-window root | same |
9. LayoutDesc fields
| Member | Kind | Type | Notes |
|---|---|---|---|
Id |
property | uint |
dat object id |
HeaderFlags |
property | DBObjHeaderFlags |
|
DBObjType |
property | DBObjType |
always LayoutDesc |
DataCategory |
property | uint |
|
Width |
field | uint |
screen-space width context (800 in all observed layouts) |
Height |
field | uint |
screen-space height context (600 in all observed layouts) |
Elements |
field | HashTable<uint, ElementDesc> (DRW-internal type) |
top-level elements, keyed by ElementId. Iterable with foreach (var kv in ld.Elements). |
10. Inheritance chain for vitals number-text elements
All three vitals text labels (0x100000EB health, 0x100000ED stamina, 0x100000EF mana) share:
Type = 0(text element, no render registration — renders via inherited machinery)BaseElement = 268436342 = 0x10000376BaseLayoutId = 553648191 = 0x2100003F
The base element 0x10000376 in 0x2100003F:
Type = 12(style prototype — zero-size, never rendered directly)StateDesc.Properties:0x1A→ArrayBaseProperty[ DataIdBaseProperty{Value=0x40000000} ]— font DID =0x400000000x1B→ArrayBaseProperty[ ColorBaseProperty{R=255,G=255,B=255,A=255} ]— white0x14→EnumBaseProperty{Value=1}— horizontal justification = 10x15→EnumBaseProperty{Value=1}— vertical justification = 10x23,0x25→IntegerBaseProperty{Value=0}— margins
The inheritance chain for the text element in the importer is:
derived (Type=0, no StateDesc media, no font prop itself)
inherits from base 0x10000376 in layout 0x2100003F (Type=12)
→ font DID = 0x40000000 (from property 0x1A)
→ font color = white ARGB(255,255,255,255) (from property 0x1B)
The derived text element overrides Width/Height/X/Y (from the dat element's fields) but inherits the font DID and color from the base element's Properties.
There is no StateDesc.Media on the text elements — the text is rendered by the UIElement_Text::DrawSelf algorithm using the font DID from properties, not a sprite. In Plan 1, the text elements are skipped entirely: Type = 0 (derived) inherits Type = 12 from the base prototype 0x10000376 via ElementReader.Merge (zero-wins-nothing rule — the derived Type 0 inherits the base's Type 12), and DatWidgetFactory returns null for Type 12. This means no UiDatElement is created for them. For the vitals window this is correct: the numbers render via UiMeter.Label bound by the VitalsController, not a dat text node. A dedicated dat-text widget (Type 0) is Plan 2.
11. Vitals window 0x2100006C — confirmed element map
Root: 0x100005F9 (160×58, Type=0x1000004D, LeftEdge=1, TopEdge=1, RightEdge=1, BottomEdge=2)
Chrome (all Type=3, DrawMode=Normal)
| Id | X | Y | W | H | LeftEdge | TopEdge | RightEdge | BottomEdge | Sprite |
|---|---|---|---|---|---|---|---|---|---|
0x10000633 |
0 | 0 | 5 | 5 | 1 | 1 | 2 | 2 | 0x060074C3 (TL corner) |
0x10000634 |
5 | 0 | 150 | 5 | 1 | 1 | 1 | 2 | 0x060074BF (top edge) |
0x10000635 |
155 | 0 | 5 | 5 | 2 | 1 | 1 | 2 | 0x060074C4 (TR corner) |
0x10000636 |
0 | 5 | 5 | 48 | 1 | 1 | 2 | 1 | 0x060074C0 (left edge) |
0x10000637 |
0 | 53 | 5 | 5 | 1 | 2 | 2 | 1 | 0x060074C5 (BL corner) |
0x10000638 |
5 | 53 | 150 | 5 | 1 | 2 | 1 | 1 | 0x060074C1 (bottom edge) |
0x10000639 |
155 | 53 | 5 | 5 | 2 | 2 | 1 | 1 | 0x060074C6 (BR corner) |
0x1000063A |
155 | 5 | 5 | 48 | 2 | 1 | 1 | 1 | 0x060074C2 (right edge) |
Drag bars (Type=2)
| Id | X | Y | W | H | Notes |
|---|---|---|---|---|---|
0x1000063C |
5 | 0 | 150 | 5 | top drag bar; also has MediaDescCursor cursor 0x06006119 |
0x10000640 |
5 | 53 | 150 | 5 | bottom drag bar; same cursor |
Resize grips (Type=9 — corners + edges)
| Id | X | Y | W | H | Corner/Edge |
|---|---|---|---|---|---|
0x1000063B |
0 | 0 | 5 | 5 | TL grip |
0x1000063D |
155 | 0 | 5 | 5 | TR grip |
0x1000063E |
0 | 5 | 5 | 48 | left grip |
0x1000063F |
0 | 53 | 5 | 5 | BL grip |
0x10000641 |
155 | 53 | 5 | 5 | BR grip |
0x10000642 |
155 | 5 | 5 | 48 | right grip |
Each grip has a MediaDescImage + a MediaDescCursor in its StateDesc.Media list.
Meter elements (Type=7 — UiMeter)
| Id | X | Y | W | H | Purpose |
|---|---|---|---|---|---|
0x100000E6 |
5 | 5 | 150 | 16 | Health meter |
0x100000EC |
5 | 21 | 150 | 16 | Stamina meter |
0x100000EE |
5 | 37 | 150 | 16 | Mana meter |
Each meter has:
- Child
0x100000E7(back layer, Type=3): three sub-childrenE8/E9/EA(left/center/right slices, back sprites)E8hasRightEdge=2(pin far right),EAhasLeftEdge=2(pin far left) — the classic 3-slice anchor pattern
- Child
0x00000002(front layer container, Type=3): three sub-childrenE8/E9/EA(front sprites), plus child0x100004A9(expand detail overlay, HideDetail/ShowDetail states) - Child
0x100000EB/ED/EF(text label, Type=0): BaseElement=0x10000376, BaseLayoutId=0x2100003F→ inherits font0x40000000
Sprite ids confirmed from dump
Health bar (back=E7 layer / front=00000002.E8-EA layer):
- Back left:
0x0600747E, center:0x0600747F, right:0x06007480 - Front left:
0x06007481, center:0x06007482, right:0x06007483 - ShowDetail overlay:
0x06007490(back) /0x06007491(front)
Stamina bar:
- Back left:
0x06007484, center:0x06007485, right:0x06007486 - Front left:
0x06007487, center:0x06007488, right:0x06007489 - ShowDetail:
0x06007492/0x06007493
Mana bar:
- Back left:
0x0600748A, center:0x0600748B, right:0x0600748C - Front left:
0x0600748D, center:0x0600748E, right:0x0600748F - ShowDetail:
0x06007494/0x06007495
12. Inheritance resolution rules
- If
d.BaseElement != 0 && d.BaseLayoutId != 0: load base layout, find base element, callResolve()recursively on it, thenMerge(base, derived). - Merge semantics: derived overrides, base is the default.
Width/Height/X/Ycome from the derived element's fields (even if zero — zero is a valid override for prototypes).FontDidis inherited if the derived element's base chain provides it and the derived doesn't explicitly set it. - Type=12 elements in the base layout (
0x2100003F) are pure property stores — never render them. They exist only to be referenced asBaseElement. - Cycle-guard: track already-visited
(BaseLayoutId, BaseElement)pairs to avoid infinite loops.
§ Corrections to plan assumptions
1. Edge-flag semantics are INVERTED from the earlier §4 reading
Original §4 reading (Task 2 shipped): 1=near, 2=far, 4=stretch → right==2||right==4 for Right anchor.
That was wrong. The correct semantics, per UIElement::UpdateForParentSizeChange @0x00462640:
| Edge value | LeftEdge meaning | RightEdge meaning |
|---|---|---|
| 0 | no anchor | no anchor |
| 1 | pin left (near) → Left | track parent's right edge (stretch) → Right |
| 2 | track parent's right edge (moves right) → Right | fixed right (no stretch) |
| 3 | centered / floating (no anchor) | centered / floating (no anchor) |
| 4 | both-sides → Left + Right | both-sides → Left + Right |
The far-axis field (RightEdge, BottomEdge) value 1 means stretch (track the parent's far edge), NOT "near-pin." This is the INVERSE of what was documented in the original §4.
Correct ToAnchors (as fixed in ElementReader.cs 2026-06-15):
// Per UIElement::UpdateForParentSizeChange @0x00462640
public static AnchorEdges ToAnchors(uint left, uint top, uint right, uint bottom)
{
var a = AnchorEdges.None;
if (left == 1 || left == 4) a |= AnchorEdges.Left;
if (right == 1 || right == 4 || left == 2) a |= AnchorEdges.Right;
if (top == 1 || top == 4) a |= AnchorEdges.Top;
if (bottom == 1 || bottom == 4 || top == 2) a |= AnchorEdges.Bottom;
if (a == AnchorEdges.None) a = AnchorEdges.Left | AnchorEdges.Top;
return a;
}
Also: the ElementReader.ToAnchors signature in the plan uses (int left, ...) but the fields are uint. Use (uint left, ...) or cast at call site.
2. X, Y, Width, Height, LeftEdge, etc. are uint, not float or int
The plan's ToInfo() code uses d.X, d.Y etc. as though they are already numeric-assignable. They are uint, so the assignment X = d.X etc. requires an explicit cast (float)d.X in the ElementInfo struct.
3. ElementDesc.Type is uint, not an enum
The plan writes (int)d.Type. d.Type is uint, so (int)d.Type is valid C# (checked context would overflow for values > int.MaxValue, but the registered types are all small or 0x10000009 which fits in int). Better: store Type as uint in ElementInfo to avoid signed overflow on game-specific ids like 0x1000004D.
4. DrawModeType has no Stretch value
The plan mentions handling Stretch in UiDatElement. The DrawModeType enum has only {Undefined=0, Normal=1, Overlay=2, Alphablend=3}. There is no Stretch draw mode in this enum. Drop the Stretch branch.
5. d.States key is UIStateId, not string
The plan writes foreach (var s in d.States) ReadState(s.Value, s.Key, info); treating s.Key as a string. The key is UIStateId (an enum). Use s.Key.ToString() for the string name, or compare directly via UIStateId.HideDetail etc.
6. Font DID is in ArrayBaseProperty, not a direct property
The plan's // font DID (property 0x1A) read here once the format doc confirms the property API. comment is the right place. The actual read is:
if (sd.Properties.TryGetValue(0x1Au, out var raw) && raw is ArrayBaseProperty arr && arr.Value.Count > 0)
if (arr.Value[0] is DataIdBaseProperty did)
info.FontDid = did.Value;
7. Fill (0x69) is NOT in the dat
The plan says SetAttribute_Float(meter, 0x69, fillRatio) is a runtime operation. Confirmed: property 0x69 does not appear in any dat layout. The fill is set at runtime by the controller. The importer should not attempt to read it.
8. Type=12 elements are style prototypes — skip them entirely
Elements with Type=12 in the base layout 0x2100003F are zero-size property bags used as BaseElement sources. They should not be instantiated as widgets. The DatWidgetFactory switch should have a 12 => null (skip) case, or the importer should skip top-level elements with Width==0 && Height==0 && Type==12 — though the safest check is just Type == 12.
§ Plan 1 surface vs long tail
Plan 1 (vitals conformance) uses:
- Types: 2, 3, 7, 9, 12 (skip), 0 (text, generic fallback), 0x10000009/0x1000004D (root window — treat as container)
- DrawModes:
Normal(1),Alphablend(3) - Media:
MediaDescImage,MediaDescCursor - Properties:
0x1A(font DID, from inheritance),0x1B(font color, from inheritance) - States:
HideDetail,ShowDetail
Plan 2 (long tail):
- Types: 1 (button), 5 (listbox), 6 (menu), 8 (panel), 0xB (scrollbar), 0xC (text widget proper), 0xD (viewport), 0x10 (color picker), 0x11 (groupbox), dialog types (0x13–0x19), all
gm*UIcustom types - DrawModes:
Overlay(2), any future additions - Media:
MediaDescAnimation,MediaDescFade,MediaDescSound,MediaDescState, etc.