---
title: "UI HUD panels widgets and world nameplates"
description: "The player needs on-screen information (score, health, timer) or floating labels over entities (nameplates, damage numbers, quest markers). Screen UI is a UiPanel; world labels are a `TextRenderableComponent`."
engineVersion: v1.0.234
date: 2026-09-28
license: "(c) Gessa, proprietary. Cite with attribution to https://gessa.ai/docs/. Terms: https://gessa.ai/terms/."
canonical: https://gessa.ai/docs/knowledge/playbooks/ui-hud-panels-and-widgets/
---

# UI HUD panels widgets and world nameplates

## When to use

The player needs on-screen information (score, health, timer) or floating labels
over entities (nameplates, damage numbers, quest markers). Screen UI is a
UiPanel; world labels are a `TextRenderableComponent`.

## Wire the whole HUD in one pass

Create the panel ONCE, then bind every widget in a SINGLE
`project_apply_script_semantic_patch` pass rather than one call per widget. Each
`wireHudStateBinding` op adds one widget-to-state binding; put them all in the
same patch so the HUD is wired as one decisive edit, not a drip of updates. A
single field update to one widget is the precision tool, for a targeted repair.

## The recipe

Screen HUD:

1. Create a panel with `project_create_ui_panel` (`project.ui_panel.create`)
   holding the widgets you want (a score readout, a bar).
2. Bind a widget to durable state from a script with
   `project_apply_script_semantic_patch` op `wireHudStateBinding`
   `{ "scope":{"kind":"handler","handlerRef":<key>}, "panelKey": <panel key>,
   "widgetKey": <widget key>, "storeKey":"progress", "recordKey":"player",
   "field":"score", "valueType":"number", "defaultValue": 0 }`. It reads the
   warehouse field and pushes it through `ctx.ui.setWidgetValue`. Add one such op
   per widget in the same patch.
3. Open and close panels from a GessaScript body with `ctx.ui.openPanel` and
   `ctx.ui.closePanel` (for a pause menu, a shop, a death screen).

World-space labels:

A player's own name over their character is NOT a `TextRenderableComponent`. The
game's character policy nameplate setting (everyone, team, or none) makes the
runtime stamp a server-written `NameplateComponent` from the account, so you
never author a player name plate by hand (see player-characters-and-spawns). Use
`TextRenderableComponent` for non-player world labels: enemy nameplates, item
names, damage numbers, and quest markers.

4. Attach `TextRenderableComponent` to the labeled entity
   `{ "type":"TextRenderableComponent", "enabled": true, "text":"Enemy",
   "textSource":"static", "color":"#ffffff", "fontSize": 18, "anchor":"bottom",
   "billboardMode":"camera", "offset":{"x":0,"y":1.8,"z":0}, "maxDistance": 80,
   "fadeDistance": 60 }`.
5. Drive the label from live data with `textSource`: `static` (fixed `text`),
   `entityName` (the entity's name), `projection` (a `projectionKey`), or `stat`
   (a `statKey`). Use `stat` for a floating value that updates from state.

## Pitfalls

- `wireHudStateBinding` reads the SAME store/record/field a save uses; wire the
  HUD to the field you already persist (see saves-and-persistence), not a
  separate copy.
- `TextRenderableComponent.text` is capped at 256 chars; `fontSize` is 6..96;
  `fontFamily` is `sans`, `mono`, or `serif`. Out-of-range values fail.
- A `TextRenderableComponent` with `textSource:"stat"` needs a `statKey`; with
  `projection` it needs a `projectionKey`. The discriminator drives which field
  is required.
- HUD widget values are a `uiEffect`; they display state, they do not persist it.

## Verify

- `engine_get_component_schema` on `TextRenderableComponent` for text-source
  enums and bounds.
- `qa_capture_renderer_viewport` (`qa.renderer.viewport.capture`) to confirm the
  HUD and nameplates render.
- `simulation_run` to confirm the bound widget updates when the state changes.
