/*

Pixel Art: tiny pictures from number grids and from math patterns.
@description Draw tiny pictures from number grids and from math patterns, one pixel at a time, the way old games did.

Part of the BLIT386 demo series.

Prerequisites:
  - Basics: https://demos.blit386.dev/basics
  - Primitives: https://demos.blit386.dev/primitives
  - Colors: https://demos.blit386.dev/colors

Live version: https://demos.blit386.dev/pixel-art

Guides:
  - Palette: https://blit386.dev/docs/guides/palette#draw-using-absolute-palette-indices
  - Overlay: https://blit386.dev/docs/guides/overlay#colors-and-hud-palette-slots

Reference:
  - Rendering: https://blit386.dev/docs/api/rendering#primitives
  - Core Types: https://blit386.dev/docs/api/core-types#color32

---

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

A deep gray-blue screen with two small pieces of artwork on the left, each under an amber
caption drawn by the shared UI kit:

- "Heart 8x8": a heart built from an 8 by 8 grid of numbers, drawn at 4 screen pixels per
  grid cell, so it is 32 pixels wide. Its body is dark red with two bright red highlight
  pixels in the second row.
- "Checkerboard": an 8 by 8 board of 10-pixel squares. The two square colors slide back
  and forth - one between red and yellow, the other between cyan and blue - so the board
  never stops changing.

The engine overlay is open from the start with its palette grid at 16 swatches per row, so
the demo's 32-slot palette shows as two rows and you can watch slots 10 and 11 change.

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

- A 2D array (a grid) of small numbers that stand for colors, like paint-by-number on
  graph paper.
- Nested loops: an outer loop for each row and an inner loop for each column, like
  reading a book line by line and word by word.
- How a grid's row and column turn into x and y positions on the screen.
- Why many BT.drawPixel() calls in a small block make one "big" chunky pixel.
- A pattern drawn only with loops and math (no picture array), with colors that move over
  time thanks to Color32.lerp() and palette.set() in update().
- A small palette (32 slots) with the shared UI theme moved to slots 20-31 by passing a
  start slot to applyTheme().

TRY THIS
--------

- Watch slots 10 and 11 in the overlay's palette grid: they are the checkerboard colors
  and they move with the squares below.
- Press the backquote key (`) to hide the overlay and see the artwork alone.

*/

import { function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>
One-liner bootstrap function for BLIT386 demos. Handles canvas retrieval and engine initialization. Backend selection (WebGPU or software fallback) is managed internally by BTAPI. This function provides a streamlined way to start a demo with sensible defaults while allowing customization through options.
@since0.2.0@changed1.4.0 Calling `bootstrap()` again while already initialized now routes to a hot swap (via {@link registerHotReload}) when a Vite HMR context is registered, or logs a double-bootstrap guard and returns `false` otherwise - previously it silently started a second, unstoppable `GameLoop`.@changed1.7.0 Exposes `BT` on `window.BT` after bootstrap finishes, gated by {@link BootstrapOptions.exposeGlobal} (default: {@link BT.isDevMode}).@paramDemoClass - Demo class constructor implementing `IBTDemo` (optional `configure()` for hardware settings).@paramoptions - Optional configuration for IDs and callbacks.@returns`true` when the demo boots successfully; otherwise `false`.@example// Simplest usage - uses default IDs. bootstrap(MyDemo);@example// With custom options. bootstrap(MyDemo, { canvasID: 'custom-canvas', containerID: 'custom-container', onSuccess: () => console.log('Demo started!'), onError: (err) => analytics.trackError(err), });@example// Await the result. const success = await bootstrap(MyDemo); if (success) { console.log('Demo is running'); }
bootstrap
,
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
, class Rect2i
Integer rectangle for pixel-perfect bounds and regions. Used throughout the engine for sprite regions, display-space bounds, and zero-allocation geometry helpers. Both convenience getters and allocation-free `*To()` helpers are provided so callers can choose between readability and hot-path efficiency.
@since0.1.0
Rect2i
, class Vector2i
Integer 2D vector for pixel-perfect positioning. Used for points, sizes, directions, and camera offsets throughout the engine. The API includes both allocation-free `*To()` / `*InPlace()` variants and convenience methods that return new vectors.
@since0.1.0
Vector2i
} from 'blit386';
import { import applyThemeapplyTheme, import uiui } from './shared/ui.js'; /** @typedef {import('blit386').Palette} Palette */ // Every color used for drawing gets a numbered slot in the palette, like a numbered paint // jar. Slot 0 is always transparent, so our own colors start at 1. const const C_WHITE: 1C_WHITE = 1; // pure white, kept in the palette but not drawn with const const C_BG: 2C_BG = 2; // deep gray-blue: fills the screen background const const C_TAG: 3C_TAG = 3; // pale blue-white: overlay bar and timing-chart accents // Heart colors, picked by the codes in HEART_PALETTE_MAP below. const const C_HEART_OUTLINE: 5C_HEART_OUTLINE = 5; // dark red: the heart's body (grid code 1) const const C_HEART_FILL: 6C_HEART_FILL = 6; // bright red: the two highlight pixels (grid code 2) // Checker colors. update() repaints these every tick so the board keeps moving. const const C_CHECKER_A: 10C_CHECKER_A = 10; // dynamic: slides between red and yellow const const C_CHECKER_B: 11C_CHECKER_B = 11; // dynamic: slides between cyan and blue // HEART_GRID is an 8 by 8 table of small whole numbers. Think of graph paper: each cell // is one tiny part of the picture. 0 means "leave empty" (we skip drawing there so the // background shows through); 1 and 2 pick palette slots from HEART_PALETTE_MAP below. const const HEART_GRID: {}HEART_GRID = [ [0, 1, 1, 0, 0, 1, 1, 0], [1, 2, 1, 1, 1, 2, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1], [0, 1, 1, 1, 1, 1, 1, 0], [0, 1, 1, 1, 1, 1, 1, 0], [0, 0, 1, 1, 1, 1, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0], ]; // HEART_PALETTE_MAP lines up with the numbers in the grid: paletteMap[1] is the palette // slot for code 1, paletteMap[2] for code 2. Index 0 is null because 0 means "no paint". // These are palette slot numbers, not Color32 objects; the palette knows the real colors. const const HEART_PALETTE_MAP: {}HEART_PALETTE_MAP = [null, const C_HEART_OUTLINE: 5C_HEART_OUTLINE, const C_HEART_FILL: 6C_HEART_FILL]; // Looks up the palette slot for a paint code. The grid only holds small numbers we wrote // ourselves, not user input, but this is still the one place that checks them: 0 (empty) // and anything outside the map both come back as null, so callers need just one check. function function indexFromPaletteMap(paletteMap: any, code: any): anyindexFromPaletteMap(paletteMap: anypaletteMap, code: anycode) { if (code: anycode < 1 || code: anycode >= paletteMap: anypaletteMap.length) { return null; } return paletteMap: anypaletteMap[code: anycode]; } // Teaches pixel grids, nested loops, mapping grid cells to the screen, and a tiny // procedural pattern. class class DemoDemo { // A clock that counts up in seconds. It only changes inside update(), so the // animation stays steady even when render() runs at an odd rate. Demo.animTime: numberanimTime = 0; // The palette holds every color this demo uses. /** @type {Palette | null} */ Demo.palette: Palette | null
@type{Palette | null}
palette
= null;
// Where the shared UI theme colors landed in the palette, filled by applyTheme() in // init(). The UI kit draws the section captions with these slots. Demo.theme: nulltheme = null; // Optional engine settings. We keep the default 320x240 screen, open the overlay from // the start, and show its palette grid at 16 swatches per row so our 32-slot palette // appears as two neat rows.
Demo.configure(): {
    isOverlayPaletteEnabled: boolean;
    overlayPaletteColumns: number;
    isOverlayVisibleAtStart: boolean;
    overlayStyle: {
        barPaletteIndex: number;
        textPaletteIndex: number;
        gapPaletteIndex: number;
    };
    overlayTimingChartStyle: {
        updateBarPaletteIndex: number;
        renderBarPaletteIndex: number;
        warningPaletteIndex: number;
        errorPaletteIndex: number;
        tagPaletteIndex: number;
    };
}
configure
() {
return { isOverlayPaletteEnabled: booleanisOverlayPaletteEnabled: true, overlayPaletteColumns: numberoverlayPaletteColumns: 16, isOverlayVisibleAtStart: booleanisOverlayVisibleAtStart: true, // Overlay bar: pale blue-white with dark text.
overlayStyle: {
    barPaletteIndex: number;
    textPaletteIndex: number;
    gapPaletteIndex: number;
}
overlayStyle
: {
barPaletteIndex: numberbarPaletteIndex: const C_TAG: 3C_TAG, textPaletteIndex: numbertextPaletteIndex: const C_BG: 2C_BG, gapPaletteIndex: numbergapPaletteIndex: const C_BG: 2C_BG, }, // Timing chart colors. The chart itself is not enabled in this demo, so these // only matter if you add isOverlayTimingChartEnabled: true above.
overlayTimingChartStyle: {
    updateBarPaletteIndex: number;
    renderBarPaletteIndex: number;
    warningPaletteIndex: number;
    errorPaletteIndex: number;
    tagPaletteIndex: number;
}
overlayTimingChartStyle
: {
updateBarPaletteIndex: numberupdateBarPaletteIndex: const C_BG: 2C_BG, renderBarPaletteIndex: numberrenderBarPaletteIndex: const C_TAG: 3C_TAG, warningPaletteIndex: numberwarningPaletteIndex: const C_TAG: 3C_TAG, errorPaletteIndex: numbererrorPaletteIndex: 4, // slot 4 has no named constant and is never set tagPaletteIndex: numbertagPaletteIndex: const C_BG: 2C_BG, }, }; } // Sets up the palette. Returns true when ready. async Demo.init(): unknowninit() { // Lay out the colors first, like putting paint on an artist's tray before // starting a painting. Every color gets a number, all chosen before drawing // begins. 32 slots are enough: the artwork uses 1 through 11 and the shared UI // theme takes 12 more (slots 20-31, installed below). this.Demo.palette: Palette | null
@type{Palette | null}
palette
=
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.paletteCreate: (size?: number) => Palette
Creates a standalone palette instance.
@since1.0.3@paramsize - Palette size. Defaults to 256 colors.@returnsNew mutable palette.
paletteCreate
(32);
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_WHITE: 1C_WHITE, 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, 255, 255)); // pure white
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_BG: 2C_BG, 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
(28, 32, 48)); // deep gray-blue background
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_TAG: 3C_TAG, 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
(200, 210, 230)); // pale blue-white
// Heart colors. this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_HEART_OUTLINE: 5C_HEART_OUTLINE, 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
(110, 10, 30)); // dark red body
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_HEART_FILL: 6C_HEART_FILL, 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
(230, 55, 75)); // bright red highlights
// Starting values for the checker colors. update() overwrites them on the very // first tick. this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_CHECKER_A: 10C_CHECKER_A, 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, 0, 0)); // starts red
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_CHECKER_B: 11C_CHECKER_B, 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
(0, 0, 255)); // starts blue
// Install the shared UI theme the captions draw with. It writes 12 colors // starting at the slot we pass - 20 through 31 here, which stays clear of the // artwork colors (1-11) and fills the top of our 32-slot palette exactly. Must // happen before BT.paletteSet(). this.Demo.theme: nulltheme = import applyThemeapplyTheme(this.Demo.palette: Palette
@type{Palette | null}
palette
, 20);
// Tell the engine to use this palette for all drawing.
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.paletteSet: (palette: Palette) => void
Stores the active engine palette. Use this to swap the **entire palette** (e.g. switch between a day and night theme). After this call the renderer uploads the new palette uniform on the next frame. **Palette-value swap (change what a slot looks like):** mutate the live {@link BT.palette } in place with `palette.set(slot, newColor)`. The renderer uploads dirty slots on the next frame; no `paletteSet()` or {@link BT.spritesRefresh } needed. **Palette-layout swap (same colors, different slot positions):** build a new palette with the same colors at new indices, call `paletteSet()`, then call {@link BT.spritesRefresh } so every sprite sheet re-maps its original RGBA pixels against the new slot layout.
@since1.0.3@parampalette - Palette to make active.
paletteSet
(this.Demo.palette: Palette
@type{Palette | null}
palette
);
return true; } // Runs at a fixed rate (60 times per second by default). Advances animTime and // repaints the checker colors in the palette. See the Basics demo for why update() // and render() are separate steps: https://demos.blit386.dev/basics Demo.update(): voidupdate() { // Add one tick's worth of time. At the default 60 ticks per second that is about // 1/60 of a second. this.Demo.animTime: numberanimTime +=
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
.deltaSeconds: number
Fixed-step seconds per update tick. Equivalent to `1 / BT.targetFPS` when `BT.targetFPS` is finite and positive. Falls back to `1 / 60` when target FPS is non-finite or non-positive.
@since1.0.4@returnsSeconds advanced by one fixed update tick.
deltaSeconds
;
// The checker squares use "lerp" (linear interpolation: a smooth blend between // two colors). wave swings from 0 to 1 and back using Math.sin(): sin returns // -1..1, so adding 1 and halving moves it to 0..1. At wave 0, color A is red and // color B is cyan; at wave 1, A is yellow and B is blue. B uses 1 - wave, so the // two colors slide in opposite directions and always contrast. const const wave: numberwave = (Math.sin(this.Demo.animTime: numberanimTime * 2) + 1) * 0.5; this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_CHECKER_A: 10C_CHECKER_A, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.red: Color32
Pure red color (255, 0, 0, 255). Cached frozen singleton - do not modify.
@returnsThe shared red Color32 instance.
red
.Color32.lerp(other: Color32, t: number): Color32
Linearly interpolates between this color and another. Useful for color transitions and gradients.
@paramother - Target color to blend toward.@paramt - Interpolation factor (0.0 = this color, 1.0 = other color).@returnsNew color blended between this and other.
lerp
(class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.yellow: Color32
Yellow color (255, 255, 0, 255). Cached frozen singleton - do not modify.
@returnsThe shared yellow Color32 instance.
yellow
, const wave: numberwave));
this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_CHECKER_B: 11C_CHECKER_B, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.blue: Color32
Pure blue color (0, 0, 255, 255). Cached frozen singleton - do not modify.
@returnsThe shared blue Color32 instance.
blue
.Color32.lerp(other: Color32, t: number): Color32
Linearly interpolates between this color and another. Useful for color transitions and gradients.
@paramother - Target color to blend toward.@paramt - Interpolation factor (0.0 = this color, 1.0 = other color).@returnsNew color blended between this and other.
lerp
(class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.cyan: Color32
Cyan color (0, 255, 255, 255). Cached frozen singleton - do not modify.
@returnsThe shared cyan Color32 instance.
cyan
, 1 - const wave: numberwave));
} // Draws the whole frame: the captions, the number-grid heart, and the checkerboard. // FPS and tick stats live in the engine overlay, not on the canvas. Demo.render(): voidrender() { // Clear to the deep gray-blue background so the artwork pops.
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.clear: (paletteIndex: number) => void
Sets the frame clear color using a palette index. The renderer uses this color when clearing the full display at the start of the next frame.
@since0.1.0@parampaletteIndex - Palette index for the full-screen clear pass.
clear
(const C_BG: 2C_BG);
// Heart on top. this.Demo.renderHeartSection(): voidrenderHeartSection(); // Checkerboard below, with colors that shift with animTime. this.Demo.renderCheckerPatternSection(): voidrenderCheckerPatternSection(); } // Turns a 2D number grid into chunky pixels on screen. `grid` holds rows of paint // codes (grid[row][col], like graph paper), `paletteMap` says which palette slot each // code means (null to skip), `originX` and `originY` are where column 0 and row 0 // land on screen, and `scale` is how many screen pixels wide and tall each grid cell // becomes. // // Nested loops: the outer `for` walks row = 0, 1, 2... (which strip of the grid), the // inner `for` walks col = 0, 1, 2... inside that row (left to right). Finish one // whole row before moving down, like reading a page. // // BT.drawPixel() paints exactly one screen pixel. To make each grid cell bigger we // use two more small loops (dx and dy) that stamp a scale-by-scale block of pixels. // Another valid way is BT.drawRectFill() with width and height equal to scale: same // math, one call per cell. Demo.drawGridWithScaledPixels(grid: any, paletteMap: any, originX: any, originY: any, scale: any): voiddrawGridWithScaledPixels(grid: anygrid, paletteMap: anypaletteMap, originX: anyoriginX, originY: anyoriginY, scale: anyscale) { // Outer loop: which row of the design (the top row is row 0). for (let let row: numberrow = 0; let row: numberrow < grid: anygrid.length; let row: numberrow++) { // One row of the picture as a normal JavaScript array. const const rowCodes: anyrowCodes = grid: anygrid[let row: numberrow]; // Inner loop: move across that row from left to right. for (let let col: numbercol = 0; let col: numbercol < const rowCodes: anyrowCodes.length; let col: numbercol++) { // Read this cell's paint code, like finding a square on graph paper. const const code: anycode = const rowCodes: anyrowCodes[let col: numbercol]; // Ask the helper which palette slot this code means. It answers // null for code 0 ("no ink here") and for any code outside the // map, so this one check is all the guarding we need: skip, and // the background stays visible. const const paletteIndex: anypaletteIndex = function indexFromPaletteMap(paletteMap: any, code: any): anyindexFromPaletteMap(paletteMap: anypaletteMap, const code: anycode); if (const paletteIndex: anypaletteIndex === null) { continue; } // Map grid (col, row) to the top-left corner of this cell on screen. The // column moves x (sideways), the row moves y (down the screen). const const baseX: anybaseX = originX: anyoriginX + let col: numbercol * scale: anyscale; const const baseY: anybaseY = originY: anyoriginY + let row: numberrow * scale: anyscale; // Two tiny loops fill a scale-by-scale square, one drawPixel at a time. for (let let dy: numberdy = 0; let dy: numberdy < scale: anyscale; let dy: numberdy++) { for (let let dx: numberdx = 0; let dx: numberdx < scale: anyscale; let dx: numberdx++) {
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
.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
(const baseX: anybaseX + let dx: numberdx, const baseY: anybaseY + let dy: numberdy), const paletteIndex: anypaletteIndex);
} } } } } // Captions and draws the 8x8 heart. Demo.renderHeartSection(): voidrenderHeartSection() { // The caption comes from ui.caption() in the shared UI kit, the same widget every // demo in the series uses, so all captions look identical everywhere. import uiui.caption(12, 52, 'Heart 8x8 (number grid, no external bitmap used)'); // scale 4 makes the 8-cell-wide picture 32 screen pixels wide. const const scale: 4scale = 4; const const originX: 12originX = 12; const const originY: 62originY = 62; this.Demo.drawGridWithScaledPixels(grid: any, paletteMap: any, originX: any, originY: any, scale: any): voiddrawGridWithScaledPixels(const HEART_GRID: {}HEART_GRID, const HEART_PALETTE_MAP: {}HEART_PALETTE_MAP, const originX: 12originX, const originY: 62originY, const scale: 4scale); } // Draws a checkerboard using only math inside nested loops - no picture array. The // colors slide around with animTime (advanced in update()) so you can see the clock // moving. Demo.renderCheckerPatternSection(): voidrenderCheckerPatternSection() { import uiui.caption(12, 102, 'Checkerboard (loops + math, no grid array)'); // How many squares along each side. const const cells: 8cells = 8; // Pixel size of one square on the 320x240 screen. const const cellSize: 10cellSize = 10; // Top-left corner of the whole board. const const startX: 12startX = 12; const const startY: 112startY = 112; // Outer loop picks the row of squares, inner loop the column: the same nested // idea as the heart. for (let let row: numberrow = 0; let row: numberrow < const cells: 8cells; let row: numberrow++) { for (let let col: numbercol = 0; let col: numbercol < const cells: 8cells; let col: numbercol++) { // Checker rule: neighbors must look different, like a chessboard. // % is "remainder after division": (row + col) % 2 is 0 for even // sums and 1 for odd sums. Each step right or down changes // row + col by one, which flips the remainder, so no two touching // squares share a color. update() already refreshed C_CHECKER_A // and C_CHECKER_B this tick. const const fill: 10 | 11fill = (let row: numberrow + let col: numbercol) % 2 === 0 ? const C_CHECKER_A: 10C_CHECKER_A : const C_CHECKER_B: 11C_CHECKER_B; // Rect2i(x, y, width, height) describes a solid rectangle in pixels. const const x: numberx = const startX: 12startX + let col: numbercol * const cellSize: 10cellSize; const const y: numbery = const startY: 112startY + let row: numberrow * const cellSize: 10cellSize;
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawRectFill: (rect: Rect2i, paletteIndex: number) => void
Draws a filled rectangle.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRectFill
(new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2i
Creates an integer rectangle, truncating all inputs toward zero.
@paramx - Left-edge X coordinate (defaults to 0).@paramy - Top-edge Y coordinate (defaults to 0).@paramwidth - Width in pixels (defaults to 0).@paramheight - Height in pixels (defaults to 0).
Rect2i
(const x: numberx, const y: numbery, const cellSize: 10cellSize, const cellSize: 10cellSize), const fill: 10 | 11fill);
} } } } // Hand the Demo class to BLIT386 to start the demo loop. function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>
One-liner bootstrap function for BLIT386 demos. Handles canvas retrieval and engine initialization. Backend selection (WebGPU or software fallback) is managed internally by BTAPI. This function provides a streamlined way to start a demo with sensible defaults while allowing customization through options.
@since0.2.0@changed1.4.0 Calling `bootstrap()` again while already initialized now routes to a hot swap (via {@link registerHotReload}) when a Vite HMR context is registered, or logs a double-bootstrap guard and returns `false` otherwise - previously it silently started a second, unstoppable `GameLoop`.@changed1.7.0 Exposes `BT` on `window.BT` after bootstrap finishes, gated by {@link BootstrapOptions.exposeGlobal} (default: {@link BT.isDevMode}).@paramDemoClass - Demo class constructor implementing `IBTDemo` (optional `configure()` for hardware settings).@paramoptions - Optional configuration for IDs and callbacks.@returns`true` when the demo boots successfully; otherwise `false`.@example// Simplest usage - uses default IDs. bootstrap(MyDemo);@example// With custom options. bootstrap(MyDemo, { canvasID: 'custom-canvas', containerID: 'custom-container', onSuccess: () => console.log('Demo started!'), onError: (err) => analytics.trackError(err), });@example// Await the result. const success = await bootstrap(MyDemo); if (success) { console.log('Demo is running'); }
bootstrap
(class DemoDemo);