/*

Tilemap: build a grid world from a 2D array and scroll the camera across it.
@description Build a grid world from a two-dimensional array and scroll a camera across it, drawing visible tiles.

Part of the BLIT386 demo series.

Prerequisites:
  - Basics: https://demos.blit386.dev/basics
  - Primitives: https://demos.blit386.dev/primitives
  - Camera: https://demos.blit386.dev/camera

Live version: https://demos.blit386.dev/tilemap

Guides:
  - Game Loop: https://blit386.dev/docs/guides/game-loop#smoothing-motion-between-updates
  - Palette: https://blit386.dev/docs/guides/palette#layout-swap-vs-value-swap
  - Overlay: https://blit386.dev/docs/guides/overlay#optional-bands

Reference:
  - Camera: https://blit386.dev/docs/api/camera
  - Game Loop: https://blit386.dev/docs/api/game-loop#interpolating-render-state-with-renderalpha
  - Overlay: https://blit386.dev/docs/api/overlay#renderer-diagnostics-optional

---

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

A 30 by 20 tile world (480 by 320 pixels at 16 pixels per tile) seen through a 320 by
240 screen. From top to bottom: sky blue air, a row of grass with two rows of dirt under
it, two dirt hills poking up at the left and right, dark green tree canopies standing on
the grass and hills, two more rows of dirt, and a pond filling the bottom six rows with
gray stepping stones scattered in it and two stones on the shoreline. The camera slowly
sweeps across the whole map in a wave pattern, both sideways and up and down.

A UI kit panel in the top-left corner names the demo and its size. A mini-map in the
bottom-right corner shows the entire map at 3 pixels per tile inside a kit panel, with a
yellow box marking the part of the world the screen is showing right now. The pond on
the main map shimmers; the mini-map's water is a calm, still blue. The engine overlay
has the timing chart with rich GPU markers and the renderer diagnostics row turned on.

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

- Storing a level as a 2D array - a list of rows, each row a list of small numbers (tile
  IDs) - and naming those numbers with constants like TILE_GRASS.
- Walking the grid with nested loops: one loop for rows, one for columns.
- Culling: working out which rows and columns overlap the screen from the camera
  position (Math.floor for the first tile, Math.ceil for the last) and drawing only
  those.
- BT.cameraSet(offset), BT.cameraReset(), and BT.cameraClamp(pos, worldSize, viewSize),
  plus the BT.renderAlpha smoothing introduced in the Camera demo.
- Palette animation: update() rewrites one slot (C_WATER) with palette.set() every tick,
  and render() keeps passing that slot number - the water shimmers for free.
- Reusing a single Rect2i with tileRect.set() for every tile instead of creating one per
  tile.
- UI kit pieces: ui.panel(), ui.label(), ui.spacer(), and ui.drawRectFill() /
  ui.drawRect() for artwork that has to land on top of a kit panel.
- configure() overlay options: isOverlayTimingChartEnabled,
  overlayTimingChartDiagnostics: 'rich', and isOverlayRendererDiagnosticsBarEnabled.

WHAT IS A TILEMAP?
------------------

A tilemap is like a floor made of same-sized square tiles. Each cell in a 2D array stores
a small number that means "which kind of tile goes here" - like a Lego instruction sheet
that says which brick color fits each stud. The computer walks the grid with nested loops
and draws only the tiles the camera can see, which is faster than drawing thousands of
off-screen tiles nobody would see.

TRY THIS
--------

- Press the backquote key (`) to open the engine overlay. The timing chart carries rich
  GPU markers, and the renderer diagnostics row below the frame metrics shows what the
  renderer is doing.
- Compare the pond on the main map with the water on the mini-map: the first uses the
  animated C_WATER slot, the second the fixed C_MINIMAP_WATER slot.

*/

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, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js'; /** @typedef {import('blit386').Palette} Palette */ // These numbers are the "tile IDs" stored inside the 2D array. Naming them helps you // remember what each number means when you read the map. const const TILE_SKY: 0TILE_SKY = 0; // empty air - skipped, the sky clear color shows through const const TILE_GRASS: 1TILE_GRASS = 1; // green ground const const TILE_DIRT: 2TILE_DIRT = 2; // brown earth below or beside grass const const TILE_STONE: 3TILE_STONE = 3; // gray rocks const const TILE_WATER: 4TILE_WATER = 4; // blue water (its shade is animated a little each tick) const const TILE_TREE_TOP: 5TILE_TREE_TOP = 5; // dark green tree canopy // How many tiles wide and tall the world is. Multiply by TILE_SIZE to get the pixel size. const const MAP_WIDTH_TILES: 30MAP_WIDTH_TILES = 30; const const MAP_HEIGHT_TILES: 20MAP_HEIGHT_TILES = 20; // Every tile is a small square on a pixel grid. 16 is a common retro size. const const TILE_SIZE: 16TILE_SIZE = 16; // World size in pixels: 30 * 16 = 480 wide, 20 * 16 = 320 tall. const const WORLD_WIDTH_PX: numberWORLD_WIDTH_PX = const MAP_WIDTH_TILES: 30MAP_WIDTH_TILES * const TILE_SIZE: 16TILE_SIZE; const const WORLD_HEIGHT_PX: numberWORLD_HEIGHT_PX = const MAP_HEIGHT_TILES: 20MAP_HEIGHT_TILES * const TILE_SIZE: 16TILE_SIZE; // Each color in this demo has a reserved palette slot (a number from 1 upward). Slot 0 is // always transparent. Giving each slot a name makes the drawing code easier to read - // "draw in C_GRASS" is clearer than "draw in slot 5". The caption panel and the mini-map // frame use the shared UI kit theme instead, which applyTheme() installs into slots // 240-251. const const C_SKY: 2C_SKY = 2; // soft sky blue: fills the screen background const const C_HUD_BAR: 3C_HUD_BAR = 3; // semi-transparent black: the engine overlay bar color const const C_TEXT_DIM: 4C_TEXT_DIM = 4; // dimmed white: the engine overlay text color const const C_GRASS: 5C_GRASS = 5; // green: grass tiles const const C_DIRT: 6C_DIRT = 6; // brown: dirt tiles const const C_STONE: 7C_STONE = 7; // gray: stone/rock tiles const const C_TREE_TOP: 8C_TREE_TOP = 8; // dark green: tree canopy tiles const const C_MINIMAP_WATER: 9C_MINIMAP_WATER = 9; // still blue: water on the mini-map only (never animated) const const C_CHART_WARNING: 11C_CHART_WARNING = 11; // near-white: overlay timing chart warning color const const C_VIEWPORT: 12C_VIEWPORT = 12; // yellow: rectangle showing the camera view on the mini-map const const C_CHART_TAG: 13C_CHART_TAG = 13; // dim gray: overlay timing chart tag labels const const C_WATER: 14C_WATER = 14; // animated water tile color, rewritten every tick in update() // A scrolling tile-based landscape with a mini-map and animated water. class class DemoDemo { // The tilemap is an array of rows: map[row][column] is one cell. Row 0 is the top of // the world; column 0 is the left edge. Think of it like a spreadsheet: the first // index is how far down, the second is how far right. Demo.map: {}map = []; // The top-left corner of the world (in pixels) that appears at the top-left of the // screen. When this moves right, the world seems to slide left. Demo.cameraPos: Vector2icameraPos = 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
(0, 0);
// Where the camera was at the start of the most recent update() tick, before that // tick's sine math moved it. render() blends between cameraPrevPos and cameraPos // using BT.renderAlpha, so the camera pans smoothly between ticks instead of jumping // - see the Game Loop guide linked in the header. Demo.cameraPrevPos: Vector2icameraPrevPos = 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
(0, 0);
// Reused every render() call for the blended camera position, so no new Vector2i is // allocated per frame. Both the actual camera offset and the visible-tile culling // window use this same value, so they never disagree about which pixel row and column // the camera is looking at. Demo.cameraRenderPos: Vector2icameraRenderPos = 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
(0, 0);
// The palette holds all the colors this demo uses. It is filled in init() so the // engine knows every color before drawing begins. /** @type {Palette | null} */ Demo.palette: Palette | null
@type{Palette | null}
palette
= null;
// Slot map for the shared UI kit theme, filled in init() by applyTheme(). It tells us // which palette slots hold the kit's panel, border, and text colors. Demo.theme: nulltheme = null; // One rectangle object rewritten for every tile we draw. Reusing it avoids making a // new Rect2i per tile, which would create garbage for the browser to clean up. Demo.tileRect: Rect2itileRect = 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
(0, 0, const TILE_SIZE: 16TILE_SIZE, const TILE_SIZE: 16TILE_SIZE);
// Turns on the overlay timing chart with rich GPU markers and the renderer // diagnostics row, and picks palette slots for the overlay bars, text, and chart.
Demo.configure(): {
    isOverlayTimingChartEnabled: boolean;
    overlayTimingChartDiagnostics: string;
    isOverlayRendererDiagnosticsBarEnabled: boolean;
    overlayStyle: {
        barPaletteIndex: number;
        textPaletteIndex: number;
        gapPaletteIndex: number;
    };
    overlayTimingChartStyle: {
        updateBarPaletteIndex: number;
        renderBarPaletteIndex: number;
        warningPaletteIndex: number;
        errorPaletteIndex: number;
        tagPaletteIndex: number;
    };
}
configure
() {
return { isOverlayTimingChartEnabled: booleanisOverlayTimingChartEnabled: true, overlayTimingChartDiagnostics: stringoverlayTimingChartDiagnostics: 'rich', isOverlayRendererDiagnosticsBarEnabled: booleanisOverlayRendererDiagnosticsBarEnabled: true,
overlayStyle: {
    barPaletteIndex: number;
    textPaletteIndex: number;
    gapPaletteIndex: number;
}
overlayStyle
: {
barPaletteIndex: numberbarPaletteIndex: const C_HUD_BAR: 3C_HUD_BAR, textPaletteIndex: numbertextPaletteIndex: const C_TEXT_DIM: 4C_TEXT_DIM, gapPaletteIndex: numbergapPaletteIndex: const C_HUD_BAR: 3C_HUD_BAR, },
overlayTimingChartStyle: {
    updateBarPaletteIndex: number;
    renderBarPaletteIndex: number;
    warningPaletteIndex: number;
    errorPaletteIndex: number;
    tagPaletteIndex: number;
}
overlayTimingChartStyle
: {
updateBarPaletteIndex: numberupdateBarPaletteIndex: const C_VIEWPORT: 12C_VIEWPORT, renderBarPaletteIndex: numberrenderBarPaletteIndex: const C_GRASS: 5C_GRASS, warningPaletteIndex: numberwarningPaletteIndex: const C_CHART_WARNING: 11C_CHART_WARNING, errorPaletteIndex: numbererrorPaletteIndex: const C_STONE: 7C_STONE, tagPaletteIndex: numbertagPaletteIndex: const C_CHART_TAG: 13C_CHART_TAG, }, }; } // Runs once at startup: builds the palette and fills the 2D tilemap array. async Demo.init(): unknowninit() { // A palette is like an artist's paint tray - we choose all our colors before // drawing anything. Each color gets a number (a slot) that we use in draw calls. 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
(256);
// Static (fixed) colors: these never change from frame to frame. Each slot's role // is described on its C_* constant at the top of the file. 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_SKY: 2C_SKY, 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
(135, 206, 250));
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_HUD_BAR: 3C_HUD_BAR, 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, 0, 185));
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_TEXT_DIM: 4C_TEXT_DIM, 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, 200, 200));
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_GRASS: 5C_GRASS, 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
(50, 160, 60));
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_DIRT: 6C_DIRT, 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
(130, 90, 55));
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_STONE: 7C_STONE, 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
(120, 120, 130));
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_TREE_TOP: 8C_TREE_TOP, 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
(15, 90, 30));
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_MINIMAP_WATER: 9C_MINIMAP_WATER, 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
(30, 110, 200));
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_CHART_WARNING: 11C_CHART_WARNING, 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 C_VIEWPORT: 12C_VIEWPORT, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(255, 230, 60));
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_CHART_TAG: 13C_CHART_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
(160, 160, 160));
// Dynamic color: the animated water tile. It gets a starting value here so there // is no empty slot on the very first frame before update() runs. 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_WATER: 14C_WATER, 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
(30, 110, 210));
// Install the shared UI kit theme. applyTheme() writes twelve UI colors into high // palette slots (240-251), far above this demo's scene slots (2-14), and returns // a map of slot numbers we can draw with (panel, border, text...). this.Demo.theme: nulltheme = import applyThemeapplyTheme(this.Demo.palette: Palette
@type{Palette | null}
palette
);
// Tell the engine to use this palette for all drawing from now on.
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
);
// Fill the tilemap with a simple outdoor scene: sky, grass, dirt, trees, water, // rocks. this.Demo.buildLandscape(): voidbuildLandscape(); return true; } // Runs at a fixed rate (60 times per second) for game logic. Moves the camera and // rewrites the animated water color in the palette so render() can use C_WATER as a // plain slot number. Demo.update(): voidupdate() { // Remember where the camera was before this tick's math moves it, so render() has // an "old" and a "new" position to blend between. this.Demo.cameraPrevPos: Vector2icameraPrevPos.Vector2i.set(x: number, y: number): Vector2i
Sets both components of this vector. Modifies this vector directly for maximum performance. WARNING: Mutates this vector. Don't use on frozen/cached singletons.
@paramx - New x component.@paramy - New y component.@returnsThis vector for chaining.
set
(this.Demo.cameraPos: Vector2icameraPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
, this.Demo.cameraPos: Vector2icameraPos.Vector2i.y: number
Vertical component (defaults to 0).
y
);
// BT.ticks counts how many fixed updates have happened since the demo started. // Multiplying by a small number makes the wave change slowly over time. const const t: numbert =
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
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
* 0.028;
// The camera can scroll from 0 up to "world size minus screen size" on each axis. // Math.sin(t) wiggles forever between -1 and +1, like a gentle wave on water; // centering it in that range and scaling it by half the range makes the camera // sweep the whole map. Math.floor turns the result into a whole pixel position // (BLIT386 uses integer pixels). const const viewSize: Vector2iviewSize =
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
.displaySize: Vector2i
Active logical render resolution in pixels. This is the game/simulation coordinate space configured by the demo, not the canvas element's CSS size. Each read returns a clone.
@since1.0.4@returnsConfigured logical size, or `Vector2i.zero()` before initialization.
displaySize
;
const const maxCamX: numbermaxCamX = const WORLD_WIDTH_PX: numberWORLD_WIDTH_PX - const viewSize: Vector2iviewSize.Vector2i.x: number
Horizontal component (defaults to 0).
x
;
const const maxCamY: numbermaxCamY = const WORLD_HEIGHT_PX: numberWORLD_HEIGHT_PX - const viewSize: Vector2iviewSize.Vector2i.y: number
Vertical component (defaults to 0).
y
;
const const centerX: numbercenterX = const maxCamX: numbermaxCamX / 2; const const centerY: numbercenterY = const maxCamY: numbermaxCamY / 2; const const amplitudeX: anyamplitudeX = Math.max(0, const maxCamX: numbermaxCamX / 2); const const amplitudeY: anyamplitudeY = Math.max(0, const maxCamY: numbermaxCamY / 2); this.Demo.cameraPos: Vector2icameraPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
= Math.floor(const centerX: numbercenterX + Math.sin(const t: numbert) * const amplitudeX: anyamplitudeX);
this.Demo.cameraPos: Vector2icameraPos.Vector2i.y: number
Vertical component (defaults to 0).
y
= Math.floor(const centerY: numbercenterY + Math.sin(const t: numbert * 0.65) * const amplitudeY: anyamplitudeY);
// Clamping keeps the camera inside the world so you never see empty void past the // edge. this.Demo.cameraPos: Vector2icameraPos =
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
.cameraClamp: (camera: Vector2i, worldSize: Vector2i, viewSize?: Vector2i) => Vector2i
Clamps a camera origin so the viewport stays within world bounds. Uses integer clamping per axis: `[0, worldSize - viewSize]`. If `viewSize` is omitted, the active {@link BT.displaySize } is used.
@since1.0.3@paramcamera - Desired camera origin in world coordinates.@paramworldSize - Full world size in pixels.@paramviewSize - Viewport size in pixels (defaults to {@link BT.displaySize}).@returnsClamped camera origin.
cameraClamp
(this.Demo.cameraPos: Vector2icameraPos, 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 WORLD_WIDTH_PX: numberWORLD_WIDTH_PX, const WORLD_HEIGHT_PX: numberWORLD_HEIGHT_PX), const viewSize: Vector2iviewSize);
// BT.cameraSet() happens in render(), using a position blended between // cameraPrevPos and cameraPos. // Update the animated water color in the palette. Instead of computing a new // Color32 inside render() every frame, we compute it here in update() and store // it in the reserved C_WATER slot. render() can then just write C_WATER as a // plain number - no Color32 needed there. This "palette animation" technique is // how retro hardware made water shimmer. // waterPulse is a gentle wave between -1 and +1; it moves the blue channel 28 // units up and down around 210. const const waterPulse: anywaterPulse = Math.sin(
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
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
* 0.12);
const const waterBlue: anywaterBlue = Math.floor(210 + const waterPulse: anywaterPulse * 28); 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_WATER: 14C_WATER, 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
(30, 110, const waterBlue: anywaterBlue));
} // Runs once per screen refresh. Clears the sky, draws the visible tiles, then draws // the HUD in screen space after resetting the camera. Demo.render(): voidrender() { // Soft sky blue behind everything. Tiles with ID 0 (sky) are not drawn, so this // color shows through in "empty" cells.
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_SKY: 2C_SKY);
// Blend cameraPrevPos toward cameraPos by BT.renderAlpha - a fraction from 0 (a // tick just finished) to just under 1 (the next tick is about to happen) - so the // camera's on-screen position matches this exact render moment instead of only // its last-tick position. Both the camera offset and the visible-tile culling // window below use this same smoothed value. this.Demo.cameraRenderPos: Vector2icameraRenderPos.Vector2i.set(x: number, y: number): Vector2i
Sets both components of this vector. Modifies this vector directly for maximum performance. WARNING: Mutates this vector. Don't use on frozen/cached singletons.
@paramx - New x component.@paramy - New y component.@returnsThis vector for chaining.
set
(
Math.floor(this.Demo.cameraPrevPos: Vector2icameraPrevPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
+ (this.Demo.cameraPos: Vector2icameraPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
- this.Demo.cameraPrevPos: Vector2icameraPrevPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
) *
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
.renderAlpha: number
Fractional progress between the last completed fixed update and the next. Intended for interpolating render state between fixed-update steps.
@since1.3.0@returnsInterpolation alpha in `[0, 1)`.
renderAlpha
),
Math.floor(this.Demo.cameraPrevPos: Vector2icameraPrevPos.Vector2i.y: number
Vertical component (defaults to 0).
y
+ (this.Demo.cameraPos: Vector2icameraPos.Vector2i.y: number
Vertical component (defaults to 0).
y
- this.Demo.cameraPrevPos: Vector2icameraPrevPos.Vector2i.y: number
Vertical component (defaults to 0).
y
) *
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
.renderAlpha: number
Fractional progress between the last completed fixed update and the next. Intended for interpolating render state between fixed-update steps.
@since1.3.0@returnsInterpolation alpha in `[0, 1)`.
renderAlpha
),
);
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
.cameraSet: (offset: Vector2i) => void
Sets the global camera offset applied to subsequent draw calls.
@since0.1.0@paramoffset - Camera translation in display pixels.
cameraSet
(this.Demo.cameraRenderPos: Vector2icameraRenderPos);
// Draw the chunk of the world that might be visible right now. this.Demo.renderVisibleTiles(): voidrenderVisibleTiles(); // The HUD and mini-map should stick to the screen, not scroll away with the // world.
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
.cameraReset: () => void
Resets the global camera offset to `(0, 0)`.
@since0.1.0@changed1.7.1 Now persists once nothing sets the camera again: previously, the offset the engine re-applies at frame start stayed at its pre-reset value forever, so a later frame that never calls `cameraSet` again still saw the old offset leak back in.
cameraReset
();
this.Demo.renderHud(): voidrenderHud(); } // Creates the 2D array and paints a simple slice of nature: sky, a ground strip, // trees, a pond at the bottom, and a few stone patches in the water. Demo.buildLandscape(): voidbuildLandscape() { // Start with a fresh empty array we will push rows into. this.Demo.map: {}map = []; // Outer loop: each row is one horizontal line of tiles from left to right. for (let let row: numberrow = 0; let row: numberrow < const MAP_HEIGHT_TILES: 20MAP_HEIGHT_TILES; let row: numberrow++) { // The tile IDs for this row. const const line: {}line = []; for (let let col: numbercol = 0; let col: numbercol < const MAP_WIDTH_TILES: 30MAP_WIDTH_TILES; let col: numbercol++) { // Default every cell to sky until we overwrite it below. const line: {}line.push(const TILE_SKY: 0TILE_SKY); } // Attach the finished row to the map (rows stack from top to bottom). this.Demo.map: {}map.push(const line: {}line); } // Ground band: a row of grass with two rows of dirt under it, just below the // middle of the map. const const grassRow: 9grassRow = 9; for (let let col: numbercol = 0; let col: numbercol < const MAP_WIDTH_TILES: 30MAP_WIDTH_TILES; let col: numbercol++) { this.Demo.map: {}map[const grassRow: 9grassRow][let col: numbercol] = const TILE_GRASS: 1TILE_GRASS; this.Demo.map: {}map[const grassRow: 9grassRow + 1][let col: numbercol] = const TILE_DIRT: 2TILE_DIRT; this.Demo.map: {}map[const grassRow: 9grassRow + 2][let col: numbercol] = const TILE_DIRT: 2TILE_DIRT; } // Small hills of dirt sticking up into the sky on the left and right. for (let let col: numbercol = 2; let col: numbercol < 8; let col: numbercol++) { this.Demo.map: {}map[const grassRow: 9grassRow - 1][let col: numbercol] = const TILE_DIRT: 2TILE_DIRT; } for (let let col: numbercol = 22; let col: numbercol < 28; let col: numbercol++) { this.Demo.map: {}map[const grassRow: 9grassRow - 1][let col: numbercol] = const TILE_DIRT: 2TILE_DIRT; } // Tree tops sit on the grass (or on a hill) like broccoli on a plate: two tiles // tall in each of these columns. const const treeCols: {}treeCols = [4, 5, 14, 15, 16, 25, 26]; for (const const col: anycol of const treeCols: {}treeCols) { this.Demo.map: {}map[const grassRow: 9grassRow - 1][const col: anycol] = const TILE_TREE_TOP: 5TILE_TREE_TOP; this.Demo.map: {}map[const grassRow: 9grassRow - 2][const col: anycol] = const TILE_TREE_TOP: 5TILE_TREE_TOP; } // Wider tree: two more canopy tiles on the upper canopy row only. this.Demo.map: {}map[const grassRow: 9grassRow - 2][6] = const TILE_TREE_TOP: 5TILE_TREE_TOP; this.Demo.map: {}map[const grassRow: 9grassRow - 2][7] = const TILE_TREE_TOP: 5TILE_TREE_TOP; // Lower area: two more rows of dirt, then water filling the bottom of the map. const const waterTopRow: 14waterTopRow = 14; for (let let row: numberrow = const grassRow: 9grassRow + 3; let row: numberrow < const waterTopRow: 14waterTopRow; let row: numberrow++) { for (let let col: numbercol = 0; let col: numbercol < const MAP_WIDTH_TILES: 30MAP_WIDTH_TILES; let col: numbercol++) { this.Demo.map: {}map[let row: numberrow][let col: numbercol] = const TILE_DIRT: 2TILE_DIRT; } } // Pond: water across the bottom six rows. for (let let row: numberrow = const waterTopRow: 14waterTopRow; let row: numberrow < const MAP_HEIGHT_TILES: 20MAP_HEIGHT_TILES; let row: numberrow++) { for (let let col: numbercol = 0; let col: numbercol < const MAP_WIDTH_TILES: 30MAP_WIDTH_TILES; let col: numbercol++) { this.Demo.map: {}map[let row: numberrow][let col: numbercol] = const TILE_WATER: 4TILE_WATER; } } // Stepping stones and little islands (stone replaces water in those cells). Each // entry is [row, col]. const const stoneSpots: {}stoneSpots = [ [15, 16], [15, 17], [16, 16], [17, 8], [17, 9], [18, 20], [18, 21], [19, 12], [19, 13], [19, 14], ]; for (const [const row: anyrow, const col: anycol] of const stoneSpots: {}stoneSpots) { this.Demo.map: {}map[const row: anyrow][const col: anycol] = const TILE_STONE: 3TILE_STONE; } // Two stone tiles on the shoreline, in the last dirt row above the water. this.Demo.map: {}map[const waterTopRow: 14waterTopRow - 1][10] = const TILE_STONE: 3TILE_STONE; this.Demo.map: {}map[const waterTopRow: 14waterTopRow - 1][11] = const TILE_STONE: 3TILE_STONE; } // Figures out which tile rows and columns overlap the screen and draws only those. // That is a simple "culling" optimization: the work scales with the visible tiles, // not the whole 30x20 map (though for a map this small either way would be fine). Demo.renderVisibleTiles(): voidrenderVisibleTiles() { // How big is the virtual screen in pixels? const const viewW: numberviewW =
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
.displaySize: Vector2i
Active logical render resolution in pixels. This is the game/simulation coordinate space configured by the demo, not the canvas element's CSS size. Each read returns a clone.
@since1.0.4@returnsConfigured logical size, or `Vector2i.zero()` before initialization.
displaySize
.Vector2i.x: number
Horizontal component (defaults to 0).
x
;
const const viewH: numberviewH =
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
.displaySize: Vector2i
Active logical render resolution in pixels. This is the game/simulation coordinate space configured by the demo, not the canvas element's CSS size. Each read returns a clone.
@since1.0.4@returnsConfigured logical size, or `Vector2i.zero()` before initialization.
displaySize
.Vector2i.y: number
Vertical component (defaults to 0).
y
;
// The camera position is the world pixel at the top-left of the view. Use the // same blended position render() just passed to BT.cameraSet(), so the culling // window always agrees with what actually got drawn. const const camX: numbercamX = this.Demo.cameraRenderPos: Vector2icameraRenderPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
;
const const camY: numbercamY = this.Demo.cameraRenderPos: Vector2icameraRenderPos.Vector2i.y: number
Vertical component (defaults to 0).
y
;
// Convert pixel edges to tile indices: Math.floor for the left/top tile, // Math.ceil for the pixel just past the right/bottom edge so partly visible tiles // are included. Math.max and Math.min keep the range inside the map. const const startCol: anystartCol = Math.max(0, Math.floor(const camX: numbercamX / const TILE_SIZE: 16TILE_SIZE)); const const startRow: anystartRow = Math.max(0, Math.floor(const camY: numbercamY / const TILE_SIZE: 16TILE_SIZE)); const const endCol: anyendCol = Math.min(const MAP_WIDTH_TILES: 30MAP_WIDTH_TILES - 1, Math.ceil((const camX: numbercamX + const viewW: numberviewW) / const TILE_SIZE: 16TILE_SIZE) - 1); const const endRow: anyendRow = Math.min(const MAP_HEIGHT_TILES: 20MAP_HEIGHT_TILES - 1, Math.ceil((const camY: numbercamY + const viewH: numberviewH) / const TILE_SIZE: 16TILE_SIZE) - 1); // Nested loops: the outer one walks down the rows, the inner one across the // columns. This visits every visible cell exactly once. for (let let row: anyrow = const startRow: anystartRow; let row: anyrow <= const endRow: anyendRow; let row: anyrow++) { for (let let col: anycol = const startCol: anystartCol; let col: anycol <= const endCol: anyendCol; let col: anycol++) { // Read which tile kind lives in this grid cell. const const id: anyid = this.Demo.map: {}map[let row: anyrow][let col: anycol]; // Sky tiles are not drawn at all; the clear color already painted the // sky. if (const id: anyid === const TILE_SKY: 0TILE_SKY) { continue; } // World pixel position of this tile's top-left corner. const const worldX: numberworldX = let col: anycol * const TILE_SIZE: 16TILE_SIZE; const const worldY: numberworldY = let row: anyrow * const TILE_SIZE: 16TILE_SIZE; // Reuse the same Rect2i: set(x, y, width, height). this.Demo.tileRect: Rect2itileRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2i
Sets all components of this rectangle. Modifies this rectangle directly for maximum performance. WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.
@paramx - New x position.@paramy - New y position.@paramwidth - New width.@paramheight - New height.@returnsThis rectangle for chaining.
set
(const worldX: numberworldX, const worldY: numberworldY, const TILE_SIZE: 16TILE_SIZE, const TILE_SIZE: 16TILE_SIZE);
// Pick the palette slot for this tile ID. if / else if is like a menu: // the first match wins. We pass just a number (C_GRASS, C_DIRT, and so // on) - the palette knows the actual color. if (const id: anyid === const TILE_GRASS: 1TILE_GRASS) {
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
(this.Demo.tileRect: Rect2itileRect, const C_GRASS: 5C_GRASS);
} else if (const id: anyid === const TILE_DIRT: 2TILE_DIRT) {
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
(this.Demo.tileRect: Rect2itileRect, const C_DIRT: 6C_DIRT);
} else if (const id: anyid === const TILE_STONE: 3TILE_STONE) {
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
(this.Demo.tileRect: Rect2itileRect, const C_STONE: 7C_STONE);
} else if (const id: anyid === const TILE_WATER: 4TILE_WATER) { // C_WATER is the animated water slot - update() already set its // 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
.drawRectFill: (rect: Rect2i, paletteIndex: number) => void
Draws a filled rectangle.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRectFill
(this.Demo.tileRect: Rect2itileRect, const C_WATER: 14C_WATER);
} else if (const id: anyid === const TILE_TREE_TOP: 5TILE_TREE_TOP) {
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
(this.Demo.tileRect: Rect2itileRect, const C_TREE_TOP: 8C_TREE_TOP);
} } } } // Draws the caption panel and the mini-map after the camera is reset, so they stay on // the screen. Demo.renderHud(): voidrenderHud() { // The caption panel is built with the shared UI kit. Widgets declared between // ui.begin() and ui.end() stack into one anchored group; the kit measures the // rows, draws the panel background, and places the group for us. The kit draws in // whatever camera space is active, so this must run after BT.cameraReset() - // otherwise the panel would scroll away with the world. import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT); import uiui.panel('Tilemap'); import uiui.label('30x20 tiles, 16px each', { color: stringcolor: 'dim' }); import uiui.label('Camera scrolls automatically', { color: stringcolor: 'dim' }); import uiui.end(); // The mini-map sits in the bottom-right, like the corner fold of a treasure map. this.Demo.renderMiniMap(): voidrenderMiniMap(); } // Scales the whole tilemap down so each tile is a 3x3 pixel square on the HUD, and // draws a yellow rectangle around the area the main camera is showing. Demo.renderMiniMap(): voidrenderMiniMap() { const const mapX: 221mapX = 221; const const mapY: 171mapY = 171; const const scale: 3scale = 3; // each world tile becomes a 3 by 3 block on the mini-map const const mapPixelW: numbermapPixelW = const MAP_WIDTH_TILES: 30MAP_WIDTH_TILES * const scale: 3scale; const const mapPixelH: numbermapPixelH = const MAP_HEIGHT_TILES: 20MAP_HEIGHT_TILES * const scale: 3scale; // Backing panel from the shared UI kit (same look as every other demo panel). // spacer() reserves the tile area; the tiles are drawn on top after end(). The // mini-map uses ui.drawRectFill() and ui.drawRect(), not the BT.* versions the // main map uses: kit panels are sprites, and the engine draws every // BT.drawRectFill() before any sprite - so plain ones would hide under the panel. // The ui.* versions take the same rectangle and palette slot, but draw sprites, // which land on top in call order. import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: const mapX: 221mapX - 5, y: numbery: const mapY: 171mapY - 5, width: numberwidth: const mapPixelW: numbermapPixelW + 10, pad: numberpad: 9, }); import uiui.panel(); import uiui.spacer(const mapPixelH: numbermapPixelH); import uiui.end(); // Walk every tile in the entire world (the map is small, so this is cheap). for (let let row: numberrow = 0; let row: numberrow < const MAP_HEIGHT_TILES: 20MAP_HEIGHT_TILES; let row: numberrow++) { for (let let col: numbercol = 0; let col: numbercol < const MAP_WIDTH_TILES: 30MAP_WIDTH_TILES; let col: numbercol++) { const const id: anyid = this.Demo.map: {}map[let row: numberrow][let col: numbercol]; const const px: numberpx = const mapX: 221mapX + let col: numbercol * const scale: 3scale; const const py: numberpy = const mapY: 171mapY + let row: numberrow * const scale: 3scale; // Pick a palette slot for each tile type. Sky tiles use C_SKY, so the map // background is the sky color. Water uses C_MINIMAP_WATER - a still // shade, not the animated C_WATER - so the mini-map stays calm while the // pond shimmers. let let c: numberc = const C_SKY: 2C_SKY; if (const id: anyid === const TILE_GRASS: 1TILE_GRASS) { let c: numberc = const C_GRASS: 5C_GRASS; } else if (const id: anyid === const TILE_DIRT: 2TILE_DIRT) { let c: numberc = const C_DIRT: 6C_DIRT; } else if (const id: anyid === const TILE_STONE: 3TILE_STONE) { let c: numberc = const C_STONE: 7C_STONE; } else if (const id: anyid === const TILE_WATER: 4TILE_WATER) { let c: numberc = const C_MINIMAP_WATER: 9C_MINIMAP_WATER; } else if (const id: anyid === const TILE_TREE_TOP: 5TILE_TREE_TOP) { let c: numberc = const C_TREE_TOP: 8C_TREE_TOP; } this.Demo.tileRect: Rect2itileRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2i
Sets all components of this rectangle. Modifies this rectangle directly for maximum performance. WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.
@paramx - New x position.@paramy - New y position.@paramwidth - New width.@paramheight - New height.@returnsThis rectangle for chaining.
set
(const px: numberpx, const py: numberpy, const scale: 3scale, const scale: 3scale);
import uiui.drawRectFill(this.Demo.tileRect: Rect2itileRect, let c: numberc); } } // Viewport indicator: where is the 320x240 window inside the 480x320 world? Use // this.cameraPos, not BT.camera: render() already called BT.cameraReset(), which // zeroes BT.camera - our own field still remembers the real position. const const cam: Vector2icam = this.Demo.cameraPos: Vector2icameraPos; const const disp: Vector2idisp =
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
.displaySize: Vector2i
Active logical render resolution in pixels. This is the game/simulation coordinate space configured by the demo, not the canvas element's CSS size. Each read returns a clone.
@since1.0.4@returnsConfigured logical size, or `Vector2i.zero()` before initialization.
displaySize
;
const const vx: anyvx = const mapX: 221mapX + Math.floor((const cam: Vector2icam.Vector2i.x: number
Horizontal component (defaults to 0).
x
/ const WORLD_WIDTH_PX: numberWORLD_WIDTH_PX) * const mapPixelW: numbermapPixelW);
const const vy: anyvy = const mapY: 171mapY + Math.floor((const cam: Vector2icam.Vector2i.y: number
Vertical component (defaults to 0).
y
/ const WORLD_HEIGHT_PX: numberWORLD_HEIGHT_PX) * const mapPixelH: numbermapPixelH);
const const vw: anyvw = Math.max(1, Math.floor((const disp: Vector2idisp.Vector2i.x: number
Horizontal component (defaults to 0).
x
/ const WORLD_WIDTH_PX: numberWORLD_WIDTH_PX) * const mapPixelW: numbermapPixelW));
const const vh: anyvh = Math.max(1, Math.floor((const disp: Vector2idisp.Vector2i.y: number
Vertical component (defaults to 0).
y
/ const WORLD_HEIGHT_PX: numberWORLD_HEIGHT_PX) * const mapPixelH: numbermapPixelH));
this.Demo.tileRect: Rect2itileRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2i
Sets all components of this rectangle. Modifies this rectangle directly for maximum performance. WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.
@paramx - New x position.@paramy - New y position.@paramwidth - New width.@paramheight - New height.@returnsThis rectangle for chaining.
set
(const vx: anyvx, const vy: anyvy, const vw: anyvw, const vh: anyvh);
import uiui.drawRect(this.Demo.tileRect: Rect2itileRect, const C_VIEWPORT: 12C_VIEWPORT); } } // bootstrap() wires this class into the engine: it creates an instance and runs the 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);