HUDyML
HUDyML is a thin, renderer-agnostic intermediate layer that sits between semantic game state and whatever surface actually draws the HUD. Its job is to convert a stream of runtime values into a sorted list of typed concepts that a renderer can display without knowing anything about the game domain.
Why it exists
Walker's GPS HUD was originally built as a direct pipeline:
GPS state → presenter → escaped HTML → DOM panel
That works fine for one surface. The moment you want the same data to feed a console readout, a Babylon overlay, a knat/OSC bridge, or a test assertion, the presenter becomes a bottleneck because it bakes in rendering choices (row strings, HTML tags) that vary per target.
HUDyML inserts a stable, portable step:
GPS state → HUDyML concepts → renderer adapter → any surface
The concept list is pure data. Any adapter can consume it independently without re-deriving game logic.
Core model
A HUD surface is described by a config object with three sections.
Concepts
A concept is a named value with a semantic type and an optional class.
interface HUDConcept {
id: string; // unique within the config
type: string; // semantic category, e.g. "resource", "counter", "objective"
class?: string; // sub-type, e.g. "threat", "waypoint_count", "runway_phase"
data?: Record<string, unknown>; // arbitrary payload, usually includes `value`
state?: Record<string, unknown>; // optional rendering-hint state
source?: string; // optional runtime source path, e.g. "/walker/gps/mode"
}
Concepts describe what exists on the HUD, not how it looks.
Widgets
Widgets describe how a concept type should appear. They are keyed first by
concept type, then by class (with default as fallback).
type HUDWidgetMap = Record<string, Record<string, HUDWidgetDefinition>>;
A widget definition can hold any renderer-relevant fields. The GPS adapter
uses { type: "label", prefix: "MODE" } for the session-state concept, so any
renderer knows it should render a prefixed label without caring that the value
came from the runway arc.
Layout
The layout section assigns concepts to named zones and sets a priority.
interface HUDLayoutPlacement {
zone?: string; // e.g. "top", "center", "left"
priority?: number; // higher = rendered first
}
Priority is the primary sort key used when building the final output list.
Pipeline
The three phases happen in order for every frame where the surface changes.
1. Resolve
HUDResolver iterates over concepts and pairs each one with the most specific
matching widget definition it can find. The lookup order is:
- Exact class match under the concept's type
- The resolver's variant name (default:
"default") - The literal key
"default" - First key in the widget map for that type
The output is a list of HUDResolvedConcept objects that carry the concept
plus its resolved widget and layout placement.
const resolved = resolveHUD(config, variant);
2. Bind
bindSources walks the resolved list and, for any concept that has a source
key, replaces data.value with the matching entry from a runtime source-state
map.
const bound = bindSources(resolved, sourceState);
The source-state map is a flat Record<string, unknown> keyed by path strings
such as "/walker/gps/mode". This keeps the binding step pure and testable:
you can pass any map in tests without touching actual stores.
Updates to individual concepts without a full re-resolve are handled by
upsertConceptUpdates, which merges incoming partial lists by id in place so
ordering stays deterministic.
3. Sort
sortResolvedByPriority sorts the bound list by layout priority, highest
first. The sorted list is the final output consumed by renderers.
const sorted = sortResolvedByPriority(bound);
GPS adapter
src/hudyml/adapters/gpsAdapter.ts is the first production adapter. It
converts a GPSSurfaceModel (the normalized output of the GPS state pipeline)
into the three HUDyML inputs and then drives the full pipeline.
Source paths used:
| Path | Field |
|---|---|
/walker/gps/mode |
model.mode |
/walker/gps/warnings/count |
model.warningCount |
/walker/gps/waypoints/count |
model.waypointCount |
/walker/gps/projections/count |
model.routeProjectionCount |
/walker/gps/runway/current_phase |
model.runwayArc.currentArcPhase |
/walker/gps/runway/next_phase |
model.runwayArc.nextArcPhase |
/walker/gps/runway/max_energy |
model.runwayArc.maxEnergy |
/walker/gps/runway/active_index |
model.runwayArc.activeIndex |
The adapter exposes two helpers:
resolveGPSHUDConcepts(model)— returns the full sorted resolved concept list.renderGPSHUDLabels(model)— flattens the list tostring[]of the form"PREFIX value", suitable for quick label rendering in any text surface.
HUD integration
gpsSurfaceHud supports an optional pilot mode that renders HUDyML labels
alongside the standard GPS overlay output:
mountGPSSurfaceHud(panel, { useHUDymlPilot: true });
When the flag is off (the default), behavior is unchanged. When on, a
HUDyML pilot section appears below the headline containing one
"PREFIX value" line per resolved concept, in priority order.
This lets you verify HUDyML parity against the existing presenter output in the live display before switching surfaces over.
To enable it at runtime, set ENABLE_HUDYML_PILOT = true in src/main.js.
Extension
To add a new HUD surface:
- Create a source-state builder that maps your model fields to path strings.
- Create a concept list describing the semantic values your surface needs.
- Create a widget map describing how each concept type should look.
- Call
resolveHUD→bindSources→sortResolvedByPriority. - Write a renderer adapter that consumes the sorted
HUDResolvedConcept[].
The GPS adapter in src/hudyml/adapters/gpsAdapter.ts is the reference
implementation.