Fix 1: Added a <para> to the VitalsController class summary citing
docs/research/2026-06-15-layoutdesc-format.md §11 as the source of the
three dat element ids, giving a paper trail back to the evidence per
the project's cite-in-comments rule.
Fix 2: Changed FakeLayout in VitalsBindingTests to accept (uint id,
UiElement e) tuples instead of (string idHex, UiElement e), and updated
all three call sites to pass VitalsController.Health/.Stamina/.Mana.
Tests now follow the constants automatically if they ever change rather
than silently passing with stale hex literals.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mirrors retail gmVitalsUI::PostInit: grab Health/Stamina/Mana meters from
the imported layout by their dat element ids (0x100000E6 / EC / EE) and
wire Func<float> fill + Func<string> label providers. Missing ids are
silently skipped (no throw). Slice sprites + dat font already set by the
factory — this is pure data wiring, not graphics.
3 TDD tests: single-meter fill+label, all-three distinct providers, missing-id no-throw.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements Task 5 of the LayoutDesc Importer (Plan 1 — vitals conformance).
Pure layer (BuildFromInfos / Build):
- ImportedLayout result type: UiElement root + O(1) FindElement(uint id) lookup
- BuildWidget dispatches via DatWidgetFactory.Create; skips Type-12 prototypes (null)
- Meters consume their children (DatWidgetFactory already extracted slice ids —
adding the dat children as UiElement nodes would duplicate geometry)
- All other element types recurse children generically via AddChild
Dat shell (Import):
- Loads LayoutDesc from dats; null-safe if layout is absent
- Resolves each top-level ElementDesc to ElementInfo via Resolve():
BaseElement/BaseLayoutId chain with (layoutId,elementId) cycle guard
- ToInfo(): reads ElementDesc scalar fields (uint → float cast) + DirectState +
named States (UIStateId.ToString() as key)
- ReadState(): extracts first MediaDescImage (File + DrawMode) per state +
font DID from Properties[0x1A] → ArrayBaseProperty → DataIdBaseProperty.Value
- Each sibling element gets a fresh base-chain set (siblings don't share guards)
DRW API: all members confirmed from VitalsLayoutDump.cs usings — no
adjustments needed: LayoutDesc in DBObjs; ElementDesc/StateDesc/MediaDescImage/
ArrayBaseProperty/DataIdBaseProperty in Types; DrawModeType/UIStateId in Enums.
Tests (3/3 green):
- BuildFromInfos_HealthMeter_IsUiMeterAtRect — Type-7 child → UiMeter, Left=5, Width=150
- BuildFromInfos_Type12Child_IsSkipped_Type3Present — prototype absent, container present
- BuildFromInfos_MeterWithChildren_MeterPresent_ChildrenNotInTree — meter findable,
both dat-children absent, UiMeter.Children empty
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix 1: SliceIds now projects the File id during Select rather than calling
TryGetValue twice (once in Where, once in the local File() helper). Added a
comment noting that OrderBy is stable so X-tie order follows insertion order.
Fix 2: BuildMeter emits a [D.2b] Console.WriteLine when the Type-3 container
count is not exactly 2, surfacing malformed or non-vitals meter elements during
Task 8 conformance testing without disturbing the existing solid-color fallback.
Fix 3: Test 5 adds two explicit NotEqual assertions confirming the
ShowDetail-only overlay sprite (OverlayFile = 0x06007490) did not leak into
FrontRight or FrontTile.
5/5 tests pass, 0 warnings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Hybrid factory mapping ElementInfo.Type to a behavioral widget or the
UiDatElement generic fallback. Type 7 (UIElement_Meter) → UiMeter with
back/front 3-slice ids populated from grandchild image elements; Type 12
(style prototypes / BaseElement stores) → null so the importer skips
them; all other types → UiDatElement. Rect + anchors are set on every
returned widget via ElementReader.ToAnchors.
BuildMeter walks two levels of the element tree: the two Type-3 slice
containers ordered by ReadOrder (back behind, front on top), then within
each container the image children that carry a DirectState ("" key)
ordered by X for left-cap/center-tile/right-cap. The expand-detail
overlay (present in the front container with only named ShowDetail/
HideDetail states and no "" entry) is excluded by the TryGetValue("")
filter automatically — no name-matching needed.
Fill/Label providers are intentionally NOT set here; Task 6
(VitalsController) binds them to live stat data.
5 TDD tests: Type7→UiMeter, UnknownType→UiDatElement, Type12→null,
rect+anchors propagation, and meter slice extraction with overlay exclusion.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Generic fallback widget for every LayoutDesc element type without a
dedicated behavioral widget (chrome corners/edges, drag bars, resize grips).
Holds an ElementInfo + active-state name; draws that state's media by tiling
(UV-repeat on both S+T axes, matching ImgTex::TileCSI). DrawMode constants
documented per format spec §6 (Undefined=0, Normal=1, Overlay=2,
Alphablend=3 — no Stretch mode). Plan 1: all modes render as the same
alpha-blended tiled quad; per-mode branches deferred to Plan 2.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Merge: defensive copy `new List<ElementInfo>(derived.Children)` so a
later mutation of the merged result or the input can't corrupt the other
- Merge: add comment on Width/Height 0-sentinel (Plan-1 safe; Plan-2
limitation and float?-upgrade path documented inline)
- Test: replace mid-sentence "Wait —" authoring trace in
EdgeFlagsToAnchors_ValueThree_FallsBackToTopLeft with a clean
conclusion-first summary of the value-3 mapping rule
9/9 ElementReaderTests pass; 0 build errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements Task 2 of the LayoutDesc Importer (Plan 1 — vitals conformance).
- ElementInfo POCO: GL-free/dat-free snapshot of a resolved layout element.
Shape matches the plan spec exactly (Id, Type as uint, X/Y/Width/Height as
float, raw Left/Top/Right/Bottom uint edge flags, ReadOrder, FontDid, StateMedia
dict, Children list). Tasks 3–6 depend on this shape.
- ElementReader.ToAnchors(uint,uint,uint,uint): maps dat edge-flag values
(0=none, 1=near-pin, 2=far-pin, 3=floating-center, 4=stretch) to AnchorEdges
bit flags. Corrects the plan's stale assumption that value 4 was the only anchor
trigger; the verified format doc §4 shows 1→Left/Top, 2→Right/Bottom, 4→both.
All-zero falls back to Left|Top (default pin top-left).
- ElementReader.Merge(base_, derived): inheritance merge mirroring BaseElement/
BaseLayoutId. Derived scalars win when non-zero; position/edge-flags/ReadOrder
always from derived; StateMedia merged (base defaults, derived overrides);
Children from derived only.
TDD: tests written first (9 tests covering ToAnchors near-pin/far-pin/stretch/
zero/value-3, Merge scalar override/font inheritance/StateMedia merge/children).
All 9 pass; dotnet build 0 errors 0 warnings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>