/**
 * Pointer Paint Demo - multi-touch finger painting with mouse + up to 3 touches.
 * @description Multi-touch finger painting across all four pointer slots: a mouse plus up to three fingers at once.
 *
 * Prerequisites: Pointer Basics - https://demos.blit386.dev/pointer-basics
 *
 * Live version: https://demos.blit386.dev/pointer-paint
 *
 * This demo shows how all four pointer slots work side by side. Each slot
 * paints in its own color:
 *   slot 0 = mouse        (white)
 *   slot 1 = first touch  (red)
 *   slot 2 = second touch (green)
 *   slot 3 = third touch  (blue)
 *
 * Mouse: hold the left button (BTN_POINTER_A) to paint. Right-click
 * (BTN_POINTER_B) clears the canvas. Middle-click (BTN_POINTER_C) cycles the
 * brush size between three preset thicknesses.
 *
 * Touch: each finger paints automatically while in contact. Up to three touches
 * are tracked at once; a fourth simultaneous touch is dropped silently. Because
 * touch devices have no right or middle button, the shared UI kit
 * (src/shared/ui.js) draws a small panel with a Clear button and a Brush button
 * that do exactly the same thing as the mouse shortcuts - so the whole demo
 * works with fingers alone.
 *
 * What this demonstrates:
 *   - BT.isPressed() for one-shot mouse actions (clear canvas, cycle brush size)
 *   - BT.isDown(BT.BTN_POINTER_A) while BT.isPointerActive(0) for mouse painting
 *   - BT.isPointerActive(slot) / BT.pointerPos(slot) for per-slot touch painting
 *   - lastPosX / lastPosY per-slot stamping: draws from the previous frame's
 *     position to the current one so fast strokes look continuous instead of dotted
 *   - ui.overWidget() to keep brush strokes from landing underneath the UI panel
 *
 * The painting happens on an offscreen palette layer (a 2D array of palette
 * indices) so brush strokes persist across frames even though render() clears
 * to a background color first.
 */

import { function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>
One-liner bootstrap function for BLIT386 demos. Handles canvas retrieval and engine initialization. Backend selection (WebGPU or software fallback) is managed internally by BTAPI. This function provides a streamlined way to start a demo with sensible defaults while allowing customization through options.
@since0.2.0@changed1.4.0 Calling `bootstrap()` again while already initialized now routes to a hot swap (via {@link registerHotReload}) when a Vite HMR context is registered, or logs a double-bootstrap guard and returns `false` otherwise - previously it silently started a second, unstoppable `GameLoop`.@changed1.7.0 Exposes `BT` on `window.BT` after bootstrap finishes, gated by {@link BootstrapOptions.exposeGlobal} (default: {@link BT.isDevMode}).@paramDemoClass - Demo class constructor implementing `IBTDemo` (optional `configure()` for hardware settings).@paramoptions - Optional configuration for IDs and callbacks.@returns`true` when the demo boots successfully; otherwise `false`.@example// Simplest usage - uses default IDs. bootstrap(MyDemo);@example// With custom options. bootstrap(MyDemo, { canvasID: 'custom-canvas', containerID: 'custom-container', onSuccess: () => console.log('Demo started!'), onError: (err) => analytics.trackError(err), });@example// Await the result. const success = await bootstrap(MyDemo); if (success) { console.log('Demo is running'); }
bootstrap
,
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
, class Vector2i
Integer 2D vector for pixel-perfect positioning. Used for points, sizes, directions, and camera offsets throughout the engine. The API includes both allocation-free `*To()` / `*InPlace()` variants and convenience methods that return new vectors.
@since0.1.0
Vector2i
} from 'blit386';
import { import applyThemeapplyTheme, import THEME_DEFAULT_START_SLOTTHEME_DEFAULT_START_SLOT, 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 */ const const DISPLAY_W: 320DISPLAY_W = 320; const const DISPLAY_H: 240DISPLAY_H = 240; // One paint color per pointer slot. The slot index is the same as the array // index here so update() can write SLOT_PAINT[slot] directly. These are scene // colors (the artwork itself); the UI panel colors come from the shared theme, // which lives far away in slots 240-251. const const SLOT_PAINT: {}SLOT_PAINT = [ 10, // slot 0 (mouse) 11, // slot 1 (first touch) 12, // slot 2 (second touch) 13, // slot 3 (third touch) ]; // Friendly names for the panel rows: which slot is which input device. const const SLOT_LABELS: {}SLOT_LABELS = ['Mouse', 'Touch 1', 'Touch 2', 'Touch 3']; // Brush sizes that middle-click (or the Brush button) cycles through. Values // are radii in pixels; a radius of 0 paints a single pixel. const const BRUSH_SIZES: {}BRUSH_SIZES = [0, 2, 4]; // Human-readable names for the same brushes, shown on the Brush button and in // the panel's Brush row. Same order as BRUSH_SIZES. const const BRUSH_NAMES: {}BRUSH_NAMES = ['Thin', 'Medium', 'Thick']; /** * Multi-touch / mouse paint demo. * * The "canvas" we paint onto is a flat array of palette indices, one entry per * display pixel. Each frame, render() copies that array onto the screen with * BT.drawPixel() so strokes persist between frames. Stroke input comes from * checking BT.isPointerActive() / BT.isDown() / BT.pointerPos() on each * of the four slots in update(). * * @implements {IBTDemo} */ class class Demo
Multi-touch / mouse paint demo. The "canvas" we paint onto is a flat array of palette indices, one entry per display pixel. Each frame, render() copies that array onto the screen with BT.drawPixel() so strokes persist between frames. Stroke input comes from checking BT.isPointerActive() / BT.isDown() / BT.pointerPos() on each of the four slots in update().
@implementsIBTDemo
Demo
{
/** @type {Palette | null} */ Demo.palette: Palette | null
@type{Palette | null}
palette
= null;
// Palette slot map for the shared UI theme, filled in by applyTheme() in // init(). Gives us named slots like this.theme.bg for our own drawing. Demo.theme: nulltheme = null; // Painting layer: one palette index per display pixel. 0 means "blank" // (the background color shows through). Length = DISPLAY_W * DISPLAY_H. /** @type {Uint8Array | null} */ Demo.layer: any
@type{Uint8Array | null}
layer
= null;
// Index into BRUSH_SIZES; cycled by middle-click or the Brush button. Demo.brushIndex: numberbrushIndex = 1; // Last known position per slot, recorded the frame the pointer became // active or last had its button pressed. Used to draw a stroke from the // previous frame's position to the current one, which fills gaps when the // pointer moves faster than one pixel per frame. Demo.lastPosX: {}lastPosX = [0, 0, 0, 0]; Demo.lastPosY: {}lastPosY = [0, 0, 0, 0]; // True while we should be painting from this slot. For mouse this is // BTN_POINTER_A held. For touch this is "slot is valid" (contact down). Demo.painting: {}painting = [false, false, false, false]; /** * Finger painting can spike render() when strokes are long; the chart makes that visible. * * @returns {Partial<HardwareSettings>} */ Demo.configure(): Partial<HardwareSettings>
Finger painting can spike render() when strokes are long; the chart makes that visible.
@returns
configure
() {
return { // Phones and tablets dim, then lock, the screen after 30-60 seconds without a touch - // easy to hit during a slow, careful painting session. This asks the browser to keep // the screen on while you paint; unsupported browsers just ignore the request. isWakeLockEnabled: booleanisWakeLockEnabled: true, isOverlayTimingChartEnabled: booleanisOverlayTimingChartEnabled: true, overlayTimingChartDiagnostics: stringoverlayTimingChartDiagnostics: 'rich', isOverlayRendererDiagnosticsBarEnabled: booleanisOverlayRendererDiagnosticsBarEnabled: true, // The engine overlay bars reuse the shared UI theme colors so // everything on screen matches. applyTheme() writes panel at // THEME_DEFAULT_START_SLOT+2 and text at +4; configure() runs before // init(), so we derive those slot numbers here.
overlayStyle: {
    barPaletteIndex: any;
    textPaletteIndex: any;
    gapPaletteIndex: any;
}
overlayStyle
: {
barPaletteIndex: anybarPaletteIndex: import THEME_DEFAULT_START_SLOTTHEME_DEFAULT_START_SLOT + 2, textPaletteIndex: anytextPaletteIndex: import THEME_DEFAULT_START_SLOTTHEME_DEFAULT_START_SLOT + 4, gapPaletteIndex: anygapPaletteIndex: import THEME_DEFAULT_START_SLOTTHEME_DEFAULT_START_SLOT + 2, },
overlayTimingChartStyle: {
    updateBarPaletteIndex: any;
    renderBarPaletteIndex: any;
    warningPaletteIndex: any;
    errorPaletteIndex: any;
    tagPaletteIndex: any;
}
overlayTimingChartStyle
: {
updateBarPaletteIndex: anyupdateBarPaletteIndex: const SLOT_PAINT: {}SLOT_PAINT[0], renderBarPaletteIndex: anyrenderBarPaletteIndex: const SLOT_PAINT: {}SLOT_PAINT[1], warningPaletteIndex: anywarningPaletteIndex: const SLOT_PAINT: {}SLOT_PAINT[2], errorPaletteIndex: anyerrorPaletteIndex: const SLOT_PAINT: {}SLOT_PAINT[3], tagPaletteIndex: anytagPaletteIndex: import THEME_DEFAULT_START_SLOTTHEME_DEFAULT_START_SLOT + 4, }, }; } /** * Sets up the palette (shared UI theme + scene paint colors) and allocates * the offscreen paint layer. * * @returns {Promise<boolean>} */ async Demo.init(): Promise<boolean>
Sets up the palette (shared UI theme + scene paint colors) and allocates the offscreen paint layer.
@returns
init
() {
this.Demo.palette: Palette | null
@type{Palette | null}
palette
=
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.paletteCreate: (size?: number) => Palette
Creates a standalone palette instance.
@since1.0.3@paramsize - Palette size. Defaults to 256 colors.@returnsNew mutable palette.
paletteCreate
(256);
// Install the shared UI colors (slots 240-251) that the kit's panel, // pips, and buttons draw with. Must happen before BT.paletteSet() so // the colors actually reach the GPU. this.Demo.theme: nulltheme = import applyThemeapplyTheme(this.Demo.palette: Palette
@type{Palette | null}
palette
);
// One paint color per slot. Slot 0 (mouse) gets a soft white; the // three touch slots get bold primary colors so multiple fingers are // easy to tell apart. this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const SLOT_PAINT: {}SLOT_PAINT[0], new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(240, 240, 240));
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const SLOT_PAINT: {}SLOT_PAINT[1], new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(255, 100, 100));
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const SLOT_PAINT: {}SLOT_PAINT[2], new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(100, 220, 120));
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const SLOT_PAINT: {}SLOT_PAINT[3], new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(100, 160, 255));
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.paletteSet: (palette: Palette) => void
Stores the active engine palette. Use this to swap the **entire palette** (e.g. switch between a day and night theme). After this call the renderer uploads the new palette uniform on the next frame. **Palette-value swap (change what a slot looks like):** mutate the live {@link BT.palette } in place with `palette.set(slot, newColor)`. The renderer uploads dirty slots on the next frame; no `paletteSet()` or {@link BT.spritesRefresh } needed. **Palette-layout swap (same colors, different slot positions):** build a new palette with the same colors at new indices, call `paletteSet()`, then call {@link BT.spritesRefresh } so every sprite sheet re-maps its original RGBA pixels against the new slot layout.
@since1.0.3@parampalette - Palette to make active.
paletteSet
(this.Demo.palette: Palette
@type{Palette | null}
palette
);
// Hide the native OS cursor so the drawn crosshair is the only cursor // visible while the pointer is over the canvas.
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
.hideCursor: () => void
Hides the native OS cursor while the pointer is over the canvas. Call once from `init()` when the demo draws its own crosshair or cursor sprite in place of the system arrow. The cursor is restored automatically when the engine shuts down. No-op before the engine is initialized.
@since1.0.3
hideCursor
();
// Allocate the paint layer. `fill(0)` makes every pixel start blank // (transparent) so the background color shows through. this.Demo.layer: any
@type{Uint8Array | null}
layer
= new Uint8Array(const DISPLAY_W: 320DISPLAY_W * const DISPLAY_H: 240DISPLAY_H);
return true; } /** * Per-tick: read input from each slot and write strokes into layer. */ Demo.update(): void
Per-tick: read input from each slot and write strokes into layer.
update
() {
// Let the UI kit do its per-tick housekeeping (touch tracking) before // we read any input ourselves. Always the first line of update(). import uiui.tick(); // Mouse shortcuts: right-click (button B) clears the canvas and // middle-click (button C) cycles the brush size. The Clear and Brush // buttons in the kit panel (see renderPanel()) call the exact same // helper methods, so mouse and touch users get identical features. // We use isPressed (edge) so a single click triggers exactly once. 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
.isPressed: (button: number, player?: number, repeatRate?: number) => boolean
Checks whether a button was pressed on the current frame. Same parameter semantics as {@link isDown } ; returns `true` only on the frame the button transitions from up to down. Call from `update()`, not `render()`, for reliable detection: for keyboard-mapped face buttons (players 0 and 1), the press edge clears once per fixed-update tick, which always runs before that frame's `render()`.
@since1.1.1@parambutton - Button constant from the `BTN_*` set.@paramplayer - Zero-based player index for gamepads, or pointer slot (0-3) for `BTN_POINTER_*`.@paramrepeatRate - Optional repeat interval in fixed ticks (`0`/omitted = edge only).@returns`true` on the transition frame.
isPressed
(
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
.type BTN_POINTER_B: number
Secondary pointer button code. Maps to mouse right for slot 0 (matches RetroBlit canonical, not the DOM `PointerEvent.button` index where 1 is middle and 2 is right). Always `false` for touch slots 1-3.
@since0.1.0
BTN_POINTER_B
, 0)) {
this.Demo.clearCanvas(): void
Wipes every painted pixel back to blank. Shared by right-click and the Clear button so both inputs behave identically.
clearCanvas
();
} 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
.isPressed: (button: number, player?: number, repeatRate?: number) => boolean
Checks whether a button was pressed on the current frame. Same parameter semantics as {@link isDown } ; returns `true` only on the frame the button transitions from up to down. Call from `update()`, not `render()`, for reliable detection: for keyboard-mapped face buttons (players 0 and 1), the press edge clears once per fixed-update tick, which always runs before that frame's `render()`.
@since1.1.1@parambutton - Button constant from the `BTN_*` set.@paramplayer - Zero-based player index for gamepads, or pointer slot (0-3) for `BTN_POINTER_*`.@paramrepeatRate - Optional repeat interval in fixed ticks (`0`/omitted = edge only).@returns`true` on the transition frame.
isPressed
(
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
.type BTN_POINTER_C: number
Tertiary pointer button code. Maps to mouse middle for slot 0 (matches RetroBlit canonical, not the DOM `PointerEvent.button` index where 1 is middle and 2 is right). Always `false` for touch slots 1-3.
@since0.1.0
BTN_POINTER_C
, 0)) {
this.Demo.cycleBrush(): void
Steps to the next brush size. Shared by middle-click and the Brush button so both inputs behave identically.
cycleBrush
();
} // Walk the four slots. Slot 0 paints while BTN_POINTER_A is held; // slots 1-3 paint while their touch is in contact (slot is valid). for (let let slot: numberslot = 0; let slot: numberslot < 4; let slot: numberslot++) { const const valid: booleanvalid =
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
.isPointerActive: (pointerIndex?: number) => boolean
Reports whether the given pointer slot has a live pointer. For slot 0 (mouse) this is true while the mouse is hovering inside the canvas; cleared on `pointerleave`. For slots 1-3 (touch / pen) this is true while the contact is down.
@since1.1.1@parampointerIndex - Pointer slot (defaults to 0 = mouse).@returns`true` while the slot has live position data.
isPointerActive
(let slot: numberslot);
// For slot 0 (mouse) painting is gated on the left button. For // touch slots there is only one button (A); the mere presence of // a contact is enough. const const wantPaint: booleanwantPaint = let slot: numberslot === 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;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.isDown: (button: number, player?: number) => boolean
Checks 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.
@since1.1.1@parambutton - Button constant from the `BTN_*` set.@paramplayer - Zero-based player index for gamepads / keyboard, or pointer slot (0-3) for `BTN_POINTER_*`.@returns`true` while the button remains pressed.
isDown
(
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
.type BTN_POINTER_A: number
Primary pointer button code. Maps to mouse left for slot 0; touch contact for slots 1-3.
@since0.1.0
BTN_POINTER_A
, 0) && const valid: booleanvalid : const valid: booleanvalid;
if (!const wantPaint: booleanwantPaint) { this.Demo.painting: {}painting[let slot: numberslot] = false; continue; } const const pos: Vector2ipos =
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
.pointerPos: (pointerIndex?: number) => Vector2i
Returns the position of the pointer in the given slot, in display coordinates. Slot 0 is the mouse; slots 1 through 3 are touch / pen contacts assigned in arrival order. Returns `Vector2i.zero()` when the engine has not been initialized, the slot index is out of `[0, 3]`, or the slot has no live pointer.
@since1.0.3@parampointerIndex - Pointer slot (defaults to 0 = mouse).@returnsPointer position in display coordinates.
pointerPos
(let slot: numberslot);
// Never paint underneath the UI. Without this check, tapping the // Clear button would also drop a dot of paint under the button, // because a tap is a pointer contact like any other. Marking the // slot as "not painting" also re-seeds the stroke start when the // pointer leaves the widget again, so no straight line gets drawn // through the area the panel covers. if (import uiui.overWidget(const pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
, const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
)) {
this.Demo.painting: {}painting[let slot: numberslot] = false; continue; } if (!this.Demo.painting: {}painting[let slot: numberslot]) { // Just started painting - seed last position so the first // stamp doesn't draw a line all the way from (0, 0). this.Demo.lastPosX: {}lastPosX[let slot: numberslot] = const pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
;
this.Demo.lastPosY: {}lastPosY[let slot: numberslot] = const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
;
this.Demo.painting: {}painting[let slot: numberslot] = true; } // Stamp from previous position to current one. This is what // makes fast strokes look continuous instead of dotted. this.Demo.stamp(x0: any, y0: any, x1: any, y1: any, color: any): void
Stamps the current brush along the line segment from (x0,y0) to (x1,y1), writing palette index `color` into the paint layer at every covered pixel. Uses a simple step-by-distance walker (good enough for small distances). For each step, paint a filled disc whose radius is the current brush.
stamp
(this.Demo.lastPosX: {}lastPosX[let slot: numberslot], this.Demo.lastPosY: {}lastPosY[let slot: numberslot], const pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
, const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
, const SLOT_PAINT: {}SLOT_PAINT[let slot: numberslot]);
this.Demo.lastPosX: {}lastPosX[let slot: numberslot] = const pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
;
this.Demo.lastPosY: {}lastPosY[let slot: numberslot] = const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
;
} } /** * Per-frame render: paint layer first, then live overlays (cursors and the * kit control panel). */ Demo.render(): void
Per-frame render: paint layer first, then live overlays (cursors and the kit control panel).
render
() {
// Clear to the shared theme's background color so the canvas matches // the UI panel and the engine overlay.
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.clear: (paletteIndex: number) => void
Sets the frame clear color using a palette index. The renderer uses this color when clearing the full display at the start of the next frame.
@since0.1.0@parampaletteIndex - Palette index for the full-screen clear pass.
clear
(this.Demo.theme: nulltheme.bg);
this.Demo.renderLayer(): void
Copies the persistent paint layer onto the screen. Pixels with palette index 0 are skipped so the background color shows through.
renderLayer
();
this.Demo.renderCursors(): void
Draws a small ring around each active pointer so the user can see where each finger / mouse is, even when not currently painting.
renderCursors
();
this.Demo.renderPanel(): void
The kit control panel: per-slot activity pips, the current brush, and two touch-friendly buttons. Anchored bottom-right so it stays clear of the engine overlay's toggle corner (the bottom-left 17x13 pixels are reserved for that).
renderPanel
();
} /** * Wipes every painted pixel back to blank. Shared by right-click and the * Clear button so both inputs behave identically. */ Demo.clearCanvas(): void
Wipes every painted pixel back to blank. Shared by right-click and the Clear button so both inputs behave identically.
clearCanvas
() {
this.Demo.layer: any
@type{Uint8Array | null}
layer
.fill(0);
} /** * Steps to the next brush size. Shared by middle-click and the Brush * button so both inputs behave identically. */ Demo.cycleBrush(): void
Steps to the next brush size. Shared by middle-click and the Brush button so both inputs behave identically.
cycleBrush
() {
// The % (remainder) operator wraps the index around: after the last // brush it lands back on 0, like a clock rolling over from 12 to 1. this.Demo.brushIndex: numberbrushIndex = (this.Demo.brushIndex: numberbrushIndex + 1) % const BRUSH_SIZES: {}BRUSH_SIZES.length; } /** * Stamps the current brush along the line segment from (x0,y0) to (x1,y1), * writing palette index `color` into the paint layer at every covered * pixel. * * Uses a simple step-by-distance walker (good enough for small distances). * For each step, paint a filled disc whose radius is the current brush. */ Demo.stamp(x0: any, y0: any, x1: any, y1: any, color: any): void
Stamps the current brush along the line segment from (x0,y0) to (x1,y1), writing palette index `color` into the paint layer at every covered pixel. Uses a simple step-by-distance walker (good enough for small distances). For each step, paint a filled disc whose radius is the current brush.
stamp
(x0: anyx0, y0: anyy0, x1: anyx1, y1: anyy1, color: anycolor) {
const const dx: numberdx = x1: anyx1 - x0: anyx0; const const dy: numberdy = y1: anyy1 - y0: anyy0; const const distance: anydistance = Math.max(1, Math.ceil(Math.hypot(const dx: numberdx, const dy: numberdy))); for (let let i: numberi = 0; let i: numberi <= const distance: anydistance; let i: numberi++) { const const t: numbert = let i: numberi / const distance: anydistance; const const x: anyx = Math.round(x0: anyx0 + const dx: numberdx * const t: numbert); const const y: anyy = Math.round(y0: anyy0 + const dy: numberdy * const t: numbert); this.Demo.stampAt(cx: any, cy: any, color: any): void
Paints a filled disc of the current brush radius centered on (cx, cy).
stampAt
(const x: anyx, const y: anyy, color: anycolor);
} } /** * Paints a filled disc of the current brush radius centered on (cx, cy). */ Demo.stampAt(cx: any, cy: any, color: any): void
Paints a filled disc of the current brush radius centered on (cx, cy).
stampAt
(cx: anycx, cy: anycy, color: anycolor) {
const const radius: anyradius = const BRUSH_SIZES: {}BRUSH_SIZES[this.Demo.brushIndex: numberbrushIndex]; if (const radius: anyradius === 0) { this.Demo.setPixel(x: any, y: any, color: any): void
Writes a palette index into layer, ignoring out-of-bounds writes.
setPixel
(cx: anycx, cy: anycy, color: anycolor);
return; } const const r2: numberr2 = const radius: anyradius * const radius: anyradius; for (let let dy: numberdy = -const radius: anyradius; let dy: numberdy <= const radius: anyradius; let dy: numberdy++) { for (let let dx: numberdx = -const radius: anyradius; let dx: numberdx <= const radius: anyradius; let dx: numberdx++) { if (let dx: numberdx * let dx: numberdx + let dy: numberdy * let dy: numberdy <= const r2: numberr2) { this.Demo.setPixel(x: any, y: any, color: any): void
Writes a palette index into layer, ignoring out-of-bounds writes.
setPixel
(cx: anycx + let dx: numberdx, cy: anycy + let dy: numberdy, color: anycolor);
} } } } /** * Writes a palette index into layer, ignoring out-of-bounds writes. */ Demo.setPixel(x: any, y: any, color: any): void
Writes a palette index into layer, ignoring out-of-bounds writes.
setPixel
(x: anyx, y: anyy, color: anycolor) {
if (x: anyx < 0 || x: anyx >= const DISPLAY_W: 320DISPLAY_W || y: anyy < 0 || y: anyy >= const DISPLAY_H: 240DISPLAY_H) { return; } this.Demo.layer: any
@type{Uint8Array | null}
layer
[y: anyy * const DISPLAY_W: 320DISPLAY_W + x: anyx] = color: anycolor;
} /** * Copies the persistent paint layer onto the screen. Pixels with palette * index 0 are skipped so the background color shows through. */ Demo.renderLayer(): void
Copies the persistent paint layer onto the screen. Pixels with palette index 0 are skipped so the background color shows through.
renderLayer
() {
for (let let y: numbery = 0; let y: numbery < const DISPLAY_H: 240DISPLAY_H; let y: numbery++) { const const row: numberrow = let y: numbery * const DISPLAY_W: 320DISPLAY_W; for (let let x: numberx = 0; let x: numberx < const DISPLAY_W: 320DISPLAY_W; let x: numberx++) { const const c: anyc = this.Demo.layer: any
@type{Uint8Array | null}
layer
[const row: numberrow + let x: numberx];
if (const c: anyc !== 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;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawPixel: (posOrX: Vector2i | number, yOrColor: number, maybeColor?: number) => void
Draws a single pixel. Accepts either: - `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index. - `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.
@since0.1.0@paramposOrX - Pixel position as `Vector2i`, or x coordinate when using numeric overload.@paramyOrColor - Palette index for vector overload, or y coordinate for numeric overload.@parammaybeColor - Palette index when using numeric overload.
drawPixel
(new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(let x: numberx, let y: numbery), const c: anyc);
} } } } /** * Draws a small ring around each active pointer so the user can see where * each finger / mouse is, even when not currently painting. */ Demo.renderCursors(): void
Draws a small ring around each active pointer so the user can see where each finger / mouse is, even when not currently painting.
renderCursors
() {
for (let let slot: numberslot = 0; let slot: numberslot < 4; let slot: numberslot++) { 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
.isPointerActive: (pointerIndex?: number) => boolean
Reports whether the given pointer slot has a live pointer. For slot 0 (mouse) this is true while the mouse is hovering inside the canvas; cleared on `pointerleave`. For slots 1-3 (touch / pen) this is true while the contact is down.
@since1.1.1@parampointerIndex - Pointer slot (defaults to 0 = mouse).@returns`true` while the slot has live position data.
isPointerActive
(let slot: numberslot)) {
continue; } const const pos: Vector2ipos =
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
.pointerPos: (pointerIndex?: number) => Vector2i
Returns the position of the pointer in the given slot, in display coordinates. Slot 0 is the mouse; slots 1 through 3 are touch / pen contacts assigned in arrival order. Returns `Vector2i.zero()` when the engine has not been initialized, the slot index is out of `[0, 3]`, or the slot has no live pointer.
@since1.0.3@parampointerIndex - Pointer slot (defaults to 0 = mouse).@returnsPointer position in display coordinates.
pointerPos
(let slot: numberslot);
const const color: anycolor = const SLOT_PAINT: {}SLOT_PAINT[let slot: numberslot]; // Crosshair that doesn't depend on a circle primitive.
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
.drawLine: (p0: Vector2i, p1: Vector2i, paletteIndex: number) => void
Draws a pixel-perfect line between two points. Uses rasterized line drawing without antialiasing.
@since0.1.0@paramp0 - Start position in display coordinates.@paramp1 - End position in display coordinates.@parampaletteIndex - Palette color index.
drawLine
(new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(const pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
- 5, const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
), new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(const pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
+ 5, const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
), const 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
.drawLine: (p0: Vector2i, p1: Vector2i, paletteIndex: number) => void
Draws a pixel-perfect line between two points. Uses rasterized line drawing without antialiasing.
@since0.1.0@paramp0 - Start position in display coordinates.@paramp1 - End position in display coordinates.@parampaletteIndex - Palette color index.
drawLine
(new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(const pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
, const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
- 5), new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(const pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
, const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
+ 5), const color: anycolor);
} } /** * The kit control panel: per-slot activity pips, the current brush, and * two touch-friendly buttons. Anchored bottom-right so it stays clear of * the engine overlay's toggle corner (the bottom-left 17x13 pixels are * reserved for that). */ Demo.renderPanel(): void
The kit control panel: per-slot activity pips, the current brush, and two touch-friendly buttons. Anchored bottom-right so it stays clear of the engine overlay's toggle corner (the bottom-left 17x13 pixels are reserved for that).
renderPanel
() {
import uiui.begin(import UI_ANCHORSUI_ANCHORS.BOTTOM_RIGHT); import uiui.panel('Paint'); // One pip per pointer slot: lit while that mouse / finger is active. // This is the same per-slot status the old hand-drawn panel showed. for (let let slot: numberslot = 0; let slot: numberslot < 4; let slot: numberslot++) { import uiui.pip(const SLOT_LABELS: {}SLOT_LABELS[let slot: numberslot],
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
.isPointerActive: (pointerIndex?: number) => boolean
Reports whether the given pointer slot has a live pointer. For slot 0 (mouse) this is true while the mouse is hovering inside the canvas; cleared on `pointerleave`. For slots 1-3 (touch / pen) this is true while the contact is down.
@since1.1.1@parampointerIndex - Pointer slot (defaults to 0 = mouse).@returns`true` while the slot has live position data.
isPointerActive
(let slot: numberslot));
} import uiui.separator(); // Which brush is selected right now, as a readable name. const const name: anyname = const BRUSH_NAMES: {}BRUSH_NAMES[this.Demo.brushIndex: numberbrushIndex]; import uiui.kv('Brush', const name: anyname); // The Brush button cycles the size - the touch equivalent of the // middle-click shortcut in update(). Its label changes with the brush, // and the kit normally recognizes a widget by its label, so we give it // a stable id to keep it the "same" button across frames. if (import uiui.button(`Brush: ${const name: anyname}`, { id: stringid: 'brush' })) { this.Demo.cycleBrush(): void
Steps to the next brush size. Shared by middle-click and the Brush button so both inputs behave identically.
cycleBrush
();
} // The Clear button wipes the canvas - the touch equivalent of the // right-click shortcut in update(). Both paths call clearCanvas(). if (import uiui.button('Clear')) { this.Demo.clearCanvas(): void
Wipes every painted pixel back to blank. Shared by right-click and the Clear button so both inputs behave identically.
clearCanvas
();
} 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.
@since0.2.0@changed1.4.0 Calling `bootstrap()` again while already initialized now routes to a hot swap (via {@link registerHotReload}) when a Vite HMR context is registered, or logs a double-bootstrap guard and returns `false` otherwise - previously it silently started a second, unstoppable `GameLoop`.@changed1.7.0 Exposes `BT` on `window.BT` after bootstrap finishes, gated by {@link BootstrapOptions.exposeGlobal} (default: {@link BT.isDevMode}).@paramDemoClass - Demo class constructor implementing `IBTDemo` (optional `configure()` for hardware settings).@paramoptions - Optional configuration for IDs and callbacks.@returns`true` when the demo boots successfully; otherwise `false`.@example// Simplest usage - uses default IDs. bootstrap(MyDemo);@example// With custom options. bootstrap(MyDemo, { canvasID: 'custom-canvas', containerID: 'custom-container', onSuccess: () => console.log('Demo started!'), onError: (err) => analytics.trackError(err), });@example// Await the result. const success = await bootstrap(MyDemo); if (success) { console.log('Demo is running'); }
bootstrap
(class Demo
Multi-touch / mouse paint demo. The "canvas" we paint onto is a flat array of palette indices, one entry per display pixel. Each frame, render() copies that array onto the screen with BT.drawPixel() so strokes persist between frames. Stroke input comes from checking BT.isPointerActive() / BT.isDown() / BT.pointerPos() on each of the four slots in update().
@implementsIBTDemo
Demo
);