Skip to content

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:

  1. Exact class match under the concept's type
  2. The resolver's variant name (default: "default")
  3. The literal key "default"
  4. 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 to string[] 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:

  1. Create a source-state builder that maps your model fields to path strings.
  2. Create a concept list describing the semantic values your surface needs.
  3. Create a widget map describing how each concept type should look.
  4. Call resolveHUDbindSourcessortResolvedByPriority.
  5. Write a renderer adapter that consumes the sorted HUDResolvedConcept[].

The GPS adapter in src/hudyml/adapters/gpsAdapter.ts is the reference implementation.