/**
* 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 AudioClipDecoded 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.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.bootstrap, const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT, class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32, class Vector2iInteger 2D vector for pixel-perfect positioning.
Used for points, sizes, directions, and camera offsets throughout the engine.
The API includes both allocation-free `*To()` / `*InPlace()` variants and
convenience methods that return new vectors.Vector2i } from 'blit386';
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): numberKeeps a number from going below `min` or above `max`.clamp(value: numbervalue, min: numbermin, max: numbermax) {
return Math.max(min: numbermin, Math.min(max: numbermax, value: numbervalue));
}
/**
* 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 DemoDrag-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.Demo {
/** @type {Palette | null} */
Demo.palette: Palette | nullpalette = 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: anyPalette slot map returned by applyTheme() - where the shared UI colors landed.
Used for BT.clear(this.theme.bg) and the crosshair cursor color.theme = null;
/** @type {AudioClip | null} Whoosh sound played when a ball is thrown. */
Demo.whooshClip: AudioClip | nullwhooshClip = null;
/** @type {AudioClip | null} Thud sound played when a ball bounces off a wall or floor. */
Demo.thudClip: AudioClip | nullthudClip = 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.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.configure() {
return {
// Set the logical display size (how many pixels the demo draws at).
displaySize: Vector2idisplaySize: new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.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.init() {
this.Demo.whooshClip: AudioClip | nullwhooshClip = await class AudioClipDecoded 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.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.synth({
SynthParams.waveform: anyOscillator waveform shape.waveform: 'sine',
SynthParams.frequency: numberBase carrier frequency in Hz at the start of the clip (before any pitch sweep or vibrato).frequency: 600,
SynthParams.duration: numberTotal clip duration in seconds. Must be greater than 0 and no more than
{@link
MAX_SYNTH_DURATION_SECONDS
}
.duration: 0.35,
SynthParams.envelope?: SynthEnvelope | undefinedOptional attack/decay/sustain/release envelope. Defaults to a full ADSR envelope; see
{@link
SynthEnvelope
}
.envelope: { SynthEnvelope.attack?: number | undefinedTime in seconds to ramp from silence to full amplitude.
Defaults to
{@link
DEFAULT_ATTACK
}
.attack: 0.005, SynthEnvelope.decay?: number | undefinedTime in seconds to fall from full amplitude to the `sustain` level.
Defaults to
{@link
DEFAULT_DECAY
}
.decay: 0.1, SynthEnvelope.sustain?: number | undefinedGain level in [0, 1] held between the decay and release phases.
Defaults to
{@link
DEFAULT_SUSTAIN
}
.sustain: 0.2, SynthEnvelope.release?: number | undefinedTime 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 | undefinedOptional linear pitch sweep from `frequency` to a target frequency across the clip.pitchSweep: { SynthPitchSweep.toFrequency: numberFrequency in Hz the carrier linearly reaches by the end of the clip.toFrequency: 150 },
SynthParams.noiseMix?: number | undefinedFraction 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: numberSeed for the deterministic PRNG driving noise generation - identical seeds render identical output.seed: 2,
});
this.Demo.thudClip: AudioClip | nullthudClip = await class AudioClipDecoded 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.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.synth({
SynthParams.waveform: anyOscillator waveform shape.waveform: 'sine',
SynthParams.frequency: numberBase carrier frequency in Hz at the start of the clip (before any pitch sweep or vibrato).frequency: 90,
SynthParams.duration: numberTotal clip duration in seconds. Must be greater than 0 and no more than
{@link
MAX_SYNTH_DURATION_SECONDS
}
.duration: 0.12,
SynthParams.envelope?: SynthEnvelope | undefinedOptional attack/decay/sustain/release envelope. Defaults to a full ADSR envelope; see
{@link
SynthEnvelope
}
.envelope: { SynthEnvelope.attack?: number | undefinedTime in seconds to ramp from silence to full amplitude.
Defaults to
{@link
DEFAULT_ATTACK
}
.attack: 0.001, SynthEnvelope.decay?: number | undefinedTime in seconds to fall from full amplitude to the `sustain` level.
Defaults to
{@link
DEFAULT_DECAY
}
.decay: 0.06, SynthEnvelope.sustain?: number | undefinedGain level in [0, 1] held between the decay and release phases.
Defaults to
{@link
DEFAULT_SUSTAIN
}
.sustain: 0, SynthEnvelope.release?: number | undefinedTime 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: numberSeed for the deterministic PRNG driving noise generation - identical seeds render identical output.seed: 3,
});
this.Demo.palette: Palette | nullpalette = 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) => PaletteCreates a standalone palette instance.paletteCreate(256);
// Scene colors: the ball rings, the timing chart bars, and the balls themselves.
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_BALL_OUTLINE: 1C_BALL_OUTLINE, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(255, 255, 255));
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_BALL_HIGHLIGHT: 2C_BALL_HIGHLIGHT, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(255, 220, 120));
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_CHART_UPDATE: 3C_CHART_UPDATE, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(150, 160, 180));
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_CHART_RENDER: 4C_CHART_RENDER, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(255, 255, 255));
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const BALL_COLORS: {}BALL_COLORS[0], new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(255, 100, 110));
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const BALL_COLORS: {}BALL_COLORS[1], new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(120, 220, 130));
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const BALL_COLORS: {}BALL_COLORS[2], new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.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: anyPalette slot map returned by applyTheme() - where the shared UI colors landed.
Used for BT.clear(this.theme.bg) and the crosshair cursor color.theme = import applyThemeapplyTheme(this.Demo.palette: Palettepalette);
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.paletteSet: (palette: Palette) => voidStores the active engine palette.
Use this to swap the **entire palette** (e.g. switch between a day and night
theme). After this call the renderer uploads the new palette uniform on the
next frame.
**Palette-value swap (change what a slot looks like):** mutate the live
{@link
BT.palette
}
in place with `palette.set(slot, newColor)`. The renderer
uploads dirty slots on the next frame; no `paletteSet()` or
{@link
BT.spritesRefresh
}
needed.
**Palette-layout swap (same colors, different slot positions):** build a new
palette with the same colors at new indices, call `paletteSet()`, then call
{@link
BT.spritesRefresh
}
so every sprite sheet re-maps its original RGBA
pixels against the new slot layout.paletteSet(this.Demo.palette: Palettepalette);
// 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: () => voidHides 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.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.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(): voidPer-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) => booleanChecks 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()`.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: numberPrimary pointer button code.
Maps to mouse left for slot 0; touch contact for slots 1-3.BTN_POINTER_A, let slot: numberslot)) {
this.Demo.tryGrab(slot: any): voidAttempts 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) => booleanChecks 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()`.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: numberPrimary pointer button code.
Maps to mouse left for slot 0; touch contact for slots 1-3.BTN_POINTER_A, let slot: numberslot)) {
this.Demo.tryThrow(slot: any): voidReleases 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.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): voidSnaps 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): voidIntegrates 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(): voidPer-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) => voidSets the frame clear color using a palette index.
The renderer uses this color when clearing the full display at the start
of the next frame.clear(this.Demo.theme: anyPalette slot map returned by applyTheme() - where the shared UI colors landed.
Used for BT.clear(this.theme.bg) and the crosshair cursor color.theme.bg);
this.Demo.renderHUD(): voidThe 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(): voidDraws 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(): voidSmall 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): voidAttempts 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) => booleanReports whether the given pointer slot has a live pointer.
For slot 0 (mouse) this is true while the mouse is hovering inside the
canvas; cleared on `pointerleave`. For slots 1-3 (touch / pen) this is
true while the contact is down.isPointerActive(slot: 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) => Vector2iReturns 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.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: numberVertical 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.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.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.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: numberHorizontal 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: numberVertical 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): voidReleases 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) => voidPlaces 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.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.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) => Vector2iReturns the position delta `(pos - prevPos)` for a pointer slot since the previous frame.
Reflects movement accumulated between the previous and current frame.
Snapshotted and reset by the engine at `endFrame()`, which runs after
`update()` and `render()`. Returns `Vector2i.zero()` when the engine is
not initialized or `pointerIndex` is out of range.pointerDelta(slot: anyslot);
let let vx: numbervx = const delta: Vector2idelta.Vector2i.x: numberHorizontal component (defaults to 0).x * const THROW_SCALE: 1.4THROW_SCALE;
let let vy: numbervy = const delta: Vector2idelta.Vector2i.y: numberVertical 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): voidPlays 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.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): voidPlays 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.playWhoosh(speed: number- 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): numberKeeps a number from going below `min` or above `max`.clamp(speed: number- 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) => SoundRefPlays 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.soundPlay(this.Demo.whooshClip: AudioClip | nullwhooshClip, { VoicePlayOptions.pitch?: number | undefinedInitial `playbackRate`. Defaults to `1`.pitch, VoicePlayOptions.volume?: number | undefinedInitial 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): voidSnaps 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) => booleanReports whether the given pointer slot has a live pointer.
For slot 0 (mouse) this is true while the mouse is hovering inside the
canvas; cleared on `pointerleave`. For slots 1-3 (touch / pen) this is
true while the contact is down.isPointerActive(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) => Vector2iReturns 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.pointerPos(const slot: anyslot);
ball: anyball.x = const pos: Vector2ipos.Vector2i.x: numberHorizontal component (defaults to 0).x;
ball: anyball.y = const pos: Vector2ipos.Vector2i.y: numberVertical 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): voidIntegrates 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): voidPlays the wall/floor bounce thud, skipping bounces too gentle to notice and scaling
volume with impact speed.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): voidPlays the wall/floor bounce thud, skipping bounces too gentle to notice and scaling
volume with impact speed.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): voidPlays the wall/floor bounce thud, skipping bounces too gentle to notice and scaling
volume with impact speed.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): voidPlays the wall/floor bounce thud, skipping bounces too gentle to notice and scaling
volume with impact speed.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): voidPlays the wall/floor bounce thud, skipping bounces too gentle to notice and scaling
volume with impact speed.playThud(impactSpeed: number- Absolute velocity component (px/tick) at the moment of impact.impactSpeed) {
if (impactSpeed: number- 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): numberKeeps a number from going below `min` or above `max`.clamp(impactSpeed: number- 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) => SoundRefPlays 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.soundPlay(this.Demo.thudClip: AudioClip | nullthudClip, { VoicePlayOptions.volume?: number | undefinedInitial 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(): voidThe 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.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(): voidDraws 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) => booleanReports whether the given pointer slot has a live pointer.
For slot 0 (mouse) this is true while the mouse is hovering inside the
canvas; cleared on `pointerleave`. For slots 1-3 (touch / pen) this is
true while the contact is down.isPointerActive(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) => Vector2iReturns 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.pointerPos(0) : null;
let let hoverIndex: numberhoverIndex = -1;
if (const mousePos: Vector2i | nullmousePos !== null && const mousePos: Vector2imousePos.Vector2i.y: numberVertical 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.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.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: numberHorizontal 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: numberVertical 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.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.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: numberFractional progress between the last completed fixed update and the next.
Intended for interpolating render state between fixed-update steps.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: numberFractional progress between the last completed fixed update and the next.
Intended for interpolating render state between fixed-update steps.renderAlpha);
this.Demo.drawDisc(cx: any, cy: any, r: any, color: any): voidFilled 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): voidHollow 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): voidHollow 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(): voidSmall 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) => booleanReports whether the given pointer slot has a live pointer.
For slot 0 (mouse) this is true while the mouse is hovering inside the
canvas; cleared on `pointerleave`. For slots 1-3 (touch / pen) this is
true while the contact is down.isPointerActive(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) => Vector2iReturns 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.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) => voidDraws a pixel-perfect line between two points.
Uses rasterized line drawing without antialiasing.drawLine(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const pos: Vector2ipos.Vector2i.x: numberHorizontal component (defaults to 0).x - 4, const pos: Vector2ipos.Vector2i.y: numberVertical component (defaults to 0).y), new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const pos: Vector2ipos.Vector2i.x: numberHorizontal component (defaults to 0).x + 4, const pos: Vector2ipos.Vector2i.y: numberVertical component (defaults to 0).y), this.Demo.theme: anyPalette slot map returned by applyTheme() - where the shared UI colors landed.
Used for BT.clear(this.theme.bg) and the crosshair cursor color.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) => voidDraws a pixel-perfect line between two points.
Uses rasterized line drawing without antialiasing.drawLine(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const pos: Vector2ipos.Vector2i.x: numberHorizontal component (defaults to 0).x, const pos: Vector2ipos.Vector2i.y: numberVertical component (defaults to 0).y - 4), new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const pos: Vector2ipos.Vector2i.x: numberHorizontal component (defaults to 0).x, const pos: Vector2ipos.Vector2i.y: numberVertical component (defaults to 0).y + 4), this.Demo.theme: anyPalette slot map returned by applyTheme() - where the shared UI colors landed.
Used for BT.clear(this.theme.bg) and the crosshair cursor color.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): voidFilled 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) => voidDraws a pixel-perfect line between two points.
Uses rasterized line drawing without antialiasing.drawLine(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(cx: anycx - const dx: anydx, cy: anycy + let dy: numberdy), new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.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): voidHollow 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) => voidDraws a single pixel.
Accepts either:
- `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index.
- `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.drawPixel(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.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) => voidDraws a single pixel.
Accepts either:
- `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index.
- `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.drawPixel(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.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) => voidDraws a single pixel.
Accepts either:
- `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index.
- `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.drawPixel(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.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) => voidDraws a single pixel.
Accepts either:
- `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index.
- `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.drawPixel(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.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) => voidDraws a single pixel.
Accepts either:
- `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index.
- `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.drawPixel(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.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) => voidDraws a single pixel.
Accepts either:
- `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index.
- `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.drawPixel(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.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) => voidDraws a single pixel.
Accepts either:
- `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index.
- `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.drawPixel(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.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) => voidDraws a single pixel.
Accepts either:
- `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index.
- `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.drawPixel(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.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.bootstrap(class DemoDrag-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.Demo);