/*
Input Map Remapping: rebind face buttons at runtime with BT.inputMap and BT.inputMapReset.
@description Remap face buttons at runtime with BT.inputMap: defaults, custom OR keys, and clearing a binding.
Part of the BLIT386 demo series.
Prerequisites:
- Basics: https://demos.blit386.dev/basics
- Keyboard Input: https://demos.blit386.dev/keyboard-input
Live version: https://demos.blit386.dev/input-map-remapping
Guides:
- Input: https://blit386.dev/docs/guides/input#keyboard
- Overlay: https://blit386.dev/docs/guides/overlay#colors-and-hud-palette-slots
Reference:
- Overlay: https://blit386.dev/docs/api/overlay#event-tags
---
WHAT YOU WILL SEE
-----------------
A wide 640x480 screen with the engine overlay open from the start: its title and timing
chart across the top, its bar along the bottom. Everything the demo draws sits in the
space between, clear of both, so the chart and the panels are visible together.
Two dim lines of intro text come first. Below them, two equal panels sit side by side:
"Player 0" on the left and "Player 1" on the right, each a column of eight pips, one per
face button (Up, Down, Left, Right, A, B, Start, Select). A pip fills while its button is
held, and next to it are the keys that feed that button right now - in green when the
active preset changed them from the defaults, "none" in orange when it cleared them.
Along the bottom, a "Presets" panel holds four kit buttons that switch between key maps,
with an "Active" row naming the map in use, and a "Calls made" panel beside it lists the
exact BT.inputMapReset() and BT.inputMap() calls that preset ran. Each switch also drops a
tag on the engine overlay's timing chart.
WHAT YOU WILL LEARN
-------------------
- The engine keeps two runtime keyboard tables, one for player 0 and one for player 1.
Each face button (BT.BTN_UP through BT.BTN_SELECT) lists zero or more KeyboardEvent.code
strings; if any listed key is held, the button counts as down (OR).
- BT.inputMap(player, button, ...keys) replaces the key list for one button. Passing
several keys makes either one count; passing none clears the binding.
- BT.inputMapReset() restores the shipped defaults (copies of BT.DEFAULT_KEYBOARD_PLAYER1
and BT.DEFAULT_KEYBOARD_PLAYER2) for both players.
- BT.isDown(BT.BTN_*, player) reads through the map; BT.isKeyDown('KeyW') does not.
Changing the map does not rename keys - it changes which keys feed face buttons.
- UI kit buttons with a { key } binding, so one click, tap, or key press all do the same
thing, and BT.isKeyPressed('KeyR') as an extra alias read in update().
- overlayStyle and overlayTimingChartStyle in configure() take literal palette slot
numbers, because configure() runs before applyTheme() in init().
TRY THIS
--------
- Press 1 (or tap the button) for the built-in defaults (BT.inputMapReset()).
- Press 2 for the custom map: player 0's A moves to Z, player 0's Left listens to Q or E,
and player 1's Up moves to I. Hold Q or E and watch player 0's Left pip light either
way (the A key no longer does); the up arrow no longer lights player 1's Up pip, but I
does.
- Press 3 to clear player 0's A button until you pick another preset - Space and B stop
lighting the A pip.
- Press 0 or R anytime to restore defaults (same idea as 1).
- Press the backquote key (`) to open the engine overlay and see a tag on the timing
chart for each preset switch.
- Click the canvas if the preset keys stop responding (focus left the page).
*/
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;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT, 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 keyLabelkeyLabel } from './shared/key-names.js';
import { import applyThemeapplyTheme, import uiui, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js';
/** @typedef {import('blit386').Palette} Palette */
// A 640x480 logical screen (set in configure(); wider than the engine default) so two
// panels of key maps fit comfortably side by side.
const const DISPLAY_W: 640DISPLAY_W = 640;
const const DISPLAY_H: 480DISPLAY_H = 480;
// The engine overlay is visible from the start and draws on top of everything: its title
// rows and timing chart cover the top 65 pixels, and its bottom bar the last 14 (measured
// from a capture of this demo). Everything below is laid out in the band between, so
// nothing of ours hides under the overlay.
const const OVERLAY_TOP_H: 65OVERLAY_TOP_H = 65;
const const OVERLAY_BOTTOM_H: 14OVERLAY_BOTTOM_H = 14;
const const FREE_TOP: 65FREE_TOP = const OVERLAY_TOP_H: 65OVERLAY_TOP_H;
const const FREE_H: numberFREE_H = const DISPLAY_H: 480DISPLAY_H - const OVERLAY_TOP_H: 65OVERLAY_TOP_H - const OVERLAY_BOTTOM_H: 14OVERLAY_BOTTOM_H;
// Panels sit 8 pixels from the screen sides and 8 apart, in two equal columns:
// 8 + 308 + 8 + 308 + 8 = 640.
const const EDGE: 8EDGE = 8;
const const GAP: 8GAP = 8;
const const PANEL_W: numberPANEL_W = (const DISPLAY_W: 640DISPLAY_W - 2 * const EDGE: 8EDGE - const GAP: 8GAP) / 2;
const const LEFT_X: 8LEFT_X = const EDGE: 8EDGE;
const const RIGHT_X: numberRIGHT_X = const EDGE: 8EDGE + const PANEL_W: numberPANEL_W + const GAP: 8GAP;
// A kit panel with a title starts its content 22 pixels below its top edge and ends 11
// pixels below the last row; text inside starts 9 pixels in from the panel edge.
const const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP = 22;
const const PANEL_BOTTOM: 11PANEL_BOTTOM = 11;
const const PANEL_PAD: 9PANEL_PAD = 9;
// Player panels: eight 10-pixel pip rows, 4 pixels apart.
const const PIP_ROW_GAP: 4PIP_ROW_GAP = 4;
const const PIP_ROW_PITCH: numberPIP_ROW_PITCH = 10 + const PIP_ROW_GAP: 4PIP_ROW_GAP;
const const PLAYER_PANEL_H: numberPLAYER_PANEL_H = const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP + 8 * 10 + 7 * const PIP_ROW_GAP: 4PIP_ROW_GAP + const PANEL_BOTTOM: 11PANEL_BOTTOM;
// The keys column starts after the pip (14 pixels) and the longest label ("Select", 6
// characters of 6 pixels) plus one character of space.
const const KEYS_X_OFFSET: numberKEYS_X_OFFSET = const PANEL_PAD: 9PANEL_PAD + 14 + 7 * 6;
// Bottom row: the Presets panel holds four 27-pixel button rows, a 4-pixel gap, and the
// 10-pixel Active row; the Calls made panel beside it is made exactly as tall.
const const PRESETS_PANEL_H: numberPRESETS_PANEL_H = const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP + 4 * 27 + 4 + 10 + const PANEL_BOTTOM: 11PANEL_BOTTOM;
const const PRESET_BUTTON_W: numberPRESET_BUTTON_W = const PANEL_W: numberPANEL_W - 2 * const PANEL_PAD: 9PANEL_PAD;
// The whole block - two intro lines (20 pixels), the player panels, and the bottom row,
// 8 pixels apart - is centered in the free band.
const const INTRO_H: 20INTRO_H = 20;
const const BLOCK_H: numberBLOCK_H = const INTRO_H: 20INTRO_H + const GAP: 8GAP + const PLAYER_PANEL_H: numberPLAYER_PANEL_H + const GAP: 8GAP + const PRESETS_PANEL_H: numberPRESETS_PANEL_H;
const const INTRO_Y: anyINTRO_Y = const FREE_TOP: 65FREE_TOP + Math.floor((const FREE_H: numberFREE_H - const BLOCK_H: numberBLOCK_H) / 2);
const const PANEL_TOP_Y: anyPANEL_TOP_Y = const INTRO_Y: anyINTRO_Y + const INTRO_H: 20INTRO_H + const GAP: 8GAP;
const const BOTTOM_ROW_Y: anyBOTTOM_ROW_Y = const PANEL_TOP_Y: anyPANEL_TOP_Y + const PLAYER_PANEL_H: numberPLAYER_PANEL_H + const GAP: 8GAP;
// The engine overlay (configured below) needs palette slot numbers, but configure() runs
// BEFORE init(), where applyTheme() installs the shared UI colors. applyTheme() always
// writes its twelve colors into slots 240-251 (its default start slot), so we can write
// the slot numbers out here as named constants instead of magic numbers.
const const THEME_SLOT_PANEL: 242THEME_SLOT_PANEL = 242; // panel fill (dark blue)
const const THEME_SLOT_TEXT: 244THEME_SLOT_TEXT = 244; // primary text (off-white)
const const THEME_SLOT_HEADER: 246THEME_SLOT_HEADER = 246; // panel titles (warm amber)
const const THEME_SLOT_ACCENT: 247THEME_SLOT_ACCENT = 247; // lit / active (phosphor green)
const const THEME_SLOT_WARM: 248THEME_SLOT_WARM = 248; // warnings (warm orange)
// The eight face buttons in the order the panels list them, each pairing an on-screen
// label with the engine's button constant and that constant's name (for the Calls made
// panel).
/** @type {Array<{ label: string, code: number, name: string }>} */
const const FACE_BUTTONS: Array<{
label: string;
code: number;
name: string;
}>
FACE_BUTTONS = [
{ label: stringlabel: 'Up', code: numbercode: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_UP: numberUp button bit flag.BTN_UP, name: stringname: 'BTN_UP' },
{ label: stringlabel: 'Down', code: numbercode: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_DOWN: numberDown button bit flag.BTN_DOWN, name: stringname: 'BTN_DOWN' },
{ label: stringlabel: 'Left', code: numbercode: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_LEFT: numberLeft button bit flag.BTN_LEFT, name: stringname: 'BTN_LEFT' },
{ label: stringlabel: 'Right', code: numbercode: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_RIGHT: numberRight button bit flag.BTN_RIGHT, name: stringname: 'BTN_RIGHT' },
{ label: stringlabel: 'A', code: numbercode: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_A: numberA button bit flag.BTN_A, name: stringname: 'BTN_A' },
{ label: stringlabel: 'B', code: numbercode: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_B: numberB button bit flag.BTN_B, name: stringname: 'BTN_B' },
{ label: stringlabel: 'Start', code: numbercode: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_START: numberStart button bit flag.BTN_START, name: stringname: 'BTN_START' },
{ label: stringlabel: 'Select', code: numbercode: const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_SELECT: numberSelect button bit flag.BTN_SELECT, name: stringname: 'BTN_SELECT' },
];
// The engine's shipped default tables, one per player, in player order.
const const DEFAULT_MAPS: {}DEFAULT_MAPS = [const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type DEFAULT_KEYBOARD_PLAYER1: Readonly<Record<number, {}>>Default `KeyboardEvent.code` values for player index 0 (first keyboard player).DEFAULT_KEYBOARD_PLAYER1, const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type DEFAULT_KEYBOARD_PLAYER2: Readonly<Record<number, {}>>Default `KeyboardEvent.code` values for player index 1 (second keyboard player).DEFAULT_KEYBOARD_PLAYER2];
// Copies one keyboard table so editing the copy never touches the original.
// Object.entries() lists [button, keys] pairs, [...keys] copies each key list, and
// Object.fromEntries() builds a new table from the pairs.
function function copyMap(map: any): anycopyMap(map: anymap) {
return Object.fromEntries(Object.entries(map: anymap).map(([button: anybutton, keys: anykeys]) => [button: anybutton, [...keys: anykeys]]));
}
// True when two key lists hold the same keys in the same order.
function function sameKeys(a: any, b: any): anysameKeys(a: anya, b: anyb) {
return a: anya.length === b: anyb.length && a: anya.every((key: anykey, i: anyi) => key: anykey === b: anyb[i: anyi]);
}
// Cycles keyboard face-button maps with BT.inputMap and BT.inputMapReset.
class class DemoDemo {
/** @type {Palette | null} */
Demo.palette: Palette | nullpalette = null;
// Palette slot map returned by applyTheme() - theme.bg, theme.text, and friends.
/** @type {ReturnType<typeof applyTheme> | null} */
Demo.theme: anytheme = null;
// Human-readable name for the active preset.
Demo.presetLabel: stringpresetLabel = 'Defaults';
// Our own copy of both players' keyboard tables, kept in step with every
// BT.inputMap() / BT.inputMapReset() call - the engine does not expose a "get current
// map" API, so this is how the panels can show which keys feed each button.
Demo.keyMaps: {}keyMaps = [{}, {}];
// What the player panels print for each button, rebuilt whenever the map changes
// (not every frame): the key text and its color role. Index [player][button].
/** @type {Array<Array<{ text: string, color: string }>>} */
Demo.keyRows: Array<Array<{
text: string;
color: string;
}>>
keyRows = [[], []];
// The calls the active preset made, in order, for the Calls made panel.
/** @type {string[]} */
Demo.calls: {}calls = [];
// A wider logical screen than the engine default so two panels of key maps fit. No
// post-process effects, so we upscale 2x in the browser (maxCanvasSize) instead of
// allocating a larger drawing buffer (drawingBufferSize).
Demo.configure(): {
displaySize: Vector2i;
maxCanvasSize: Vector2i;
targetFPS: number;
isOverlayVisibleAtStart: boolean;
overlayStyle: {
barPaletteIndex: number;
textPaletteIndex: number;
gapPaletteIndex: number;
};
isOverlayTimingChartEnabled: boolean;
overlayTimingChartStyle: {
updateBarPaletteIndex: number;
renderBarPaletteIndex: number;
warningPaletteIndex: number;
errorPaletteIndex: number;
tagPaletteIndex: number;
};
isCapturingKeyboardScroll: boolean;
}
configure() {
return {
displaySize: Vector2idisplaySize: new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const DISPLAY_W: 640DISPLAY_W, const DISPLAY_H: 480DISPLAY_H),
maxCanvasSize: Vector2imaxCanvasSize: new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const DISPLAY_W: 640DISPLAY_W * 2, const DISPLAY_H: 480DISPLAY_H * 2),
targetFPS: numbertargetFPS: 60,
isOverlayVisibleAtStart: booleanisOverlayVisibleAtStart: true,
overlayStyle: {
barPaletteIndex: number;
textPaletteIndex: number;
gapPaletteIndex: number;
}
overlayStyle: {
barPaletteIndex: numberbarPaletteIndex: const THEME_SLOT_PANEL: 242THEME_SLOT_PANEL,
textPaletteIndex: numbertextPaletteIndex: const THEME_SLOT_TEXT: 244THEME_SLOT_TEXT,
gapPaletteIndex: numbergapPaletteIndex: const THEME_SLOT_PANEL: 242THEME_SLOT_PANEL,
},
isOverlayTimingChartEnabled: booleanisOverlayTimingChartEnabled: true,
overlayTimingChartStyle: {
updateBarPaletteIndex: number;
renderBarPaletteIndex: number;
warningPaletteIndex: number;
errorPaletteIndex: number;
tagPaletteIndex: number;
}
overlayTimingChartStyle: {
updateBarPaletteIndex: numberupdateBarPaletteIndex: const THEME_SLOT_ACCENT: 247THEME_SLOT_ACCENT,
renderBarPaletteIndex: numberrenderBarPaletteIndex: const THEME_SLOT_HEADER: 246THEME_SLOT_HEADER,
warningPaletteIndex: numberwarningPaletteIndex: const THEME_SLOT_WARM: 248THEME_SLOT_WARM,
errorPaletteIndex: numbererrorPaletteIndex: const THEME_SLOT_WARM: 248THEME_SLOT_WARM,
tagPaletteIndex: numbertagPaletteIndex: const THEME_SLOT_ACCENT: 247THEME_SLOT_ACCENT,
},
// Arrow keys and Space scroll the host page by default. This demo maps those
// keys (player 1 uses arrows), so opt in so pressing them does not move the
// page.
isCapturingKeyboardScroll: booleanisCapturingKeyboardScroll: true,
};
}
// Palette setup and a clean slate for the keyboard maps when the page loads.
async Demo.init(): unknowninit() {
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;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.paletteCreate: (size?: number) => PaletteCreates a standalone palette instance.paletteCreate(256);
// Install the shared UI theme (twelve colors in slots 240-251) before handing the
// palette to the engine. Every panel, button, and pip below draws with these.
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;
... 124 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);
// Copy fresh maps from BT.DEFAULT_KEYBOARD_PLAYER1 / ...PLAYER2 so hot reload or
// revisiting this URL does not inherit another demo's edits.
this.Demo.resetMaps(): voidresetMaps();
this.Demo.presetLabel: stringpresetLabel = 'Defaults';
return true;
}
// Update-side housekeeping for the UI kit, plus one extra keyboard alias.
Demo.update(): voidupdate() {
// Let the kit latch keyboard shortcuts and touch contacts for this frame's
// buttons. This must be the first line of update() when buttons use { key }.
import uiui.tick();
// The reset button below is bound to Digit0, but this demo also accepts R as a
// second reset key. A kit button can only carry one key binding, so this alias
// stays here in update() (keyboard edges are safe to read here - never in
// render()). isKeyPressed without a repeat rate only fires once per press.
if (const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isKeyPressed: (key: string, repeatRate?: number) => 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('KeyR')) {
this.Demo.applyPresetDefaults(): voidapplyPresetDefaults();
}
}
// Draws the intro text, both player panels, the preset buttons, and the calls list.
Demo.render(): voidrender() {
// Wipe the whole screen with the theme's deep-navy background color.
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.clear: (paletteIndex: number) => 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);
// Full-width 22-pixel title strip across the top. The overlay covers it while
// it is open; press the backquote key (`) to hide the overlay and see it.
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_BAR);
import uiui.panel('Input Map Remapping - BT.inputMap / BT.inputMapReset');
import uiui.end();
// Two dim intro lines, lined up with the text inside the panels below them. A
// caption's text starts 4 pixels above the y it is given.
const const textX: numbertextX = const LEFT_X: 8LEFT_X + const PANEL_PAD: 9PANEL_PAD;
import uiui.caption(
const textX: numbertextX,
const INTRO_Y: anyINTRO_Y + 4,
'Face buttons use BT.isDown(BTN_*, player). Remap them at runtime with BT.inputMap.',
{
color: stringcolor: 'dim',
},
);
import uiui.caption(const textX: numbertextX, const INTRO_Y: anyINTRO_Y + 14, 'Tap a preset button or press its number key. Click the canvas if keys stop.', {
color: stringcolor: 'dim',
});
// One panel of live face-button pips per player, side by side.
this.Demo.renderPlayerPanel(player: any, originX: any): voidrenderPlayerPanel(0, const LEFT_X: 8LEFT_X);
this.Demo.renderPlayerPanel(player: any, originX: any): voidrenderPlayerPanel(1, const RIGHT_X: numberRIGHT_X);
// The bottom row: the preset buttons on the left, the calls they made on the
// right.
this.Demo.renderPresetsPanel(): voidrenderPresetsPanel();
this.Demo.renderCallsPanel(): voidrenderCallsPanel();
}
// Restores the shipped tables, both in the engine and in our own copy.
Demo.resetMaps(): voidresetMaps() {
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.inputMapReset: () => voidRestores built-in default keyboard maps for players `0` and `1`.
Uses the tables for the active `keyboardLayout` from `configure()`:
`BT.DEFAULT_KEYBOARD_PLAYER1` / `BT.DEFAULT_KEYBOARD_PLAYER2` for `'versus'` (the
default), `BT.DEFAULT_KEYBOARD_SINGLE_PLAYER1` / `BT.DEFAULT_KEYBOARD_SINGLE_PLAYER2`
for `'single'`.inputMapReset();
this.Demo.keyMaps: {}keyMaps = const DEFAULT_MAPS: {}DEFAULT_MAPS.map(function copyMap(map: any): anycopyMap);
this.Demo.calls: {}calls = ['BT.inputMapReset()'];
this.Demo.refreshKeyRows(): voidrefreshKeyRows();
}
// Replaces the key list of one button, both in the engine and in our own copy, and
// records the call for the Calls made panel. Passing no keys clears the button.
Demo.remap(player: any, code: any, ...keys: {}): voidremap(player: anyplayer, code: anycode, ...keys: {}keys) {
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.inputMap: (player: number, button: number, ...keys: string[]) => voidAssigns one or more `KeyboardEvent.code` values to a face button for a keyboard player.
Logical button state is the OR of all listed keys. Only players `0` and `1`
support keyboard; other indices no-op. `button` must be one face-button
bit flag (`BT.BTN_UP` … `BT.BTN_SELECT`). Pass an empty key list
to clear keyboard bindings for that button until remapped again.inputMap(player: anyplayer, code: anycode, ...keys: {}keys);
this.Demo.keyMaps: {}keyMaps[player: anyplayer][code: anycode] = keys: {}keys;
// Build the call as it would be written in code: the button constant's name,
// then every key in quotes. .find() returns the first FACE_BUTTONS entry with
// this code.
const const name: anyname = const FACE_BUTTONS: Array<{
label: string;
code: number;
name: string;
}>
FACE_BUTTONS.find((button: anybutton) => button: anybutton.code === code: anycode).name;
const const keyArgs: anykeyArgs = keys: {}keys.map((key: anykey) => `, '${key: anykey}'`).join('');
this.Demo.calls: {}calls.push(`BT.inputMap(${player: anyplayer}, BT.${const name: anyname}${const keyArgs: anykeyArgs})`);
this.Demo.refreshKeyRows(): voidrefreshKeyRows();
}
// Rebuilds what the player panels print for every button: the key labels joined
// with " or ", green when they differ from the shipped defaults, and an orange
// "none" when the button has no keys at all.
Demo.refreshKeyRows(): voidrefreshKeyRows() {
this.Demo.keyRows: Array<Array<{
text: string;
color: string;
}>>
keyRows = this.Demo.keyMaps: {}keyMaps.map((map: anymap, player: anyplayer) =>
const FACE_BUTTONS: Array<{
label: string;
code: number;
name: string;
}>
FACE_BUTTONS.map(({ code: anycode }) => {
const const keys: anykeys = map: anymap[code: anycode] ?? [];
const const defaults: anydefaults = const DEFAULT_MAPS: {}DEFAULT_MAPS[player: anyplayer][code: anycode] ?? [];
if (const keys: anykeys.length === 0) {
return { text: stringtext: 'none', color: stringcolor: const defaults: anydefaults.length === 0 ? 'dim' : 'warm' };
}
return { text: anytext: const keys: anykeys.map(import keyLabelkeyLabel).join(' or '), color: stringcolor: function sameKeys(a: any, b: any): anysameKeys(const keys: anykeys, const defaults: anydefaults) ? 'dim' : 'accent' };
}),
);
}
// Restores the shipped tables (copies of BT.DEFAULT_KEYBOARD_PLAYER1 / PLAYER2).
Demo.applyPresetDefaults(): voidapplyPresetDefaults() {
this.Demo.resetMaps(): voidresetMaps();
this.Demo.presetLabel: stringpresetLabel = 'Defaults';
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.assignTag: (label?: string) => voidPlaces a labeled marker on the overlay timing chart at the current tick.
Requires `isOverlayTimingChartEnabled: true` in `configure()`. Tags scroll with the chart
history and are pruned when they leave the visible window. Empty labels become
`"Untitled"`. Chart width resets add an automatic `"Start"` tag.assignTag('Map: defaults');
}
// Custom layout: remaps a few buttons, and gives TWO keys to one direction so either
// key counts (OR) - hold Q or E and player 0's Left pip should light.
Demo.applyPresetCustom(): voidapplyPresetCustom() {
// Start from known defaults, then layer edits (order matters only for clarity).
this.Demo.resetMaps(): voidresetMaps();
// Player 0: A is Z only for this preset (Space and B no longer map here until
// reset).
this.Demo.remap(player: any, code: any, ...keys: {}): voidremap(0, const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_A: numberA button bit flag.BTN_A, 'KeyZ');
// Player 0: Left listens to TWO keys at once - either one held counts.
this.Demo.remap(player: any, code: any, ...keys: {}): voidremap(0, const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_LEFT: numberLeft button bit flag.BTN_LEFT, 'KeyQ', 'KeyE');
// Player 1: move Up to I so the up arrow no longer drives it for this preset
// until we reset (we replace the whole key list for that button).
this.Demo.remap(player: any, code: any, ...keys: {}): voidremap(1, const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_UP: numberUp button bit flag.BTN_UP, 'KeyI');
this.Demo.presetLabel: stringpresetLabel = 'Custom';
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.assignTag: (label?: string) => voidPlaces a labeled marker on the overlay timing chart at the current tick.
Requires `isOverlayTimingChartEnabled: true` in `configure()`. Tags scroll with the chart
history and are pruned when they leave the visible window. Empty labels become
`"Untitled"`. Chart width resets add an automatic `"Start"` tag.assignTag('Map: custom');
}
// Defaults everywhere, then REMOVES the keyboard bindings for player 0's A button.
// Passing no key strings clears that slot until you map again.
Demo.applyPresetClearPlayer0A(): voidapplyPresetClearPlayer0A() {
this.Demo.resetMaps(): voidresetMaps();
// No keys after the button means an empty list is stored, so no key lights BTN_A
// for player 0.
this.Demo.remap(player: any, code: any, ...keys: {}): voidremap(0, const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_A: numberA button bit flag.BTN_A);
this.Demo.presetLabel: stringpresetLabel = 'Cleared P0 A';
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.assignTag: (label?: string) => voidPlaces a labeled marker on the overlay timing chart at the current tick.
Requires `isOverlayTimingChartEnabled: true` in `configure()`. Tags scroll with the chart
history and are pruned when they leave the visible window. Empty labels become
`"Untitled"`. Chart width resets add an automatic `"Start"` tag.assignTag('Map: cleared A');
}
// Draws one player's panel: a column of live face-button pips, each with the keys
// that feed it right now. `player` is 0 or 1; `originX` is the panel's left edge.
Demo.renderPlayerPanel(player: any, originX: any): voidrenderPlayerPanel(player: anyplayer, originX: anyoriginX) {
// Pin the group at an exact position and force both panels to the same width so
// the two columns line up.
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: anyx: originX: anyoriginX, y: anyy: const PANEL_TOP_Y: anyPANEL_TOP_Y, width: numberwidth: const PANEL_W: numberPANEL_W });
import uiui.panel(player: anyplayer === 0 ? 'Player 0' : 'Player 1');
// One read-only pip per logical face button. The pip lights up while the button
// is held. `code` is a BT.BTN_* constant (Up, A, Start, ...), not a raw key
// string - the runtime map (changed by BT.inputMap) decides which physical keys
// feed it. BT.isDown reads held state (not a press edge), so it is safe here in
// render(). A small spacer after each row spreads the column out.
for (let let i: numberi = 0; let i: numberi < const FACE_BUTTONS: Array<{
label: string;
code: number;
name: string;
}>
FACE_BUTTONS.length; let i: numberi++) {
const { const label: Array<{
label: string;
code: number;
name: string;
}>
label, const code: Array<{
label: string;
code: number;
name: string;
}>
code } = const FACE_BUTTONS: Array<{
label: string;
code: number;
name: string;
}>
FACE_BUTTONS[let i: numberi];
import uiui.pip(const label: Array<{
label: string;
code: number;
name: string;
}>
label, const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isDown: (button: number, player?: number) => booleanChecks whether a button is currently held.
For pointer buttons (`BTN_POINTER_A..D`), the second parameter is the
pointer slot index (0 = mouse, 1-3 = touch / pen). For mouse slot 0:
`A` is left, `B` is right, `C` is middle, `D` is back / forward
(matches RetroBlit canonical, not DOM `PointerEvent.button` index).
Touch / pen slots only support `A`; B/C/D return `false`.
`button` accepts one or more bit flags from the `BTN_*` set (for example
`BT.BTN_A | BT.BTN_B`). Matching uses ANY semantics: returns `true` when
any selected button is held.
For face buttons (`BTN_UP`…`BTN_SELECT`), players `0` and `1` merge keyboard
and gamepad input (logical OR). Players `2` and `3` use gamepad only.
Pointer flags (`BTN_POINTER_*`) use the `player` argument as pointer slot.
`BTN_L2`, `BTN_R2` and `BTN_TRIGGER` are gamepad only (no keyboard mapping).isDown(const code: Array<{
label: string;
code: number;
name: string;
}>
code, player: anyplayer));
if (let i: numberi < const FACE_BUTTONS: Array<{
label: string;
code: number;
name: string;
}>
FACE_BUTTONS.length - 1) {
import uiui.spacer(const PIP_ROW_GAP: 4PIP_ROW_GAP);
}
}
import uiui.end();
// The keys column, on the same rows as the pip labels. The kit prints a pip's
// label 1 pixel below the top of its row, and a caption 4 pixels above the y it
// is given, so y = rowTop + 5 puts both texts on the same line.
for (let let i: numberi = 0; let i: numberi < const FACE_BUTTONS: Array<{
label: string;
code: number;
name: string;
}>
FACE_BUTTONS.length; let i: numberi++) {
const { const text: Array<Array<{
text: string;
color: string;
}>>
text, const color: Array<Array<{
text: string;
color: string;
}>>
color } = this.Demo.keyRows: Array<Array<{
text: string;
color: string;
}>>
keyRows[player: anyplayer][let i: numberi];
const const rowTop: anyrowTop = const PANEL_TOP_Y: anyPANEL_TOP_Y + const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP + let i: numberi * const PIP_ROW_PITCH: numberPIP_ROW_PITCH;
import uiui.caption(originX: anyoriginX + const KEYS_X_OFFSET: numberKEYS_X_OFFSET, const rowTop: anyrowTop + 5, const text: Array<Array<{
text: string;
color: string;
}>>
text, { color: Array<Array<{
text: string;
color: string;
}>>
color });
}
}
// The preset switcher. Each ui.button() returns true on the one frame it is clicked,
// tapped, or its bound key goes down - all three inputs behave exactly the same.
Demo.renderPresetsPanel(): voidrenderPresetsPanel() {
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: const LEFT_X: 8LEFT_X, y: anyy: const BOTTOM_ROW_Y: anyBOTTOM_ROW_Y, width: numberwidth: const PANEL_W: numberPANEL_W });
import uiui.panel('Presets');
if (import uiui.button('1 - Defaults (inputMapReset)', { key: stringkey: 'Digit1', width: numberwidth: const PRESET_BUTTON_W: numberPRESET_BUTTON_W })) {
this.Demo.applyPresetDefaults(): voidapplyPresetDefaults();
}
if (import uiui.button('2 - Custom map (Z, Q/E, I)', { key: stringkey: 'Digit2', width: numberwidth: const PRESET_BUTTON_W: numberPRESET_BUTTON_W })) {
this.Demo.applyPresetCustom(): voidapplyPresetCustom();
}
if (import uiui.button('3 - Clear P0 A button', { key: stringkey: 'Digit3', width: numberwidth: const PRESET_BUTTON_W: numberPRESET_BUTTON_W })) {
this.Demo.applyPresetClearPlayer0A(): voidapplyPresetClearPlayer0A();
}
if (import uiui.button('0 - Reset (also R key)', { key: stringkey: 'Digit0', width: numberwidth: const PRESET_BUTTON_W: numberPRESET_BUTTON_W })) {
this.Demo.applyPresetDefaults(): voidapplyPresetDefaults();
}
// A small gap, then the name of whichever preset is active right now.
import uiui.spacer(4);
import uiui.kv('Active', this.Demo.presetLabel: stringpresetLabel);
import uiui.end();
}
// Lists the calls the active preset made, as code, with a dim note on what the green
// and orange key text in the player panels means. A spacer pads the panel to the
// Presets panel's height, so the two end on the same line.
Demo.renderCallsPanel(): voidrenderCallsPanel() {
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: const RIGHT_X: numberRIGHT_X, y: anyy: const BOTTOM_ROW_Y: anyBOTTOM_ROW_Y, width: numberwidth: const PANEL_W: numberPANEL_W });
import uiui.panel('Calls made');
for (const const call: anycall of this.Demo.calls: {}calls) {
import uiui.label(const call: anycall, { color: stringcolor: 'info' });
}
const const contentH: numbercontentH = const PRESETS_PANEL_H: numberPRESETS_PANEL_H - const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP - const PANEL_BOTTOM: 11PANEL_BOTTOM;
import uiui.spacer(const contentH: numbercontentH - (this.Demo.calls: {}calls.length + 2) * 10);
import uiui.label('Green keys: changed from the defaults', { color: stringcolor: 'dim' });
import uiui.label('Orange "none": the button has no keys', { color: stringcolor: 'dim' });
import uiui.end();
}
}
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 DemoDemo);