/*

Hypercube: a Fez-style rotating tesseract wireframe you can spin by dragging.
@description A Fez-style rotating tesseract: a four-dimensional cube turning in PICO-8 colors on a 320x320 canvas.

Part of the BLIT386 demo series.

Prerequisites:
  - Basics: https://demos.blit386.dev/basics
  - Primitives: https://demos.blit386.dev/primitives
  - Palette Presets: https://demos.blit386.dev/palette-presets
  - Pointer Basics: https://demos.blit386.dev/pointer-basics
  - Pointer Drag Flick: https://demos.blit386.dev/pointer-drag-flick

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

Guides:
  - Input: https://blit386.dev/docs/guides/input#pointer-slot-model
  - Input: https://blit386.dev/docs/guides/input#position-and-delta
  - Palette Presets: https://blit386.dev/docs/guides/palette-presets#pico8-16-slots
  - Random: https://blit386.dev/docs/guides/random#deterministic-by-default
  - Overlay: https://blit386.dev/docs/guides/overlay#optional-bands

Reference:
  - Rendering: https://blit386.dev/docs/api/rendering#primitives
  - Palette: https://blit386.dev/docs/api/palette#built-in-presets
  - Core: https://blit386.dev/docs/api/core#hardware-settings

---

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

A 320x320 canvas, cleared to PICO-8 slot 1 (dark blue), with a wireframe tesseract turning
in the middle: two cubes drawn in two colors, eight struts linking matching corners, and a
dot on each of the 16 corners. The four line colors drift around the hue wheel at 28
degrees per second with a slow lightness pulse. Edges are drawn farthest first, so the
"inner" cube visibly passes through the "outer" one as the 4D rotation carries it around.

The engine overlay is open from the first frame with its palette grid switched on, drawn
in white bars (slot 7) with dark text (slot 1).

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

- A 4D rotation as a sequence of plane rotations (XW, YZ, XY, ZW, then a screen-space XZ
  yaw), each one an ordinary 2D rotation of two coordinates.
- Two perspective divides: 4D to 3D with DIST_4, then 3D to 2D with DIST_3, followed by
  scaling and centering on the canvas.
- The painter's algorithm: every tick the 32 edges are sorted by averaged depth, with a
  stable tie-break so two edges at equal depth never swap colors.
- Palette.pico8() as the palette, and Color32.fromHSL() rewriting four slots per tick.
- Pointer slots: BT.isPointerActive(slot), BT.pointerDelta(slot), and
  BT.isDown(BT.BTN_POINTER_A, 0) for the mouse button.
- Frame-rate-independent integration with BT.deltaSeconds, and BT.timeSeconds as the clock
  for the color drift.
- Exponential smoothing, 1 - Math.exp(-k * dt), both for filtering the drag velocity and
  for easing the spin back to its home value.
- BT.random.float() for the starting hue, and configure() for displaySize, maxCanvasSize,
  and the overlay palette grid.

HOW THE DRAG WORKS
------------------

Any of the four pointer slots can steer. The mouse (slot 0) counts as held while
BT.BTN_POINTER_A is down; a touch slot counts as held while its contact is active. The
first held slot is claimed and kept until it releases. While a slot is held, each tick
reads BT.pointerDelta(slot): horizontal pixels turn the XZ plane (yaw around Y) and
vertical pixels turn the YZ plane (pitch around X), DRAG_SENSITIVITY radians per pixel.
The automatic tumble pauses for the duration of the drag.

The per-tick angular velocity is smoothed with an exponential moving average
(VELOCITY_SMOOTH) so a single noisy frame does not decide the throw. On release the
smoothed velocity becomes spin.xz and spin.yz, clamped to MAX_FLICK_SPIN, and the three
4D tumble planes are zeroed. From then on the spin eases toward HOME_SPIN at rate
SPIN_FADE, so the flick coasts and the Fez tumble gradually returns.

TRY THIS
--------

- Drag with the mouse or a finger to spin the tesseract; flick and let go to make it
  coast.
- Watch the palette grid in the engine overlay: slots 8-11 change color every tick while
  the rest of the PICO-8 palette holds still. Press the backquote key (`) to hide the
  overlay.

*/

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 Palette
Mutable palette of indexed {@link Color32 } entries. The palette is the central color authority for all rendering: - **Index 0 is always transparent.** It is initialized with `Color32.transparent` and cannot be set to an opaque color. The primitive and sprite shaders discard any fragment whose palette index resolves to alpha 0. - **Variable sizes:** valid sizes are `2, 4, 16, 32, 64, 128, 256`. The active size determines the range for `set()` / `get()` and named-color lookups. - **Fixed GPU layout:** `toFloat32Array()` always outputs `256 * 4` floats so the renderer can upload a stable 4 KB uniform block regardless of palette size. Slots beyond the active size are padded with transparent black. - **Named aliases:** optional string tags map human-readable names to indices, e.g. `setNamed('player', 3)`. They carry no runtime cost when unused. - **Mutable by design:** palette-effect features modify entries in place. Use `clone()` when a snapshot is needed before modification.
@since1.0.3@changed1.7.0 Added the `fillBlock` method, writing a transformed block of colors into contiguous slots via `set()` and returning the next free slot, so consecutive blocks chain.
Palette
, 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';
/** @typedef {import('blit386').Palette} PaletteType */ /** @typedef {import('blit386').Vector2i} Vector2iType */ /** @typedef {{ i: number, j: number, depth: number, color: number }} Edge */ /** @typedef {{ xw: number, yz: number, xy: number, zw: number, xz: number }} Spin */ // Canvas size in pixels and the projection scale: half the tesseract's projected extent, // in pixels, before perspective. const const SIZE: 320SIZE = 320; const const SCALE: 36SCALE = 36; // Perspective distance for the 4D to 3D step (larger = flatter). const const DIST_4: 2.6DIST_4 = 2.6; // Perspective distance for the 3D to 2D step. const const DIST_3: 3.2DIST_3 = 3.2; // PICO-8 slots: dark blue background, white overlay bars. const const C_BG: 1C_BG = 1; const const C_NEAR: 7C_NEAR = 7; // One fixed slot per wire kind. An Edge carries its slot, so depth-sorting the edges // never swaps which cube a wire belongs to. const const C_CUBE_A: 8C_CUBE_A = 8; const const C_CUBE_B: 9C_CUBE_B = 9; const const C_LINK: 10C_LINK = 10; const const C_DOT: 11C_DOT = 11; const const LINE_SLOTS: {}LINE_SLOTS = [const C_CUBE_A: 8C_CUBE_A, const C_CUBE_B: 9C_CUBE_B, const C_LINK: 10C_LINK, const C_DOT: 11C_DOT]; // Hue drift in degrees per second, the fixed saturation and base lightness of the four // line colors, and the amplitude and rate of the lightness pulse. const const HUE_SPEED: 28HUE_SPEED = 28; const const LINE_SAT: 88LINE_SAT = 88; const const LINE_LIGHT: 58LINE_LIGHT = 58; const const LIGHT_PULSE: 10LIGHT_PULSE = 10; const const LIGHT_PULSE_RATE: 1.1LIGHT_PULSE_RATE = 1.1; // The automatic Fez tumble: angular velocity in each rotation plane, in radians per // second. This is the "home" spin the demo always eases back to. `xz` stays 0 at rest; // drag and flick use it for screen-space yaw. /** @type {Readonly<Spin>} */ const const HOME_SPIN: Readonly<Spin>
@type{Readonly<Spin>}
HOME_SPIN
= Object.freeze({
xw: numberxw: 0.55, yz: numberyz: 0.38, xy: numberxy: 0.12, zw: numberzw: 0.22, xz: numberxz: 0, }); // Radians of trackball turn per pixel of drag. Horizontal pixels yaw (XZ); vertical // pixels pitch (YZ). const const DRAG_SENSITIVITY: 0.01DRAG_SENSITIVITY = 0.01; // How quickly the finger's instantaneous velocity is smoothed while dragging (higher = // snappier, lower = softer), so a noisy last frame does not become a wild flick. const const VELOCITY_SMOOTH: 14VELOCITY_SMOOTH = 14; // How quickly free spin eases back toward HOME_SPIN after release, per second. Lower = // a longer coast on the flick before the Fez tumble returns. const const SPIN_FADE: 1.15SPIN_FADE = 1.15; // Cap on flick spin so a frantic swipe cannot spin forever. const const MAX_FLICK_SPIN: 8MAX_FLICK_SPIN = 8; // The 16 corners of a unit tesseract (+1 or -1 on x, y, z, w). Built once at load and // never mutated; each tick copies a corner into a scratch vector before rotating it. /** @type {number[][]} */ const const VERTICES: {}
@type{number[][]}
VERTICES
= [];
for (let let i: numberi = 0; let i: numberi < 16; let i: numberi++) { // Each bit of i picks +1 or -1 on one axis (bit 0 = X, 1 = Y, 2 = Z, 3 = W). const VERTICES: {}
@type{number[][]}
VERTICES
.push([(let i: numberi & 1) !== 0 ? 1 : -1, (let i: numberi & 2) !== 0 ? 1 : -1, (let i: numberi & 4) !== 0 ? 1 : -1, (let i: numberi & 8) !== 0 ? 1 : -1]);
} // Sort comparator: farther edges first. Ties break on the endpoint indices so equal // depths keep a stable order, which avoids color flicker when two edges share a depth. function function compareEdgeDepth(a: any, b: any): numbercompareEdgeDepth(a: anya, b: anyb) { return a: anya.depth - b: anyb.depth || a: anya.i - b: anyb.i || a: anya.j - b: anyb.j; } // Clamps one spin component into the range -MAX_FLICK_SPIN..MAX_FLICK_SPIN. function function clampFlick(value: any): anyclampFlick(value: anyvalue) { return Math.max(-const MAX_FLICK_SPIN: 8MAX_FLICK_SPIN, Math.min(const MAX_FLICK_SPIN: 8MAX_FLICK_SPIN, value: anyvalue)); } // A tesseract wireframe rotated in 4D, projected to 2D, and drawn with the painter's // algorithm. A pointer drag spins it like a trackball; a flick coasts and eases back to // the automatic tumble. class class DemoDemo { /** @type {PaletteType | null} */ Demo.palette: Palette | null
@type{PaletteType | null}
palette
= null;
// Screen positions after the 4D to 2D projection, reused every tick. /** @type {Vector2iType[]} */ Demo.projected: {}
@type{Vector2iType[]}
projected
= [];
// Per-vertex depth for the painter's algorithm. /** @type {number[]} */ Demo.depths: {}
@type{number[]}
depths
= [];
// The 32 edges, kept sorted farthest first. /** @type {Edge[]} */ Demo.edgeOrder: {}
@type{Edge[]}
edgeOrder
= [];
// Starting hues for cube A, cube B, the links, and the dots, in degrees. /** @type {number[]} */ Demo.baseHues: {}
@type{number[]}
baseHues
= [];
// Scratch 4D point reused while rotating each corner, so no array is allocated per // tick. /** @type {number[]} */ Demo.scratch: {}
@type{number[]}
scratch
= [0, 0, 0, 0];
// Current rotation angle in each 4D plane, in radians. /** @type {number} */ Demo.angleXW: number
@type{number}
angleXW
= 0.35;
/** @type {number} */ Demo.angleYZ: number
@type{number}
angleYZ
= 0.9;
/** @type {number} */ Demo.angleXY: number
@type{number}
angleXY
= 0.1;
/** @type {number} */ Demo.angleZW: number
@type{number}
angleZW
= 0.2;
// Screen-space yaw (XZ plane, around Y), driven by drag and flick. /** @type {number} */ Demo.angleXZ: number
@type{number}
angleXZ
= 0;
// Free-motion angular velocity; eases toward HOME_SPIN. /** @type {Spin} */ Demo.spin: Spin
@type{Spin}
spin
= { ...const HOME_SPIN: Readonly<Spin>
@type{Readonly<Spin>}
HOME_SPIN
};
// Smoothed finger yaw and pitch in radians per second while dragging. On release // these become spin.xz and spin.yz so the model coasts. /** @type {{ xz: number, yz: number }} */
Demo.fingerSpin: {
    xz: number;
    yz: number;
}
@type{{ xz: number, yz: number }}
fingerSpin
= { xz: numberxz: 0, yz: numberyz: 0 };
// Pointer slot currently steering (-1 = none). /** @type {number} */ Demo.dragSlot: number
@type{number}
dragSlot
= -1;
// A square canvas with the engine overlay open from the start, palette grid included, // so the four animated slots can be watched alongside the picture.
Demo.configure(): {
    displaySize: Vector2i;
    maxCanvasSize: Vector2i;
    targetFPS: number;
    isOverlayEnabled: boolean;
    isOverlayVisibleAtStart: boolean;
    isOverlayPaletteEnabled: boolean;
    overlayStyle: {
        barPaletteIndex: number;
        textPaletteIndex: number;
        gapPaletteIndex: number;
    };
}
configure
() {
return { displaySize: Vector2idisplaySize: 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 SIZE: 320SIZE, const SIZE: 320SIZE),
maxCanvasSize: Vector2imaxCanvasSize: 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 SIZE: 320SIZE * 2, const SIZE: 320SIZE * 2),
targetFPS: numbertargetFPS: 60, isOverlayEnabled: booleanisOverlayEnabled: true, isOverlayVisibleAtStart: booleanisOverlayVisibleAtStart: true, isOverlayPaletteEnabled: booleanisOverlayPaletteEnabled: true,
overlayStyle: {
    barPaletteIndex: number;
    textPaletteIndex: number;
    gapPaletteIndex: number;
}
overlayStyle
: {
barPaletteIndex: numberbarPaletteIndex: const C_NEAR: 7C_NEAR, textPaletteIndex: numbertextPaletteIndex: const C_BG: 1C_BG, gapPaletteIndex: numbergapPaletteIndex: const C_BG: 1C_BG, }, }; } // Loads the PICO-8 palette, picks the starting hues, and builds the vertex and edge // tables. async Demo.init(): unknowninit() { this.Demo.palette: Palette | null
@type{PaletteType | null}
palette
= class Palette
Mutable palette of indexed {@link Color32 } entries. The palette is the central color authority for all rendering: - **Index 0 is always transparent.** It is initialized with `Color32.transparent` and cannot be set to an opaque color. The primitive and sprite shaders discard any fragment whose palette index resolves to alpha 0. - **Variable sizes:** valid sizes are `2, 4, 16, 32, 64, 128, 256`. The active size determines the range for `set()` / `get()` and named-color lookups. - **Fixed GPU layout:** `toFloat32Array()` always outputs `256 * 4` floats so the renderer can upload a stable 4 KB uniform block regardless of palette size. Slots beyond the active size are padded with transparent black. - **Named aliases:** optional string tags map human-readable names to indices, e.g. `setNamed('player', 3)`. They carry no runtime cost when unused. - **Mutable by design:** palette-effect features modify entries in place. Use `clone()` when a snapshot is needed before modification.
@since1.0.3@changed1.7.0 Added the `fillBlock` method, writing a transformed block of colors into contiguous slots via `set()` and returning the next free slot, so consecutive blocks chain.
Palette
.Palette.pico8(): Palette
Creates the PICO-8 16-color palette.
@returnsNew PICO-8 preset palette.
pico8
();
// Spread the four line colors evenly around the hue wheel from a random starting // angle. BT.random.float(min, max) returns a value in [min, max). const const start: numberstart =
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
.random: Random
Default engine PRNG (live reference - not a copy). Time-seeded when the engine singleton is created. Call {@link BT.randomSeed } for a reproducible run, or open the page with `?seed=N` - applied in `BT.init()` just before the demo's own `init()`, so a `randomSeed` call there still wins. Mutating the instance (for example `BT.random.int(10)`) advances the shared stream.
@since1.5.0@changed1.7.1 `?seed=N` URL parameter seeds the shared generator before the demo's `init()`.@returnsThe shared {@link Random} instance.@exampleBT.randomSeed(42); BT.random.int(150, 420); BT.random.pick(['a', 'b', 'c']);
random
.Random.float(min: number, max: number): number
Returns the next pseudo-random float in [min, max).
@parammin - Inclusive lower bound.@parammax - Exclusive upper bound.@returnsFloat in [min, max).@since1.5.0
float
(0, 360);
this.Demo.baseHues: {}
@type{number[]}
baseHues
= [const start: numberstart, const start: numberstart + 90, const start: numberstart + 180, const start: numberstart + 270];
this.Demo.applyLineColors(timeSeconds: any): voidapplyLineColors(0);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.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{PaletteType | null}
palette
);
for (let let i: numberi = 0; let i: numberi < 16; let i: numberi++) { this.Demo.projected: {}
@type{Vector2iType[]}
projected
.push(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));
this.Demo.depths: {}
@type{number[]}
depths
.push(0);
} // Two corners form an edge when their indices differ in exactly one bit: they are // neighbors on the 4D hypercube. for (let let i: numberi = 0; let i: numberi < 16; let i: numberi++) { for (let let j: numberj = let i: numberi + 1; let j: numberj < 16; let j: numberj++) { const const axis: numberaxis = let i: numberi ^ let j: numberj; // Exactly one bit set: axis is a power of two. if (const axis: numberaxis !== 0 && (const axis: numberaxis & (const axis: numberaxis - 1)) === 0) { // Bit 3 (value 8) means the edge spans W - a strut between the two // cubes. Otherwise the edge lives on the cube whose W sign matches // endpoint i. const const color: 8 | 9 | 10color = const axis: numberaxis === 8 ? const C_LINK: 10C_LINK : (let i: numberi & 8) !== 0 ? const C_CUBE_B: 9C_CUBE_B : const C_CUBE_A: 8C_CUBE_A; this.Demo.edgeOrder: {}
@type{Edge[]}
edgeOrder
.push({ i: numberi, j: numberj, depth: numberdepth: 0, color: numbercolor });
} } } return true; } // Rewrites the four line slots for the given time: each hue drifts at HUE_SPEED and // the lightness pulses on a sine, phase-shifted per slot. Demo.applyLineColors(timeSeconds: any): voidapplyLineColors(timeSeconds: anytimeSeconds) { for (let let i: numberi = 0; let i: numberi < const LINE_SLOTS: {}LINE_SLOTS.length; let i: numberi++) { const const hue: numberhue = (this.Demo.baseHues: {}
@type{number[]}
baseHues
[let i: numberi] + timeSeconds: anytimeSeconds * const HUE_SPEED: 28HUE_SPEED) % 360;
const const light: numberlight = const LINE_LIGHT: 58LINE_LIGHT + Math.sin(timeSeconds: anytimeSeconds * const LIGHT_PULSE_RATE: 1.1LIGHT_PULSE_RATE + let i: numberi) * const LIGHT_PULSE: 10LIGHT_PULSE; // init() always sets this.palette before update() runs. this.Demo.palette: Palette | null
@type{PaletteType | 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 LINE_SLOTS: {}LINE_SLOTS[let i: numberi], class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.fromHSL(h: number, s: number, l: number, a?: number): Color32
Creates a color from HSL values.
@paramh - Hue in degrees (0-360).@params - Saturation as percentage (0-100).@paraml - Lightness as percentage (0-100).@parama - Alpha channel (0-255, defaults to 255).@returnsNew color converted from HSL values.
fromHSL
(const hue: numberhue, const LINE_SAT: 88LINE_SAT, const light: numberlight));
} } // Rotates the 4D point `v` in the plane of axes `a` and `b` (0 = X, 1 = Y, 2 = Z, // 3 = W). `c` and `s` are the cosine and sine of the angle, precomputed once per // tick. Demo.rotatePlane(v: any, c: any, s: any, a: any, b: any): voidrotatePlane(v: anyv, c: anyc, s: anys, a: anya, b: anyb) { const const x: anyx = v: anyv[a: anya]; const const y: anyy = v: anyv[b: anyb]; v: anyv[a: anya] = const x: anyx * c: anyc - const y: anyy * s: anys; v: anyv[b: anyb] = const x: anyx * s: anys + const y: anyy * c: anyc; } // True while a pointer slot should steer. The mouse (slot 0) counts while its primary // button is held; a touch slot counts while its contact is active. Same rule as the // Pointer Paint demo. Demo.isDragHeld(slot: any): booleanisDragHeld(slot: anyslot) { if (!
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.isPointerActive: (pointerIndex?: number) => boolean
Reports whether the given pointer slot has a live pointer. For slot 0 (mouse) this is true while the mouse is hovering inside the canvas; cleared on `pointerleave`. For slots 1-3 (touch / pen) this is true while the contact is down.
@since1.1.1@parampointerIndex - Pointer slot (defaults to 0 = mouse).@returns`true` while the slot has live position data.
isPointerActive
(slot: anyslot)) {
return false; } return slot: anyslot === 0 ?
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.isDown: (button: number, player?: number) => boolean
Checks whether a button is currently held. For pointer buttons (`BTN_POINTER_A..D`), the second parameter is the pointer slot index (0 = mouse, 1-3 = touch / pen). For mouse slot 0: `A` is left, `B` is right, `C` is middle, `D` is back / forward (matches RetroBlit canonical, not DOM `PointerEvent.button` index). Touch / pen slots only support `A`; B/C/D return `false`. `button` accepts one or more bit flags from the `BTN_*` set (for example `BT.BTN_A | BT.BTN_B`). Matching uses ANY semantics: returns `true` when any selected button is held. For face buttons (`BTN_UP`…`BTN_SELECT`), players `0` and `1` merge keyboard and gamepad input (logical OR). Players `2` and `3` use gamepad only. Pointer flags (`BTN_POINTER_*`) use the `player` argument as pointer slot. `BTN_L2`, `BTN_R2` and `BTN_TRIGGER` are gamepad only (no keyboard mapping).
@since1.1.1@changed1.8.0 Also accepts BTN_L2 / BTN_R2 / BTN_TRIGGER (gamepad only).@parambutton - Button constant from the `BTN_*` set.@paramplayer - Zero-based player index for gamepads / keyboard, or pointer slot (0-3) for `BTN_POINTER_*`.@returns`true` while the button remains pressed.
isDown
(
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.type BTN_POINTER_A: number
Primary pointer button code. Maps to mouse left for slot 0; touch contact for slots 1-3.
@since0.1.0
BTN_POINTER_A
, 0) : true;
} // Advances every plane angle by its angular velocity over `dt` seconds. Demo.integrateSpin(dt: any): voidintegrateSpin(dt: anydt) { this.Demo.angleXW: number
@type{number}
angleXW
+= this.Demo.spin: Spin
@type{Spin}
spin
.xw: numberxw * dt: anydt;
this.Demo.angleYZ: number
@type{number}
angleYZ
+= this.Demo.spin: Spin
@type{Spin}
spin
.yz: numberyz * dt: anydt;
this.Demo.angleXY: number
@type{number}
angleXY
+= this.Demo.spin: Spin
@type{Spin}
spin
.xy: numberxy * dt: anydt;
this.Demo.angleZW: number
@type{number}
angleZW
+= this.Demo.spin: Spin
@type{Spin}
spin
.zw: numberzw * dt: anydt;
this.Demo.angleXZ: number
@type{number}
angleXZ
+= this.Demo.spin: Spin
@type{Spin}
spin
.xz: numberxz * dt: anydt;
} // Exponential ease of `spin` toward HOME_SPIN: a soft settle with no hard stop. Demo.fadeSpinToHome(dt: any): voidfadeSpinToHome(dt: anydt) { const const t: numbert = 1 - Math.exp(-const SPIN_FADE: 1.15SPIN_FADE * dt: anydt); this.Demo.spin: Spin
@type{Spin}
spin
.xw: numberxw += (const HOME_SPIN: Readonly<Spin>
@type{Readonly<Spin>}
HOME_SPIN
.xw - this.Demo.spin: Spin
@type{Spin}
spin
.xw: numberxw) * const t: numbert;
this.Demo.spin: Spin
@type{Spin}
spin
.yz: numberyz += (const HOME_SPIN: Readonly<Spin>
@type{Readonly<Spin>}
HOME_SPIN
.yz - this.Demo.spin: Spin
@type{Spin}
spin
.yz: numberyz) * const t: numbert;
this.Demo.spin: Spin
@type{Spin}
spin
.xy: numberxy += (const HOME_SPIN: Readonly<Spin>
@type{Readonly<Spin>}
HOME_SPIN
.xy - this.Demo.spin: Spin
@type{Spin}
spin
.xy: numberxy) * const t: numbert;
this.Demo.spin: Spin
@type{Spin}
spin
.zw: numberzw += (const HOME_SPIN: Readonly<Spin>
@type{Readonly<Spin>}
HOME_SPIN
.zw - this.Demo.spin: Spin
@type{Spin}
spin
.zw: numberzw) * const t: numbert;
this.Demo.spin: Spin
@type{Spin}
spin
.xz: numberxz += (const HOME_SPIN: Readonly<Spin>
@type{Readonly<Spin>}
HOME_SPIN
.xz - this.Demo.spin: Spin
@type{Spin}
spin
.xz: numberxz) * const t: numbert;
} // Trackball drag plus flick inertia. While held: yaw and pitch from the pointer // delta, and an exponential moving average of the finger velocity. On release: that // velocity becomes `spin`, which then fades back to HOME_SPIN. Demo.updateDrag(dt: any): voidupdateDrag(dt: anydt) { const const wasDragging: booleanwasDragging = this.Demo.dragSlot: number
@type{number}
dragSlot
>= 0;
// Stick with the current finger; otherwise claim the first held slot. if (this.Demo.dragSlot: number
@type{number}
dragSlot
>= 0 && !this.Demo.isDragHeld(slot: any): booleanisDragHeld(this.Demo.dragSlot: number
@type{number}
dragSlot
)) {
this.Demo.dragSlot: number
@type{number}
dragSlot
= -1;
} if (this.Demo.dragSlot: number
@type{number}
dragSlot
< 0) {
for (let let slot: numberslot = 0; let slot: numberslot < 4; let slot: numberslot++) { if (this.Demo.isDragHeld(slot: any): booleanisDragHeld(let slot: numberslot)) { this.Demo.dragSlot: number
@type{number}
dragSlot
= let slot: numberslot;
// Drop press-frame jitter so it cannot become a throw. this.
Demo.fingerSpin: {
    xz: number;
    yz: number;
}
@type{{ xz: number, yz: number }}
fingerSpin
.xz: numberxz = 0;
this.
Demo.fingerSpin: {
    xz: number;
    yz: number;
}
@type{{ xz: number, yz: number }}
fingerSpin
.yz: numberyz = 0;
break; } } } if (this.Demo.dragSlot: number
@type{number}
dragSlot
>= 0) {
const const delta: Vector2idelta =
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
.pointerDelta: (pointerIndex?: number) => Vector2i
Returns the position delta `(pos - prevPos)` for a pointer slot since the previous frame. Reflects movement accumulated between the previous and current frame. Snapshotted and reset by the engine at `endFrame()`, which runs after `update()` and `render()`. Returns `Vector2i.zero()` when the engine is not initialized or `pointerIndex` is out of range.
@since1.0.3@parampointerIndex - Pointer slot (defaults to 0 = mouse).@returnsPer-frame movement in display coordinates.
pointerDelta
(this.Demo.dragSlot: number
@type{number}
dragSlot
);
// Trackball: horizontal pixels yaw (XZ, around Y); vertical pixels pitch (YZ, // around X). const const dYaw: numberdYaw = const delta: Vector2idelta.Vector2i.x: number
Horizontal component (defaults to 0).
x
* const DRAG_SENSITIVITY: 0.01DRAG_SENSITIVITY;
const const dPitch: numberdPitch = const delta: Vector2idelta.Vector2i.y: number
Vertical component (defaults to 0).
y
* const DRAG_SENSITIVITY: 0.01DRAG_SENSITIVITY;
this.Demo.angleXZ: number
@type{number}
angleXZ
+= const dYaw: numberdYaw;
this.Demo.angleYZ: number
@type{number}
angleYZ
+= const dPitch: numberdPitch;
// Guard dt so a hitch cannot explode the velocity sample. if (dt: anydt > 0.0001) { const const alpha: numberalpha = 1 - Math.exp(-const VELOCITY_SMOOTH: 14VELOCITY_SMOOTH * dt: anydt); this.
Demo.fingerSpin: {
    xz: number;
    yz: number;
}
@type{{ xz: number, yz: number }}
fingerSpin
.xz: numberxz += (const dYaw: numberdYaw / dt: anydt - this.
Demo.fingerSpin: {
    xz: number;
    yz: number;
}
@type{{ xz: number, yz: number }}
fingerSpin
.xz: numberxz) * const alpha: numberalpha;
this.
Demo.fingerSpin: {
    xz: number;
    yz: number;
}
@type{{ xz: number, yz: number }}
fingerSpin
.yz: numberyz += (const dPitch: numberdPitch / dt: anydt - this.
Demo.fingerSpin: {
    xz: number;
    yz: number;
}
@type{{ xz: number, yz: number }}
fingerSpin
.yz: numberyz) * const alpha: numberalpha;
} // The automatic tumble pauses while a drag is in progress. return; } // Coast on the flick (trackball axes only); the fade restores the Fez planes. if (const wasDragging: booleanwasDragging) { this.Demo.spin: Spin
@type{Spin}
spin
.xz: numberxz = function clampFlick(value: any): anyclampFlick(this.
Demo.fingerSpin: {
    xz: number;
    yz: number;
}
@type{{ xz: number, yz: number }}
fingerSpin
.xz: numberxz);
this.Demo.spin: Spin
@type{Spin}
spin
.yz: numberyz = function clampFlick(value: any): anyclampFlick(this.
Demo.fingerSpin: {
    xz: number;
    yz: number;
}
@type{{ xz: number, yz: number }}
fingerSpin
.yz: numberyz);
this.Demo.spin: Spin
@type{Spin}
spin
.xw: numberxw = 0;
this.Demo.spin: Spin
@type{Spin}
spin
.xy: numberxy = 0;
this.Demo.spin: Spin
@type{Spin}
spin
.zw: numberzw = 0;
} this.Demo.integrateSpin(dt: any): voidintegrateSpin(dt: anydt); this.Demo.fadeSpinToHome(dt: any): voidfadeSpinToHome(dt: anydt); } // Advances the drag/spin state, rotates and projects all 16 corners, and re-sorts the // edges by depth. Demo.update(): voidupdate() { const const dt: numberdt =
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
;
this.Demo.updateDrag(dt: any): voidupdateDrag(const dt: numberdt); this.Demo.applyLineColors(timeSeconds: any): voidapplyLineColors(
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
.timeSeconds: number
Fixed-step elapsed time in seconds (`BT.ticks * BT.deltaSeconds`).
@since1.0.4@returnsElapsed fixed-step time in seconds since initialization.
timeSeconds
);
const const cx: numbercx = const SIZE: 320SIZE / 2; const const cy: numbercy = const SIZE: 320SIZE / 2; // Sine and cosine of each plane angle, computed once per tick rather than once // per corner. const const cXW: anycXW = Math.cos(this.Demo.angleXW: number
@type{number}
angleXW
);
const const sXW: anysXW = Math.sin(this.Demo.angleXW: number
@type{number}
angleXW
);
const const cYZ: anycYZ = Math.cos(this.Demo.angleYZ: number
@type{number}
angleYZ
);
const const sYZ: anysYZ = Math.sin(this.Demo.angleYZ: number
@type{number}
angleYZ
);
const const cXY: anycXY = Math.cos(this.Demo.angleXY: number
@type{number}
angleXY
);
const const sXY: anysXY = Math.sin(this.Demo.angleXY: number
@type{number}
angleXY
);
const const cZW: anycZW = Math.cos(this.Demo.angleZW: number
@type{number}
angleZW
);
const const sZW: anysZW = Math.sin(this.Demo.angleZW: number
@type{number}
angleZW
);
const const cXZ: anycXZ = Math.cos(this.Demo.angleXZ: number
@type{number}
angleXZ
);
const const sXZ: anysXZ = Math.sin(this.Demo.angleXZ: number
@type{number}
angleXZ
);
const const v: {}v = this.Demo.scratch: {}
@type{number[]}
scratch
;
for (let let i: numberi = 0; let i: numberi < 16; let i: numberi++) { const const src: anysrc = const VERTICES: {}
@type{number[][]}
VERTICES
[let i: numberi];
const v: {}v[0] = const src: anysrc[0]; const v: {}v[1] = const src: anysrc[1]; const v: {}v[2] = const src: anysrc[2]; const v: {}v[3] = const src: anysrc[3]; // The 4D tumble planes first, then the screen-space yaw (XZ), so a horizontal // drag turns the projected object like a solid in front of the viewer. this.Demo.rotatePlane(v: any, c: any, s: any, a: any, b: any): voidrotatePlane(const v: {}v, const cXW: anycXW, const sXW: anysXW, 0, 3); this.Demo.rotatePlane(v: any, c: any, s: any, a: any, b: any): voidrotatePlane(const v: {}v, const cYZ: anycYZ, const sYZ: anysYZ, 1, 2); this.Demo.rotatePlane(v: any, c: any, s: any, a: any, b: any): voidrotatePlane(const v: {}v, const cXY: anycXY, const sXY: anysXY, 0, 1); this.Demo.rotatePlane(v: any, c: any, s: any, a: any, b: any): voidrotatePlane(const v: {}v, const cZW: anycZW, const sZW: anysZW, 2, 3); this.Demo.rotatePlane(v: any, c: any, s: any, a: any, b: any): voidrotatePlane(const v: {}v, const cXZ: anycXZ, const sXZ: anysXZ, 0, 2); // 4D to 3D perspective: points farther along W shrink toward the origin. const const w: numberw = const DIST_4: 2.6DIST_4 / (const DIST_4: 2.6DIST_4 - const v: {}v[3]); const const x3: numberx3 = const v: {}v[0] * const w: numberw; const const y3: numbery3 = const v: {}v[1] * const w: numberw; const const z3: numberz3 = const v: {}v[2] * const w: numberw; // 3D to 2D perspective, then scale and center on the canvas. const const p: numberp = const DIST_3: 3.2DIST_3 / (const DIST_3: 3.2DIST_3 - const z3: numberz3); const const x2: numberx2 = const x3: numberx3 * const p: numberp * const SCALE: 36SCALE + const cx: numbercx; const const y2: numbery2 = const y3: numbery3 * const p: numberp * const SCALE: 36SCALE + const cy: numbercy; this.Demo.projected: {}
@type{Vector2iType[]}
projected
[let i: numberi].set(const x2: numberx2, const y2: numbery2);
// Blend Z and W so links that dive "into" W sort behind nearer faces. this.Demo.depths: {}
@type{number[]}
depths
[let i: numberi] = const z3: numberz3 + const v: {}v[3] * 0.35;
} // Update the depth on each edge in place. Rebuilding the edges from a sorted // index list would pair the wrong color with the wrong endpoints and flash. for (let let e: numbere = 0; let e: numbere < this.Demo.edgeOrder: {}
@type{Edge[]}
edgeOrder
.length; let e: numbere++) {
const const edge: anyedge = this.Demo.edgeOrder: {}
@type{Edge[]}
edgeOrder
[let e: numbere];
const edge: anyedge.depth = (this.Demo.depths: {}
@type{number[]}
depths
[const edge: anyedge.i] + this.Demo.depths: {}
@type{number[]}
depths
[const edge: anyedge.j]) * 0.5;
} this.Demo.edgeOrder: {}
@type{Edge[]}
edgeOrder
.sort(function compareEdgeDepth(a: any, b: any): numbercompareEdgeDepth);
} // Draws the edges farthest first, then a dot on every corner. Demo.render(): voidrender() {
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: 1C_BG);
for (let let e: numbere = 0; let e: numbere < this.Demo.edgeOrder: {}
@type{Edge[]}
edgeOrder
.length; let e: numbere++) {
const const edge: anyedge = this.Demo.edgeOrder: {}
@type{Edge[]}
edgeOrder
[let e: numbere];
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
.drawLine: (p0: Vector2i, p1: Vector2i, paletteIndex: number) => void
Draws a pixel-perfect line between two points. Uses rasterized line drawing without antialiasing.
@since0.1.0@paramp0 - Start position in display coordinates.@paramp1 - End position in display coordinates.@parampaletteIndex - Palette color index.
drawLine
(this.Demo.projected: {}
@type{Vector2iType[]}
projected
[const edge: anyedge.i], this.Demo.projected: {}
@type{Vector2iType[]}
projected
[const edge: anyedge.j], const edge: anyedge.color);
} for (let let i: numberi = 0; let i: numberi < 16; let i: numberi++) {
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
(this.Demo.projected: {}
@type{Vector2iType[]}
projected
[let i: numberi], const C_DOT: 11C_DOT);
} } } 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);