feat(D.2b): wire UiHost + live retail Vitals panel (render-only); retire TS-30

Wires the dormant AcDream.App/UI retained-mode tree into GameWindow under
ACDREAM_RETAIL_UI=1: an 8-piece dat-sprite UiNineSlicePanel framing three
UiMeter vital bars bound to the existing VitalsVM. Render-only (UiHost input not
yet bridged to the InputDispatcher — next sub-phase). Coexists with the ImGui
devtools path; no regression there.

Visually verified against a live retail client: the bars match retail's vitals
structure (three stacked horizontal bars, current/max numbers centered) — so the
earlier "orbs" assumption was wrong (retail vitals ARE bars), and stamina is GOLD
not cyan (the #10F0F0 research note was wrong). UiMeter gains a centered numeric
Label (stub debug font for now). Spec §8 + the markup example corrected to match.

Bookkeeping: retired divergence row TS-30 (flat-rect panels -> real dat chrome)
and added IA-15 (our UiHost/markup engine vs keystone.dll's LayoutDesc tree).

Remaining polish (filed, §15): glassy gradient bar fill sprite + the retail dat
font for the numbers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-14 16:56:57 +02:00
parent 064ef41ce4
commit b18403da02
4 changed files with 102 additions and 15 deletions

View file

@ -220,7 +220,7 @@ constant** (with a divergence row) until the `LayoutDesc` tree is parsed
```xml
<panel id="acdream.vitals" x="10" y="30" w="220" h="96" title="Vitals">
<meter id="health" x="8" y="24" w="200" h="13" fill="{HealthPercent}" color="#FF0000"/>
<meter id="stamina" x="8" y="44" w="200" h="13" fill="{StaminaPercent}" color="#10F0F0"/>
<meter id="stamina" x="8" y="44" w="200" h="13" fill="{StaminaPercent}" color="#D9A626"/>
<meter id="mana" x="8" y="64" w="200" h="13" fill="{ManaPercent}" color="#0000FF"/>
</panel>
```
@ -249,12 +249,15 @@ real `VitalsVM` ([VitalsVM.cs:67](../../../src/AcDream.UI.Abstractions/Panels/Vi
VM already does all server plumbing, so we do **not** re-derive vitals from the
retail `gmVitalsUI`/`CACQualities` decomp.
`UiMeter.OnDraw` draws the empty bar (`ctx.DrawRect`) then the filled portion as a
**partial-size rect** (`width = pct * Width`) in the bar color — Health `#FF0000`,
Stamina `#10F0F0`, Mana `#0000FF`. (For rectangular solid bars this is equivalent
to retail's orb scissor-fill and avoids per-quad scissor state inside the batch;
scissor/UV-crop comes when the actual orb *sprite* is drawn, later.) A `null`
fill (stamina/mana pre-`PlayerDescription`) draws an empty bar.
`UiMeter.OnDraw` draws the empty bar (`ctx.DrawRect`), the filled portion as a
**partial-size rect** (`width = pct * Width`), and a centered `current/max` numeric
overlay (`Func<string?> Label`). **Retail's vitals ARE exactly this — three stacked
horizontal bars (confirmed against a live retail client 2026-06-14), NOT orbs.**
Colors: Health red, **Stamina gold** (the earlier `#10F0F0` cyan research note was
wrong), Mana blue. A `null` fill/label (pre-`PlayerDescription`) renders gracefully.
The remaining gap to pixel-retail is the **glassy gradient bar fill sprite** + the
**retail dat font** for the numbers (today the stub `BitmapFont` draws them) — both
polish, deferred to §15.
The `VitalsVM` is constructed and given the player GUID the same way as today
([GameWindow.cs:1330](../../../src/AcDream.App/Rendering/GameWindow.cs) ctor,