# 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-layout` on `0x2100006C`, `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` | named states (e.g. `HideDetail`, `ShowDetail`) | | `Children` | **field** | `Dictionary` | 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` | keyed by property-id (uint); see §3 | | `Media` | **field** | `List` | polymorphic list of media items | ### States dictionary key type `ElementDesc.States` is `Dictionary`. 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: ```csharp // Key: UIStateId.HideDetail = 268435462 (0x10000006) // Key: UIStateId.ShowDetail = 268435463 (0x10000007) ``` See §6 for the full `UIStateId` enum. **Iterating in code:** ```csharp 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`. The `BaseProperty` base class has: - `BasePropertyType PropertyType` (enum) - `uint MasterPropertyId` - `bool 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` | 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` | | ### 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: ```csharp 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` | **Pinned to near edge** (left for LeftEdge, top for TopEdge) | Everywhere in vitals | | `2` | **Pinned to far edge** (right for LeftEdge, bottom for TopEdge) | Corners/bottom elements | | `3` | **Centered / pinned to both far edges** (floated, centered between two sides) | The expand-detail overlay child `0x100004A9` | | `4` | **Stretch / pinned to BOTH sides** | Meter elements in `0x21000014`/`0x21000075`; means the element stretches with parent resize | ### Anchor logic (correcting the plan's assumption) **The plan assumed value `4` = "pinned to that side."** The correct semantics are: - `1` = pinned to the **near** edge of that axis (left, or top) - `2` = pinned to the **far** edge (right, or bottom) - `3` = pinned to BOTH far edges (centered/floating between the two anchors on that axis) - `4` = stretch anchor: pinned to BOTH the near AND far edges simultaneously (element stretches) - `0` = no anchor (zero-size elements used as font/style prototypes in the base layout) Evidence from the `0x21000014` dump: the health meter (`0x100000E6`) has `LeftEdge=1, RightEdge=4` meaning "pin left edge, stretch right" — the meter fills from the left to the window's right edge. The stamina meter (`0x100000EC`) has `LeftEdge=4, RightEdge=4` meaning it stretches on both sides (centered at 270px, fills width with parent). **Revised `ToAnchors` logic:** ```csharp public static AnchorEdges ToAnchors(uint left, uint top, uint right, uint bottom) { // 1 = near-pin, 2 = far-pin, 3 = both-far (floating center), 4 = stretch (both sides) var a = AnchorEdges.None; if (left == 1 || left == 4) a |= AnchorEdges.Left; if (top == 1 || top == 4) a |= AnchorEdges.Top; if (right == 2 || right == 4) a |= AnchorEdges.Right; if (bottom == 2 || bottom == 4) a |= AnchorEdges.Bottom; if (a == AnchorEdges.None) a = AnchorEdges.Left | AnchorEdges.Top; // default: pin top-left return a; } ``` Value `3` (floating center) is a "pin far but not near" on both axes — maps to Right+Bottom anchors but NOT Left+Top. This shows up only on the hide/show-detail overlay child (`0x100004A9`) which is visually centered in the bar. --- ## 5. `MediaDesc` kinds `StateDesc.Media` is `List`. 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 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 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 - `0x2100006C` root element `0x100005F9`: `Type = 268435533 = 0x10000009` → `gmVitalsUI::Register` registers type `0x10000009` - `0x21000014` root element `0x100000E5`: `Type = 268435465 = 0x10000009` — wait, `268435465 = 0x10000009` ✓ Actually: `268435533 = 0x1000000D` (not 9). Let me recompute: - `268435533 decimal`: `268435456 + 77 = 0x10000000 + 0x4D = 0x1000004D` — that's `gmVitalsUI`-ish but a different id. - `268435465`: `268435456 + 9 = 0x10000009` — confirmed `gmVitalsUI` type. 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` (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 = 0x10000376` - `BaseLayoutId = 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 = `0x40000000`** - `0x1B` → `ArrayBaseProperty[ ColorBaseProperty{R=255,G=255,B=255,A=255} ]` — white - `0x14` → `EnumBaseProperty{Value=1}` — horizontal justification = 1 - `0x15` → `EnumBaseProperty{Value=1}` — vertical justification = 1 - `0x23`, `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 render as `UiDatElement` (generic fallback) until a dedicated text widget is implemented in 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-children `E8`/`E9`/`EA` (left/center/right slices, back sprites) - `E8` has `RightEdge=2` (pin far right), `EA` has `LeftEdge=2` (pin far left) — the classic 3-slice anchor pattern - Child `0x00000002` (front layer container, Type=3): three sub-children `E8`/`E9`/`EA` (front sprites), plus child `0x100004A9` (expand detail overlay, HideDetail/ShowDetail states) - Child `0x100000EB/ED/EF` (text label, Type=0): BaseElement=`0x10000376`, BaseLayoutId=`0x2100003F` → inherits font `0x40000000` ### 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 1. If `d.BaseElement != 0 && d.BaseLayoutId != 0`: load base layout, find base element, call `Resolve()` recursively on it, then `Merge(base, derived)`. 2. Merge semantics: **derived overrides, base is the default**. `Width`/`Height`/`X`/`Y` come from the derived element's fields (even if zero — zero is a valid override for prototypes). `FontDid` is inherited if the derived element's base chain provides it and the derived doesn't explicitly set it. 3. Type=12 elements in the base layout (`0x2100003F`) are pure property stores — **never render them**. They exist only to be referenced as `BaseElement`. 4. Cycle-guard: track already-visited `(BaseLayoutId, BaseElement)` pairs to avoid infinite loops. --- ## § Corrections to plan assumptions ### 1. Edge-flag "pinned" value is NOT simply `4` **Plan assumed:** `if (left == 4) a |= AnchorEdges.Left;` **Correct semantics:** | Edge value | Meaning | |-----------|---------| | 0 | no anchor (prototype-only elements) | | 1 | pinned to **near** edge (left/top) | | 2 | pinned to **far** edge (right/bottom) | | 3 | pinned to BOTH far edges (centered/floating) | | 4 | stretch: pinned to BOTH near AND far edges simultaneously | **Fix for Task 2:** ```csharp 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 (top == 1 || top == 4) a |= AnchorEdges.Top; if (right == 2 || right == 4) a |= AnchorEdges.Right; if (bottom == 2 || bottom == 4) 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: ```csharp 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*UI` custom types - DrawModes: `Overlay` (2), any future additions - Media: `MediaDescAnimation`, `MediaDescFade`, `MediaDescSound`, `MediaDescState`, etc.