/**
 * Keyboard Diagnostic - visual keyboard with press / hold / 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)
 *
 * Port of a standalone blit386 keyboard test: every key is drawn on screen and
 * lights up green while held, yellow on `BT.isKeyPressed` (edge), red on
 * `BT.isKeyReleased`. Use this page to verify that fast repeated taps are not
 * dropped on high-refresh displays (120 Hz monitor with `targetFPS: 60`).
 *
 * The title strip and the status readouts (last event, tick, press/release
 * counts) come from the shared UI kit; the keyboard drawing itself stays
 * hand-rolled. On touch devices the kit shows a warm notice that this demo
 * needs a physical keyboard.
 *
 * Live version: https://demos.blit386.dev/keyboard-diagnostic
 */

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;
    ... 106 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 uiui, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js'; /** @typedef {import('blit386').IBTDemo} IBTDemo */ /** @typedef {import('blit386').HardwareSettings} HardwareSettings */ /** @typedef {import('blit386').Palette} Palette */ /** * @typedef {Object} KeyDef * @property {string} code KeyboardEvent.code string. * @property {string} label Short label drawn on the key cap. * @property {number} x Left edge in display pixels. * @property {number} y Top edge in display pixels. * @property {number} w Width in pixels. * @property {number} h Height in pixels. * @property {number} pressTimer Frames left to show the press flash. * @property {number} releaseTimer Frames left to show the release flash. */ /** * One key inside a row descriptor. Most keys are standard-sized and sit one key width * plus one KEY_GAP apart, so each entry only spells out its quirks (a wider cap, an extra gap, a jump * to a fixed column) and everything else falls back to the standard measurements. * * @typedef {Object} KeyRowEntry * @property {string} code KeyboardEvent.code string. * @property {string} label Short label drawn on the key cap. * @property {number} [w] Width in pixels, when the cap is wider or narrower than standard. * @property {number} [gapBefore] Extra empty pixels before this key (for cluster gaps). * @property {number} [x] Absolute left edge, when the key jumps to a fixed column. */ /** * One horizontal row of the on-screen keyboard picture. * * @typedef {Object} KeyRow * @property {number} startX Left edge of the first key in the row, in display pixels. * @property {number} y Top edge of every key in the row, in display pixels. * @property {KeyRowEntry[]} keys The keys, left to right. */ // 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 (panel // fills, borders, text) now comes from the shared UI theme installed in init(). const const C_LIT_HELD: 1C_LIT_HELD = 1; // Key-cap color while BT.isKeyDown is true (green). const const C_LIT_PRESS: 2C_LIT_PRESS = 2; // Key-cap color during the press flash (yellow). const const C_LIT_RELEASE: 3C_LIT_RELEASE = 3; // Key-cap color during the release flash (red). /** How many fixed ticks a press or release flash stays visible. */ const const FLASH_TICKS: 12
How many fixed ticks a press or release flash stays visible.
FLASH_TICKS
= 12;
const const KEY_WIDTH: 18KEY_WIDTH = 18; // Width of a standard 1u key cap, in pixels. const const KEY_HEIGHT: 18KEY_HEIGHT = 18; // Height of a standard key cap, in pixels. const const KEY_GAP: 2KEY_GAP = 2; // Empty space between adjacent key caps, in pixels. /** * The whole keyboard picture as data: six rows of key descriptors, top to bottom. * addKeyRow() walks each row like laying tiles on a shelf - it keeps a running x * position, places a key, then moves right by the key's width plus KEY_GAP. A key * only needs extra fields when it breaks the pattern: `w` for wide caps (Backspace, * Enter, Space), `gapBefore` for the small gaps between F-key clusters, and `x` * when a key jumps to a fixed column (the arrow cluster on the right). * * @type {KeyRow[]} */ const const KEYBOARD_ROWS: {}
The whole keyboard picture as data: six rows of key descriptors, top to bottom. addKeyRow() walks each row like laying tiles on a shelf - it keeps a running x position, places a key, then moves right by the key's width plus KEY_GAP. A key only needs extra fields when it breaks the pattern: `w` for wide caps (Backspace, Enter, Space), `gapBefore` for the small gaps between F-key clusters, and `x` when a key jumps to a fixed column (the arrow cluster on the right).
@type{KeyRow[]}
KEYBOARD_ROWS
= [
// Function row: Esc is a little wider, and the twelve F-keys sit in three // clusters of four with a 6-pixel breather between clusters (F4|F5 and F8|F9). { startX: numberstartX: 10, y: numbery: 50, keys: {}keys: [ { code: stringcode: 'Escape', label: stringlabel: 'Esc', w: numberw: 22 }, { code: stringcode: 'F1', label: stringlabel: 'F1', gapBefore: numbergapBefore: 4 }, { 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: 6 }, { 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: 6 }, { 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 wide Backspace at the end. { startX: numberstartX: 10, y: numbery: 72, keys: {}keys: [ { 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: 38 }, ], }, // QWERTY row: a wider Tab first, then standard keys, with a wider backslash cap. { startX: numberstartX: 10, y: numbery: 92, keys: {}keys: [ { code: stringcode: 'Tab', label: stringlabel: 'Tab', w: numberw: 27 }, { 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: 29 }, ], }, // Home row: a wide Caps Lock, standard letter keys, and a wide Enter at the end. { startX: numberstartX: 10, y: numbery: 112, keys: {}keys: [ { code: stringcode: 'CapsLock', label: stringlabel: 'Caps', w: numberw: 32 }, { 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: 44 }, ], }, // Bottom letter row: wide left Shift, standard keys, a narrow right Shift, and // the up arrow pinned to its own column on the right edge. { startX: numberstartX: 10, y: numbery: 132, keys: {}keys: [ { code: stringcode: 'ShiftLeft', label: stringlabel: 'Shift', w: numberw: 42 }, { 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: 14 }, { code: stringcode: 'ArrowUp', label: stringlabel: '^', x: numberx: 268 }, ], }, // Modifier row: every cap has its own width, and the left/down/right arrows jump // to a fixed column so they line up under the up arrow above. { startX: numberstartX: 10, y: numbery: 152, keys: {}keys: [ { code: stringcode: 'ControlLeft', label: stringlabel: 'Ctrl', w: numberw: 24 }, { code: stringcode: 'MetaLeft', label: stringlabel: 'Win', w: numberw: 18 }, { code: stringcode: 'AltLeft', label: stringlabel: 'Alt', w: numberw: 18 }, { code: stringcode: 'Space', label: stringlabel: 'Space', w: numberw: 96 }, { code: stringcode: 'AltRight', label: stringlabel: 'Alt', w: numberw: 18 }, { code: stringcode: 'MetaRight', label: stringlabel: 'Win', w: numberw: 18 }, { code: stringcode: 'ControlRight', label: stringlabel: 'Ctrl', w: numberw: 24 }, { code: stringcode: 'ArrowLeft', label: stringlabel: '<', x: numberx: 248 }, { code: stringcode: 'ArrowDown', label: stringlabel: 'v' }, { code: stringcode: 'ArrowRight', label: stringlabel: '>' }, ], }, ]; /** * Full keyboard layout diagnostic for edge-trigger testing. * * @implements {IBTDemo} */ class class Demo
Full keyboard layout diagnostic for edge-trigger testing.
@implementsIBTDemo
Demo
{
/** @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;
/** @type {KeyDef[]} */ Demo.keys: {}
@type{KeyDef[]}
keys
= [];
/** Human-readable description of the most recent key edge, shown in the status panel. */ Demo.lastEvent: string
Human-readable description of the most recent key edge, shown in the status panel.
lastEvent
= 'press any key';
/** Engine tick of the most recent key edge, or null before the first one. */ Demo.lastTick: null
Engine tick of the most recent key edge, or null before the first one.
lastTick
= null;
/** How many press edges we have seen in total. Fast-tap test: this must match releases. */ Demo.pressCount: number
How many press edges we have seen in total. Fast-tap test: this must match releases.
pressCount
= 0;
/** How many release edges we have seen in total. */ Demo.releaseCount: number
How many release edges we have seen in total.
releaseCount
= 0;
/** * @returns {Partial<HardwareSettings>} */ Demo.configure(): Partial<HardwareSettings>
Optional hook to declare display size, optional output drawing-buffer size, upscale filter, target fixed-update rate, rendering backend, and overlay. When omitted, the engine uses {@link defaultConfig } (`320x240` logical, `640x480` drawing buffer, `60` FPS, overlay enabled). When present, you may return only the fields you want to change; the engine merges them with {@link defaultConfig } via {@link mergeHardwareSettings } . Omit `displaySize` to inherit the full default resolution and output buffer. Include `displaySize` when you want a custom logical size; optional fields you omit then stay unset (for example no `drawingBufferSize` means a 1:1 drawing buffer).
@returns
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
(320, 240),
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
(960, 720),
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
(960, 720),
outputUpscaleFilter: stringoutputUpscaleFilter: 'nearest', targetFPS: numbertargetFPS: 60, // 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, }; } /** * @returns {Promise<boolean>} */ async Demo.init(): Promise<boolean>
Called once after the selected rendering backend has been initialized. Load assets and prepare a demo state here.
@returns
init
() {
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;
    ... 106 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, panel, // border, 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
);
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;
    ... 106 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
);
this.Demo.initKeyboardLayout(): void
Builds the on-screen key cap list (positions only; state lives on each KeyDef).
initKeyboardLayout
();
return true; } Demo.update(): void
Called zero or more times per frame at the fixed timestep declared by `targetFPS`. The accumulator pattern ensures the target rate is met on average, but a single frame may invoke this multiple times (catch-up) or not at all. Update simulation, timers, and input-driven state here. This is a hot path. Minimize allocations, reuse objects, and prefer in-place vector operations where possible. Avoid rendering work here; draw in `render()` instead.
update
() {
// 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;
    ... 106 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: 12
How many fixed ticks a press or release flash stays visible.
FLASH_TICKS
;
const key: anykey.releaseTimer = 0; this.Demo.lastEvent: string
Human-readable description of the most recent key edge, shown in the status panel.
lastEvent
= `PRESSED ${const key: anykey.code}`;
this.Demo.lastTick: null
Engine tick of the most recent key edge, or null before the first one.
lastTick
=
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;
    ... 106 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: number
How many press edges we have seen in total. Fast-tap test: this must match releases.
pressCount
+= 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;
    ... 106 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: 12
How many fixed ticks a press or release flash stays visible.
FLASH_TICKS
;
const key: anykey.pressTimer = 0; this.Demo.lastEvent: string
Human-readable description of the most recent key edge, shown in the status panel.
lastEvent
= `RELEASED ${const key: anykey.code}`;
this.Demo.lastTick: null
Engine tick of the most recent key edge, or null before the first one.
lastTick
=
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;
    ... 106 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: number
How many release edges we have seen in total.
releaseCount
+= 1;
} if (const key: anykey.pressTimer > 0) { const key: anykey.pressTimer -= 1; } if (const key: anykey.releaseTimer > 0) { const key: anykey.releaseTimer -= 1; } } } Demo.render(): void
Called once per `requestAnimationFrame` tick (browser refresh rate). Issue all draw calls for the current frame here. When {@link HardwareSettings.isOverlayEnabled } is `true` (default), the engine draws a screen-space overlay HUD after this method returns (present FPS, target FPS, draw calls, frame/update()/render() timings, backend, demo title). Optional {@link overlayRows } adds stacked bars above the footer. Demos do not need to duplicate engine overlay text. Reserve about ~42 px at the top and space for the bottom palette grid (or ~13 px when {@link HardwareSettings.isOverlayPaletteEnabled } is `false`) at the bottom (plus ~14 px per custom overlay row) for overlay bars, or disable the overlay in `configure()` when using custom full-screen HUD layouts. This is a hot path. Batch draws by texture to reduce GPU state changes and reuse Color32/Vector2i instances instead of allocating per frame. Avoid mutating the simulation state here unless it is strictly visual.
render
() {
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;
    ... 106 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);
// Draw every key cap first, so the kit panels below layer on top of nothing. for (let let i: numberi = 0; let i: numberi < this.Demo.keys: {}
@type{KeyDef[]}
keys
.length; let i: numberi++) {
this.Demo.renderKey(key: KeyDef): void
@paramkey
renderKey
(this.Demo.keys: {}
@type{KeyDef[]}
keys
[let i: numberi]);
} // The color legend stays hand-drawn: each word is printed in the actual // scene flash color it describes, which the kit's fixed theme roles cannot do.
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;
    ... 106 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
(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
(10, 173), const C_LIT_HELD: 1C_LIT_HELD, 'HELD (green)');
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;
    ... 106 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
(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
(104, 173), const C_LIT_PRESS: 2C_LIT_PRESS, 'PRESS (yellow)');
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;
    ... 106 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
(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
(210, 173), const C_LIT_RELEASE: 3C_LIT_RELEASE, 'RELEASE (red)');
// 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 120 Hz - yellow flash must not skip', { color: stringcolor: 'dim' }); } import uiui.end(); // Status readout: which edge fired last, and on which engine tick. import uiui.begin(import UI_ANCHORSUI_ANCHORS.BOTTOM_LEFT); import uiui.panel(); import uiui.kv('Last', this.Demo.lastEvent: string
Human-readable description of the most recent key edge, shown in the status panel.
lastEvent
);
import uiui.kv('Tick', this.Demo.lastTick: null
Engine tick of the most recent key edge, or null before the first one.
lastTick
=== null ? '-' : this.Demo.lastTick: never
Engine tick of the most recent key edge, or null before the first one.
lastTick
);
import uiui.end(); // Edge counters: after a burst of fast taps both numbers must match - a // mismatch means an edge was dropped somewhere. import uiui.begin(import UI_ANCHORSUI_ANCHORS.BOTTOM_RIGHT); import uiui.panel(); import uiui.kv('Presses', this.Demo.pressCount: number
How many press edges we have seen in total. Fast-tap test: this must match releases.
pressCount
);
import uiui.kv('Releases', this.Demo.releaseCount: number
How many release edges we have seen in total.
releaseCount
);
import uiui.end(); } /** * @param {KeyDef} key */ Demo.renderKey(key: KeyDef): void
@paramkey
renderKey
(key: KeyDef
@paramkey
key
) {
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;
    ... 106 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: KeyDef
@paramkey
key
.code: string
KeyboardEvent.code string.
code
);
const const isPressed: booleanisPressed = key: KeyDef
@paramkey
key
.pressTimer: number
Frames left to show the press flash.
pressTimer
> 0;
const const isReleased: booleanisReleased = key: KeyDef
@paramkey
key
.releaseTimer: number
Frames left to show the release flash.
releaseTimer
> 0;
// Resting keys use the shared theme's panel / dim-text colors; lit keys // switch to the scene flash colors with a contrasting label. let let color: anycolor = this.Demo.theme: any
@type{ReturnType<typeof applyTheme> | null}
theme
.panel;
let let textColor: anytextColor = this.Demo.theme: any
@type{ReturnType<typeof applyTheme> | null}
theme
.dim;
if (const isDown: booleanisDown) { let color: anycolor = const C_LIT_HELD: 1C_LIT_HELD; let textColor: anytextColor = this.Demo.theme: any
@type{ReturnType<typeof applyTheme> | null}
theme
.bg;
} else if (const isPressed: booleanisPressed) { let color: anycolor = const C_LIT_PRESS: 2C_LIT_PRESS; let textColor: anytextColor = this.Demo.theme: any
@type{ReturnType<typeof applyTheme> | null}
theme
.bg;
} else if (const isReleased: booleanisReleased) { let color: anycolor = const C_LIT_RELEASE: 3C_LIT_RELEASE; let textColor: anytextColor = this.Demo.theme: any
@type{ReturnType<typeof applyTheme> | null}
theme
.text;
} const const rect: Rect2irect = 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
(key: KeyDef
@paramkey
key
.x: number
Left edge in display pixels.
x
, key: KeyDef
@paramkey
key
.y: number
Top edge in display pixels.
y
, key: KeyDef
@paramkey
key
.w: number
Width in pixels.
w
, key: KeyDef
@paramkey
key
.h: number
Height in pixels.
h
);
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;
    ... 106 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
(const rect: Rect2irect, let color: anycolor);
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;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawRect: (rect: Rect2i, paletteIndex: number) => void
Draws an unfilled rectangle outline.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRect
(const rect: Rect2irect, this.Demo.theme: any
@type{ReturnType<typeof applyTheme> | null}
theme
.border);
const const labelSize: Vector2ilabelSize =
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;
    ... 106 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
(key: KeyDef
@paramkey
key
.label: string
Short label drawn on the key cap.
label
);
const const lx: anylx = key: KeyDef
@paramkey
key
.x: number
Left edge in display pixels.
x
+ Math.floor((key: KeyDef
@paramkey
key
.w: number
Width in pixels.
w
- const labelSize: Vector2ilabelSize.Vector2i.x: number
Horizontal component (defaults to 0).
x
) / 2);
const const ly: anyly = key: KeyDef
@paramkey
key
.y: number
Top edge in display pixels.
y
+ Math.floor((key: KeyDef
@paramkey
key
.h: number
Height in pixels.
h
- const labelSize: Vector2ilabelSize.Vector2i.y: number
Vertical component (defaults to 0).
y
) / 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;
    ... 106 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
(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 lx: anylx, const ly: anyly), let textColor: anytextColor, key: KeyDef
@paramkey
key
.label: string
Short label drawn on the key cap.
label
);
} /** * Appends one key definition to `this.keys` with its flash timers reset to zero. * * @param {string} code KeyboardEvent.code string. * @param {string} label Short label drawn on the key cap. * @param {number} x Left edge in display pixels. * @param {number} y Top edge in display pixels. * @param {number} w Width in pixels. * @param {number} h Height in pixels. */ Demo.addKey(code: string, label: string, x: number, y: number, w: number, h: number): void
Appends one key definition to `this.keys` with its flash timers reset to zero.
@paramcode KeyboardEvent.code string.@paramlabel Short label drawn on the key cap.@paramx Left edge in display pixels.@paramy Top edge in display pixels.@paramw Width in pixels.@paramh Height in pixels.
addKey
(code: string
KeyboardEvent.code string.
@paramcode KeyboardEvent.code string.
code
, label: string
Short label drawn on the key cap.
@paramlabel Short label drawn on the key cap.
label
, x: number
Left edge in display pixels.
@paramx Left edge in display pixels.
x
, y: number
Top edge in display pixels.
@paramy Top edge in display pixels.
y
, w: number
Width in pixels.
@paramw Width in pixels.
w
, h: number
Height in pixels.
@paramh Height in pixels.
h
) {
this.Demo.keys: {}
@type{KeyDef[]}
keys
.push({ code: stringcode, label: stringlabel, x: numberx, y: numbery, w: numberw, h: numberh, pressTimer: numberpressTimer: 0, releaseTimer: numberreleaseTimer: 0 });
} /** * Lays out one row of key caps from its descriptor, left to right. * * The running x position starts at the row's startX. For each key we first honor * its quirks - jump to an absolute column (`x`) or skip a few extra pixels * (`gapBefore`) - then place the cap and step right by its width plus the * standard KEY_GAP, ready for the next key. * * @param {KeyRow} row One entry of KEYBOARD_ROWS. */ Demo.addKeyRow(row: KeyRow): void
Lays out one row of key caps from its descriptor, left to right. The running x position starts at the row's startX. For each key we first honor its quirks - jump to an absolute column (`x`) or skip a few extra pixels (`gapBefore`) - then place the cap and step right by its width plus the standard KEY_GAP, ready for the next key.
@paramrow One entry of KEYBOARD_ROWS.
addKeyRow
(row: KeyRow
One entry of KEYBOARD_ROWS.
@paramrow One entry of KEYBOARD_ROWS.
row
) {
let let x: numberx = row: KeyRow
One entry of KEYBOARD_ROWS.
@paramrow One entry of KEYBOARD_ROWS.
row
.startX: number
Left edge of the first key in the row, in display pixels.
startX
;
for (const const key: anykey of row: KeyRow
One entry of KEYBOARD_ROWS.
@paramrow One entry of KEYBOARD_ROWS.
row
.keys: {}
The keys, left to right.
keys
) {
// A fixed column wins over the flowing position (used by the arrow cluster). if (typeof const key: anykey.x === 'number') { let x: numberx = const key: anykey.x; } // 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 x: numberx += const key: anykey.gapBefore ?? 0; // Wide and narrow caps say so; everyone else gets the standard width. const const w: anyw = const key: anykey.w ?? const KEY_WIDTH: 18KEY_WIDTH; this.Demo.addKey(code: string, label: string, x: number, y: number, w: number, h: number): void
Appends one key definition to `this.keys` with its flash timers reset to zero.
@paramcode KeyboardEvent.code string.@paramlabel Short label drawn on the key cap.@paramx Left edge in display pixels.@paramy Top edge in display pixels.@paramw Width in pixels.@paramh Height in pixels.
addKey
(const key: anykey.code, const key: anykey.label, let x: numberx, row: KeyRow
One entry of KEYBOARD_ROWS.
@paramrow One entry of KEYBOARD_ROWS.
row
.y: number
Top edge of every key in the row, in display pixels.
y
, const w: anyw, const KEY_HEIGHT: 18KEY_HEIGHT);
// Step past this cap and the standard gap so the next key lands beside it. let x: numberx += const w: anyw + const KEY_GAP: 2KEY_GAP; } } /** Builds the on-screen key cap list (positions only; state lives on each KeyDef). */ Demo.initKeyboardLayout(): void
Builds the on-screen key cap list (positions only; state lives on each KeyDef).
initKeyboardLayout
() {
// 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. for (const const row: anyrow of const KEYBOARD_ROWS: {}
The whole keyboard picture as data: six rows of key descriptors, top to bottom. addKeyRow() walks each row like laying tiles on a shelf - it keeps a running x position, places a key, then moves right by the key's width plus KEY_GAP. A key only needs extra fields when it breaks the pattern: `w` for wide caps (Backspace, Enter, Space), `gapBefore` for the small gaps between F-key clusters, and `x` when a key jumps to a fixed column (the arrow cluster on the right).
@type{KeyRow[]}
KEYBOARD_ROWS
) {
this.Demo.addKeyRow(row: KeyRow): void
Lays out one row of key caps from its descriptor, left to right. The running x position starts at the row's startX. For each key we first honor its quirks - jump to an absolute column (`x`) or skip a few extra pixels (`gapBefore`) - then place the cap and step right by its width plus the standard KEY_GAP, ready for the next key.
@paramrow One entry of KEYBOARD_ROWS.
addKeyRow
(const row: anyrow);
} } } 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 Demo
Full keyboard layout diagnostic for edge-trigger testing.
@implementsIBTDemo
Demo
);