feat(client): self-only vitals HUD — STR/DEX/INT plates + HP/STAM/MANA bars (#181) #205
No reviewers
Labels
No labels
alpha:wave-0
alpha:wave-1
alpha:wave-2
alpha:wave-3
area:assets
area:combat
area:ecology
area:infra
area:render
area:scripting
area:ui
area:world
enhancement
epic
migration
post-alpha
roadmap
tech-debt
type:bug
type:chore
type:design
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
marco/IsoMmo!205
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/vitals-hud"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds the self player's Vitals HUD (#181): three primary-stat plates (STR / DEX / INT) and three pool bars (HP / STAM / MANA) showing current/max, anchored bottom-left and laid out to the agreed gothic mockup. This is the procedural first pass — the decision was "1 poi 2": the layout + live wiring land now, and the carved gothic frame skin is a deliberate follow-up once the art exists. STAM is wired end-to-end but reads full until stamina drain lands (#185).
The vitals are server-authoritative and self-only. Each tick computes every player's vitals and emits a
VitalsStateonly when they change — detected by comparing a fresh snapshot against the last one sent, held on the entity (PlayerMobile.LastSentVitals), so there's no dirty-flag plumbing threaded through the mutators. The change is routed to that player's own connection (SendToAsyncby id), never broadcast and never sent to observers — mirroring howInventoryStateandSkillGainsare already scoped. It's a new wire message, soProtocolVersion.Currentis bumped to 14 in the same change.Client side: the dispatcher stores the latest
VitalsStateonClientWorld; the newVitalsHudview reads it and paints the plates + bars (VitalsBar.Fillgives the clamped fill fraction, NaN-safe at zero max). The HUD draws nothing until the firstVitalsStatearrives.VitalsHudis a thin view owned byWorldRenderer— no game state, consistent with theScreenthin-coordinator gate. Engine-agnostic fill math lives inClient.Core(VitalsBar), unit-tested there.Screenshots / recording
Captured by piloting the client via the debug harness (
docs/debug-harness.md) from a fresh database:Full vitals (matches the mockup layout):
After
/set hits 18on self — the HP bar reacts live (serverVitalsState→ HUD), STAM/MANA untouched:How it was tested
Client.Core) —VitalsBar.Fillclamps to[0,1]across under/over/normal inputs and returns0(notNaN) at zero max.World) — the tick emits a player's vitals on its first tick, then only on change; aSetHits(10)re-emits carrying the new value; the payload carries the stats + both pools./set str 99on herself receives her ownVitalsState(MaxHits re-clamped to 99); Bob, standing nearby, says "ping" and reads his stream to his own echo without ever receiving aVitalsState { Str: 99 }. The AoI "hears nothing" helper now ignores a player's own self-onlyVitalsState(it is not an area-of-interest leak).dotnet csharpier checkclean, whole-solutiondotnet build0 warnings (analyzers as errors),dotnet testgreen across every project (GameServer 242).Checklist
just lintpasses (CSharpier + analyzers, zero warnings)just testis greenReviewed: scope + body + code all consistent. VitalsHud correctly follows the passive-HUD-overlay pattern (like InventoryPanel/DamageNumbers/Journal — standalone, LineRenderer + fonts, drawn by WorldRenderer), not the Gump framework (which is for interactive draggable windows). Server side is self-only + AoI-safe (trust-boundary integration-tested). Merge conflict with #203 (both added a TickResult field) resolved by keeping both; build + 543 tests green.