/*

Named Colors: the Color32 named color registry and custom registration.
@description The Color32 named color registry: resolve a name to a color, register your own, and update it.

Part of the BLIT386 demo series.

Prerequisites:
  - Basics: https://demos.blit386.dev/basics
  - Colors: https://demos.blit386.dev/colors

Live version: https://demos.blit386.dev/named-colors

Guides:
  - Overlay: https://blit386.dev/docs/guides/overlay#optional-bands

Reference:
  - Core Types: https://blit386.dev/docs/api/core-types#color32
  - Overlay: https://blit386.dev/docs/api/overlay#event-tags

---

WHAT YOU WILL SEE
-----------------

A deep navy screen with a full-width title strip across the top that reads "Built-in
lookups + register / update / unregister". Below it, two UI kit panels share one width.
The "Named colors" panel holds four color swatches in a row, each with its name and a dim
"built-in" or "custom" tag centered under it:

- tomato: the built-in named color "tomato", a warm red.
- cornflower: the built-in named color "cornflowerblue", a soft blue.
- dynamic: a custom name, "demo-dynamic", whose color slides back and forth between the
  two built-ins.
- optional: a custom name, "demo-optional", a pink that disappears every 3 seconds. While
  the name is unregistered the swatch turns gray.

Under the swatches, three lines name the two custom colors and report whether
"demo-optional" is currently registered (green) or missing (orange). A "Tips" panel below
lists three rules of the registry.

WHAT YOU WILL LEARN
-------------------

- Color32.resolveNamedColor(name) looks a name up in the global registry and returns the
  Color32, or undefined when the name is unknown.
- Color32.registerColor(name, color) adds a new name. It throws if the name is taken.
- Color32.updateColor(name, color) changes an existing name's color. It throws if the name
  is missing.
- Color32.unregisterColor(name) removes a name. It throws if the name is missing.
- Names are trimmed and lowercased before matching, so 'Tomato' and ' tomato ' both find
  "tomato".
- Named colors are plain Color32 values: to draw with one, copy it into a palette slot in
  update() and draw with the slot number in render().
- Color32.lerp() blends two colors, and BT.assignTag() drops a label on the overlay's
  timing chart when something changes.

THINK OF THE REGISTRY AS A DICTIONARY
-------------------------------------

- The "word" is a name like "cornflowerblue".
- The "definition" is a Color32 value (r, g, b, a).
- resolveNamedColor(name) asks the dictionary: "Do you know this word?"
- registerColor() adds a new word, updateColor() rewrites a definition, and
  unregisterColor() tears the page out.

TRY THIS
--------

- Watch the "optional" swatch and the status line below it: every 3 seconds the name is
  unregistered (gray swatch, orange "missing" text) and 3 seconds later registered again
  (pink swatch, green "registered" text).
- Press the backquote key (`) to open the engine overlay. The timing chart is enabled
  here, and each toggle drops an "Optional registered" or "Optional removed" tag on it.

*/

import { function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>
One-liner bootstrap function for BLIT386 demos. Handles canvas retrieval and engine initialization. Backend selection (WebGPU or software fallback) is managed internally by BTAPI. This function provides a streamlined way to start a demo with sensible defaults while allowing customization through options.
@since0.2.0@changed1.4.0 Calling `bootstrap()` again while already initialized now routes to a hot swap (via {@link registerHotReload}) when a Vite HMR context is registered, or logs a double-bootstrap guard and returns `false` otherwise - previously it silently started a second, unstoppable `GameLoop`.@changed1.7.0 Exposes `BT` on `window.BT` after bootstrap finishes, gated by {@link BootstrapOptions.exposeGlobal} (default: {@link BT.isDevMode}).@paramDemoClass - Demo class constructor implementing `IBTDemo` (optional `configure()` for hardware settings).@paramoptions - Optional configuration for IDs and callbacks.@returns`true` when the demo boots successfully; otherwise `false`.@example// Simplest usage - uses default IDs. bootstrap(MyDemo);@example// With custom options. bootstrap(MyDemo, { canvasID: 'custom-canvas', containerID: 'custom-container', onSuccess: () => console.log('Demo started!'), onError: (err) => analytics.trackError(err), });@example// Await the result. const success = await bootstrap(MyDemo); if (success) { console.log('Demo is running'); }
bootstrap
,
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
, class Rect2i
Integer rectangle for pixel-perfect bounds and regions. Used throughout the engine for sprite regions, display-space bounds, and zero-allocation geometry helpers. Both convenience getters and allocation-free `*To()` helpers are provided so callers can choose between readability and hot-path efficiency.
@since0.1.0
Rect2i
} from 'blit386';
// The shared demo UI kit. applyTheme() installs the kit's twelve UI colors high in the // palette (slots 240-251, far above this demo's slots 1-9), and ui.* draws the title // strip, the captions, and the Tips panel. import { import applyThemeapplyTheme, import FONT_WFONT_W, import uiui, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js'; /** @typedef {import('blit386').Palette} Palette */ // Palette slots for this demo's own colors. Slot 0 stays transparent. Panels, captions, // and status text draw with the shared UI theme instead (installed by applyTheme() in // init()), so only two kinds of slots live down here: the overlay timing chart's bar // colors, and the lesson's swatch slots. // // The chart slots exist because configure() runs BEFORE init() installs the theme, so the // chart style cannot point at theme slots. init() copies the matching theme colors here. const const C_CHART_UPDATE: 1C_CHART_UPDATE = 1; // timing chart: update() bar (the theme's blue-gray border) const const C_CHART_RENDER: 2C_CHART_RENDER = 2; // timing chart: render() bar (the theme's off-white text) const const C_CHART_TAG: 3C_CHART_TAG = 3; // timing chart: tag labels (the theme's green accent) const const C_TOMATO: 5C_TOMATO = 5; // swatch slot for the built-in name "tomato" const const C_CORNFLOWER: 6C_CORNFLOWER = 6; // swatch slot for the built-in name "cornflowerblue" const const C_CUSTOM_DYNAMIC: 7C_CUSTOM_DYNAMIC = 7; // swatch slot for the animated custom name "demo-dynamic" const const C_OPTIONAL: 8C_OPTIONAL = 8; // swatch slot for the toggled custom name "demo-optional" const const C_OPTIONAL_FALLBACK: 9C_OPTIONAL_FALLBACK = 9; // gray shown while demo-optional is unregistered // The two custom names this demo registers. const const CUSTOM_DYNAMIC_NAME: "demo-dynamic"CUSTOM_DYNAMIC_NAME = 'demo-dynamic'; const const CUSTOM_OPTIONAL_NAME: "demo-optional"CUSTOM_OPTIONAL_NAME = 'demo-optional'; // The four swatches: which palette slot each one shows, its label, and whether the name // behind it is built into the engine or one this demo registers. const const SWATCHES: {}SWATCHES = [ { slot: numberslot: const C_TOMATO: 5C_TOMATO, label: stringlabel: 'tomato', kind: stringkind: 'built-in' }, { slot: numberslot: const C_CORNFLOWER: 6C_CORNFLOWER, label: stringlabel: 'cornflower', kind: stringkind: 'built-in' }, { slot: numberslot: const C_CUSTOM_DYNAMIC: 7C_CUSTOM_DYNAMIC, label: stringlabel: 'dynamic', kind: stringkind: 'custom' }, { slot: numberslot: const C_OPTIONAL: 8C_OPTIONAL, label: stringlabel: 'optional', kind: stringkind: 'custom' }, ]; // Swatch size in pixels. const const SWATCH_W: 56SWATCH_W = 56; const const SWATCH_H: 24SWATCH_H = 24; // Layout: two panels 4 pixels from the screen sides (312 pixels wide) and 4 pixels apart, // centered in the space between the 22-pixel title strip and the bottom 13 pixels the // engine overlay keeps for its toggle hint. // // - Named colors panel: 22 title + the swatch band (2 + 24 swatch + 3 + two 10-pixel // caption lines + 6) + three 10-pixel status lines + 11 bottom. // - Tips panel: 22 title + three 10-pixel lines + 11 bottom. const const EDGE: 4EDGE = 4; const const GAP: 4GAP = 4; const const PANEL_W: numberPANEL_W = 320 - 2 * const EDGE: 4EDGE; const const PANEL_PAD: 9PANEL_PAD = 9; const const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP = 22; const const SWATCH_BAND_H: numberSWATCH_BAND_H = 2 + const SWATCH_H: 24SWATCH_H + 3 + 2 * 10 + 6; const const COLORS_PANEL_H: numberCOLORS_PANEL_H = const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP + const SWATCH_BAND_H: numberSWATCH_BAND_H + 3 * 10 + 11; const const TIPS_PANEL_H: numberTIPS_PANEL_H = const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP + 3 * 10 + 11; const const FREE_TOP: 22FREE_TOP = 22; const const FREE_BOTTOM: numberFREE_BOTTOM = 240 - 13; const const COLORS_PANEL_Y: anyCOLORS_PANEL_Y = const FREE_TOP: 22FREE_TOP + Math.floor((const FREE_BOTTOM: numberFREE_BOTTOM - const FREE_TOP: 22FREE_TOP - const COLORS_PANEL_H: numberCOLORS_PANEL_H - const GAP: 4GAP - const TIPS_PANEL_H: numberTIPS_PANEL_H) / 2); const const TIPS_PANEL_Y: anyTIPS_PANEL_Y = const COLORS_PANEL_Y: anyCOLORS_PANEL_Y + const COLORS_PANEL_H: numberCOLORS_PANEL_H + const GAP: 4GAP; // The swatches sit in four equal cells across the panel's content width. const const CELL_W: numberCELL_W = (const PANEL_W: numberPANEL_W - 2 * const PANEL_PAD: 9PANEL_PAD) / const SWATCHES: {}SWATCHES.length; // Shows built-in and custom named colors: four swatches, a live status line, and tips. class class DemoDemo { // The palette holds every color this demo draws with. /** @type {Palette | null} */ Demo.palette: Palette | null
@type{Palette | null}
palette
= null;
// The palette slot numbers of the shared UI kit colors, filled in by applyTheme() in // init(). We use them for the background, the swatch panel frame, and the swatch // outlines and labels. Demo.theme: nulltheme = null; // Whether "demo-optional" is currently in the registry. update() flips it every 3 // seconds. Demo.optionalRegistered: booleanoptionalRegistered = true; // A clock that counts up in seconds, driving the blend and the 3-second toggle. Demo.elapsed: numberelapsed = 0; // Reused for every swatch rectangle drawn in render(), so drawing allocates no new // Rect2i. Demo.swatchRect: Rect2iswatchRect = new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2i
Creates an integer rectangle, truncating all inputs toward zero.
@paramx - Left-edge X coordinate (defaults to 0).@paramy - Top-edge Y coordinate (defaults to 0).@paramwidth - Width in pixels (defaults to 0).@paramheight - Height in pixels (defaults to 0).
Rect2i
(0, 0, 0, 0);
// Opt in to the overlay timing chart, with bar colors that match the UI theme.
Demo.configure(): {
    isOverlayTimingChartEnabled: boolean;
    overlayTimingChartStyle: {
        updateBarPaletteIndex: number;
        renderBarPaletteIndex: number;
        tagPaletteIndex: number;
    };
}
configure
() {
return { isOverlayTimingChartEnabled: booleanisOverlayTimingChartEnabled: true,
overlayTimingChartStyle: {
    updateBarPaletteIndex: number;
    renderBarPaletteIndex: number;
    tagPaletteIndex: number;
}
overlayTimingChartStyle
: {
updateBarPaletteIndex: numberupdateBarPaletteIndex: const C_CHART_UPDATE: 1C_CHART_UPDATE, renderBarPaletteIndex: numberrenderBarPaletteIndex: const C_CHART_RENDER: 2C_CHART_RENDER, tagPaletteIndex: numbertagPaletteIndex: const C_CHART_TAG: 3C_CHART_TAG, }, }; } // Builds the palette and registers the custom named colors. Returns true when ready. async Demo.init(): unknowninit() { this.Demo.palette: Palette | null
@type{Palette | null}
palette
=
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.paletteCreate: (size?: number) => Palette
Creates a standalone palette instance.
@since1.0.3@paramsize - Palette size. Defaults to 256 colors.@returnsNew mutable palette.
paletteCreate
(256);
// Overlay timing chart colors. These copy the shared theme's hex values into the // low slots configure() pointed the chart style at (see the note on the C_CHART_* // constants above for why the chart cannot use theme slots directly). this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_CHART_UPDATE: 1C_CHART_UPDATE, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.fromHex(hex: string): Color32
Parses a CSS-style hex color string. Supports `#RGB`, `#RGBA`, `#RRGGBB`, and `#RRGGBBAA`.
@paramhex - Hex color string with or without leading #.@returnsParsed color.@throwsError if the hex string format is invalid.
fromHex
('#5a6480')); // theme border
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_CHART_RENDER: 2C_CHART_RENDER, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.fromHex(hex: string): Color32
Parses a CSS-style hex color string. Supports `#RGB`, `#RGBA`, `#RRGGBB`, and `#RRGGBBAA`.
@paramhex - Hex color string with or without leading #.@returnsParsed color.@throwsError if the hex string format is invalid.
fromHex
('#e6ecf5')); // theme text
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_CHART_TAG: 3C_CHART_TAG, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.fromHex(hex: string): Color32
Parses a CSS-style hex color string. Supports `#RGB`, `#RGBA`, `#RRGGBB`, and `#RRGGBBAA`.
@paramhex - Hex color string with or without leading #.@returnsParsed color.@throwsError if the hex string format is invalid.
fromHex
('#7dffa5')); // theme accent green
// Fallback gray for the moments when demo-optional is unregistered. this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_OPTIONAL_FALLBACK: 9C_OPTIONAL_FALLBACK, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(80, 80, 80));
// Install the shared UI kit colors. They land in slots 240-251, far above the // swatch slots this demo animates (5-9), so the two can never collide. The // returned map remembers which slot each UI color went to (theme.bg, theme.text, // ...). this.Demo.theme: nulltheme = import applyThemeapplyTheme(this.Demo.palette: Palette
@type{Palette | null}
palette
);
// Remove old custom names before registering fresh ones. Hot reload can leave // names in memory from a previous run, and registerColor() throws when a name // already exists. this.Demo.removeIfExists(name: any): voidremoveIfExists(const CUSTOM_DYNAMIC_NAME: "demo-dynamic"CUSTOM_DYNAMIC_NAME); this.Demo.removeIfExists(name: any): voidremoveIfExists(const CUSTOM_OPTIONAL_NAME: "demo-optional"CUSTOM_OPTIONAL_NAME); // registerColor() adds a NEW entry to the global name table. It throws if the // name is already taken, which is why we cleared it above. 'demo-dynamic' gets // rewritten every tick with updateColor() in update(). class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.registerColor(name: string, color: Color32): void
Registers a named color in the global color registry. Name matching is case-insensitive and trims surrounding whitespace.
@paramname - Named color key (for example `cornflowerblue`).@paramcolor - Color value to store for that name.@throwsError when name is empty after normalization or already exists.
registerColor
(const CUSTOM_DYNAMIC_NAME: "demo-dynamic"CUSTOM_DYNAMIC_NAME, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(90, 170, 255));
// 'demo-optional' starts registered. update() unregisters and re-registers it on // a timer so you can watch resolveNamedColor() fall back to gray. class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.registerColor(name: string, color: Color32): void
Registers a named color in the global color registry. Name matching is case-insensitive and trims surrounding whitespace.
@paramname - Named color key (for example `cornflowerblue`).@paramcolor - Color value to store for that name.@throwsError when name is empty after normalization or already exists.
registerColor
(const CUSTOM_OPTIONAL_NAME: "demo-optional"CUSTOM_OPTIONAL_NAME, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(255, 90, 150));
this.Demo.optionalRegistered: booleanoptionalRegistered = true; this.Demo.elapsed: numberelapsed = 0; // Tell the engine to use this palette for all drawing.
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.paletteSet: (palette: Palette) => void
Stores the active engine palette. Use this to swap the **entire palette** (e.g. switch between a day and night theme). After this call the renderer uploads the new palette uniform on the next frame. **Palette-value swap (change what a slot looks like):** mutate the live {@link BT.palette } in place with `palette.set(slot, newColor)`. The renderer uploads dirty slots on the next frame; no `paletteSet()` or {@link BT.spritesRefresh } needed. **Palette-layout swap (same colors, different slot positions):** build a new palette with the same colors at new indices, call `paletteSet()`, then call {@link BT.spritesRefresh } so every sprite sheet re-maps its original RGBA pixels against the new slot layout.
@since1.0.3@parampalette - Palette to make active.
paletteSet
(this.Demo.palette: Palette
@type{Palette | null}
palette
);
// Run one update so the swatch slots hold real colors before the first render. // Without it, a frame drawn before the first update() shows four black swatches. // This priming call also advances the clock by one tick, the same as every // regular call from the engine's game loop. this.Demo.update(): voidupdate(); return true; } // Animates the custom named colors every tick. Demo.update(): voidupdate() { this.Demo.elapsed: numberelapsed +=
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.deltaSeconds: number
Fixed-step seconds per update tick. Equivalent to `1 / BT.targetFPS` when `BT.targetFPS` is finite and positive. Falls back to `1 / 60` when target FPS is non-finite or non-positive.
@since1.0.4@returnsSeconds advanced by one fixed update tick.
deltaSeconds
;
// Read two built-in names from the registry. resolveOr() carries the same values // as a fallback, so the swatches would still work if a name were ever missing. const const tomato: anytomato = this.Demo.resolveOr(name: any, fallback: any): anyresolveOr('tomato', new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(255, 99, 71));
const const cornflower: anycornflower = this.Demo.resolveOr(name: any, fallback: any): anyresolveOr('cornflowerblue', new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(100, 149, 237));
// Blend between the two built-ins to make a moving custom color. Math.sin() // returns -1..1, so (sin + 1) / 2 moves it to 0..1, and lerp() picks the color // that far between tomato and cornflower. const const t: numbert = (Math.sin(this.Demo.elapsed: numberelapsed * 2) + 1) / 2; const const dynamicColor: anydynamicColor = const tomato: anytomato.lerp(const cornflower: anycornflower, const t: numbert); class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.updateColor(name: string, color: Color32): void
Updates an existing named color in the global color registry. Name matching is case-insensitive and trims surrounding whitespace. All alias keys that share the same object reference are updated in sync.
@paramname - Existing named color key (or any alias).@paramcolor - Replacement color value.@throwsError when name is empty after normalization or not registered.
updateColor
(const CUSTOM_DYNAMIC_NAME: "demo-dynamic"CUSTOM_DYNAMIC_NAME, const dynamicColor: anydynamicColor);
// Every 3 seconds toggle demo-optional: registered, unregistered, registered, ... // Math.floor(elapsed / 3) counts whole 3-second blocks, and % 2 (the remainder // after dividing by 2) turns that count into 0, 1, 0, 1. const const cycle: numbercycle = Math.floor(this.Demo.elapsed: numberelapsed / 3) % 2; const const shouldBeRegistered: booleanshouldBeRegistered = const cycle: numbercycle === 0; if (const shouldBeRegistered: booleanshouldBeRegistered && !this.Demo.optionalRegistered: booleanoptionalRegistered) { // Put the name back and mark the moment on the overlay's timing chart. class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.registerColor(name: string, color: Color32): void
Registers a named color in the global color registry. Name matching is case-insensitive and trims surrounding whitespace.
@paramname - Named color key (for example `cornflowerblue`).@paramcolor - Color value to store for that name.@throwsError when name is empty after normalization or already exists.
registerColor
(const CUSTOM_OPTIONAL_NAME: "demo-optional"CUSTOM_OPTIONAL_NAME, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(255, 90, 150));
this.Demo.optionalRegistered: booleanoptionalRegistered = true;
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.assignTag: (label?: string) => void
Places a labeled marker on the overlay timing chart at the current tick. Requires `isOverlayTimingChartEnabled: true` in `configure()`. Tags scroll with the chart history and are pruned when they leave the visible window. Empty labels become `"Untitled"`. Chart width resets add an automatic `"Start"` tag.
@since1.1.0@paramlabel - Short event name (for example `'Round start'`).
assignTag
('Optional registered');
} else if (!const shouldBeRegistered: booleanshouldBeRegistered && this.Demo.optionalRegistered: booleanoptionalRegistered) { // Take the name out and mark that too. class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.unregisterColor(name: string): void
Removes a named color from the global color registry. Name matching is case-insensitive and trims surrounding whitespace. All alias keys that share the same object reference are removed in sync.
@paramname - Existing named color key (or any alias).@throwsError when name is empty after normalization or not registered.
unregisterColor
(const CUSTOM_OPTIONAL_NAME: "demo-optional"CUSTOM_OPTIONAL_NAME);
this.Demo.optionalRegistered: booleanoptionalRegistered = false;
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.assignTag: (label?: string) => void
Places a labeled marker on the overlay timing chart at the current tick. Requires `isOverlayTimingChartEnabled: true` in `configure()`. Tags scroll with the chart history and are pruned when they leave the visible window. Empty labels become `"Untitled"`. Chart width resets add an automatic `"Start"` tag.
@since1.1.0@paramlabel - Short event name (for example `'Round start'`).
assignTag
('Optional removed');
} // Copy the current named colors into the palette slots the draw calls use. this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_TOMATO: 5C_TOMATO, const tomato: anytomato);
this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_CORNFLOWER: 6C_CORNFLOWER, const cornflower: anycornflower);
this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_CUSTOM_DYNAMIC: 7C_CUSTOM_DYNAMIC, this.Demo.resolveOr(name: any, fallback: any): anyresolveOr(const CUSTOM_DYNAMIC_NAME: "demo-dynamic"CUSTOM_DYNAMIC_NAME, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(255, 255, 255)));
// While demo-optional is unregistered, show the reserved C_OPTIONAL_FALLBACK gray // (set in init()) instead of looking up the built-in name "gray". this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_OPTIONAL: 8C_OPTIONAL, this.Demo.resolveOr(name: any, fallback: any): anyresolveOr(const CUSTOM_OPTIONAL_NAME: "demo-optional"CUSTOM_OPTIONAL_NAME, this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.get(index: number): Color32
Returns a copy of the color stored at a palette index. Returns a clone so callers cannot mutate internal state without going through {@link set } , which keeps the dirty flag accurate.
@paramindex - Palette index to read.@returnsClone of the stored color entry.@throwsError if the index is invalid.
get
(const C_OPTIONAL_FALLBACK: 9C_OPTIONAL_FALLBACK)));
} // Draws the two kit panels: the swatches with the live registry status, and the // tips. Demo.render(): voidrender() { // Fill the screen with the shared UI theme's deep navy background.
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.clear: (paletteIndex: number) => void
Sets the frame clear color using a palette index. The renderer uses this color when clearing the full display at the start of the next frame.
@since0.1.0@parampaletteIndex - Palette index for the full-screen clear pass.
clear
(this.Demo.theme: nulltheme.bg);
// Full-width title strip across the top, drawn by the shared UI kit. 'topBar' is // the classic 22-pixel strip; panel() gives it a background and a title. import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_BAR); import uiui.panel('Built-in lookups + register / update / unregister'); import uiui.end(); // The Named colors panel. ui.spacer() reserves the band the swatches are drawn // into after ui.end(); the three status lines follow under it. import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: const EDGE: 4EDGE, y: anyy: const COLORS_PANEL_Y: anyCOLORS_PANEL_Y, width: numberwidth: const PANEL_W: numberPANEL_W }); import uiui.panel('Named colors'); import uiui.spacer(const SWATCH_BAND_H: numberSWATCH_BAND_H); import uiui.label("Custom dynamic name: 'demo-dynamic'"); import uiui.label("Custom optional name: 'demo-optional'"); // The status line flips between the theme's green 'accent' and orange 'warm' // roles, so you can watch registerColor / unregisterColor take effect every 3 // seconds (update() drives the toggle). if (this.Demo.optionalRegistered: booleanoptionalRegistered) { import uiui.label('demo-optional is registered (registerColor)', { color: stringcolor: 'accent' }); } else { import uiui.label('demo-optional is missing (unregisterColor)', { color: stringcolor: 'warm' }); } import uiui.end(); // The four swatches, one per cell, on top of the panel. for (let let i: numberi = 0; let i: numberi < const SWATCHES: {}SWATCHES.length; let i: numberi++) { this.Demo.drawSwatch(cellX: any, swatch: any): voiddrawSwatch(const EDGE: 4EDGE + const PANEL_PAD: 9PANEL_PAD + let i: numberi * const CELL_W: numberCELL_W, const SWATCHES: {}SWATCHES[let i: numberi]); } // The Tips panel below it, the same width. import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: const EDGE: 4EDGE, y: anyy: const TIPS_PANEL_Y: anyTIPS_PANEL_Y, width: numberwidth: const PANEL_W: numberPANEL_W }); import uiui.panel('Tips'); import uiui.label('Names are trim + lowercase normalized.', { color: stringcolor: 'dim' }); import uiui.label('registerColor throws if the name exists.', { color: stringcolor: 'dim' }); import uiui.label('updateColor / unregisterColor throw if missing.', { color: stringcolor: 'dim' }); import uiui.end(); } // Looks up a named color and returns `fallback` when the name is missing. Demo.resolveOr(name: any, fallback: any): anyresolveOr(name: anyname, fallback: anyfallback) { const const resolved: Color32 | undefinedresolved = class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.resolveNamedColor(name: string): Color32 | undefined
Looks up a named color from the global registry. Name matching is case-insensitive and trims surrounding whitespace.
@paramname - Named color key to resolve.@returnsFrozen singleton color, or `undefined` when the name is unknown.
resolveNamedColor
(name: anyname);
if (const resolved: Color32 | undefinedresolved === var undefinedundefined) { return fallback: anyfallback; } return const resolved: Color32resolved; } // Removes a custom name if it exists, so init() can register it cleanly. This makes // hot reload safer during development. Demo.removeIfExists(name: any): voidremoveIfExists(name: anyname) { if (class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.resolveNamedColor(name: string): Color32 | undefined
Looks up a named color from the global registry. Name matching is case-insensitive and trims surrounding whitespace.
@paramname - Named color key to resolve.@returnsFrozen singleton color, or `undefined` when the name is unknown.
resolveNamedColor
(name: anyname) !== var undefinedundefined) {
class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.unregisterColor(name: string): void
Removes a named color from the global color registry. Name matching is case-insensitive and trims surrounding whitespace. All alias keys that share the same object reference are removed in sync.
@paramname - Existing named color key (or any alias).@throwsError when name is empty after normalization or not registered.
unregisterColor
(name: anyname);
} } // Draws one swatch centered in the cell that starts at `cellX`, with its label and // its built-in / custom tag centered under it. `swatch` is one SWATCHES entry. Demo.drawSwatch(cellX: any, swatch: any): voiddrawSwatch(cellX: anycellX, swatch: anyswatch) { const const centerX: anycenterX = cellX: anycellX + const CELL_W: numberCELL_W / 2; const const x: anyx = Math.round(const centerX: anycenterX - const SWATCH_W: 56SWATCH_W / 2); const const y: anyy = const COLORS_PANEL_Y: anyCOLORS_PANEL_Y + const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP + 2; // Fill the swatch with the palette slot we are demonstrating: the lesson itself. // The outline is chrome, so it uses the shared UI theme's border color. Both use // the kit's sprite drawing (ui.drawRectFill / ui.drawRect): the engine draws // every BT.drawRectFill() before any sprite, and the kit panel is a sprite, so a // plain rectangle would hide under it. this.Demo.swatchRect: Rect2iswatchRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2i
Sets all components of this rectangle. Modifies this rectangle directly for maximum performance. WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.
@paramx - New x position.@paramy - New y position.@paramwidth - New width.@paramheight - New height.@returnsThis rectangle for chaining.
set
(const x: anyx, const y: anyy, const SWATCH_W: 56SWATCH_W, const SWATCH_H: 24SWATCH_H);
import uiui.drawRectFill(this.Demo.swatchRect: Rect2iswatchRect, swatch: anyswatch.slot); import uiui.drawRect(this.Demo.swatchRect: Rect2iswatchRect, this.Demo.theme: nulltheme.border); // Two caption lines centered under the swatch. Every character of the system // font is FONT_W (6) pixels wide, so a line starts half its width left of the // middle; a caption's text starts 4 pixels above the y it is given. const const textY: anytextY = const y: anyy + const SWATCH_H: 24SWATCH_H + 3; import uiui.caption(Math.round(const centerX: anycenterX - (swatch: anyswatch.label.length * import FONT_WFONT_W) / 2), const textY: anytextY + 4, swatch: anyswatch.label, { color: stringcolor: 'text', }); import uiui.caption(Math.round(const centerX: anycenterX - (swatch: anyswatch.kind.length * import FONT_WFONT_W) / 2), const textY: anytextY + 14, swatch: anyswatch.kind, { color: stringcolor: 'dim' }); } } // Hand the Demo class to BLIT386 to start the demo loop. function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>
One-liner bootstrap function for BLIT386 demos. Handles canvas retrieval and engine initialization. Backend selection (WebGPU or software fallback) is managed internally by BTAPI. This function provides a streamlined way to start a demo with sensible defaults while allowing customization through options.
@since0.2.0@changed1.4.0 Calling `bootstrap()` again while already initialized now routes to a hot swap (via {@link registerHotReload}) when a Vite HMR context is registered, or logs a double-bootstrap guard and returns `false` otherwise - previously it silently started a second, unstoppable `GameLoop`.@changed1.7.0 Exposes `BT` on `window.BT` after bootstrap finishes, gated by {@link BootstrapOptions.exposeGlobal} (default: {@link BT.isDevMode}).@paramDemoClass - Demo class constructor implementing `IBTDemo` (optional `configure()` for hardware settings).@paramoptions - Optional configuration for IDs and callbacks.@returns`true` when the demo boots successfully; otherwise `false`.@example// Simplest usage - uses default IDs. bootstrap(MyDemo);@example// With custom options. bootstrap(MyDemo, { canvasID: 'custom-canvas', containerID: 'custom-container', onSuccess: () => console.log('Demo started!'), onError: (err) => analytics.trackError(err), });@example// Await the result. const success = await bootstrap(MyDemo); if (success) { console.log('Demo is running'); }
bootstrap
(class DemoDemo);