/**
* 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.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 Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32, class Rect2iInteger 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.Rect2i, class Vector2iInteger 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.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: 12How 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).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 DemoFull keyboard layout diagnostic for edge-trigger testing.Demo {
/** @type {Palette | null} */
Demo.palette: Palette | nullpalette = null;
// Palette slots of the shared UI theme colors, filled by applyTheme() in init().
/** @type {ReturnType<typeof applyTheme> | null} */
Demo.theme: anytheme = null;
/** @type {KeyDef[]} */
Demo.keys: {}keys = [];
/** Human-readable description of the most recent key edge, shown in the status panel. */
Demo.lastEvent: stringHuman-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: nullEngine 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: numberHow 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: numberHow 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).configure() {
return {
displaySize: Vector2idisplaySize: new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(320, 240),
drawingBufferSize: Vector2idrawingBufferSize: new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(960, 720),
maxCanvasSize: Vector2imaxCanvasSize: new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.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.init() {
this.Demo.palette: Palette | nullpalette = 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) => PaletteCreates a standalone palette instance.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: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_LIT_HELD: 1C_LIT_HELD, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(75, 210, 120, 255));
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_LIT_PRESS: 2C_LIT_PRESS, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(255, 230, 80, 255));
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_LIT_RELEASE: 3C_LIT_RELEASE, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.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: anytheme = import applyThemeapplyTheme(this.Demo.palette: Palettepalette);
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) => voidStores 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.paletteSet(this.Demo.palette: Palettepalette);
this.Demo.initKeyboardLayout(): voidBuilds the on-screen key cap list (positions only; state lives on each KeyDef).initKeyboardLayout();
return true;
}
Demo.update(): voidCalled 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: {}keys.length; let i: numberi++) {
const const key: anykey = this.Demo.keys: {}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) => booleanChecks 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.isKeyPressed(const key: anykey.code)) {
const key: anykey.pressTimer = const FLASH_TICKS: 12How many fixed ticks a press or release flash stays visible.FLASH_TICKS;
const key: anykey.releaseTimer = 0;
this.Demo.lastEvent: stringHuman-readable description of the most recent key edge, shown in the status panel.lastEvent = `PRESSED ${const key: anykey.code}`;
this.Demo.lastTick: nullEngine 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: numberCurrent fixed-update tick counter.
Increments once per engine update. Reset via
{@link
BT.ticksReset
}
.ticks;
this.Demo.pressCount: numberHow 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) => booleanChecks 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.isKeyReleased(const key: anykey.code)) {
const key: anykey.releaseTimer = const FLASH_TICKS: 12How many fixed ticks a press or release flash stays visible.FLASH_TICKS;
const key: anykey.pressTimer = 0;
this.Demo.lastEvent: stringHuman-readable description of the most recent key edge, shown in the status panel.lastEvent = `RELEASED ${const key: anykey.code}`;
this.Demo.lastTick: nullEngine 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: numberCurrent fixed-update tick counter.
Increments once per engine update. Reset via
{@link
BT.ticksReset
}
.ticks;
this.Demo.releaseCount: numberHow 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(): voidCalled 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) => voidSets 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.clear(this.Demo.theme: anytheme.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: {}keys.length; let i: numberi++) {
this.Demo.renderKey(key: KeyDef): voidrenderKey(this.Demo.keys: {}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) => voidDraws 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.systemPrint(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.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) => voidDraws 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.systemPrint(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.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) => voidDraws 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.systemPrint(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.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: stringHuman-readable description of the most recent key edge, shown in the status panel.lastEvent);
import uiui.kv('Tick', this.Demo.lastTick: nullEngine tick of the most recent key edge, or null before the first one.lastTick === null ? '-' : this.Demo.lastTick: neverEngine 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: numberHow many press edges we have seen in total. Fast-tap test: this must match releases.pressCount);
import uiui.kv('Releases', this.Demo.releaseCount: numberHow many release edges we have seen in total.releaseCount);
import uiui.end();
}
/**
* @param {KeyDef} key
*/
Demo.renderKey(key: KeyDef): voidrenderKey(key: KeyDefkey) {
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) => booleanChecks whether a keyboard key is currently held.
Uses `KeyboardEvent.code` (for example `"KeyW"`, `"Space"`, `"ArrowUp"`).isKeyDown(key: KeyDefkey.code: stringKeyboardEvent.code string.code);
const const isPressed: booleanisPressed = key: KeyDefkey.pressTimer: numberFrames left to show the press flash.pressTimer > 0;
const const isReleased: booleanisReleased = key: KeyDefkey.releaseTimer: numberFrames 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: anytheme.panel;
let let textColor: anytextColor = this.Demo.theme: anytheme.dim;
if (const isDown: booleanisDown) {
let color: anycolor = const C_LIT_HELD: 1C_LIT_HELD;
let textColor: anytextColor = this.Demo.theme: anytheme.bg;
} else if (const isPressed: booleanisPressed) {
let color: anycolor = const C_LIT_PRESS: 2C_LIT_PRESS;
let textColor: anytextColor = this.Demo.theme: anytheme.bg;
} else if (const isReleased: booleanisReleased) {
let color: anycolor = const C_LIT_RELEASE: 3C_LIT_RELEASE;
let textColor: anytextColor = this.Demo.theme: anytheme.text;
}
const const rect: Rect2irect = new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2iCreates an integer rectangle, truncating all inputs toward zero.Rect2i(key: KeyDefkey.x: numberLeft edge in display pixels.x, key: KeyDefkey.y: numberTop edge in display pixels.y, key: KeyDefkey.w: numberWidth in pixels.w, key: KeyDefkey.h: numberHeight 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) => voidDraws a filled rectangle.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) => voidDraws an unfilled rectangle outline.drawRect(const rect: Rect2irect, this.Demo.theme: anytheme.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) => Vector2iMeasures the pixel dimensions of a string rendered with the built-in
system font.systemPrintMeasure(key: KeyDefkey.label: stringShort label drawn on the key cap.label);
const const lx: anylx = key: KeyDefkey.x: numberLeft edge in display pixels.x + Math.floor((key: KeyDefkey.w: numberWidth in pixels.w - const labelSize: Vector2ilabelSize.Vector2i.x: numberHorizontal component (defaults to 0).x) / 2);
const const ly: anyly = key: KeyDefkey.y: numberTop edge in display pixels.y + Math.floor((key: KeyDefkey.h: numberHeight in pixels.h - const labelSize: Vector2ilabelSize.Vector2i.y: numberVertical 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) => voidDraws 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.systemPrint(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const lx: anylx, const ly: anyly), let textColor: anytextColor, key: KeyDefkey.label: stringShort 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): voidAppends one key definition to `this.keys` with its flash timers reset to zero.addKey(code: stringKeyboardEvent.code string.code, label: stringShort label drawn on the key cap.label, x: numberLeft edge in display pixels.x, y: numberTop edge in display pixels.y, w: numberWidth in pixels.w, h: numberHeight in pixels.h) {
this.Demo.keys: {}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): voidLays 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.addKeyRow(row: KeyRowOne entry of KEYBOARD_ROWS.row) {
let let x: numberx = row: KeyRowOne entry of KEYBOARD_ROWS.row.startX: numberLeft edge of the first key in the row, in display pixels.startX;
for (const const key: anykey of row: KeyRowOne 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): voidAppends one key definition to `this.keys` with its flash timers reset to zero.addKey(const key: anykey.code, const key: anykey.label, let x: numberx, row: KeyRowOne entry of KEYBOARD_ROWS.row.y: numberTop 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(): voidBuilds 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).KEYBOARD_ROWS) {
this.Demo.addKeyRow(row: KeyRow): voidLays 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.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.bootstrap(class DemoFull keyboard layout diagnostic for edge-trigger testing.Demo);