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 hiddenPalette 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
| State | What draws |
|---|---|
| Body hidden (default) | Optional bottom-left toggle hint icon when isOverlayToggleHintVisible |
| Body visible | Title, metrics, optional timing chart, optional palette grid, optional custom rows, footer hint |
Toggle the body at runtime:
- Backquote (
~) whenisOverlayToggleEnabledis 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
| Feature | Configure flag | Notes |
|---|---|---|
| Timing chart | isOverlayTimingChartEnabled | Scrolling dots for update/render ms; BT.assignTag('label') marks events |
| Palette grid | isOverlayPaletteEnabled | Live swatch grid; wheel scroll when rows exceed visible cap (forces capture over the band) |
| GPU diagnostics row | isOverlayRendererDiagnosticsBarEnabled | Text row below frame metrics |
| Chart GPU markers | overlayTimingChartDiagnostics | 'minimal' (default when chart on), 'rich', or false |
| Audio meters | isOverlayAudioMetersEnabled | Per-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
Targetin the overlay =BT.targetFPS(fixedupdate()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
API: Overlay
API: Assets - System Font
API: Palette
Input Guide
Palette Presets
Testing
Deprecation Timeline
Last updated on October 18, 2026