BLIT386BLIT386

Overlay

The engine overlay HUD: metrics, timing chart, palette grid, toggle, and layout.

The engine overlay is a screen-space HUD drawn after each demo render() when HardwareSettings.isOverlayEnabled is true (default). It shows present FPS, target FPS, draw calls, frame timings, active backend, resolution, and demo title. Demos should not duplicate this text.

Configure-time flags, style objects, and worked examples live in API: Overlay. This guide maps the internal subsystem and common integration patterns.

Subsystem layout

src/overlay/
  Overlay.ts              # Orchestrator: sample, toggle, layout plan, delegate draws
  layout/layoutPlan.ts    # Computes Y positions for bars, chart, palette grid, custom rows
  bars/Bars.ts            # Filled bar bands + label text (OverlayBars)
  audio-meter/            # Per-bus level bars + voices/steal/drop readout
  timing-chart/           # Scrolling update/render timing dots + severity/tags
  palette/                # Live swatch grid, hover tooltip, clipboard copy, scroll
  sampling/               # FpsSampler, TimingSampler
  input/Toggle.ts         # Backquote + bottom-left corner toggle
  OverlayToggleIcon.ts    # Bitmap hint icon when body is hidden

Palette index usage for the swatch grid is tracked in src/core/RenderPaletteUsage.ts (only when the overlay body is visible and isOverlayPaletteEnabled is true).

Visibility model

StateWhat draws
Body hidden (default)Optional bottom-left toggle hint icon when isOverlayToggleHintVisible
Body visibleTitle, metrics, optional timing chart, optional palette grid, optional custom rows, footer hint

Toggle the body at runtime:

  • Backquote (~) when isOverlayToggleEnabled is true
  • Primary pointer press in the bottom-left 17×13 px corner

Set isOverlayVisibleAtStart: true to show the body on the first frame. Set isOverlayEnabled: false to disable the subsystem entirely (for example release builds or full-screen custom HUD demos). Set isOverlayToggleHitDebugVisible: true to outline the 17×13 toggle hit region while you tune it.

Custom rows

Demos may implement optional overlayRows() on IBTDemo. The engine calls it once per frame after render() when the overlay body is visible. Return a reused array of row objects when possible - avoid allocating new strings every frame.

/** @implements {IBTDemo} */
class  {
  readonly #overlayRows = [{ : 'Score: 0' }];
   = 0;

  () {
    this.#overlayRows[0]. = `Score: ${this.}`;
    return this.#overlayRows;
  }
}

Each OverlayRow supports leftText, optional rightText, and optional per-row barPaletteIndex / textPaletteIndex. Rows stack upward from the footer with 1 px gaps.

Optional bands

FeatureConfigure flagNotes
Timing chartisOverlayTimingChartEnabledScrolling dots for update/render ms; BT.assignTag('label') marks events
Palette gridisOverlayPaletteEnabledLive swatch grid; wheel scroll when rows exceed visible cap (forces capture over the band)
GPU diagnostics rowisOverlayRendererDiagnosticsBarEnabledText row below frame metrics
Chart GPU markersoverlayTimingChartDiagnostics'minimal' (default when chart on), 'rich', or false
Audio metersisOverlayAudioMetersEnabledPer-bus (main/music/sfx) level bars plus a voices/steal/drop text readout

Reserve vertical space in demo layouts: ~42 px top, ~14 px per custom row, timing chart height (default 22 px), audio meter band height plus a 1 px gap when enabled (14 px default), palette grid height when enabled, and ~13 px footer hint bar. See API: Overlay for layout formulas.

Colors and HUD palette slots

Overlay chrome uses palette indices from overlayStyle (defaults: bar/gap 1, text 2). The gap index also fills the 1px vertical dividers separating segments within the engine rows (metrics, timing, diagnostics, and the audio meter readout), so those separators match the row gaps between bands; each divider keeps 7px of space to the text on both sides, matching the 7px text inset from the screen edges. For demos that draw their own HUD text with BT.systemPrint, call palette.applyHUD(startSlot?) once at init to fill the six common UI slots (white, background, label, header, dim, FPS) and register hud_* name aliases. See API: Palette - applyHUD and Palette Presets - HUD.

const  = .();
.(1); // slots 1-6 + hud_* aliases
.();

.(new (8, 8), .('hud_label'), 'Custom row');

Present FPS vs. target FPS

  • Target in the overlay = BT.targetFPS (fixed update() rate).
  • Present: N FPS = measured browser refresh cadence while the overlay body is visible - not the same as target FPS.

Use BT.deltaSeconds / BT.ticks for gameplay timing; use present FPS to spot GPU or draw-call bottlenecks.

Stable column widths

The present-FPS, timing (Frame/update()/render()), and GPU diagnostics rows pad their numeric fields to a fixed character width before drawing. Without this, a value's digit count changing between frames (for example 8.3 becoming 16.7, or the update() step suffix like x3 appearing only when the fixed-update loop runs a catch-up burst) would shift every later segment on that row, since segments are drawn left to right based on the previous segment's rendered width. Padding keeps the | dividers and the segments after them in place regardless of how the values change.

API history

See also

Last updated on October 18, 2026

On this page