/**
 * Pointer Drag-and-Flick Demo - grab balls, drag them, release to throw.
 * @description Grab one of three bouncing balls, drag it, and release to throw it, with synthesized sound effects.
 *
 * Part of the BLIT386 demo series.
 * Prerequisites:
 *   Pointer Basics - https://demos.blit386.dev/pointer-basics
 *   Pointer Paint  - https://demos.blit386.dev/pointer-paint
 *
 * Live version: https://demos.blit386.dev/pointer-drag-flick
 *
 * This is the action-oriented sibling of pointer-basics and pointer-paint. Where
 * pointer-basics reads pointer state and pointer-paint paints onto a canvas, this demo couples the pointer
 * to a tiny physics simulation:
 *
 *   - Three balls bounce around inside a closed box under gravity.
 *   - Click and HOLD on a ball to grab it (the ball follows the pointer).
 *   - RELEASE to throw it - the release-frame BT.pointerDelta becomes the
 *     ball's launch velocity.
 *
 * On a touchscreen each finger can grab its own ball; up to three balls can
 * be dragged at once (slots 1, 2, 3). The mouse uses slot 0.
 *
 * What this demonstrates that pointer-basics and pointer-paint do not:
 *
 *   - BT.isPressed(...) as a "grab" edge: only fires the frame the
 *     button transitions to down, used to start the drag exactly once.
 *   - BT.isReleased(...) as a "throw" edge: only fires the frame the
 *     button transitions to up. We sample BT.pointerDelta during this
 *     edge to capture the user's release-time hand velocity.
 *   - BT.pointerDelta actively driving simulation, not just shown as text.
 *
 * Two custom sounds, both built with AudioClip.synth() (the technique Synth Toy
 * explores in depth): a whoosh on every throw, whose pitch and volume scale with how hard
 * you flicked, and a thud every time a ball hits a wall or the floor hard enough to notice.
 *
 * The HUD (the title strip along the top and the per-slot pointer indicators in the
 * top-right corner) is drawn with the shared UI kit (src/shared/ui.js), so it uses the
 * same colors and layout as every other demo. The balls themselves keep their own scene
 * colors and are grabbed with raw pointer reads - the kit only handles the readouts.
 *
 * Coordinate convention: balls store position with sub-pixel precision
 * (floats) so physics integrates smoothly, but every render call rounds to
 * integer display coordinates so pixels stay crisp.
 */

import { class AudioClip
Decoded audio asset with its winning source URL and buffer-derived metadata. Construct instances with {@link AudioClip.load } , {@link AudioClip.loadAll } , or {@link AudioClip.synth } ; there is no public constructor.
@since1.3.0
AudioClip
, function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>
One-liner bootstrap function for BLIT386 demos. Handles canvas retrieval and engine initialization. Backend selection (WebGPU or software fallback) is managed internally by BTAPI. This function provides a streamlined way to start a demo with sensible defaults while allowing customization through options.
@since0.2.0@changed1.4.0 Calling `bootstrap()` again while already initialized now routes to a hot swap (via {@link registerHotReload}) when a Vite HMR context is registered, or logs a double-bootstrap guard and returns `false` otherwise - previously it silently started a second, unstoppable `GameLoop`.@changed1.7.0 Exposes `BT` on `window.BT` after bootstrap finishes, gated by {@link BootstrapOptions.exposeGlobal} (default: {@link BT.isDevMode}).@paramDemoClass - Demo class constructor implementing `IBTDemo` (optional `configure()` for hardware settings).@paramoptions - Optional configuration for IDs and callbacks.@returns`true` when the demo boots successfully; otherwise `false`.@example// Simplest usage - uses default IDs. bootstrap(MyDemo);@example// With custom options. bootstrap(MyDemo, { canvasID: 'custom-canvas', containerID: 'custom-container', onSuccess: () => console.log('Demo started!'), onError: (err) => analytics.trackError(err), });@example// Await the result. const success = await bootstrap(MyDemo); if (success) { console.log('Demo is running'); }
bootstrap
,
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
, class Vector2i
Integer 2D vector for pixel-perfect positioning. Used for points, sizes, directions, and camera offsets throughout the engine. The API includes both allocation-free `*To()` / `*InPlace()` variants and convenience methods that return new vectors.
@since0.1.0
Vector2i
} from 'blit386';
import { import applyThemeapplyTheme, import uiui, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js'; /** @typedef {import('blit386').IBTDemo} IBTDemo */ /** @typedef {import('blit386').HardwareSettings} HardwareSettings */ /** @typedef {import('blit386').Palette} Palette */ const const DISPLAY_W: 320DISPLAY_W = 320; const const DISPLAY_H: 240DISPLAY_H = 240; // Vertical strip at the top reserved for the shared UI kit's full-width title bar // ('topBar' is 22 pixels tall). Balls cannot enter it, and grabs are only registered // below it, so the strip acts as the ceiling of the physics box. const const HUD_HEIGHT: 22HUD_HEIGHT = 22; // Scene palette slots. Index 0 is always transparent. UI colors (panel, text, borders) // are no longer listed here - the shared UI kit installs them into high slots (240+) // via applyTheme() in init(), so only the demo's own scene colors remain. const const C_BALL_OUTLINE: 1C_BALL_OUTLINE = 1; // outline drawn around any grabbed ball const const C_BALL_HIGHLIGHT: 2C_BALL_HIGHLIGHT = 2; // tint for the ball under the mouse cursor when no slot is grabbing it const const C_CHART_UPDATE: 3C_CHART_UPDATE = 3; // dim gray for the overlay timing chart's update bars const const C_CHART_RENDER: 4C_CHART_RENDER = 4; // white for the chart's render bars and milestone tags // Three balls, each its own color so it's easy to track which is which. const const BALL_COLORS: {}BALL_COLORS = [5, 6, 7]; // Physics parameters, all expressed in "display pixels per fixed update tick" // since the engine runs `update()` at a fixed rate (here 60 Hz). const const BALL_RADIUS: 10BALL_RADIUS = 10; const const GRAVITY: 0.35GRAVITY = 0.35; // px/tick² downward const const WALL_DAMPING: 0.78WALL_DAMPING = 0.78; // velocity multiplier on wall bounce (energy loss) const const FLOOR_FRICTION: 0.985FLOOR_FRICTION = 0.985; // horizontal velocity multiplier per tick on the floor const const AIR_DRAG: 0.999AIR_DRAG = 0.999; // gentle air drag so flicks decay over time const const MIN_SPEED: 0.05MIN_SPEED = 0.05; // velocities below this are clamped to zero (avoid tiny jitter) // Multiplier applied to BT.pointerDelta when a ball is released. The delta is // already in "display pixels moved during the previous fixed update tick", // which is roughly velocity in px/tick. Scale up slightly so easy flicks feel // energetic. const const THROW_SCALE: 1.4THROW_SCALE = 1.4; // Maximum allowed launch speed (px/tick). Caps the velocity from a very fast // flick so balls don't escape the box in a single tick. const const MAX_THROW_SPEED: 16MAX_THROW_SPEED = 16; // Throw whoosh: pitch and volume scale between these min/max values based on how fast the // ball was thrown, so a gentle nudge sounds different from a hard flick. const const WHOOSH_PITCH_MIN: 0.85WHOOSH_PITCH_MIN = 0.85; const const WHOOSH_PITCH_MAX: 1.6WHOOSH_PITCH_MAX = 1.6; const const WHOOSH_VOLUME_MIN: 0.35WHOOSH_VOLUME_MIN = 0.35; const const WHOOSH_VOLUME_MAX: 1WHOOSH_VOLUME_MAX = 1.0; // Wall/floor thud: bounces gentler than this speed are skipped entirely, so a ball settling // to rest does not spam quiet thuds. THUD_VOLUME_MAX_SPEED is the impact speed at or above // which the thud plays at full volume. const const THUD_MIN_SPEED: 0.5THUD_MIN_SPEED = 0.5; const const THUD_VOLUME_MAX_SPEED: 10THUD_VOLUME_MAX_SPEED = 10; /** * Keeps a number from going below `min` or above `max`. * * @param {number} value * @param {number} min * @param {number} max * @returns {number} */ function function clamp(value: number, min: number, max: number): number
Keeps a number from going below `min` or above `max`.
@paramvalue@parammin@parammax@returns
clamp
(value: number
@paramvalue
value
, min: number
@parammin
min
, max: number
@parammax
max
) {
return Math.max(min: number
@parammin
min
, Math.min(max: number
@parammax
max
, value: number
@paramvalue
value
));
} /** * Drag-and-flick physics demo. * * Each ball is a small object: { x, y, vx, vy, color, grabbedBy }. `grabbedBy` * is -1 when free or a pointer slot index 0..3 when held. While held, physics * integration is skipped and the ball is teleported to the pointer position * each frame. On release we read `BT.pointerDelta(slot)` and convert it to * the ball's launch velocity. * * @implements {IBTDemo} */ class class Demo
Drag-and-flick physics demo. Each ball is a small object: { x, y, vx, vy, color, grabbedBy }. `grabbedBy` is -1 when free or a pointer slot index 0..3 when held. While held, physics integration is skipped and the ball is teleported to the pointer position each frame. On release we read `BT.pointerDelta(slot)` and convert it to the ball's launch velocity.
@implementsIBTDemo
Demo
{
/** @type {Palette | null} */ Demo.palette: Palette | null
@type{Palette | null}
palette
= null;
/** * Palette slot map returned by applyTheme() - where the shared UI colors landed. * Used for BT.clear(this.theme.bg) and the crosshair cursor color. * * @type {ReturnType<typeof applyTheme> | null} */ Demo.theme: any
Palette slot map returned by applyTheme() - where the shared UI colors landed. Used for BT.clear(this.theme.bg) and the crosshair cursor color.
@type{ReturnType<typeof applyTheme> | null}
theme
= null;
/** @type {AudioClip | null} Whoosh sound played when a ball is thrown. */ Demo.whooshClip: AudioClip | null
@type{AudioClip | null} Whoosh sound played when a ball is thrown.
whooshClip
= null;
/** @type {AudioClip | null} Thud sound played when a ball bounces off a wall or floor. */ Demo.thudClip: AudioClip | null
@type{AudioClip | null} Thud sound played when a ball bounces off a wall or floor.
thudClip
= null;
/** * Active balls. Created in init(). * * prevX/prevY remember where each ball was at the START of the most recent * update() tick, before physics moved it. render() blends between prevX/prevY * and x/y using BT.renderAlpha so the ball glides smoothly across render * frames instead of only moving once per physics tick - see "Interpolating * render state with renderAlpha" in the engine's docs/api-game-loop.md. * * @type {Array<{x: number, y: number, prevX: number, prevY: number, vx: number, vy: number, color: number, grabbedBy: number}>} */
Demo.balls: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
Active balls. Created in init(). prevX/prevY remember where each ball was at the START of the most recent update() tick, before physics moved it. render() blends between prevX/prevY and x/y using BT.renderAlpha so the ball glides smoothly across render frames instead of only moving once per physics tick - see "Interpolating render state with renderAlpha" in the engine's docs/api-game-loop.md.
@type{Array<{x: number, y: number, prevX: number, prevY: number, vx: number, vy: number, color: number, grabbedBy: number}>}
balls
= [];
/** * Tells the engine the screen size and which palette slots to use for the * timing chart overlay. The chart shows update() and render() time side by side * so you can see when a ball throw causes a spike. * * @returns {Partial<HardwareSettings>} */ Demo.configure(): Partial<HardwareSettings>
Tells the engine the screen size and which palette slots to use for the timing chart overlay. The chart shows update() and render() time side by side so you can see when a ball throw causes a spike.
@returns
configure
() {
return { // Set the logical display size (how many pixels the demo draws at). 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 DISPLAY_W: 320DISPLAY_W, const DISPLAY_H: 240DISPLAY_H),
// Phones and tablets dim, then lock, the screen after 30-60 seconds without a touch - // easy to hit while you are just watching a ball settle before flicking it again. This // asks the browser to keep the screen on while you play; unsupported browsers ignore it. isWakeLockEnabled: booleanisWakeLockEnabled: true, // Show the scrolling timing chart in the overlay so each frame's cost is visible. // configure() runs before init(), so the shared theme slots do not exist yet - // the chart uses two dedicated scene slots filled in init() instead. isOverlayTimingChartEnabled: booleanisOverlayTimingChartEnabled: true,
overlayTimingChartStyle: {
    updateBarPaletteIndex: number;
    renderBarPaletteIndex: number;
    tagPaletteIndex: number;
}
overlayTimingChartStyle
: {
// Dim gray makes update bars subtle so render bars stand out by contrast. updateBarPaletteIndex: numberupdateBarPaletteIndex: const C_CHART_UPDATE: 3C_CHART_UPDATE, // White gives render bars and milestone labels high contrast against the dark background. renderBarPaletteIndex: numberrenderBarPaletteIndex: const C_CHART_RENDER: 4C_CHART_RENDER, tagPaletteIndex: numbertagPaletteIndex: const C_CHART_RENDER: 4C_CHART_RENDER, }, }; } /** * Sets up the palette and seeds three balls at varied starting positions. * * @returns {Promise<boolean>} */ async Demo.init(): Promise<boolean>
Sets up the palette and seeds three balls at varied starting positions.
@returns
init
() {
this.Demo.whooshClip: AudioClip | null
@type{AudioClip | null} Whoosh sound played when a ball is thrown.
whooshClip
= await class AudioClip
Decoded audio asset with its winning source URL and buffer-derived metadata. Construct instances with {@link AudioClip.load } , {@link AudioClip.loadAll } , or {@link AudioClip.synth } ; there is no public constructor.
@since1.3.0
AudioClip
.AudioClip.synth(params: SynthParams): Promise<AudioClip>
Synthesizes a clip from deterministic procedural parameters - no source file, no `OfflineAudioContext`, and no audio graph involved. Rendering happens entirely on the CPU via the pure {@link renderSynthSamples } function against an `AudioBuffer` allocated from the registered decode context. The returned clip flows through the same {@link buffer } getter and playback path as a loaded clip, but uses a synthetic, non-cached identifier (`synth:<waveform>`) - it is never added to the URL-keyed resolved cache and never deduplicated, so identical `params` still render a fresh, independent `AudioBuffer` on every call. See {@link SynthParams } for the full parameter set.
@paramparams - Deterministic synthesis parameters.@returnsA new clip wrapping the synthesized buffer.@throwsError if `params` fails validation, or the engine has not registered a decode context yet (see {@link audioClipNotReadyError }).
synth
({
SynthParams.waveform: any
Oscillator waveform shape.
waveform
: 'sine',
SynthParams.frequency: number
Base carrier frequency in Hz at the start of the clip (before any pitch sweep or vibrato).
frequency
: 600,
SynthParams.duration: number
Total clip duration in seconds. Must be greater than 0 and no more than {@link MAX_SYNTH_DURATION_SECONDS } .
duration
: 0.35,
SynthParams.envelope?: SynthEnvelope | undefined
Optional attack/decay/sustain/release envelope. Defaults to a full ADSR envelope; see {@link SynthEnvelope } .
envelope
: { SynthEnvelope.attack?: number | undefined
Time in seconds to ramp from silence to full amplitude. Defaults to {@link DEFAULT_ATTACK } .
attack
: 0.005, SynthEnvelope.decay?: number | undefined
Time in seconds to fall from full amplitude to the `sustain` level. Defaults to {@link DEFAULT_DECAY } .
decay
: 0.1, SynthEnvelope.sustain?: number | undefined
Gain level in [0, 1] held between the decay and release phases. Defaults to {@link DEFAULT_SUSTAIN } .
sustain
: 0.2, SynthEnvelope.release?: number | undefined
Time in seconds to fall from the sustain level to silence at the end of the clip. Defaults to {@link DEFAULT_RELEASE } .
release
: 0.2 },
SynthParams.pitchSweep?: SynthPitchSweep | undefined
Optional linear pitch sweep from `frequency` to a target frequency across the clip.
pitchSweep
: { SynthPitchSweep.toFrequency: number
Frequency in Hz the carrier linearly reaches by the end of the clip.
toFrequency
: 150 },
SynthParams.noiseMix?: number | undefined
Fraction of white noise mixed into the oscillator output, in [0, 1] (`0` is pure tone, `1` is pure noise). Ignored when `waveform` is already `'noise'`. Defaults to {@link DEFAULT_NOISE_MIX } .
noiseMix
: 0.15,
SynthParams.seed: number
Seed for the deterministic PRNG driving noise generation - identical seeds render identical output.
seed
: 2,
}); this.Demo.thudClip: AudioClip | null
@type{AudioClip | null} Thud sound played when a ball bounces off a wall or floor.
thudClip
= await class AudioClip
Decoded audio asset with its winning source URL and buffer-derived metadata. Construct instances with {@link AudioClip.load } , {@link AudioClip.loadAll } , or {@link AudioClip.synth } ; there is no public constructor.
@since1.3.0
AudioClip
.AudioClip.synth(params: SynthParams): Promise<AudioClip>
Synthesizes a clip from deterministic procedural parameters - no source file, no `OfflineAudioContext`, and no audio graph involved. Rendering happens entirely on the CPU via the pure {@link renderSynthSamples } function against an `AudioBuffer` allocated from the registered decode context. The returned clip flows through the same {@link buffer } getter and playback path as a loaded clip, but uses a synthetic, non-cached identifier (`synth:<waveform>`) - it is never added to the URL-keyed resolved cache and never deduplicated, so identical `params` still render a fresh, independent `AudioBuffer` on every call. See {@link SynthParams } for the full parameter set.
@paramparams - Deterministic synthesis parameters.@returnsA new clip wrapping the synthesized buffer.@throwsError if `params` fails validation, or the engine has not registered a decode context yet (see {@link audioClipNotReadyError }).
synth
({
SynthParams.waveform: any
Oscillator waveform shape.
waveform
: 'sine',
SynthParams.frequency: number
Base carrier frequency in Hz at the start of the clip (before any pitch sweep or vibrato).
frequency
: 90,
SynthParams.duration: number
Total clip duration in seconds. Must be greater than 0 and no more than {@link MAX_SYNTH_DURATION_SECONDS } .
duration
: 0.12,
SynthParams.envelope?: SynthEnvelope | undefined
Optional attack/decay/sustain/release envelope. Defaults to a full ADSR envelope; see {@link SynthEnvelope } .
envelope
: { SynthEnvelope.attack?: number | undefined
Time in seconds to ramp from silence to full amplitude. Defaults to {@link DEFAULT_ATTACK } .
attack
: 0.001, SynthEnvelope.decay?: number | undefined
Time in seconds to fall from full amplitude to the `sustain` level. Defaults to {@link DEFAULT_DECAY } .
decay
: 0.06, SynthEnvelope.sustain?: number | undefined
Gain level in [0, 1] held between the decay and release phases. Defaults to {@link DEFAULT_SUSTAIN } .
sustain
: 0, SynthEnvelope.release?: number | undefined
Time in seconds to fall from the sustain level to silence at the end of the clip. Defaults to {@link DEFAULT_RELEASE } .
release
: 0.05 },
SynthParams.seed: number
Seed for the deterministic PRNG driving noise generation - identical seeds render identical output.
seed
: 3,
}); this.Demo.palette: Palette | null
@type{Palette | null}
palette
=
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.paletteCreate: (size?: number) => Palette
Creates a standalone palette instance.
@since1.0.3@paramsize - Palette size. Defaults to 256 colors.@returnsNew mutable palette.
paletteCreate
(256);
// Scene colors: the ball rings, the timing chart bars, and the balls themselves. this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_BALL_OUTLINE: 1C_BALL_OUTLINE, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(255, 255, 255));
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_BALL_HIGHLIGHT: 2C_BALL_HIGHLIGHT, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(255, 220, 120));
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_CHART_UPDATE: 3C_CHART_UPDATE, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(150, 160, 180));
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_CHART_RENDER: 4C_CHART_RENDER, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(255, 255, 255));
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const BALL_COLORS: {}BALL_COLORS[0], new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(255, 100, 110));
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const BALL_COLORS: {}BALL_COLORS[1], new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(120, 220, 130));
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const BALL_COLORS: {}BALL_COLORS[2], new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(120, 170, 255));
// Install the shared UI theme (panel, text, border colors and so on) into high // palette slots (240 and up), far away from the scene colors above. The returned // map tells us which slot each theme color landed in, so the demo can reuse them // (for example this.theme.bg as the screen clear color). this.Demo.theme: any
Palette slot map returned by applyTheme() - where the shared UI colors landed. Used for BT.clear(this.theme.bg) and the crosshair cursor color.
@type{ReturnType<typeof applyTheme> | null}
theme
= import applyThemeapplyTheme(this.Demo.palette: Palette
@type{Palette | null}
palette
);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.paletteSet: (palette: Palette) => void
Stores the active engine palette. Use this to swap the **entire palette** (e.g. switch between a day and night theme). After this call the renderer uploads the new palette uniform on the next frame. **Palette-value swap (change what a slot looks like):** mutate the live {@link BT.palette } in place with `palette.set(slot, newColor)`. The renderer uploads dirty slots on the next frame; no `paletteSet()` or {@link BT.spritesRefresh } needed. **Palette-layout swap (same colors, different slot positions):** build a new palette with the same colors at new indices, call `paletteSet()`, then call {@link BT.spritesRefresh } so every sprite sheet re-maps its original RGBA pixels against the new slot layout.
@since1.0.3@parampalette - Palette to make active.
paletteSet
(this.Demo.palette: Palette
@type{Palette | null}
palette
);
// Hide the native OS cursor so the drawn crosshair markers are the only // cursors visible while the pointer is over the canvas.
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.hideCursor: () => void
Hides the native OS cursor while the pointer is over the canvas. Call once from `init()` when the demo draws its own crosshair or cursor sprite in place of the system arrow. The cursor is restored automatically when the engine shuts down. No-op before the engine is initialized.
@since1.0.3
hideCursor
();
// Stagger the balls horizontally and give each a small initial velocity // so the simulation looks alive on first frame. this.
Demo.balls: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
Active balls. Created in init(). prevX/prevY remember where each ball was at the START of the most recent update() tick, before physics moved it. render() blends between prevX/prevY and x/y using BT.renderAlpha so the ball glides smoothly across render frames instead of only moving once per physics tick - see "Interpolating render state with renderAlpha" in the engine's docs/api-game-loop.md.
@type{Array<{x: number, y: number, prevX: number, prevY: number, vx: number, vy: number, color: number, grabbedBy: number}>}
balls
= [
{ x: numberx: 80, y: numbery: 60, prevX: numberprevX: 80, prevY: numberprevY: 60, vx: numbervx: 1.2, vy: numbervy: 0, color: anycolor: const BALL_COLORS: {}BALL_COLORS[0], grabbedBy: numbergrabbedBy: -1 }, { x: numberx: 160, y: numbery: 50, prevX: numberprevX: 160, prevY: numberprevY: 50, vx: numbervx: -0.6, vy: numbervy: 0.4, color: anycolor: const BALL_COLORS: {}BALL_COLORS[1], grabbedBy: numbergrabbedBy: -1 }, { x: numberx: 240, y: numbery: 70, prevX: numberprevX: 240, prevY: numberprevY: 70, vx: numbervx: 0.8, vy: numbervy: -0.2, color: anycolor: const BALL_COLORS: {}BALL_COLORS[2], grabbedBy: numbergrabbedBy: -1 }, ]; return true; } /** * Per-tick: route press / release edges to grab / throw, follow held * balls to their owning pointer, integrate physics for free balls. */ Demo.update(): void
Per-tick: route press / release edges to grab / throw, follow held balls to their owning pointer, integrate physics for free balls.
update
() {
// Walk every pointer slot. Mouse (slot 0) and touches (1-3) all use // BTN_POINTER_A as the "primary" button; for touches that's automatic // ("contact made = A held"), for the mouse it's the left button. for (let let slot: numberslot = 0; let slot: numberslot < 4; let slot: numberslot++) { // Edge: pointer just went down on this slot. Try to grab a ball. if (
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.isPressed: (button: number, player?: number, repeatRate?: number) => boolean
Checks whether a button was pressed on the current frame. Same parameter semantics as {@link isDown } ; returns `true` only on the frame the button transitions from up to down. Call from `update()`, not `render()`, for reliable detection: for keyboard-mapped face buttons (players 0 and 1), the press edge clears once per fixed-update tick, which always runs before that frame's `render()`.
@since1.1.1@parambutton - Button constant from the `BTN_*` set.@paramplayer - Zero-based player index for gamepads, or pointer slot (0-3) for `BTN_POINTER_*`.@paramrepeatRate - Optional repeat interval in fixed ticks (`0`/omitted = edge only).@returns`true` on the transition frame.
isPressed
(
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.type BTN_POINTER_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
, let slot: numberslot)) {
this.Demo.tryGrab(slot: any): void
Attempts to grab a ball whose center is under this slot's pointer. Skips if no live pointer, or pointer is inside the HUD strip, or if this slot is already holding a ball.
tryGrab
(let slot: numberslot);
} // Edge: pointer just released on this slot. Throw whatever it held. 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
.isReleased: (button: number, player?: number) => boolean
Checks whether a button was released on the current frame. Same parameter semantics as {@link isDown } ; returns `true` only on the frame the button transitions from down to up. Call from `update()`, not `render()`, for reliable detection: for keyboard-mapped face buttons (players 0 and 1), the release edge clears once per fixed-update tick, which always runs before that frame's `render()`.
@since1.1.1@parambutton - Button constant from the `BTN_*` set.@paramplayer - Zero-based player index for gamepads, or pointer slot (0-3) for `BTN_POINTER_*`.@returns`true` on the release frame.
isReleased
(
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.type BTN_POINTER_A: number
Primary pointer button code. Maps to mouse left for slot 0; touch contact for slots 1-3.
@since0.1.0
BTN_POINTER_A
, let slot: numberslot)) {
this.Demo.tryThrow(slot: any): void
Releases whatever ball this slot is holding, launching it with the pointer's release-frame velocity (scaled and clamped to MAX_THROW_SPEED).
tryThrow
(let slot: numberslot);
} } // Move every ball: held ones follow their pointer, free ones obey // gravity / drag / wall bounce. for (const
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
of this.
Demo.balls: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
Active balls. Created in init(). prevX/prevY remember where each ball was at the START of the most recent update() tick, before physics moved it. render() blends between prevX/prevY and x/y using BT.renderAlpha so the ball glides smoothly across render frames instead of only moving once per physics tick - see "Interpolating render state with renderAlpha" in the engine's docs/api-game-loop.md.
@type{Array<{x: number, y: number, prevX: number, prevY: number, vx: number, vy: number, color: number, grabbedBy: number}>}
balls
) {
// Snapshot "where was this ball a moment ago" BEFORE moving it, so // render() can draw a smooth in-between position instead of a pop.
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.prevX =
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.x;
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.prevY =
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.y;
if (
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.grabbedBy >= 0) {
this.Demo.updateHeldBall(ball: any): void
Snaps a held ball to its owning pointer's position. If the pointer went invalid mid-grab (pointer left the canvas, touch canceled) we release the ball gently with zero velocity.
updateHeldBall
(
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
);
} else { this.Demo.updateFreeBall(ball: any): void
Integrates one tick of physics for a free ball: gravity, air drag, floor friction, and wall bounces with damping.
updateFreeBall
(
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
);
} } } /** * Per-frame render: clear, draw HUD, draw balls, draw cursor markers. */ Demo.render(): void
Per-frame render: clear, draw HUD, draw balls, draw cursor markers.
render
() {
// Clear the whole screen with the shared theme's background color.
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.clear: (paletteIndex: number) => void
Sets the frame clear color using a palette index. The renderer uses this color when clearing the full display at the start of the next frame.
@since0.1.0@parampaletteIndex - Palette index for the full-screen clear pass.
clear
(this.Demo.theme: any
Palette slot map returned by applyTheme() - where the shared UI colors landed. Used for BT.clear(this.theme.bg) and the crosshair cursor color.
@type{ReturnType<typeof applyTheme> | null}
theme
.bg);
this.Demo.renderHUD(): void
The HUD, built from shared UI kit groups: the full-width title strip at the top (it doubles as the ceiling of the physics box - see HUD_HEIGHT), plus a compact corner panel with one pip per pointer slot. A pip lights up while that slot (M = mouse, T1-T3 = touch fingers) is grabbing a ball.
renderHUD
();
this.Demo.renderBalls(): void
Draws each ball as a filled disc. Highlights the ball under the mouse (for hover feedback) and outlines any ball currently grabbed.
renderBalls
();
this.Demo.renderCursors(): void
Small crosshair at every active pointer position so users can see where each finger / mouse currently is.
renderCursors
();
} /** * Attempts to grab a ball whose center is under this slot's pointer. * Skips if no live pointer, or pointer is inside the HUD strip, or if * this slot is already holding a ball. */ Demo.tryGrab(slot: any): void
Attempts to grab a ball whose center is under this slot's pointer. Skips if no live pointer, or pointer is inside the HUD strip, or if this slot is already holding a ball.
tryGrab
(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;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.isPointerActive: (pointerIndex?: number) => boolean
Reports whether the given pointer slot has a live pointer. For slot 0 (mouse) this is true while the mouse is hovering inside the canvas; cleared on `pointerleave`. For slots 1-3 (touch / pen) this is true while the contact is down.
@since1.1.1@parampointerIndex - Pointer slot (defaults to 0 = mouse).@returns`true` while the slot has live position data.
isPointerActive
(slot: anyslot)) {
return; } const const pos: Vector2ipos =
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.pointerPos: (pointerIndex?: number) => Vector2i
Returns the position of the pointer in the given slot, in display coordinates. Slot 0 is the mouse; slots 1 through 3 are touch / pen contacts assigned in arrival order. Returns `Vector2i.zero()` when the engine has not been initialized, the slot index is out of `[0, 3]`, or the slot has no live pointer.
@since1.0.3@parampointerIndex - Pointer slot (defaults to 0 = mouse).@returnsPointer position in display coordinates.
pointerPos
(slot: anyslot);
// Don't try to grab through the HUD - the press at HUD level is a // miss-click rather than a deliberate grab. if (const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
< const HUD_HEIGHT: 22HUD_HEIGHT) {
return; } // If this slot is already holding something, leave it alone. for (const
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
of this.
Demo.balls: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
Active balls. Created in init(). prevX/prevY remember where each ball was at the START of the most recent update() tick, before physics moved it. render() blends between prevX/prevY and x/y using BT.renderAlpha so the ball glides smoothly across render frames instead of only moving once per physics tick - see "Interpolating render state with renderAlpha" in the engine's docs/api-game-loop.md.
@type{Array<{x: number, y: number, prevX: number, prevY: number, vx: number, vy: number, color: number, grabbedBy: number}>}
balls
) {
if (
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.grabbedBy === slot: anyslot) {
return; } } // Find the topmost ball whose disc covers the pointer. Iterating in // reverse so the visually-topmost ball wins when balls overlap. for (let let i: numberi = this.
Demo.balls: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
Active balls. Created in init(). prevX/prevY remember where each ball was at the START of the most recent update() tick, before physics moved it. render() blends between prevX/prevY and x/y using BT.renderAlpha so the ball glides smoothly across render frames instead of only moving once per physics tick - see "Interpolating render state with renderAlpha" in the engine's docs/api-game-loop.md.
@type{Array<{x: number, y: number, prevX: number, prevY: number, vx: number, vy: number, color: number, grabbedBy: number}>}
balls
.length - 1; let i: numberi >= 0; let i: numberi--) {
const
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
= this.
Demo.balls: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
Active balls. Created in init(). prevX/prevY remember where each ball was at the START of the most recent update() tick, before physics moved it. render() blends between prevX/prevY and x/y using BT.renderAlpha so the ball glides smoothly across render frames instead of only moving once per physics tick - see "Interpolating render state with renderAlpha" in the engine's docs/api-game-loop.md.
@type{Array<{x: number, y: number, prevX: number, prevY: number, vx: number, vy: number, color: number, grabbedBy: number}>}
balls
[let i: numberi];
if (
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.grabbedBy !== -1) {
continue; } const const dx: numberdx =
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.x - const pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
;
const const dy: numberdy =
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.y - const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
;
if (const dx: numberdx * const dx: numberdx + const dy: numberdy * const dy: numberdy <= const BALL_RADIUS: 10BALL_RADIUS * const BALL_RADIUS: 10BALL_RADIUS) {
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.grabbedBy = slot: anyslot;
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.vx = 0;
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.vy = 0;
return; } } } /** * Releases whatever ball this slot is holding, launching it with the * pointer's release-frame velocity (scaled and clamped to MAX_THROW_SPEED). */ Demo.tryThrow(slot: any): void
Releases whatever ball this slot is holding, launching it with the pointer's release-frame velocity (scaled and clamped to MAX_THROW_SPEED).
tryThrow
(slot: anyslot) {
// Mark this throw event on the overlay timing chart so you can see exactly // when a throw happened and which pointer slot caused it. The template string // inserts the slot number so repeated throws from different fingers are distinct.
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
.assignTag: (label?: string) => void
Places a labeled marker on the overlay timing chart at the current tick. Requires `isOverlayTimingChartEnabled: true` in `configure()`. Tags scroll with the chart history and are pruned when they leave the visible window. Empty labels become `"Untitled"`. Chart width resets add an automatic `"Start"` tag.
@since1.1.0@paramlabel - Short event name (for example `'Round start'`).
assignTag
(`Throw slot ${slot: anyslot}`);
for (const
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
of this.
Demo.balls: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
Active balls. Created in init(). prevX/prevY remember where each ball was at the START of the most recent update() tick, before physics moved it. render() blends between prevX/prevY and x/y using BT.renderAlpha so the ball glides smoothly across render frames instead of only moving once per physics tick - see "Interpolating render state with renderAlpha" in the engine's docs/api-game-loop.md.
@type{Array<{x: number, y: number, prevX: number, prevY: number, vx: number, vy: number, color: number, grabbedBy: number}>}
balls
) {
if (
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.grabbedBy !== slot: anyslot) {
continue; } // BT.pointerDelta is the movement during the most recent tick, // which is approximately velocity in px/tick. We can use it // directly as launch velocity (with a small scale factor). 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) => 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
(slot: anyslot);
let let vx: numbervx = const delta: Vector2idelta.Vector2i.x: number
Horizontal component (defaults to 0).
x
* const THROW_SCALE: 1.4THROW_SCALE;
let let vy: numbervy = const delta: Vector2idelta.Vector2i.y: number
Vertical component (defaults to 0).
y
* const THROW_SCALE: 1.4THROW_SCALE;
// Clamp the launch speed so a frantic flick can't escape the box. const const speed: anyspeed = Math.hypot(let vx: numbervx, let vy: numbervy); if (const speed: anyspeed > const MAX_THROW_SPEED: 16MAX_THROW_SPEED) { const const k: numberk = const MAX_THROW_SPEED: 16MAX_THROW_SPEED / const speed: anyspeed; let vx: numbervx *= const k: numberk; let vy: numbervy *= const k: numberk; } this.Demo.playWhoosh(speed: number): void
Plays the flick whoosh, using throw speed to control pitch (faster flick = higher, more urgent pitch) and volume (faster flick = louder). BT.soundPlay's `pitch` option is a playback-rate multiplier, the same trick Audio Basics uses for its blip sound.
@paramspeed - Pre-clamp launch speed in px/tick, from Math.hypot(vx, vy).
playWhoosh
(const speed: anyspeed);
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.vx = let vx: numbervx;
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.vy = let vy: numbervy;
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.grabbedBy = -1;
return; } } /** * Plays the flick whoosh, using throw speed to control pitch (faster flick = higher, * more urgent pitch) and volume (faster flick = louder). BT.soundPlay's `pitch` option * is a playback-rate multiplier, the same trick Audio Basics uses for its blip sound. * * @param {number} speed - Pre-clamp launch speed in px/tick, from Math.hypot(vx, vy). */ Demo.playWhoosh(speed: number): void
Plays the flick whoosh, using throw speed to control pitch (faster flick = higher, more urgent pitch) and volume (faster flick = louder). BT.soundPlay's `pitch` option is a playback-rate multiplier, the same trick Audio Basics uses for its blip sound.
@paramspeed - Pre-clamp launch speed in px/tick, from Math.hypot(vx, vy).
playWhoosh
(speed: number
- Pre-clamp launch speed in px/tick, from Math.hypot(vx, vy).
@paramspeed - Pre-clamp launch speed in px/tick, from Math.hypot(vx, vy).
speed
) {
const const speedFraction: numberspeedFraction = function clamp(value: number, min: number, max: number): number
Keeps a number from going below `min` or above `max`.
@paramvalue@parammin@parammax@returns
clamp
(speed: number
- Pre-clamp launch speed in px/tick, from Math.hypot(vx, vy).
@paramspeed - Pre-clamp launch speed in px/tick, from Math.hypot(vx, vy).
speed
/ const MAX_THROW_SPEED: 16MAX_THROW_SPEED, 0, 1);
const const pitch: numberpitch = const WHOOSH_PITCH_MIN: 0.85WHOOSH_PITCH_MIN + const speedFraction: numberspeedFraction * (const WHOOSH_PITCH_MAX: 1.6WHOOSH_PITCH_MAX - const WHOOSH_PITCH_MIN: 0.85WHOOSH_PITCH_MIN); const const volume: numbervolume = const WHOOSH_VOLUME_MIN: 0.35WHOOSH_VOLUME_MIN + const speedFraction: numberspeedFraction * (const WHOOSH_VOLUME_MAX: 1WHOOSH_VOLUME_MAX - const WHOOSH_VOLUME_MIN: 0.35WHOOSH_VOLUME_MIN);
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
.soundPlay: (clip: AudioClip, options?: SoundPlayOptions) => SoundRef
Plays a loaded audio clip through the SFX voice pool. Returns an inert {@link SoundRef } without allocating a voice when the clip hasn't finished loading yet (or was already unloaded), when the pool has no free or stealable voice at this priority, or before the engine has unlocked audio playback.
@since1.3.0@paramclip - Loaded audio clip to play.@paramoptions - Playback options.@returnsA handle identifying the new voice; pass it to {@link BT.soundStop} and the other per-sound controls. Safe to use even when playback was silently dropped - every accessor on an inert handle is a no-op.
soundPlay
(this.Demo.whooshClip: AudioClip | null
@type{AudioClip | null} Whoosh sound played when a ball is thrown.
whooshClip
, { VoicePlayOptions.pitch?: number | undefined
Initial `playbackRate`. Defaults to `1`.
pitch
, VoicePlayOptions.volume?: number | undefined
Initial gain in `[0, 1]` (unclamped). Defaults to `1`.
volume
});
} /** * Snaps a held ball to its owning pointer's position. If the pointer * went invalid mid-grab (pointer left the canvas, touch canceled) we * release the ball gently with zero velocity. */ Demo.updateHeldBall(ball: any): void
Snaps a held ball to its owning pointer's position. If the pointer went invalid mid-grab (pointer left the canvas, touch canceled) we release the ball gently with zero velocity.
updateHeldBall
(ball: anyball) {
const const slot: anyslot = ball: anyball.grabbedBy; if (!
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.isPointerActive: (pointerIndex?: number) => boolean
Reports whether the given pointer slot has a live pointer. For slot 0 (mouse) this is true while the mouse is hovering inside the canvas; cleared on `pointerleave`. For slots 1-3 (touch / pen) this is true while the contact is down.
@since1.1.1@parampointerIndex - Pointer slot (defaults to 0 = mouse).@returns`true` while the slot has live position data.
isPointerActive
(const slot: anyslot)) {
// Pointer disappeared - drop the ball where it is. ball: anyball.grabbedBy = -1; ball: anyball.vx = 0; ball: anyball.vy = 0; return; } const const pos: Vector2ipos =
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.pointerPos: (pointerIndex?: number) => Vector2i
Returns the position of the pointer in the given slot, in display coordinates. Slot 0 is the mouse; slots 1 through 3 are touch / pen contacts assigned in arrival order. Returns `Vector2i.zero()` when the engine has not been initialized, the slot index is out of `[0, 3]`, or the slot has no live pointer.
@since1.0.3@parampointerIndex - Pointer slot (defaults to 0 = mouse).@returnsPointer position in display coordinates.
pointerPos
(const slot: anyslot);
ball: anyball.x = const pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
;
ball: anyball.y = const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
;
} /** * Integrates one tick of physics for a free ball: gravity, air drag, * floor friction, and wall bounces with damping. */ Demo.updateFreeBall(ball: any): void
Integrates one tick of physics for a free ball: gravity, air drag, floor friction, and wall bounces with damping.
updateFreeBall
(ball: anyball) {
// Gravity pulls the ball down each tick. ball: anyball.vy += const GRAVITY: 0.35GRAVITY; // Gentle air drag on both axes. ball: anyball.vx *= const AIR_DRAG: 0.999AIR_DRAG; ball: anyball.vy *= const AIR_DRAG: 0.999AIR_DRAG; // Integrate position. ball: anyball.x += ball: anyball.vx; ball: anyball.y += ball: anyball.vy; // Bounce off walls. The HUD strip at the top acts as the ceiling. if (ball: anyball.x - const BALL_RADIUS: 10BALL_RADIUS < 0) { ball: anyball.x = const BALL_RADIUS: 10BALL_RADIUS; this.Demo.playThud(impactSpeed: number): void
Plays the wall/floor bounce thud, skipping bounces too gentle to notice and scaling volume with impact speed.
@paramimpactSpeed - Absolute velocity component (px/tick) at the moment of impact.
playThud
(Math.abs(ball: anyball.vx));
ball: anyball.vx = -ball: anyball.vx * const WALL_DAMPING: 0.78WALL_DAMPING; } else if (ball: anyball.x + const BALL_RADIUS: 10BALL_RADIUS > const DISPLAY_W: 320DISPLAY_W) { ball: anyball.x = const DISPLAY_W: 320DISPLAY_W - const BALL_RADIUS: 10BALL_RADIUS; this.Demo.playThud(impactSpeed: number): void
Plays the wall/floor bounce thud, skipping bounces too gentle to notice and scaling volume with impact speed.
@paramimpactSpeed - Absolute velocity component (px/tick) at the moment of impact.
playThud
(Math.abs(ball: anyball.vx));
ball: anyball.vx = -ball: anyball.vx * const WALL_DAMPING: 0.78WALL_DAMPING; } if (ball: anyball.y - const BALL_RADIUS: 10BALL_RADIUS < const HUD_HEIGHT: 22HUD_HEIGHT) { ball: anyball.y = const HUD_HEIGHT: 22HUD_HEIGHT + const BALL_RADIUS: 10BALL_RADIUS; this.Demo.playThud(impactSpeed: number): void
Plays the wall/floor bounce thud, skipping bounces too gentle to notice and scaling volume with impact speed.
@paramimpactSpeed - Absolute velocity component (px/tick) at the moment of impact.
playThud
(Math.abs(ball: anyball.vy));
ball: anyball.vy = -ball: anyball.vy * const WALL_DAMPING: 0.78WALL_DAMPING; } else if (ball: anyball.y + const BALL_RADIUS: 10BALL_RADIUS > const DISPLAY_H: 240DISPLAY_H) { ball: anyball.y = const DISPLAY_H: 240DISPLAY_H - const BALL_RADIUS: 10BALL_RADIUS; this.Demo.playThud(impactSpeed: number): void
Plays the wall/floor bounce thud, skipping bounces too gentle to notice and scaling volume with impact speed.
@paramimpactSpeed - Absolute velocity component (px/tick) at the moment of impact.
playThud
(Math.abs(ball: anyball.vy));
ball: anyball.vy = -ball: anyball.vy * const WALL_DAMPING: 0.78WALL_DAMPING; // Touching the floor: scrub a little horizontal speed so balls // come to rest after a few rolls. ball: anyball.vx *= const FLOOR_FRICTION: 0.985FLOOR_FRICTION; } // Snap negligible velocities to zero so balls truly stop instead of // creeping forever. if (Math.abs(ball: anyball.vx) < const MIN_SPEED: 0.05MIN_SPEED) { ball: anyball.vx = 0; } if (Math.abs(ball: anyball.vy) < const MIN_SPEED: 0.05MIN_SPEED) { ball: anyball.vy = 0; } } /** * Plays the wall/floor bounce thud, skipping bounces too gentle to notice and scaling * volume with impact speed. * * @param {number} impactSpeed - Absolute velocity component (px/tick) at the moment of impact. */ Demo.playThud(impactSpeed: number): void
Plays the wall/floor bounce thud, skipping bounces too gentle to notice and scaling volume with impact speed.
@paramimpactSpeed - Absolute velocity component (px/tick) at the moment of impact.
playThud
(impactSpeed: number
- Absolute velocity component (px/tick) at the moment of impact.
@paramimpactSpeed - Absolute velocity component (px/tick) at the moment of impact.
impactSpeed
) {
if (impactSpeed: number
- Absolute velocity component (px/tick) at the moment of impact.
@paramimpactSpeed - Absolute velocity component (px/tick) at the moment of impact.
impactSpeed
< const THUD_MIN_SPEED: 0.5THUD_MIN_SPEED) {
return; } const const volume: numbervolume = function clamp(value: number, min: number, max: number): number
Keeps a number from going below `min` or above `max`.
@paramvalue@parammin@parammax@returns
clamp
(impactSpeed: number
- Absolute velocity component (px/tick) at the moment of impact.
@paramimpactSpeed - Absolute velocity component (px/tick) at the moment of impact.
impactSpeed
/ const THUD_VOLUME_MAX_SPEED: 10THUD_VOLUME_MAX_SPEED, 0.2, 1);
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
.soundPlay: (clip: AudioClip, options?: SoundPlayOptions) => SoundRef
Plays a loaded audio clip through the SFX voice pool. Returns an inert {@link SoundRef } without allocating a voice when the clip hasn't finished loading yet (or was already unloaded), when the pool has no free or stealable voice at this priority, or before the engine has unlocked audio playback.
@since1.3.0@paramclip - Loaded audio clip to play.@paramoptions - Playback options.@returnsA handle identifying the new voice; pass it to {@link BT.soundStop} and the other per-sound controls. Safe to use even when playback was silently dropped - every accessor on an inert handle is a no-op.
soundPlay
(this.Demo.thudClip: AudioClip | null
@type{AudioClip | null} Thud sound played when a ball bounces off a wall or floor.
thudClip
, { VoicePlayOptions.volume?: number | undefined
Initial gain in `[0, 1]` (unclamped). Defaults to `1`.
volume
});
} /** * The HUD, built from shared UI kit groups: the full-width title strip at the top * (it doubles as the ceiling of the physics box - see HUD_HEIGHT), plus a compact * corner panel with one pip per pointer slot. A pip lights up while that slot * (M = mouse, T1-T3 = touch fingers) is grabbing a ball. */ Demo.renderHUD(): void
The HUD, built from shared UI kit groups: the full-width title strip at the top (it doubles as the ceiling of the physics box - see HUD_HEIGHT), plus a compact corner panel with one pip per pointer slot. A pip lights up while that slot (M = mouse, T1-T3 = touch fingers) is grabbing a ball.
renderHUD
() {
// The classic full-width 22 px title strip along the top edge. import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_BAR); import uiui.panel('Drag a ball, release to flick'); // Browsers refuse to play any sound until the page is clicked or a key // is pressed. This shared row shows the standard warm "enable sound" // prompt and disappears on its own the moment audio unlocks - which // here happens on the very first grab. import uiui.audioUnlockHint(); import uiui.end(); // Per-slot grab indicators, tucked into the top-right corner just below the // strip. Balls may fly behind this panel - that is fine, it is a readout, not // a wall. ui.pip() draws a small square that fills in while its state is true. const const labels: {}labels = ['M', 'T1', 'T2', 'T3']; import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_RIGHT, { y: numbery: const HUD_HEIGHT: 22HUD_HEIGHT + 6 }); import uiui.panel('Grabs'); for (let let slot: numberslot = 0; let slot: numberslot < 4; let slot: numberslot++) { // .some() asks: is there at least one ball whose grabbedBy equals this slot? const const grabbing: anygrabbing = this.
Demo.balls: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
Active balls. Created in init(). prevX/prevY remember where each ball was at the START of the most recent update() tick, before physics moved it. render() blends between prevX/prevY and x/y using BT.renderAlpha so the ball glides smoothly across render frames instead of only moving once per physics tick - see "Interpolating render state with renderAlpha" in the engine's docs/api-game-loop.md.
@type{Array<{x: number, y: number, prevX: number, prevY: number, vx: number, vy: number, color: number, grabbedBy: number}>}
balls
.some((b: anyb) => b: anyb.grabbedBy === let slot: numberslot);
import uiui.pip(const labels: {}labels[let slot: numberslot], const grabbing: anygrabbing); } import uiui.end(); } /** * Draws each ball as a filled disc. Highlights the ball under the mouse * (for hover feedback) and outlines any ball currently grabbed. */ Demo.renderBalls(): void
Draws each ball as a filled disc. Highlights the ball under the mouse (for hover feedback) and outlines any ball currently grabbed.
renderBalls
() {
// Determine which ball, if any, the mouse is currently hovering over. // We only do this for the mouse (slot 0) since touch slots only have // a position while in contact (which means they're already grabbing). const const mousePos: Vector2i | nullmousePos =
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.isPointerActive: (pointerIndex?: number) => boolean
Reports whether the given pointer slot has a live pointer. For slot 0 (mouse) this is true while the mouse is hovering inside the canvas; cleared on `pointerleave`. For slots 1-3 (touch / pen) this is true while the contact is down.
@since1.1.1@parampointerIndex - Pointer slot (defaults to 0 = mouse).@returns`true` while the slot has live position data.
isPointerActive
(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
.pointerPos: (pointerIndex?: number) => Vector2i
Returns the position of the pointer in the given slot, in display coordinates. Slot 0 is the mouse; slots 1 through 3 are touch / pen contacts assigned in arrival order. Returns `Vector2i.zero()` when the engine has not been initialized, the slot index is out of `[0, 3]`, or the slot has no live pointer.
@since1.0.3@parampointerIndex - Pointer slot (defaults to 0 = mouse).@returnsPointer position in display coordinates.
pointerPos
(0) : null;
let let hoverIndex: numberhoverIndex = -1; if (const mousePos: Vector2i | nullmousePos !== null && const mousePos: Vector2imousePos.Vector2i.y: number
Vertical component (defaults to 0).
y
>= const HUD_HEIGHT: 22HUD_HEIGHT) {
for (let let i: numberi = this.
Demo.balls: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
Active balls. Created in init(). prevX/prevY remember where each ball was at the START of the most recent update() tick, before physics moved it. render() blends between prevX/prevY and x/y using BT.renderAlpha so the ball glides smoothly across render frames instead of only moving once per physics tick - see "Interpolating render state with renderAlpha" in the engine's docs/api-game-loop.md.
@type{Array<{x: number, y: number, prevX: number, prevY: number, vx: number, vy: number, color: number, grabbedBy: number}>}
balls
.length - 1; let i: numberi >= 0; let i: numberi--) {
const
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
= this.
Demo.balls: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
Active balls. Created in init(). prevX/prevY remember where each ball was at the START of the most recent update() tick, before physics moved it. render() blends between prevX/prevY and x/y using BT.renderAlpha so the ball glides smoothly across render frames instead of only moving once per physics tick - see "Interpolating render state with renderAlpha" in the engine's docs/api-game-loop.md.
@type{Array<{x: number, y: number, prevX: number, prevY: number, vx: number, vy: number, color: number, grabbedBy: number}>}
balls
[let i: numberi];
const const dx: numberdx =
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.x - const mousePos: Vector2imousePos.Vector2i.x: number
Horizontal component (defaults to 0).
x
;
const const dy: numberdy =
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.y - const mousePos: Vector2imousePos.Vector2i.y: number
Vertical component (defaults to 0).
y
;
if (const dx: numberdx * const dx: numberdx + const dy: numberdy * const dy: numberdy <= const BALL_RADIUS: 10BALL_RADIUS * const BALL_RADIUS: 10BALL_RADIUS) { let hoverIndex: numberhoverIndex = let i: numberi; break; } } } for (let let i: numberi = 0; let i: numberi < this.
Demo.balls: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
Active balls. Created in init(). prevX/prevY remember where each ball was at the START of the most recent update() tick, before physics moved it. render() blends between prevX/prevY and x/y using BT.renderAlpha so the ball glides smoothly across render frames instead of only moving once per physics tick - see "Interpolating render state with renderAlpha" in the engine's docs/api-game-loop.md.
@type{Array<{x: number, y: number, prevX: number, prevY: number, vx: number, vy: number, color: number, grabbedBy: number}>}
balls
.length; let i: numberi++) {
const
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
= this.
Demo.balls: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
Active balls. Created in init(). prevX/prevY remember where each ball was at the START of the most recent update() tick, before physics moved it. render() blends between prevX/prevY and x/y using BT.renderAlpha so the ball glides smoothly across render frames instead of only moving once per physics tick - see "Interpolating render state with renderAlpha" in the engine's docs/api-game-loop.md.
@type{Array<{x: number, y: number, prevX: number, prevY: number, vx: number, vy: number, color: number, grabbedBy: number}>}
balls
[let i: numberi];
// BT.renderAlpha is a fraction from 0 (a physics tick just finished) to just // under 1 (the next tick is about to happen). Blending prevX/prevY toward // x/y by that fraction gives us the ball's position AT THIS EXACT RENDER // MOMENT, not just its position as of the last physics tick. Picture a movie: // physics ticks are the individual film frames, and render() is the projector // running faster than the film advances - renderAlpha tells the projector how // far to "tween" between the current frame and the next one so playback looks // smooth instead of jerky. const const drawX: anydrawX = Math.round(
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.prevX + (
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.x -
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.prevX) *
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
.renderAlpha: number
Fractional progress between the last completed fixed update and the next. Intended for interpolating render state between fixed-update steps.
@since1.3.0@returnsInterpolation alpha in `[0, 1)`.
renderAlpha
);
const const drawY: anydrawY = Math.round(
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.prevY + (
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.y -
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.prevY) *
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
.renderAlpha: number
Fractional progress between the last completed fixed update and the next. Intended for interpolating render state between fixed-update steps.
@since1.3.0@returnsInterpolation alpha in `[0, 1)`.
renderAlpha
);
this.Demo.drawDisc(cx: any, cy: any, r: any, color: any): void
Filled disc using a midpoint-style scan: for each row in the bounding box, draw a horizontal line of pixels covered by the circle equation. Cheaper than per-pixel testing and produces clean edges at this scale.
drawDisc
(const drawX: anydrawX, const drawY: anydrawY, const BALL_RADIUS: 10BALL_RADIUS,
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.color);
if (
const ball: Array<{
    x: number;
    y: number;
    prevX: number;
    prevY: number;
    vx: number;
    vy: number;
    color: number;
    grabbedBy: number;
}>
ball
.grabbedBy !== -1) {
// Outline grabbed balls so you can tell which slot owns each. this.Demo.drawCircle(cx: any, cy: any, r: any, color: any): void
Hollow circle outline (Bresenham midpoint algorithm). Used to ring the grabbed and hovered balls without splatting a full disc on top.
drawCircle
(const drawX: anydrawX, const drawY: anydrawY, const BALL_RADIUS: 10BALL_RADIUS + 1, const C_BALL_OUTLINE: 1C_BALL_OUTLINE);
} else if (let i: numberi === let hoverIndex: numberhoverIndex) { // Hover highlight: a thin amber ring on the topmost free ball // under the mouse cursor. this.Demo.drawCircle(cx: any, cy: any, r: any, color: any): void
Hollow circle outline (Bresenham midpoint algorithm). Used to ring the grabbed and hovered balls without splatting a full disc on top.
drawCircle
(const drawX: anydrawX, const drawY: anydrawY, const BALL_RADIUS: 10BALL_RADIUS + 1, const C_BALL_HIGHLIGHT: 2C_BALL_HIGHLIGHT);
} } } /** * Small crosshair at every active pointer position so users can see where * each finger / mouse currently is. */ Demo.renderCursors(): void
Small crosshair at every active pointer position so users can see where each finger / mouse currently is.
renderCursors
() {
for (let let slot: numberslot = 0; let slot: numberslot < 4; let slot: numberslot++) { if (!
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.isPointerActive: (pointerIndex?: number) => boolean
Reports whether the given pointer slot has a live pointer. For slot 0 (mouse) this is true while the mouse is hovering inside the canvas; cleared on `pointerleave`. For slots 1-3 (touch / pen) this is true while the contact is down.
@since1.1.1@parampointerIndex - Pointer slot (defaults to 0 = mouse).@returns`true` while the slot has live position data.
isPointerActive
(let slot: numberslot)) {
continue; } const const pos: Vector2ipos =
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.pointerPos: (pointerIndex?: number) => Vector2i
Returns the position of the pointer in the given slot, in display coordinates. Slot 0 is the mouse; slots 1 through 3 are touch / pen contacts assigned in arrival order. Returns `Vector2i.zero()` when the engine has not been initialized, the slot index is out of `[0, 3]`, or the slot has no live pointer.
@since1.0.3@parampointerIndex - Pointer slot (defaults to 0 = mouse).@returnsPointer position in display coordinates.
pointerPos
(let slot: numberslot);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawLine: (p0: Vector2i, p1: Vector2i, paletteIndex: number) => void
Draws a pixel-perfect line between two points. Uses rasterized line drawing without antialiasing.
@since0.1.0@paramp0 - Start position in display coordinates.@paramp1 - End position in display coordinates.@parampaletteIndex - Palette color index.
drawLine
(new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(const pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
- 4, const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
), new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(const pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
+ 4, const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
), this.Demo.theme: any
Palette slot map returned by applyTheme() - where the shared UI colors landed. Used for BT.clear(this.theme.bg) and the crosshair cursor color.
@type{ReturnType<typeof applyTheme> | null}
theme
.text);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawLine: (p0: Vector2i, p1: Vector2i, paletteIndex: number) => void
Draws a pixel-perfect line between two points. Uses rasterized line drawing without antialiasing.
@since0.1.0@paramp0 - Start position in display coordinates.@paramp1 - End position in display coordinates.@parampaletteIndex - Palette color index.
drawLine
(new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(const pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
, const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
- 4), new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(const pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
, const pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
+ 4), this.Demo.theme: any
Palette slot map returned by applyTheme() - where the shared UI colors landed. Used for BT.clear(this.theme.bg) and the crosshair cursor color.
@type{ReturnType<typeof applyTheme> | null}
theme
.text);
} } /** * Filled disc using a midpoint-style scan: for each row in the bounding * box, draw a horizontal line of pixels covered by the circle equation. * Cheaper than per-pixel testing and produces clean edges at this scale. */ Demo.drawDisc(cx: any, cy: any, r: any, color: any): void
Filled disc using a midpoint-style scan: for each row in the bounding box, draw a horizontal line of pixels covered by the circle equation. Cheaper than per-pixel testing and produces clean edges at this scale.
drawDisc
(cx: anycx, cy: anycy, r: anyr, color: anycolor) {
const const r2: numberr2 = r: anyr * r: anyr; for (let let dy: numberdy = -r: anyr; let dy: numberdy <= r: anyr; let dy: numberdy++) { // Width of the row at this y, derived from x² + y² <= r². const const dx: anydx = Math.floor(Math.sqrt(const r2: numberr2 - let dy: numberdy * let dy: numberdy));
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawLine: (p0: Vector2i, p1: Vector2i, paletteIndex: number) => void
Draws a pixel-perfect line between two points. Uses rasterized line drawing without antialiasing.
@since0.1.0@paramp0 - Start position in display coordinates.@paramp1 - End position in display coordinates.@parampaletteIndex - Palette color index.
drawLine
(new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(cx: anycx - const dx: anydx, cy: anycy + let dy: numberdy), 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
(cx: anycx + const dx: anydx, cy: anycy + let dy: numberdy), color: anycolor);
} } /** * Hollow circle outline (Bresenham midpoint algorithm). Used to ring the * grabbed and hovered balls without splatting a full disc on top. */ Demo.drawCircle(cx: any, cy: any, r: any, color: any): void
Hollow circle outline (Bresenham midpoint algorithm). Used to ring the grabbed and hovered balls without splatting a full disc on top.
drawCircle
(cx: anycx, cy: anycy, r: anyr, color: anycolor) {
let let x: anyx = r: anyr; let let y: numbery = 0; let let err: numbererr = 0; while (let x: anyx >= let y: numbery) {
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawPixel: (posOrX: Vector2i | number, yOrColor: number, maybeColor?: number) => void
Draws a single pixel. Accepts either: - `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index. - `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.
@since0.1.0@paramposOrX - Pixel position as `Vector2i`, or x coordinate when using numeric overload.@paramyOrColor - Palette index for vector overload, or y coordinate for numeric overload.@parammaybeColor - Palette index when using numeric overload.
drawPixel
(new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(cx: anycx + let x: anyx, cy: anycy + let y: numbery), color: anycolor);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawPixel: (posOrX: Vector2i | number, yOrColor: number, maybeColor?: number) => void
Draws a single pixel. Accepts either: - `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index. - `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.
@since0.1.0@paramposOrX - Pixel position as `Vector2i`, or x coordinate when using numeric overload.@paramyOrColor - Palette index for vector overload, or y coordinate for numeric overload.@parammaybeColor - Palette index when using numeric overload.
drawPixel
(new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(cx: anycx + let y: numbery, cy: anycy + let x: anyx), color: anycolor);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawPixel: (posOrX: Vector2i | number, yOrColor: number, maybeColor?: number) => void
Draws a single pixel. Accepts either: - `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index. - `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.
@since0.1.0@paramposOrX - Pixel position as `Vector2i`, or x coordinate when using numeric overload.@paramyOrColor - Palette index for vector overload, or y coordinate for numeric overload.@parammaybeColor - Palette index when using numeric overload.
drawPixel
(new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(cx: anycx - let y: numbery, cy: anycy + let x: anyx), color: anycolor);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawPixel: (posOrX: Vector2i | number, yOrColor: number, maybeColor?: number) => void
Draws a single pixel. Accepts either: - `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index. - `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.
@since0.1.0@paramposOrX - Pixel position as `Vector2i`, or x coordinate when using numeric overload.@paramyOrColor - Palette index for vector overload, or y coordinate for numeric overload.@parammaybeColor - Palette index when using numeric overload.
drawPixel
(new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(cx: anycx - let x: anyx, cy: anycy + let y: numbery), color: anycolor);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawPixel: (posOrX: Vector2i | number, yOrColor: number, maybeColor?: number) => void
Draws a single pixel. Accepts either: - `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index. - `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.
@since0.1.0@paramposOrX - Pixel position as `Vector2i`, or x coordinate when using numeric overload.@paramyOrColor - Palette index for vector overload, or y coordinate for numeric overload.@parammaybeColor - Palette index when using numeric overload.
drawPixel
(new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(cx: anycx - let x: anyx, cy: anycy - let y: numbery), color: anycolor);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawPixel: (posOrX: Vector2i | number, yOrColor: number, maybeColor?: number) => void
Draws a single pixel. Accepts either: - `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index. - `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.
@since0.1.0@paramposOrX - Pixel position as `Vector2i`, or x coordinate when using numeric overload.@paramyOrColor - Palette index for vector overload, or y coordinate for numeric overload.@parammaybeColor - Palette index when using numeric overload.
drawPixel
(new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(cx: anycx - let y: numbery, cy: anycy - let x: anyx), color: anycolor);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawPixel: (posOrX: Vector2i | number, yOrColor: number, maybeColor?: number) => void
Draws a single pixel. Accepts either: - `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index. - `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.
@since0.1.0@paramposOrX - Pixel position as `Vector2i`, or x coordinate when using numeric overload.@paramyOrColor - Palette index for vector overload, or y coordinate for numeric overload.@parammaybeColor - Palette index when using numeric overload.
drawPixel
(new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(cx: anycx + let y: numbery, cy: anycy - let x: anyx), color: anycolor);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawPixel: (posOrX: Vector2i | number, yOrColor: number, maybeColor?: number) => void
Draws a single pixel. Accepts either: - `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index. - `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.
@since0.1.0@paramposOrX - Pixel position as `Vector2i`, or x coordinate when using numeric overload.@paramyOrColor - Palette index for vector overload, or y coordinate for numeric overload.@parammaybeColor - Palette index when using numeric overload.
drawPixel
(new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(cx: anycx + let x: anyx, cy: anycy - let y: numbery), color: anycolor);
let y: numbery += 1; let err: numbererr += 1 + 2 * let y: numbery; if (2 * (let err: numbererr - let x: anyx) + 1 > 0) { let x: anyx -= 1; let err: numbererr += 1 - 2 * let x: anyx; } } } } function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>
One-liner bootstrap function for BLIT386 demos. Handles canvas retrieval and engine initialization. Backend selection (WebGPU or software fallback) is managed internally by BTAPI. This function provides a streamlined way to start a demo with sensible defaults while allowing customization through options.
@since0.2.0@changed1.4.0 Calling `bootstrap()` again while already initialized now routes to a hot swap (via {@link registerHotReload}) when a Vite HMR context is registered, or logs a double-bootstrap guard and returns `false` otherwise - previously it silently started a second, unstoppable `GameLoop`.@changed1.7.0 Exposes `BT` on `window.BT` after bootstrap finishes, gated by {@link BootstrapOptions.exposeGlobal} (default: {@link BT.isDevMode}).@paramDemoClass - Demo class constructor implementing `IBTDemo` (optional `configure()` for hardware settings).@paramoptions - Optional configuration for IDs and callbacks.@returns`true` when the demo boots successfully; otherwise `false`.@example// Simplest usage - uses default IDs. bootstrap(MyDemo);@example// With custom options. bootstrap(MyDemo, { canvasID: 'custom-canvas', containerID: 'custom-container', onSuccess: () => console.log('Demo started!'), onError: (err) => analytics.trackError(err), });@example// Await the result. const success = await bootstrap(MyDemo); if (success) { console.log('Demo is running'); }
bootstrap
(class Demo
Drag-and-flick physics demo. Each ball is a small object: { x, y, vx, vy, color, grabbedBy }. `grabbedBy` is -1 when free or a pointer slot index 0..3 when held. While held, physics integration is skipped and the ball is teleported to the pointer position each frame. On release we read `BT.pointerDelta(slot)` and convert it to the ball's launch velocity.
@implementsIBTDemo
Demo
);