/**
* Hypercube - Fez-style rotating tesseract wireframe.
* @description A Fez-style rotating tesseract: watch a four-dimensional cube turn on a 256x256 PICO-8 sized canvas.
*
* A tesseract is a 4D cube: two 3D cubes linked along a fourth axis (W).
* We rotate in 4D, then project down to 2D so you can see the links stretch
* and the "inner" cube pass through the "outer" one - like the Fez logo.
*
* Drag (mouse or touch) spins it like a trackball: horizontal = yaw around Y,
* vertical = pitch around X. On release the spin keeps the finger's 2D velocity
* (inertia), then that spin vector slowly fades back to the automatic tumble.
*/
import { function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>One-liner bootstrap function for BLIT386 demos.
Handles canvas retrieval and engine initialization. Backend selection
(WebGPU or software fallback) is managed internally by BTAPI.
This function provides a streamlined way to start a demo with sensible defaults
while allowing customization through options.bootstrap, const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT, class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32, class PaletteMutable 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.Palette, class Vector2iInteger 2D vector for pixel-perfect positioning.
Used for points, sizes, directions, and camera offsets throughout the engine.
The API includes both allocation-free `*To()` / `*InPlace()` variants and
convenience methods that return new vectors.Vector2i } from 'blit386';
/** @typedef {import('blit386').IBTDemo} IBTDemo */
/** @typedef {import('blit386').HardwareSettings} HardwareSettings */
/** @typedef {import('blit386').Palette} PaletteType */
/** @typedef {import('blit386').Vector2i} Vector2iType */
/**
* One wire of the tesseract. `color` is a fixed palette slot so painter's-order
* sorting never swaps which cube an edge belongs to.
*
* @typedef {object} Edge
* @property {number} i
* @property {number} j
* @property {number} depth
* @property {number} color
*/
/**
* Angular velocity in each rotation plane (radians per second).
*
* @typedef {object} Spin
* @property {number} xw
* @property {number} yz
* @property {number} xy
* @property {number} zw
* @property {number} xz
*/
const const SIZE: 320SIZE = 320;
const const SCALE: 36SCALE = 36;
/** Perspective distance for the 4D → 3D step (larger = flatter). */
const const DIST_4: 2.6Perspective distance for the 4D → 3D step (larger = flatter).DIST_4 = 2.6;
/** Perspective distance for the 3D → 2D step. */
const const DIST_3: 3.2Perspective distance for the 3D → 2D step.DIST_3 = 3.2;
const const C_BG: 1C_BG = 1;
const const C_NEAR: 7C_NEAR = 7;
/** Fixed slots - stable colors that do not swap when edges are depth-sorted. */
const const C_CUBE_A: 8Fixed slots - stable colors that do not swap when edges are depth-sorted.C_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: 8Fixed slots - stable colors that do not swap when edges are depth-sorted.C_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. */
const const HUE_SPEED: 28Hue drift in degrees per second.HUE_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 - the "home" spin vector we always fade back to.
* `xz` stays 0 at rest; drag/flick uses it for screen-space yaw.
*
* @type {Readonly<Spin>}
*/
const const HOME_SPIN: Readonly<Spin>The automatic Fez tumble - the "home" spin vector we always fade back to.
`xz` stays 0 at rest; drag/flick uses it for screen-space yaw.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.01Radians of trackball turn per pixel of drag.
Horizontal pixels yaw (XZ); vertical pixels pitch (YZ).DRAG_SENSITIVITY = 0.01;
/**
* How quickly we smooth the finger's instantaneous velocity while dragging
* (higher = snappier, lower = softer). Used so a noisy last frame does not
* become a wild flick.
*/
const const VELOCITY_SMOOTH: 14How quickly we smooth the finger's instantaneous velocity while dragging
(higher = snappier, lower = softer). Used so a noisy last frame does not
become a wild flick.VELOCITY_SMOOTH = 14;
/**
* How quickly free spin eases back toward HOME_SPIN after release (per second).
* Lower = longer coast on the flick before the Fez tumble returns.
*/
const const SPIN_FADE: 1.15How quickly free spin eases back toward HOME_SPIN after release (per second).
Lower = longer coast on the flick before the Fez tumble returns.SPIN_FADE = 1.15;
/** Cap on flick spin so a frantic swipe cannot spin forever. */
const const MAX_FLICK_SPIN: 8Cap on flick spin so a frantic swipe cannot spin forever.MAX_FLICK_SPIN = 8;
/**
* The 16 corners of a unit tesseract (±1 on x, y, z, w).
* Built once at load; never mutated - each frame copies into a scratch vector.
*
* @type {number[][]}
*/
const const VERTICES: {}The 16 corners of a unit tesseract (±1 on x, y, z, w).
Built once at load; never mutated - each frame copies into a scratch vector.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: {}The 16 corners of a unit tesseract (±1 on x, y, z, w).
Built once at load; never mutated - each frame copies into a scratch vector.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]);
}
/**
* Farther edges first. Tie-break on endpoints so equal depths stay stable
* (avoids color flicker when two edges share a depth).
*
* @param {Edge} a
* @param {Edge} b
* @returns {number}
*/
function function compareEdgeDepth(a: Edge, b: Edge): numberFarther edges first. Tie-break on endpoints so equal depths stay stable
(avoids color flicker when two edges share a depth).compareEdgeDepth(a: Edgea, b: Edgeb) {
return a: Edgea.depth: numberdepth - b: Edgeb.depth: numberdepth || a: Edgea.i: numberi - b: Edgeb.i: numberi || a: Edgea.j: numberj - b: Edgeb.j: numberj;
}
/**
* Clamp one spin component into ±MAX_FLICK_SPIN.
*
* @param {number} value
* @returns {number}
*/
function function clampFlick(value: number): numberClamp one spin component into ±MAX_FLICK_SPIN.clampFlick(value: numbervalue) {
return Math.max(-const MAX_FLICK_SPIN: 8Cap on flick spin so a frantic swipe cannot spin forever.MAX_FLICK_SPIN, Math.min(const MAX_FLICK_SPIN: 8Cap on flick spin so a frantic swipe cannot spin forever.MAX_FLICK_SPIN, value: numbervalue));
}
/** @implements {IBTDemo} */
class class DemoDemo {
/** @type {PaletteType | null} */
Demo.palette: Palette | nullpalette = null;
/** Screen positions after 4D → 2D projection (reused every frame). */
/** @type {Vector2iType[]} */
Demo.projected: {}projected = [];
/** Per-vertex depth for painter's algorithm. */
/** @type {number[]} */
Demo.depths: {}depths = [];
/** @type {Edge[]} */
Demo.edgeOrder: {}edgeOrder = [];
/** Starting hues for cube A / cube B / link / dot (degrees). */
/** @type {number[]} */
Demo.baseHues: {}baseHues = [];
/** Scratch 4D point reused while rotating each corner (avoids per-frame arrays). */
/** @type {number[]} */
Demo.scratch: {}scratch = [0, 0, 0, 0];
/** @type {number} */
Demo.angleXW: numberangleXW = 0.35;
/** @type {number} */
Demo.angleYZ: numberangleYZ = 0.9;
/** @type {number} */
Demo.angleXY: numberangleXY = 0.1;
/** @type {number} */
Demo.angleZW: numberangleZW = 0.2;
/** Screen-space yaw (XZ plane - around Y). @type {number} */
Demo.angleXZ: numberScreen-space yaw (XZ plane - around Y).angleXZ = 0;
/** Free-motion angular velocity; eases toward HOME_SPIN. @type {Spin} */
Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin = { ...const HOME_SPIN: Readonly<Spin>The automatic Fez tumble - the "home" spin vector we always fade back to.
`xz` stays 0 at rest; drag/flick uses it for screen-space yaw.HOME_SPIN };
/**
* Smoothed finger yaw/pitch (radians / sec) while dragging.
* On release these become spin.xz / spin.yz so the model coasts.
*
* @type {{ xz: number, yz: number }}
*/
Demo.fingerSpin: {
xz: number;
yz: number;
}
Smoothed finger yaw/pitch (radians / sec) while dragging.
On release these become spin.xz / spin.yz so the model coasts.fingerSpin = { xz: numberxz: 0, yz: numberyz: 0 };
/** Pointer slot currently steering (−1 = none). @type {number} */
Demo.dragSlot: numberPointer slot currently steering (−1 = none).dragSlot = -1;
/**
* @returns {Partial<HardwareSettings>}
*/
Demo.configure(): Partial<HardwareSettings>Optional hook to declare display size, optional output drawing-buffer size,
upscale filter, target fixed-update rate, rendering backend, and overlay.
When omitted, the engine uses
{@link
defaultConfig
}
(`320x240` logical,
`640x480` drawing buffer, `60` FPS, overlay enabled).
When present, you may return only the fields you want to change; the
engine merges them with
{@link
defaultConfig
}
via
{@link
mergeHardwareSettings
}
. Omit `displaySize` to inherit the full
default resolution and output buffer. Include `displaySize` when you
want a custom logical size; optional fields you omit then stay unset
(for example no `drawingBufferSize` means a 1:1 drawing buffer).configure() {
return {
displaySize: Vector2idisplaySize: new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const SIZE: 320SIZE, const SIZE: 320SIZE),
maxCanvasSize: Vector2imaxCanvasSize: new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.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,
},
};
}
/**
* @returns {Promise<boolean>}
*/
async Demo.init(): Promise<boolean>Called once after the selected rendering backend has been initialized.
Load assets and prepare a demo state here.init() {
this.Demo.palette: Palette | nullpalette = class PaletteMutable 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.Palette.Palette.pico8(): PaletteCreates the PICO-8 16-color palette.pico8();
// Spread the four line colors around the wheel; randomize the starting angle.
// BT.random is the engine's shared random number generator.
// Its float() method returns a decimal from the first value up to (but not including) the second,
// so this lands anywhere on the 360-degree color wheel.
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;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.random: RandomDefault engine PRNG (live reference - not a copy).
Time-seeded when the engine singleton is created. Call
{@link
BT.randomSeed
}
for a reproducible run. Mutating the instance (for example `BT.random.int(10)`)
advances the shared stream.random.Random.float(min: number, max: number): numberReturns the next pseudo-random float in [min, max).float(0, 360);
this.Demo.baseHues: {}baseHues = [const start: numberstart, const start: numberstart + 90, const start: numberstart + 180, const start: numberstart + 270];
this.Demo.applyLineColors(timeSeconds: number): 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;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.paletteSet: (palette: Palette) => voidStores the active engine palette.
Use this to swap the **entire palette** (e.g. switch between a day and night
theme). After this call the renderer uploads the new palette uniform on the
next frame.
**Palette-value swap (change what a slot looks like):** mutate the live
{@link
BT.palette
}
in place with `palette.set(slot, newColor)`. The renderer
uploads dirty slots on the next frame; no `paletteSet()` or
{@link
BT.spritesRefresh
}
needed.
**Palette-layout swap (same colors, different slot positions):** build a new
palette with the same colors at new indices, call `paletteSet()`, then call
{@link
BT.spritesRefresh
}
so every sprite sheet re-maps its original RGBA
pixels against the new slot layout.paletteSet(this.Demo.palette: Palettepalette);
for (let let i: numberi = 0; let i: numberi < 16; let i: numberi++) {
this.Demo.projected: {}projected.push(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(0, 0));
this.Demo.depths: {}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: 8Fixed slots - stable colors that do not swap when edges are depth-sorted.C_CUBE_A;
this.Demo.edgeOrder: {}edgeOrder.push({ i: numberi, j: numberj, depth: numberdepth: 0, color: numbercolor });
}
}
}
return true;
}
/**
* @param {number} timeSeconds
* @returns {void}
*/
Demo.applyLineColors(timeSeconds: number): voidapplyLineColors(timeSeconds: numbertimeSeconds) {
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: {}baseHues[let i: numberi] + timeSeconds: numbertimeSeconds * const HUE_SPEED: 28Hue drift in degrees per second.HUE_SPEED) % 360;
const const light: numberlight = const LINE_LIGHT: 58LINE_LIGHT + Math.sin(timeSeconds: numbertimeSeconds * 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 | nullpalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const LINE_SLOTS: {}LINE_SLOTS[let i: numberi], class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.fromHSL(h: number, s: number, l: number, a?: number): Color32Creates a color from HSL values.fromHSL(const hue: numberhue, const LINE_SAT: 88LINE_SAT, const light: numberlight));
}
}
/**
* Rotate a 4D point in the plane of axes `a` and `b` (0=X, 1=Y, 2=Z, 3=W).
* `c` / `s` are cos/sin of the angle - precomputed once per frame.
*
* @param {number[]} v
* @param {number} c
* @param {number} s
* @param {number} a
* @param {number} b
* @returns {void}
*/
Demo.rotatePlane(v: number[], c: number, s: number, a: number, b: number): voidRotate a 4D point in the plane of axes `a` and `b` (0=X, 1=Y, 2=Z, 3=W).
`c` / `s` are cos/sin of the angle - precomputed once per frame.rotatePlane(v: {}v, c: numberc, s: numbers, a: numbera, b: numberb) {
const const x: anyx = v: {}v[a: numbera];
const const y: anyy = v: {}v[b: numberb];
v: {}v[a: numbera] = const x: anyx * c: numberc - const y: anyy * s: numbers;
v: {}v[b: numberb] = const x: anyx * s: numbers + const y: anyy * c: numberc;
}
/**
* Mouse (slot 0): primary button held. Touch slots: contact active.
* Same rule as pointer-paint.
*
* @param {number} slot
* @returns {boolean}
*/
Demo.isDragHeld(slot: number): booleanMouse (slot 0): primary button held. Touch slots: contact active.
Same rule as pointer-paint.isDragHeld(slot: numberslot) {
if (!const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isPointerActive: (pointerIndex?: number) => booleanReports 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.isPointerActive(slot: numberslot)) {
return false;
}
return slot: numberslot === 0 ? const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isDown: (button: number, player?: number) => booleanChecks whether a button is currently held.
For pointer buttons (`BTN_POINTER_A..D`), the second parameter is the
pointer slot index (0 = mouse, 1-3 = touch / pen). For mouse slot 0:
`A` is left, `B` is right, `C` is middle, `D` is back / forward
(matches RetroBlit canonical, not DOM `PointerEvent.button` index).
Touch / pen slots only support `A`; B/C/D return `false`.
`button` accepts one or more bit flags from the `BTN_*` set (for example
`BT.BTN_A | BT.BTN_B`). Matching uses ANY semantics: returns `true` when
any selected button is held.
For face buttons (`BTN_UP`…`BTN_SELECT`), players `0` and `1` merge keyboard
and gamepad input (logical OR). Players `2` and `3` use gamepad only.
Pointer flags (`BTN_POINTER_*`) use the `player` argument as pointer slot.isDown(const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.type BTN_POINTER_A: numberPrimary pointer button code.
Maps to mouse left for slot 0; touch contact for slots 1-3.BTN_POINTER_A, 0) : true;
}
/**
* @param {number} dt
* @returns {void}
*/
Demo.integrateSpin(dt: number): voidintegrateSpin(dt: numberdt) {
this.Demo.angleXW: numberangleXW += this.Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin.xw: numberxw * dt: numberdt;
this.Demo.angleYZ: numberangleYZ += this.Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin.yz: numberyz * dt: numberdt;
this.Demo.angleXY: numberangleXY += this.Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin.xy: numberxy * dt: numberdt;
this.Demo.angleZW: numberangleZW += this.Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin.zw: numberzw * dt: numberdt;
this.Demo.angleXZ: numberScreen-space yaw (XZ plane - around Y).angleXZ += this.Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin.xz: numberxz * dt: numberdt;
}
/**
* Exponential ease of `spin` toward HOME_SPIN (soft settle, no hard stop).
*
* @param {number} dt
* @returns {void}
*/
Demo.fadeSpinToHome(dt: number): voidExponential ease of `spin` toward HOME_SPIN (soft settle, no hard stop).fadeSpinToHome(dt: numberdt) {
const const t: numbert = 1 - Math.exp(-const SPIN_FADE: 1.15How quickly free spin eases back toward HOME_SPIN after release (per second).
Lower = longer coast on the flick before the Fez tumble returns.SPIN_FADE * dt: numberdt);
this.Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin.xw: numberxw += (const HOME_SPIN: Readonly<Spin>The automatic Fez tumble - the "home" spin vector we always fade back to.
`xz` stays 0 at rest; drag/flick uses it for screen-space yaw.HOME_SPIN.xw - this.Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin.xw: numberxw) * const t: numbert;
this.Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin.yz: numberyz += (const HOME_SPIN: Readonly<Spin>The automatic Fez tumble - the "home" spin vector we always fade back to.
`xz` stays 0 at rest; drag/flick uses it for screen-space yaw.HOME_SPIN.yz - this.Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin.yz: numberyz) * const t: numbert;
this.Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin.xy: numberxy += (const HOME_SPIN: Readonly<Spin>The automatic Fez tumble - the "home" spin vector we always fade back to.
`xz` stays 0 at rest; drag/flick uses it for screen-space yaw.HOME_SPIN.xy - this.Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin.xy: numberxy) * const t: numbert;
this.Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin.zw: numberzw += (const HOME_SPIN: Readonly<Spin>The automatic Fez tumble - the "home" spin vector we always fade back to.
`xz` stays 0 at rest; drag/flick uses it for screen-space yaw.HOME_SPIN.zw - this.Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin.zw: numberzw) * const t: numbert;
this.Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin.xz: numberxz += (const HOME_SPIN: Readonly<Spin>The automatic Fez tumble - the "home" spin vector we always fade back to.
`xz` stays 0 at rest; drag/flick uses it for screen-space yaw.HOME_SPIN.xz - this.Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin.xz: numberxz) * const t: numbert;
}
/**
* Trackball drag + flick inertia. While held: yaw/pitch from pointer delta
* and EMA the finger velocity. On release: that velocity becomes `spin`,
* then fades back to HOME_SPIN.
*
* @param {number} dt
* @returns {void}
*/
Demo.updateDrag(dt: number): voidTrackball drag + flick inertia. While held: yaw/pitch from pointer delta
and EMA the finger velocity. On release: that velocity becomes `spin`,
then fades back to HOME_SPIN.updateDrag(dt: numberdt) {
const const wasDragging: booleanwasDragging = this.Demo.dragSlot: numberPointer slot currently steering (−1 = none).dragSlot >= 0;
// Stick with the current finger; otherwise claim the first held slot.
if (this.Demo.dragSlot: numberPointer slot currently steering (−1 = none).dragSlot >= 0 && !this.Demo.isDragHeld(slot: number): booleanMouse (slot 0): primary button held. Touch slots: contact active.
Same rule as pointer-paint.isDragHeld(this.Demo.dragSlot: numberPointer slot currently steering (−1 = none).dragSlot)) {
this.Demo.dragSlot: numberPointer slot currently steering (−1 = none).dragSlot = -1;
}
if (this.Demo.dragSlot: numberPointer slot currently steering (−1 = none).dragSlot < 0) {
for (let let slot: numberslot = 0; let slot: numberslot < 4; let slot: numberslot++) {
if (this.Demo.isDragHeld(slot: number): booleanMouse (slot 0): primary button held. Touch slots: contact active.
Same rule as pointer-paint.isDragHeld(let slot: numberslot)) {
this.Demo.dragSlot: numberPointer slot currently steering (−1 = none).dragSlot = let slot: numberslot;
// Drop press-frame jitter so it cannot become a throw.
this.Demo.fingerSpin: {
xz: number;
yz: number;
}
Smoothed finger yaw/pitch (radians / sec) while dragging.
On release these become spin.xz / spin.yz so the model coasts.fingerSpin.xz: numberxz = 0;
this.Demo.fingerSpin: {
xz: number;
yz: number;
}
Smoothed finger yaw/pitch (radians / sec) while dragging.
On release these become spin.xz / spin.yz so the model coasts.fingerSpin.yz: numberyz = 0;
break;
}
}
}
if (this.Demo.dragSlot: numberPointer slot currently steering (−1 = none).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;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.pointerDelta: (pointerIndex?: number) => Vector2iReturns 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.pointerDelta(this.Demo.dragSlot: numberPointer slot currently steering (−1 = none).dragSlot);
// Trackball: horizontal → yaw (XZ / around Y); vertical → pitch (YZ / around X).
const const dYaw: numberdYaw = const delta: Vector2idelta.Vector2i.x: numberHorizontal component (defaults to 0).x * const DRAG_SENSITIVITY: 0.01Radians of trackball turn per pixel of drag.
Horizontal pixels yaw (XZ); vertical pixels pitch (YZ).DRAG_SENSITIVITY;
const const dPitch: numberdPitch = const delta: Vector2idelta.Vector2i.y: numberVertical component (defaults to 0).y * const DRAG_SENSITIVITY: 0.01Radians of trackball turn per pixel of drag.
Horizontal pixels yaw (XZ); vertical pixels pitch (YZ).DRAG_SENSITIVITY;
this.Demo.angleXZ: numberScreen-space yaw (XZ plane - around Y).angleXZ += const dYaw: numberdYaw;
this.Demo.angleYZ: numberangleYZ += const dPitch: numberdPitch;
// Guard dt so a hitch cannot explode the velocity sample.
if (dt: numberdt > 0.0001) {
const const alpha: numberalpha = 1 - Math.exp(-const VELOCITY_SMOOTH: 14How quickly we smooth the finger's instantaneous velocity while dragging
(higher = snappier, lower = softer). Used so a noisy last frame does not
become a wild flick.VELOCITY_SMOOTH * dt: numberdt);
this.Demo.fingerSpin: {
xz: number;
yz: number;
}
Smoothed finger yaw/pitch (radians / sec) while dragging.
On release these become spin.xz / spin.yz so the model coasts.fingerSpin.xz: numberxz += (const dYaw: numberdYaw / dt: numberdt - this.Demo.fingerSpin: {
xz: number;
yz: number;
}
Smoothed finger yaw/pitch (radians / sec) while dragging.
On release these become spin.xz / spin.yz so the model coasts.fingerSpin.xz: numberxz) * const alpha: numberalpha;
this.Demo.fingerSpin: {
xz: number;
yz: number;
}
Smoothed finger yaw/pitch (radians / sec) while dragging.
On release these become spin.xz / spin.yz so the model coasts.fingerSpin.yz: numberyz += (const dPitch: numberdPitch / dt: numberdt - this.Demo.fingerSpin: {
xz: number;
yz: number;
}
Smoothed finger yaw/pitch (radians / sec) while dragging.
On release these become spin.xz / spin.yz so the model coasts.fingerSpin.yz: numberyz) * const alpha: numberalpha;
}
return;
}
// Coast on the flick (trackball axes only); fade restores the Fez planes.
if (const wasDragging: booleanwasDragging) {
this.Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin.xz: numberxz = function clampFlick(value: number): numberClamp one spin component into ±MAX_FLICK_SPIN.clampFlick(this.Demo.fingerSpin: {
xz: number;
yz: number;
}
Smoothed finger yaw/pitch (radians / sec) while dragging.
On release these become spin.xz / spin.yz so the model coasts.fingerSpin.xz: numberxz);
this.Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin.yz: numberyz = function clampFlick(value: number): numberClamp one spin component into ±MAX_FLICK_SPIN.clampFlick(this.Demo.fingerSpin: {
xz: number;
yz: number;
}
Smoothed finger yaw/pitch (radians / sec) while dragging.
On release these become spin.xz / spin.yz so the model coasts.fingerSpin.yz: numberyz);
this.Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin.xw: numberxw = 0;
this.Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin.xy: numberxy = 0;
this.Demo.spin: SpinFree-motion angular velocity; eases toward HOME_SPIN.spin.zw: numberzw = 0;
}
this.Demo.integrateSpin(dt: number): voidintegrateSpin(dt: numberdt);
this.Demo.fadeSpinToHome(dt: number): voidExponential ease of `spin` toward HOME_SPIN (soft settle, no hard stop).fadeSpinToHome(dt: numberdt);
}
/**
* @returns {void}
*/
Demo.update(): voidCalled zero or more times per frame at the fixed timestep declared by
`targetFPS`. The accumulator pattern ensures the target rate is met on
average, but a single frame may invoke this multiple times (catch-up) or
not at all. Update simulation, timers, and input-driven state here.
This is a hot path. Minimize allocations, reuse objects, and prefer
in-place vector operations where possible.
Avoid rendering work here; draw in `render()` instead.update() {
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;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.deltaSeconds: numberFixed-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.deltaSeconds;
this.Demo.updateDrag(dt: number): voidTrackball drag + flick inertia. While held: yaw/pitch from pointer delta
and EMA the finger velocity. On release: that velocity becomes `spin`,
then fades back to HOME_SPIN.updateDrag(const dt: numberdt);
this.Demo.applyLineColors(timeSeconds: number): 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;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.timeSeconds: numberFixed-step elapsed time in seconds (`BT.ticks * BT.deltaSeconds`).timeSeconds);
const const cx: numbercx = const SIZE: 320SIZE / 2;
const const cy: numbercy = const SIZE: 320SIZE / 2;
const const cXW: anycXW = Math.cos(this.Demo.angleXW: numberangleXW);
const const sXW: anysXW = Math.sin(this.Demo.angleXW: numberangleXW);
const const cYZ: anycYZ = Math.cos(this.Demo.angleYZ: numberangleYZ);
const const sYZ: anysYZ = Math.sin(this.Demo.angleYZ: numberangleYZ);
const const cXY: anycXY = Math.cos(this.Demo.angleXY: numberangleXY);
const const sXY: anysXY = Math.sin(this.Demo.angleXY: numberangleXY);
const const cZW: anycZW = Math.cos(this.Demo.angleZW: numberangleZW);
const const sZW: anysZW = Math.sin(this.Demo.angleZW: numberangleZW);
const const cXZ: anycXZ = Math.cos(this.Demo.angleXZ: numberScreen-space yaw (XZ plane - around Y).angleXZ);
const const sXZ: anysXZ = Math.sin(this.Demo.angleXZ: numberScreen-space yaw (XZ plane - around Y).angleXZ);
const const v: {}v = this.Demo.scratch: {}scratch;
for (let let i: numberi = 0; let i: numberi < 16; let i: numberi++) {
const const src: anysrc = const VERTICES: {}The 16 corners of a unit tesseract (±1 on x, y, z, w).
Built once at load; never mutated - each frame copies into a scratch vector.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];
// 4D tumble planes, then screen-space yaw (XZ) so a horizontal drag
// turns the projected object like a solid in front of you.
this.Demo.rotatePlane(v: number[], c: number, s: number, a: number, b: number): voidRotate a 4D point in the plane of axes `a` and `b` (0=X, 1=Y, 2=Z, 3=W).
`c` / `s` are cos/sin of the angle - precomputed once per frame.rotatePlane(const v: {}v, const cXW: anycXW, const sXW: anysXW, 0, 3);
this.Demo.rotatePlane(v: number[], c: number, s: number, a: number, b: number): voidRotate a 4D point in the plane of axes `a` and `b` (0=X, 1=Y, 2=Z, 3=W).
`c` / `s` are cos/sin of the angle - precomputed once per frame.rotatePlane(const v: {}v, const cYZ: anycYZ, const sYZ: anysYZ, 1, 2);
this.Demo.rotatePlane(v: number[], c: number, s: number, a: number, b: number): voidRotate a 4D point in the plane of axes `a` and `b` (0=X, 1=Y, 2=Z, 3=W).
`c` / `s` are cos/sin of the angle - precomputed once per frame.rotatePlane(const v: {}v, const cXY: anycXY, const sXY: anysXY, 0, 1);
this.Demo.rotatePlane(v: number[], c: number, s: number, a: number, b: number): voidRotate a 4D point in the plane of axes `a` and `b` (0=X, 1=Y, 2=Z, 3=W).
`c` / `s` are cos/sin of the angle - precomputed once per frame.rotatePlane(const v: {}v, const cZW: anycZW, const sZW: anysZW, 2, 3);
this.Demo.rotatePlane(v: number[], c: number, s: number, a: number, b: number): voidRotate a 4D point in the plane of axes `a` and `b` (0=X, 1=Y, 2=Z, 3=W).
`c` / `s` are cos/sin of the angle - precomputed once per frame.rotatePlane(const v: {}v, const cXZ: anycXZ, const sXZ: anysXZ, 0, 2);
// 4D → 3D perspective: points farther in W shrink toward the origin.
const const w: numberw = const DIST_4: 2.6Perspective distance for the 4D → 3D step (larger = flatter).DIST_4 / (const DIST_4: 2.6Perspective distance for the 4D → 3D step (larger = flatter).DIST_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 → 2D perspective, then center on the canvas.
const const p: numberp = const DIST_3: 3.2Perspective distance for the 3D → 2D step.DIST_3 / (const DIST_3: 3.2Perspective distance for the 3D → 2D step.DIST_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: {}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: {}depths[let i: numberi] = const z3: numberz3 + const v: {}v[3] * 0.35;
}
// Update depth on each edge in place. Do NOT rebuild edges from a sorted
// index list - that pairs the wrong color with the wrong endpoints and flashes.
for (let let e: numbere = 0; let e: numbere < this.Demo.edgeOrder: {}edgeOrder.length; let e: numbere++) {
const const edge: anyedge = this.Demo.edgeOrder: {}edgeOrder[let e: numbere];
const edge: anyedge.depth = (this.Demo.depths: {}depths[const edge: anyedge.i] + this.Demo.depths: {}depths[const edge: anyedge.j]) * 0.5;
}
this.Demo.edgeOrder: {}edgeOrder.sort(function compareEdgeDepth(a: Edge, b: Edge): numberFarther edges first. Tie-break on endpoints so equal depths stay stable
(avoids color flicker when two edges share a depth).compareEdgeDepth);
}
/**
* @returns {void}
*/
Demo.render(): voidCalled once per `requestAnimationFrame` tick (browser refresh rate).
Issue all draw calls for the current frame here.
When
{@link
HardwareSettings.isOverlayEnabled
}
is `true` (default), the engine
draws a screen-space overlay HUD after this method returns (present FPS, target FPS, draw calls,
frame/update()/render() timings, backend, demo title). Optional
{@link
overlayRows
}
adds stacked bars above
the footer.
Demos do not need to duplicate engine overlay text. Reserve about ~42 px at the top and space for the bottom palette
grid (or ~13 px when
{@link
HardwareSettings.isOverlayPaletteEnabled
}
is `false`) at the bottom (plus ~14 px per
custom overlay row) for overlay bars, or disable the overlay in `configure()` when using custom full-screen HUD
layouts.
This is a hot path. Batch draws by texture to reduce GPU state changes
and reuse Color32/Vector2i instances instead of allocating per frame.
Avoid mutating the simulation state here unless it is strictly visual.render() {
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.clear: (paletteIndex: number) => voidSets the frame clear color using a palette index.
The renderer uses this color when clearing the full display at the start
of the next frame.clear(const C_BG: 1C_BG);
for (let let e: numbere = 0; let e: numbere < this.Demo.edgeOrder: {}edgeOrder.length; let e: numbere++) {
const const edge: anyedge = this.Demo.edgeOrder: {}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;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.drawLine: (p0: Vector2i, p1: Vector2i, paletteIndex: number) => voidDraws a pixel-perfect line between two points.
Uses rasterized line drawing without antialiasing.drawLine(this.Demo.projected: {}projected[const edge: anyedge.i], this.Demo.projected: {}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;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.drawPixel: (posOrX: Vector2i | number, yOrColor: number, maybeColor?: number) => voidDraws 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)`.drawPixel(this.Demo.projected: {}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.bootstrap(class DemoDemo);