/*

Keyboard Diagnostic: a visual keyboard with press, hold, and release feedback.
@description A full on-screen keyboard with press, hold, and release feedback, to verify fast taps on any display.

Part of the BLIT386 demo series.

Prerequisites:
  - Keyboard Input: https://demos.blit386.dev/keyboard-input

Live version: https://demos.blit386.dev/keyboard-diagnostic

Guides:
  - Input: https://blit386.dev/docs/guides/input#keyboard
  - Input: https://blit386.dev/docs/guides/input#frame-timing-semantics

---

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

A 600x315 picture of a full keyboard: six rows of key caps from the function row down to
the modifier row, with an arrow cluster on the right. Every cap is the same rounded block
the System Font Glyphs demo uses for its cells (a UI kit button face), with a centered
label; the arrow keys are labeled with the system font's arrow characters. The whole cap
turns green while its key is held, flashes yellow for 12 ticks on the press edge, and
flashes red for 12 ticks on the release edge, with a lighter rim in the same color and
its label in the dark background color. A legend under the keyboard names the three
colors next to small solid squares.

A title strip runs across the top with a one-line hint under it (a warm "This demo needs
a keyboard" notice on touch devices). Two equal panels sit under the legend, lined up
with the keyboard's edges: "Last edge" shows the most recent edge, its key code, and the
engine tick it fired on; "Edge counts" shows the running press and release counts and a
"Counts match" pip that stays lit while the two are equal.

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

- BT.isKeyDown(code) is held state and can be read from render(); BT.isKeyPressed(code)
  and BT.isKeyReleased(code) are one-tick edges that clear at the end of every fixed
  update, so they are read in update() and remembered in per-key flash timers.
- Keyboard edges are buffered on the DOM events and consumed by the next fixed update, so
  a tap shorter than one tick still produces one press and one release.
- KeyboardEvent.code strings (KeyQ, ShiftLeft, ArrowUp) name physical keys independent of
  the OS layout.
- A keyboard layout described as data: a row table measured in key widths ("units"),
  which addKeyRow() walks to place caps, with per-key overrides for width, extra gaps,
  and fixed columns.
- Hardware settings: a 600x315 display drawn into a 1200x630 drawing buffer with
  outputUpscaleFilter 'nearest' and targetFPS 60, plus isCapturingKeyboardScroll.
- The shared UI kit's ui.drawButtonFace() and ui.drawButtonFill() for the caps, and
  BT.systemPrint with BT.systemPrintMeasure for centering a label on a cap.

TRY THIS
--------

- Tap a key as fast as you can, several times in a row. Every tap must show a yellow
  flash followed by a red one, and the "Presses" and "Releases" counts must stay equal -
  the "Counts match" pip goes dark the moment they differ. A mismatch means an edge was
  dropped.
- Run the page on a high-refresh display (120 Hz or more). targetFPS is 60, so some drawn
  frames run no update at all; the edge buffering keeps fast taps from falling into those
  frames.
- Hold a key and watch it stay green; the yellow flash ends after 12 ticks while the key
  is still held.
- Press Shift, Ctrl, Alt, and the OS key on both sides to see that left and right have
  separate codes.
- Click the canvas if keys stop responding - focus has moved elsewhere on the page.

WHY 600 BY 315?
---------------

That is exactly half of the 1200x630 social card the demo site generates, so the card
shows the screen at a clean 2x scale with no blurry resampling - the same size the System
Font Glyphs demo uses, so the two pages' key-cap blocks match pixel for pixel.

*/

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
, class Vector2i
Integer 2D vector for pixel-perfect positioning. Used for points, sizes, directions, and camera offsets throughout the engine. The API includes both allocation-free `*To()` / `*InPlace()` variants and convenience methods that return new vectors.
@since0.1.0
Vector2i
} from 'blit386';
import { import applyThemeapplyTheme, import BUTTON_LOOKBUTTON_LOOK, import FONT_WFONT_W, import litRimColorlitRimColor, import uiui, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js'; /** @typedef {import('blit386').Palette} Palette */ /** @typedef {{ code: string, label: string, x: number, y: number, w: number, pressTimer: number, releaseTimer: number }} KeyDef */ /** @typedef {{ code: string, label: string, w?: number, gapBefore?: number, at?: number }} KeyRowEntry */ // The screen is 600x315 display pixels: exactly half of the 1200x630 social card, and the // same size as the System Font Glyphs demo whose cells the key caps match. const const DISPLAY_W: 600DISPLAY_W = 600; const const DISPLAY_H: 315DISPLAY_H = 315; // Scene palette slots for the three key-cap flash states. These stay demo-owned (not // shared UI theme colors) because the legend and the key caps must show the exact same // green / yellow / red the diagnostic is about. Everything else (cap faces, panels, text) // comes from the shared UI theme installed in init(). const const C_LIT_HELD: 1C_LIT_HELD = 1; // key-cap fill while BT.isKeyDown is true (green) const const C_LIT_PRESS: 2C_LIT_PRESS = 2; // key-cap fill during the press flash (yellow) const const C_LIT_RELEASE: 3C_LIT_RELEASE = 3; // key-cap fill during the release flash (red) // A lighter shade of each flash color for the lit cap's rim, written in init(). Each is // the flash color brightened by exactly the step between the theme's resting button fill // and its border, so a lit cap's rim stands out from its fill as much as a resting cap's // does. const const C_RIM_HELD: 4C_RIM_HELD = 4; const const C_RIM_PRESS: 5C_RIM_PRESS = 5; const const C_RIM_RELEASE: 6C_RIM_RELEASE = 6; // The three flash colors, each with the slot of its lighter rim. const const FLASH_RIMS: {}FLASH_RIMS = [ { fill: numberfill: const C_LIT_HELD: 1C_LIT_HELD, rim: numberrim: const C_RIM_HELD: 4C_RIM_HELD }, { fill: numberfill: const C_LIT_PRESS: 2C_LIT_PRESS, rim: numberrim: const C_RIM_PRESS: 5C_RIM_PRESS }, { fill: numberfill: const C_LIT_RELEASE: 3C_LIT_RELEASE, rim: numberrim: const C_RIM_RELEASE: 6C_RIM_RELEASE }, ]; // How many fixed ticks a press or release flash stays visible. const const FLASH_TICKS: 12FLASH_TICKS = 12; // Key caps match the System Font Glyphs demo's cells: 23 pixels square with a 2-pixel // gap, so one "unit" (the width of a standard key) is 25 pixels from one cap's left edge // to the next. A key that is w units wide is w x 25 - 2 pixels wide, so its right edge // still leaves the 2-pixel gap before the next cap. const const KEY_SIZE: 23KEY_SIZE = 23; const const KEY_GAP: 2KEY_GAP = 2; const const UNIT: numberUNIT = const KEY_SIZE: 23KEY_SIZE + const KEY_GAP: 2KEY_GAP; // The whole keyboard picture as data: six rows, top to bottom, measured in units. Each // key has a `code` (KeyboardEvent.code string) and a `label` drawn on the cap. // addKeyRow() walks each row like laying tiles on a shelf: it keeps a running position, // places a key, then moves right by the key's width. A key only needs extra fields when // it breaks the pattern: `w` for wide or narrow caps (Backspace, Enter, Space), // `gapBefore` for the half-unit gaps between F-key clusters, and `at` when a key jumps to // a fixed column (the arrow cluster on the right). Every row of the main block is 15 // units wide, the standard keyboard proportions; the arrows sit half a unit to its right. /** @type {KeyRowEntry[][]} */ const const KEYBOARD_ROWS: {}
@type{KeyRowEntry[][]}
KEYBOARD_ROWS
= [
// Function row: Esc, then the twelve F-keys in three clusters of four, half a unit // apart. [ { code: stringcode: 'Escape', label: stringlabel: 'Esc' }, { code: stringcode: 'F1', label: stringlabel: 'F1', gapBefore: numbergapBefore: 1 }, { code: stringcode: 'F2', label: stringlabel: 'F2' }, { code: stringcode: 'F3', label: stringlabel: 'F3' }, { code: stringcode: 'F4', label: stringlabel: 'F4' }, { code: stringcode: 'F5', label: stringlabel: 'F5', gapBefore: numbergapBefore: 0.5 }, { code: stringcode: 'F6', label: stringlabel: 'F6' }, { code: stringcode: 'F7', label: stringlabel: 'F7' }, { code: stringcode: 'F8', label: stringlabel: 'F8' }, { code: stringcode: 'F9', label: stringlabel: 'F9', gapBefore: numbergapBefore: 0.5 }, { code: stringcode: 'F10', label: stringlabel: 'F10' }, { code: stringcode: 'F11', label: stringlabel: 'F11' }, { code: stringcode: 'F12', label: stringlabel: 'F12' }, ], // Number row: thirteen standard keys, then a 2-unit Backspace. [ { code: stringcode: 'Backquote', label: stringlabel: '`' }, { code: stringcode: 'Digit1', label: stringlabel: '1' }, { code: stringcode: 'Digit2', label: stringlabel: '2' }, { code: stringcode: 'Digit3', label: stringlabel: '3' }, { code: stringcode: 'Digit4', label: stringlabel: '4' }, { code: stringcode: 'Digit5', label: stringlabel: '5' }, { code: stringcode: 'Digit6', label: stringlabel: '6' }, { code: stringcode: 'Digit7', label: stringlabel: '7' }, { code: stringcode: 'Digit8', label: stringlabel: '8' }, { code: stringcode: 'Digit9', label: stringlabel: '9' }, { code: stringcode: 'Digit0', label: stringlabel: '0' }, { code: stringcode: 'Minus', label: stringlabel: '-' }, { code: stringcode: 'Equal', label: stringlabel: '=' }, { code: stringcode: 'Backspace', label: stringlabel: 'Back', w: numberw: 2 }, ], // QWERTY row: a 1.5-unit Tab, standard keys, and a 1.5-unit backslash. [ { code: stringcode: 'Tab', label: stringlabel: 'Tab', w: numberw: 1.5 }, { code: stringcode: 'KeyQ', label: stringlabel: 'Q' }, { code: stringcode: 'KeyW', label: stringlabel: 'W' }, { code: stringcode: 'KeyE', label: stringlabel: 'E' }, { code: stringcode: 'KeyR', label: stringlabel: 'R' }, { code: stringcode: 'KeyT', label: stringlabel: 'T' }, { code: stringcode: 'KeyY', label: stringlabel: 'Y' }, { code: stringcode: 'KeyU', label: stringlabel: 'U' }, { code: stringcode: 'KeyI', label: stringlabel: 'I' }, { code: stringcode: 'KeyO', label: stringlabel: 'O' }, { code: stringcode: 'KeyP', label: stringlabel: 'P' }, { code: stringcode: 'BracketLeft', label: stringlabel: '[' }, { code: stringcode: 'BracketRight', label: stringlabel: ']' }, { code: stringcode: 'Backslash', label: stringlabel: '\\', w: numberw: 1.5 }, ], // Home row: a 1.75-unit Caps Lock, standard letter keys, and a 2.25-unit Enter. [ { code: stringcode: 'CapsLock', label: stringlabel: 'Caps', w: numberw: 1.75 }, { code: stringcode: 'KeyA', label: stringlabel: 'A' }, { code: stringcode: 'KeyS', label: stringlabel: 'S' }, { code: stringcode: 'KeyD', label: stringlabel: 'D' }, { code: stringcode: 'KeyF', label: stringlabel: 'F' }, { code: stringcode: 'KeyG', label: stringlabel: 'G' }, { code: stringcode: 'KeyH', label: stringlabel: 'H' }, { code: stringcode: 'KeyJ', label: stringlabel: 'J' }, { code: stringcode: 'KeyK', label: stringlabel: 'K' }, { code: stringcode: 'KeyL', label: stringlabel: 'L' }, { code: stringcode: 'Semicolon', label: stringlabel: ';' }, { code: stringcode: 'Quote', label: stringlabel: "'" }, { code: stringcode: 'Enter', label: stringlabel: 'Enter', w: numberw: 2.25 }, ], // Bottom letter row: a 2.25-unit left Shift, standard keys, a 2.75-unit right Shift, // and the up arrow pinned to the middle column of the arrow cluster. [ { code: stringcode: 'ShiftLeft', label: stringlabel: 'Shift', w: numberw: 2.25 }, { code: stringcode: 'KeyZ', label: stringlabel: 'Z' }, { code: stringcode: 'KeyX', label: stringlabel: 'X' }, { code: stringcode: 'KeyC', label: stringlabel: 'C' }, { code: stringcode: 'KeyV', label: stringlabel: 'V' }, { code: stringcode: 'KeyB', label: stringlabel: 'B' }, { code: stringcode: 'KeyN', label: stringlabel: 'N' }, { code: stringcode: 'KeyM', label: stringlabel: 'M' }, { code: stringcode: 'Comma', label: stringlabel: ',' }, { code: stringcode: 'Period', label: stringlabel: '.' }, { code: stringcode: 'Slash', label: stringlabel: '/' }, { code: stringcode: 'ShiftRight', label: stringlabel: 'Shift', w: numberw: 2.75 }, { code: stringcode: 'ArrowUp', label: stringlabel: '\u2191', at: numberat: 16.5 }, ], // Modifier row: six 1.5-unit modifiers around a 6-unit Space, filling the same 15 // units as every other row (6 x 1.5 + 6 = 15), then the left, down, and right arrows // starting half a unit past the main block, under the up arrow. [ { code: stringcode: 'ControlLeft', label: stringlabel: 'Ctrl', w: numberw: 1.5 }, { code: stringcode: 'MetaLeft', label: stringlabel: 'Win', w: numberw: 1.5 }, { code: stringcode: 'AltLeft', label: stringlabel: 'Alt', w: numberw: 1.5 }, { code: stringcode: 'Space', label: stringlabel: 'Space', w: numberw: 6 }, { code: stringcode: 'AltRight', label: stringlabel: 'Alt', w: numberw: 1.5 }, { code: stringcode: 'MetaRight', label: stringlabel: 'Win', w: numberw: 1.5 }, { code: stringcode: 'ControlRight', label: stringlabel: 'Ctrl', w: numberw: 1.5 }, { code: stringcode: 'ArrowLeft', label: stringlabel: '\u2190', at: numberat: 15.5 }, { code: stringcode: 'ArrowDown', label: stringlabel: '\u2193' }, { code: stringcode: 'ArrowRight', label: stringlabel: '\u2192' }, ], ]; // The keyboard is 18.5 units wide (15 for the main block, half a unit of gap, 3 for the // arrows) and 6 rows tall, with an extra 6 pixels between the function row and the rest. const const KEYBOARD_UNITS_W: 18.5KEYBOARD_UNITS_W = 18.5; const const KEYBOARD_W: numberKEYBOARD_W = const KEYBOARD_UNITS_W: 18.5KEYBOARD_UNITS_W * const UNIT: numberUNIT - const KEY_GAP: 2KEY_GAP; const const FN_ROW_GAP: 6FN_ROW_GAP = 6; const const KEYBOARD_H: numberKEYBOARD_H = const KEYBOARD_ROWS: {}
@type{KeyRowEntry[][]}
KEYBOARD_ROWS
.length * const UNIT: numberUNIT - const KEY_GAP: 2KEY_GAP + const FN_ROW_GAP: 6FN_ROW_GAP;
// Vertical layout under the 32-pixel title strip (a 22-pixel strip that grows by its hint // row): the keyboard, a 10-pixel gap, the 12-pixel legend row, another 10-pixel gap, and // the two 63-pixel status panels (22 title + 3 rows of 10 + 11 bottom). The whole block // is centered in the space below the strip, and everything is centered across the // screen. const const TITLE_STRIP_H: 32TITLE_STRIP_H = 32; const const LEGEND_H: 12LEGEND_H = 12; const const STATUS_PANEL_H: numberSTATUS_PANEL_H = 22 + 3 * 10 + 11; const const BLOCK_H: numberBLOCK_H = const KEYBOARD_H: numberKEYBOARD_H + 10 + const LEGEND_H: 12LEGEND_H + 10 + const STATUS_PANEL_H: numberSTATUS_PANEL_H; const const KEYBOARD_X: anyKEYBOARD_X = Math.floor((const DISPLAY_W: 600DISPLAY_W - const KEYBOARD_W: numberKEYBOARD_W) / 2); const const KEYBOARD_Y: anyKEYBOARD_Y = const TITLE_STRIP_H: 32TITLE_STRIP_H + Math.floor((const DISPLAY_H: 315DISPLAY_H - const TITLE_STRIP_H: 32TITLE_STRIP_H - const BLOCK_H: numberBLOCK_H) / 2); const const LEGEND_Y: anyLEGEND_Y = const KEYBOARD_Y: anyKEYBOARD_Y + const KEYBOARD_H: numberKEYBOARD_H + 10; const const STATUS_PANEL_Y: anySTATUS_PANEL_Y = const LEGEND_Y: anyLEGEND_Y + const LEGEND_H: 12LEGEND_H + 10; // The two status panels share the keyboard's width, 4 pixels apart. const const STATUS_PANEL_W: numberSTATUS_PANEL_W = (const KEYBOARD_W: numberKEYBOARD_W - 4) / 2; // The legend: one small block per flash color and a dim label after it. const const LEGEND_ITEMS: {}LEGEND_ITEMS = [ { slot: numberslot: const C_LIT_HELD: 1C_LIT_HELD, label: stringlabel: 'Held' }, { slot: numberslot: const C_LIT_PRESS: 2C_LIT_PRESS, label: stringlabel: 'Press flash' }, { slot: numberslot: const C_LIT_RELEASE: 3C_LIT_RELEASE, label: stringlabel: 'Release flash' }, ]; const const LEGEND_BLOCK: 8LEGEND_BLOCK = 8; // Full keyboard layout diagnostic for edge-trigger testing. class class DemoDemo { /** @type {Palette | null} */ Demo.palette: Palette | null
@type{Palette | null}
palette
= null;
// Palette slots of the shared UI theme colors, filled by applyTheme() in init(). /** @type {ReturnType<typeof applyTheme> | null} */ Demo.theme: any
@type{ReturnType<typeof applyTheme> | null}
theme
= null;
// One entry per on-screen key cap, built by initKeyboardLayout(): `code`, `label`, // the cap's left edge, top edge, and width (`x`, `y`, `w`; every cap is KEY_SIZE // tall), and two countdowns in ticks, `pressTimer` and `releaseTimer`, for the press // and release flashes. /** @type {KeyDef[]} */ Demo.keys: {}
@type{KeyDef[]}
keys
= [];
// The most recent key edge for the Last edge panel: which kind it was, the key code, // and the engine tick it fired on (null before the first one). Demo.lastEdge: stringlastEdge = '-'; Demo.lastCode: stringlastCode = 'press any key'; Demo.lastTick: nulllastTick = null; // How many press edges we have seen in total. Fast-tap test: this must match // releases. Demo.pressCount: numberpressCount = 0; // How many release edges we have seen in total. Demo.releaseCount: numberreleaseCount = 0; // Reused for every rectangle and label position drawn in render(), so drawing // allocates no new objects. Demo.tempRect: Rect2itempRect = 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);
Demo.tempVec: Vector2itempVec = new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(0, 0);
// Glyph height of the system font, measured in init() for centering labels. Demo.glyphHeight: numberglyphHeight = 14; // A 600x315 display rendered into a 1200x630 buffer with nearest-neighbor upscaling, // locked to 60 ticks per second so the display refresh rate and the tick rate differ // on fast monitors.
Demo.configure(): {
    displaySize: Vector2i;
    drawingBufferSize: Vector2i;
    maxCanvasSize: Vector2i;
    outputUpscaleFilter: string;
    targetFPS: number;
    isOverlayEnabled: boolean;
    isCapturingKeyboardScroll: boolean;
}
configure
() {
return { displaySize: Vector2idisplaySize: new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(const DISPLAY_W: 600DISPLAY_W, const DISPLAY_H: 315DISPLAY_H),
drawingBufferSize: Vector2idrawingBufferSize: new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(const DISPLAY_W: 600DISPLAY_W * 2, const DISPLAY_H: 315DISPLAY_H * 2),
maxCanvasSize: Vector2imaxCanvasSize: new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(const DISPLAY_W: 600DISPLAY_W * 2, const DISPLAY_H: 315DISPLAY_H * 2),
outputUpscaleFilter: stringoutputUpscaleFilter: 'nearest', targetFPS: numbertargetFPS: 60, isOverlayEnabled: booleanisOverlayEnabled: false, // This page lights up arrow keys and Space on the on-screen keyboard. Opt in // so those keys do not scroll the demo page while you are testing them. isCapturingKeyboardScroll: booleanisCapturingKeyboardScroll: true, }; } // Palette setup, shared UI theme, and the key cap layout. 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);
// The three flash colors live in low scene slots; the legend and the key caps // both draw with them so the colors always match. 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_LIT_HELD: 1C_LIT_HELD, 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
(75, 210, 120, 255));
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_LIT_PRESS: 2C_LIT_PRESS, 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, 230, 80, 255));
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_LIT_RELEASE: 3C_LIT_RELEASE, 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
(240, 80, 80, 255));
// applyTheme() installs the twelve shared UI colors (background, button faces, // text, ...) high in the palette and reports their slots, so the key caps and the // kit widgets all draw from one consistent theme. this.Demo.theme: any
@type{ReturnType<typeof applyTheme> | null}
theme
= import applyThemeapplyTheme(this.Demo.palette: Palette
@type{Palette | null}
palette
);
// The lit caps' rims: each flash color brightened by the step from the theme's // resting button fill to its border. litRimColor() reads both from the palette, // so the two always match. palette.get() returns a copy of the color in a slot. for (const { const fill: anyfill, const rim: anyrim } of const FLASH_RIMS: {}FLASH_RIMS) { 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 rim: anyrim, import litRimColorlitRimColor(this.Demo.palette: Palette
@type{Palette | null}
palette
, this.Demo.palette: Palette
@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 fill: anyfill)));
}
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
);
// Measure the system font once so every label can be centered on its cap. this.Demo.glyphHeight: numberglyphHeight =
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
.systemPrintMeasure: (text: string) => Vector2i
Measures the pixel dimensions of a string rendered with the built-in system font.
@since1.0.3@paramtext - Text string to measure.@returnsWidth and height in pixels, or `Vector2i.zero()` before engine initialization.
systemPrintMeasure
('M').Vector2i.y: number
Vertical component (defaults to 0).
y
;
// Start every run from a clean slate. init() runs again whenever BT.renderAt() // replays the demo from the start (the /shots page and the social card do that), // and fields keep their old values across that replay - so the counters and the // last edge are reset here, and the key list is rebuilt rather than added to. // Without this, every key would sit in the list twice and each press would count // twice. this.Demo.lastEdge: stringlastEdge = '-'; this.Demo.lastCode: stringlastCode = 'press any key'; this.Demo.lastTick: nulllastTick = null; this.Demo.pressCount: numberpressCount = 0; this.Demo.releaseCount: numberreleaseCount = 0; this.Demo.initKeyboardLayout(): voidinitKeyboardLayout(); return true; } // Reads the press and release edges for every key and runs the flash countdowns. Demo.update(): voidupdate() { // Let the UI kit track touch contacts first - ui.hasTouch() in render() relies on // this housekeeping running every update tick. import uiui.tick(); for (let let i: numberi = 0; let i: numberi < this.Demo.keys: {}
@type{KeyDef[]}
keys
.length; let i: numberi++) {
const const key: anykey = this.Demo.keys: {}
@type{KeyDef[]}
keys
[let i: numberi];
// Whichever edge fires most recently owns the flash: starting one flash // cancels the other, so a fast tap cannot let two competing countdowns reach // zero together and skip the release color entirely. if (
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
.isKeyPressed: (key: string, repeatRate?: number) => boolean
Checks whether a keyboard key was pressed on the current fixed-update tick. Optional `repeatRate` is in fixed ticks between repeats (`0` or omitted = edge only). When `repeatRate > 0`, repeats fire while held per `(ticks - firstPressTick) > 0 && (ticks - firstPressTick) % repeatRate === 0`. Call from `update()`, not `render()`: the press edge clears once per fixed-update tick, which always runs before that frame's `render()`, so a press read from `render()` can be intermittently missed under rapid input.
@since1.1.1@paramkey - DOM keyboard code string.@paramrepeatRate - Ticks between repeat triggers; omit or `0` for no repeat.@returns`true` on the press edge (and on repeat ticks when configured).
isKeyPressed
(const key: anykey.code)) {
const key: anykey.pressTimer = const FLASH_TICKS: 12FLASH_TICKS; const key: anykey.releaseTimer = 0; this.Demo.lastEdge: stringlastEdge = 'pressed'; this.Demo.lastCode: stringlastCode = const key: anykey.code; this.Demo.lastTick: nulllastTick =
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
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
;
this.Demo.pressCount: numberpressCount += 1; } if (
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
.isKeyReleased: (key: string) => boolean
Checks whether a keyboard key was released on the current frame. Call from `update()`, not `render()`: the release edge clears once per fixed-update tick, which always runs before that frame's `render()`, so a release read from `render()` can be intermittently missed under rapid input.
@since1.1.1@paramkey - DOM keyboard code string.@returns`true` on the release edge.
isKeyReleased
(const key: anykey.code)) {
const key: anykey.releaseTimer = const FLASH_TICKS: 12FLASH_TICKS; const key: anykey.pressTimer = 0; this.Demo.lastEdge: stringlastEdge = 'released'; this.Demo.lastCode: stringlastCode = const key: anykey.code; this.Demo.lastTick: nulllastTick =
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
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
;
this.Demo.releaseCount: numberreleaseCount += 1; } // Count both flashes down by one tick. if (const key: anykey.pressTimer > 0) { const key: anykey.pressTimer -= 1; } if (const key: anykey.releaseTimer > 0) { const key: anykey.releaseTimer -= 1; } } } // Draws the title strip, the key caps, the legend, and the two status panels. Demo.render(): voidrender() {
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: any
@type{ReturnType<typeof applyTheme> | null}
theme
.bg);
// Full-width title strip. The second row is contextual: touch devices get a // warning that the demo is pointless without a keyboard, everyone else gets the // fast-tap testing hint. import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_BAR); import uiui.panel('BLIT386 Keyboard Diagnostic'); if (import uiui.hasTouch()) { import uiui.label('This demo needs a keyboard', { color: stringcolor: 'warm' }); } else { import uiui.label('Tap fast on a 120 Hz display - every press flash needs a release flash', { color: stringcolor: 'dim' }); } import uiui.end(); for (let let i: numberi = 0; let i: numberi < this.Demo.keys: {}
@type{KeyDef[]}
keys
.length; let i: numberi++) {
this.Demo.renderKey(key: any): voidrenderKey(this.Demo.keys: {}
@type{KeyDef[]}
keys
[let i: numberi]);
} this.Demo.renderLegend(): voidrenderLegend(); this.Demo.renderStatusPanels(): voidrenderStatusPanels(); } // Draws one key cap in its current state: held wins over the press flash, which wins // over the release flash. Demo.renderKey(key: any): voidrenderKey(key: anykey) { const const isDown: booleanisDown =
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
.isKeyDown: (key: string) => boolean
Checks whether a keyboard key is currently held. Uses `KeyboardEvent.code` (for example `"KeyW"`, `"Space"`, `"ArrowUp"`).
@since1.1.1@paramkey - DOM keyboard code string.@returns`true` while the key remains pressed.
isKeyDown
(key: anykey.code);
const const isPressed: booleanisPressed = key: anykey.pressTimer > 0; const const isReleased: booleanisReleased = key: anykey.releaseTimer > 0; // Which flash color the cap wears right now (0 for none), and its lighter rim. let let fillSlot: numberfillSlot = 0; let let rimSlot: numberrimSlot = 0; if (const isDown: booleanisDown) { let fillSlot: numberfillSlot = const C_LIT_HELD: 1C_LIT_HELD; let rimSlot: numberrimSlot = const C_RIM_HELD: 4C_RIM_HELD; } else if (const isPressed: booleanisPressed) { let fillSlot: numberfillSlot = const C_LIT_PRESS: 2C_LIT_PRESS; let rimSlot: numberrimSlot = const C_RIM_PRESS: 5C_RIM_PRESS; } else if (const isReleased: booleanisReleased) { let fillSlot: numberfillSlot = const C_LIT_RELEASE: 3C_LIT_RELEASE; let rimSlot: numberrimSlot = const C_RIM_RELEASE: 6C_RIM_RELEASE; } // The cap is the same rounded block the System Font Glyphs demo draws its cells // with: a kit button face. A resting cap uses the plain face and a dim label. A // lit cap uses ui.drawButtonFill() - the same face, filled with the scene flash // color and rimmed with its lighter shade - and prints its label in the dark // background color, so it reads clearly on the bright yellow, green, or red. this.Demo.tempRect: Rect2itempRect.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
(key: anykey.x, key: anykey.y, key: anykey.w, const KEY_SIZE: 23KEY_SIZE);
if (let fillSlot: numberfillSlot === 0) { import uiui.drawButtonFace(this.Demo.tempRect: Rect2itempRect, import BUTTON_LOOKBUTTON_LOOK.REST); } else { import uiui.drawButtonFill(this.Demo.tempRect: Rect2itempRect, let fillSlot: numberfillSlot, let rimSlot: numberrimSlot); } const const textSlot: anytextSlot = let fillSlot: numberfillSlot === 0 ? this.Demo.theme: any
@type{ReturnType<typeof applyTheme> | null}
theme
.dim : this.Demo.theme: any
@type{ReturnType<typeof applyTheme> | null}
theme
.bg;
// Center the label on the cap. Every character of the system font is FONT_W (6) // pixels wide, but its pixels fill only the first 5 columns - the 6th is the gap // before the next character - so the visible text is one pixel narrower than // length x 6, and centering on that keeps it truly in the middle. Vertically, the // glyph cell sits one pixel below plain centering, which balances the capital // letters between the cap's top edge and its slightly heavier bottom rim. // BT.systemPrint() draws text as sprites too, so it lands on top of the face. const const textW: numbertextW = key: anykey.label.length * import FONT_WFONT_W - 1; this.Demo.tempVec: Vector2itempVec.Vector2i.set(x: number, y: number): Vector2i
Sets both components of this vector. Modifies this vector directly for maximum performance. WARNING: Mutates this vector. Don't use on frozen/cached singletons.
@paramx - New x component.@paramy - New y component.@returnsThis vector for chaining.
set
(
key: anykey.x + Math.floor((key: anykey.w - const textW: numbertextW) / 2), key: anykey.y + Math.floor((const KEY_SIZE: 23KEY_SIZE - this.Demo.glyphHeight: numberglyphHeight) / 2) + 1, );
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
.systemPrint: (pos: Vector2i, paletteIndex: number, text: string) => void
Draws text using the built-in 6x14 system font. The system font covers printable ASCII (characters 32-126). For custom bitmap fonts with proportional glyphs, use {@link BT.printFont } instead.
@since1.0.3@parampos - Text origin in display coordinates.@parampaletteIndex - Palette color index for the text.@paramtext - String to render.
systemPrint
(this.Demo.tempVec: Vector2itempVec, const textSlot: anytextSlot, key: anykey.label);
} // Draws the legend under the keyboard: for each flash color, a small solid square of // that color and a dim label, the row centered across the screen. Demo.renderLegend(): voidrenderLegend() { // Work out the row's width first: block, 4 pixels, label, then 16 pixels before // the next item. let let rowW: numberrowW = 0; for (let let i: numberi = 0; let i: numberi < const LEGEND_ITEMS: {}LEGEND_ITEMS.length; let i: numberi++) { let rowW: numberrowW += const LEGEND_BLOCK: 8LEGEND_BLOCK + 4 + const LEGEND_ITEMS: {}LEGEND_ITEMS[let i: numberi].label.length * import FONT_WFONT_W + (let i: numberi > 0 ? 16 : 0); } let let x: anyx = Math.floor((const DISPLAY_W: 600DISPLAY_W - let rowW: numberrowW) / 2); for (const const item: anyitem of const LEGEND_ITEMS: {}LEGEND_ITEMS) { // The square covers rows LEGEND_Y + 2 to LEGEND_Y + 9 of the 12-pixel row. this.Demo.tempRect: Rect2itempRect.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
(let x: anyx, const LEGEND_Y: anyLEGEND_Y + 2, const LEGEND_BLOCK: 8LEGEND_BLOCK, const LEGEND_BLOCK: 8LEGEND_BLOCK);
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
.drawRectFill: (rect: Rect2i, paletteIndex: number) => void
Draws a filled rectangle.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRectFill
(this.Demo.tempRect: Rect2itempRect, const item: anyitem.slot);
// A caption's capital letters cover 7 rows starting 1 row above the y it is // given, so LEGEND_Y + 4 puts them on rows LEGEND_Y + 3 to LEGEND_Y + 9, // level with the square. import uiui.caption(let x: anyx + const LEGEND_BLOCK: 8LEGEND_BLOCK + 4, const LEGEND_Y: anyLEGEND_Y + 4, const item: anyitem.label, { color: stringcolor: 'dim' }); let x: anyx += const LEGEND_BLOCK: 8LEGEND_BLOCK + 4 + const item: anyitem.label.length * import FONT_WFONT_W + 16; } } // The two status panels under the legend, lined up with the keyboard's edges. Demo.renderStatusPanels(): voidrenderStatusPanels() { // Which edge fired last, on which key, and on which engine tick. import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: anyx: const KEYBOARD_X: anyKEYBOARD_X, y: anyy: const STATUS_PANEL_Y: anySTATUS_PANEL_Y, width: numberwidth: const STATUS_PANEL_W: numberSTATUS_PANEL_W }); import uiui.panel('Last edge'); import uiui.kv('Edge', this.Demo.lastEdge: stringlastEdge); import uiui.kv('Code', this.Demo.lastCode: stringlastCode); import uiui.kv('Tick', this.Demo.lastTick: nulllastTick === null ? '-' : this.Demo.lastTick: neverlastTick); import uiui.end(); // Edge counters: after a burst of fast taps both numbers must match - a mismatch // means an edge was dropped somewhere. The pip makes that visible at a glance. import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: anyx: const KEYBOARD_X: anyKEYBOARD_X + const STATUS_PANEL_W: numberSTATUS_PANEL_W + 4, y: anyy: const STATUS_PANEL_Y: anySTATUS_PANEL_Y, width: numberwidth: const STATUS_PANEL_W: numberSTATUS_PANEL_W, }); import uiui.panel('Edge counts'); import uiui.kv('Presses', this.Demo.pressCount: numberpressCount); import uiui.kv('Releases', this.Demo.releaseCount: numberreleaseCount); import uiui.pip('Counts match', this.Demo.pressCount: numberpressCount === this.Demo.releaseCount: numberreleaseCount); import uiui.end(); } // Appends one key definition to `this.keys` with its flash timers reset to zero. // `x`, `y`, `w` are the cap's left edge, top edge, and width in display pixels. Demo.addKey(code: any, label: any, x: any, y: any, w: any): voidaddKey(code: anycode, label: anylabel, x: anyx, y: anyy, w: anyw) { this.Demo.keys: {}
@type{KeyDef[]}
keys
.push({ code: anycode, label: anylabel, x: anyx, y: anyy, w: anyw, pressTimer: numberpressTimer: 0, releaseTimer: numberreleaseTimer: 0 });
} // Lays out one row of KEYBOARD_ROWS, left to right, at the top edge `y`. The running // position `u` (in units) starts at 0, the keyboard's left edge. For each key we // first honor its quirks - jump to an absolute column (`at`) or skip extra space // (`gapBefore`) - then place the cap and step right by its width, ready for the next // key. Units become pixels by multiplying by UNIT; Math.round keeps every edge on a // whole pixel for the quarter-unit widths. Demo.addKeyRow(row: any, y: any): voidaddKeyRow(row: anyrow, y: anyy) { let let u: numberu = 0; for (const const key: anykey of row: anyrow) { // A fixed column wins over the flowing position (used by the arrow cluster). if (typeof const key: anykey.at === 'number') { let u: numberu = const key: anykey.at; } // Extra breathing room before this key, like the gap between F-key clusters. // The ?? operator means "use the left value unless it is missing, then 0". let u: numberu += const key: anykey.gapBefore ?? 0; // Wide and narrow caps say so; everyone else is one unit wide. const const w: anyw = const key: anykey.w ?? 1; const const x: anyx = const KEYBOARD_X: anyKEYBOARD_X + Math.round(let u: numberu * const UNIT: numberUNIT); const const right: numberright = const KEYBOARD_X: anyKEYBOARD_X + Math.round((let u: numberu + const w: anyw) * const UNIT: numberUNIT) - const KEY_GAP: 2KEY_GAP; this.Demo.addKey(code: any, label: any, x: any, y: any, w: any): voidaddKey(const key: anykey.code, const key: anykey.label, const x: anyx, y: anyy, const right: numberright - const x: anyx); let u: numberu += const w: anyw; } } // Builds the on-screen key cap list (positions only; state lives on each entry). Demo.initKeyboardLayout(): voidinitKeyboardLayout() { // A fresh, empty list every time, so a second init() replaces the caps instead of // adding a second copy of each. this.Demo.keys: {}
@type{KeyDef[]}
keys
= [];
// The layout itself lives in KEYBOARD_ROWS near the top of the file; here we just // walk the six rows and let addKeyRow() place every cap. Every row after the // function row sits FN_ROW_GAP pixels lower, like a real keyboard. for (let let i: numberi = 0; let i: numberi < const KEYBOARD_ROWS: {}
@type{KeyRowEntry[][]}
KEYBOARD_ROWS
.length; let i: numberi++) {
const const y: anyy = const KEYBOARD_Y: anyKEYBOARD_Y + let i: numberi * const UNIT: numberUNIT + (let i: numberi > 0 ? const FN_ROW_GAP: 6FN_ROW_GAP : 0); this.Demo.addKeyRow(row: any, y: any): voidaddKeyRow(const KEYBOARD_ROWS: {}
@type{KeyRowEntry[][]}
KEYBOARD_ROWS
[let i: numberi], const y: anyy);
} } } 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);