feat(D.5.3a): selected-object meter — Health bar + name on the action bar

Port of gmToolbarUI::HandleSelectionChanged (acclient_2013_pseudo_c.txt:198635).
When the player selects a world object the action bar's bottom strip shows the
object name + (for player/pet/attackable targets) a live Health meter; deselect
clears it. Mana (#140) + stack slider deferred.

- SelectedObjectController (new): clear-then-populate on selection change; sets
  name (UiText child, VitalsController pattern), overlay state (ObjectSelected /
  StackedItemSelected via UiDatElement.ActiveState), shows the health meter and
  sends QueryHealth for health targets. Subscribes via a delegate seam (no
  GameWindow coupling).
- GameWindow: _selectedGuid field -> SelectedGuid property + SelectionChanged
  event (fires on actual change only); 3 write sites converted, reads untouched.
  All selection-write paths (LMB pick, Tab/Q, despawn-clear via Tick()) run on
  the render thread, so the event-driven UI mutation is single-threaded.
- WorldSession.SendQueryHealth (0x01BF) — wraps SocialActions.BuildQueryHealth.
- DatWidgetFactory.BuildMeter: handle the single-image toolbar meter shape
  (back-track on the element's own DirectState, fill on one Type-3 child). The
  sprites go in the TILE slot (DrawMode=Normal tiles to full bar geometry per
  UIElement_Meter::DrawChildren) — a left-cap assignment would gap/clamp a
  sub-140px sprite. Vitals 3-slice path unchanged.
- ToolbarController.HiddenIds: A1 (health) now owned by SelectedObjectController;
  A2 (mana) + A4 (stack) stay hidden (deferred) so their dat back-tracks don't
  render as stray empty bars.

Adversarial Opus review found + fixed: the mana-meter orphan (A2 left unhidden)
and the meter tile-vs-cap render bug (C1). Divergence rows AP-46 (health gate
approximation: IsLiveCreatureTarget vs IsPlayer||pet||attackable) + AP-47
(meter shown on select vs on UpdateHealth reply). Spec §5 corrected.

Build + full test suite green (2,684 passed / 4 skipped). Health meter render
fidelity (full-width fill + fraction mapping) pending the user's visual gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-18 22:47:24 +02:00
parent e8562fc4e2
commit 6636e50c2a
11 changed files with 851 additions and 30 deletions

View file

@ -223,9 +223,12 @@ AcDream.App.UI.Layout.SelectedObjectController.Bind(
datFont: vitalsDatFont);
```
Also: remove `0x100001A1` and `0x100001A2` from `ToolbarController.HiddenIds` (single-owner: the
selected-object meters are now owned by `SelectedObjectController`); `0x100001A4` (stack slider) stays
in `HiddenIds` (deferred). Convert the `_selectedGuid` field → the `SelectedGuid` property (unit 1).
Also: remove **only** `0x100001A1` from `ToolbarController.HiddenIds` — the health meter is now owned
by `SelectedObjectController` (it hides A1 at bind, shows on a health-target select). `0x100001A2`
(mana, deferred #140) and `0x100001A4` (stack slider, deferred) **stay** in `HiddenIds`: they have no
controller yet, so they must stay hidden or their dat back-track sprites render as stray empty bars.
(`HiddenIds = { 0x100001A2, 0x100001A4 }`.) Convert the `_selectedGuid` field → the `SelectedGuid`
property (unit 1).
## Data flow