/*

Animation: tick-based timing for walk cycles, state machines, cooldowns, and particles.
@description Tick-based animation: cycle walk frames, drive a small state machine, and spawn particles that fade.

Part of the BLIT386 demo series.

Prerequisites:
  - Basics: https://demos.blit386.dev/basics
  - Primitives: https://demos.blit386.dev/primitives
  - Sprites: https://demos.blit386.dev/sprites

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

Guides:
  - Game Loop: https://blit386.dev/docs/guides/game-loop#why-motion-can-look-stale-or-jittery
  - Palette: https://blit386.dev/docs/guides/palette#layout-swap-vs-value-swap
  - Overlay: https://blit386.dev/docs/guides/overlay#custom-rows
  - Random: https://blit386.dev/docs/guides/random#deterministic-by-default

Reference:
  - Game Loop: https://blit386.dev/docs/api/game-loop#timer
  - Easing: https://blit386.dev/docs/api/easing#applyeasing
  - Core Types: https://blit386.dev/docs/api/core-types#color32
  - Overlay: https://blit386.dev/docs/api/overlay#custom-rows-optional

---

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

A small gray walker - an 8x8 body with one eye on two short legs, built on an offscreen
canvas and drawn at twice its size - stands on a dark green ground strip along the bottom
of a 320x240 screen, with a soft shadow under its feet. Every 2 seconds it changes state,
in a loop that repeats every 6 seconds:

  1. Idle - it stands still.
  2. Walking - it slides sideways 1 pixel per tick, legs cycling through three poses,
     turning around (and facing the other way) whenever it reaches x = 64 or x = 220.
  3. Jumping - it rises along a smooth arc up to 40 pixels high and lands again after one
     second, twice in a row, while its shadow stays on the ground and shrinks as it rises.

Two UI kit panels sit along the top edge. Timers, on the left, shows an ability cooldown
(a countdown and a meter), the ticks until the next particle spawn, and how many
particles are alive. State, on the right, lists the three states with a lit pip next to
the current one. On the ground strip, a short list names the concepts on show.

Every 45 ticks (three quarters of a second) a burst of 32 tiny sparks shoots up from the
walker's feet like a fountain, arcs over under gravity, and falls back to the ground. Each
burst has its own hue, tied to the moment it spawned. A spark glows for most of its
1.5-second life and then winks out quickly. The engine overlay gets a custom row with the
state and the tick count, and the timing chart is on.

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

- BT.ticks goes up once per update() call at a fixed rate (targetFPS), not once per screen
  refresh. All timing here compares BT.ticks with a stored start tick; at 60 ticks per
  second, one tick is 1/60 of a second.
- A state machine: AnimState names the three states, and update() switches between them
  on a 360-tick cycle with `tick % 360`.
- A cooldown timer: a tick count that counts down to zero, shown as seconds and as a
  ui.meter().
- Periodic events with the Timer class: new Timer(45), timer.fireIfElapsed(tick), and
  timer.remainingTicks(tick).
- Simple particle physics: each spark has a velocity, and gravity adds a little to its
  downward speed every tick, which bends its path into an arc.
- A jump arc from Math.sin(): progress from 0 to 1 becomes a smooth hill.
- Palette animation for particles: each particle owns a palette slot, update() writes its
  current color there with palette.set(), and render() just passes the slot number.
- applyEasing(t, 'ease-in') and Color32.fromHSL(hue, saturation, lightness) for the fade
  and the hue.
- Building a four-frame walk strip on an OffscreenCanvas and cutting frames out of it with
  sheet.tileRect().
- Drawing one sprite at twice its size, mirrored to face the way it walks, with the
  params form of BT.drawSprite(): { scale: 2, flags: BT.FLIP_H }.

HOW PARTICLE COLORS WORK
------------------------

Each particle gets its own palette slot (50-177, a rotating pool of 128) the moment it is
spawned. In update(), the color is computed - hue picked at spawn time, alpha from age -
and written into that slot with palette.set(). In render(), the particle is drawn with
the slot number alone; no Color32 objects are needed there. The Palette Animation demo
explores this idea in depth: https://demos.blit386.dev/palette-animation

TRY THIS
--------

- Press the backquote key (`) to open the engine overlay. The custom row shows the state
  on the left and the tick count on the right - watch the state flip every 120 ticks.
- Watch the cooldown: it starts at 2 seconds when a jump begins, turns from orange to
  green as it reaches zero, and the meter empties with it.

*/

import { function applyEasing(t: number, easing: EasingFunction): number
Applies an easing curve to a normalized time value.
@since1.0.3@changed1.5.0 Added sine, cubic, quartic, quintic, expo, circ, back, elastic, and bounce families.@paramt - Normalized time in [0, 1]. Values outside this range are not clamped.@parameasing - Easing curve to apply.@returnsEased value. Guaranteed to return 0 for `t = 0` and 1 for `t = 1`.
applyEasing
, function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>
One-liner bootstrap function for BLIT386 demos. Handles canvas retrieval and engine initialization. Backend selection (WebGPU or software fallback) is managed internally by BTAPI. This function provides a streamlined way to start a demo with sensible defaults while allowing customization through options.
@since0.2.0@changed1.4.0 Calling `bootstrap()` again while already initialized now routes to a hot swap (via {@link registerHotReload}) when a Vite HMR context is registered, or logs a double-bootstrap guard and returns `false` otherwise - previously it silently started a second, unstoppable `GameLoop`.@changed1.7.0 Exposes `BT` on `window.BT` after bootstrap finishes, gated by {@link BootstrapOptions.exposeGlobal} (default: {@link BT.isDevMode}).@paramDemoClass - Demo class constructor implementing `IBTDemo` (optional `configure()` for hardware settings).@paramoptions - Optional configuration for IDs and callbacks.@returns`true` when the demo boots successfully; otherwise `false`.@example// Simplest usage - uses default IDs. bootstrap(MyDemo);@example// With custom options. bootstrap(MyDemo, { canvasID: 'custom-canvas', containerID: 'custom-container', onSuccess: () => console.log('Demo started!'), onError: (err) => analytics.trackError(err), });@example// Await the result. const success = await bootstrap(MyDemo); if (success) { console.log('Demo is running'); }
bootstrap
,
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
, class Rect2i
Integer rectangle for pixel-perfect bounds and regions. Used throughout the engine for sprite regions, display-space bounds, and zero-allocation geometry helpers. Both convenience getters and allocation-free `*To()` helpers are provided so callers can choose between readability and hot-path efficiency.
@since0.1.0
Rect2i
,
type SpriteSheet = SpriteSheet
class SpriteSheet
Sprite-sheet wrapper around a loaded image asset. The class keeps the original image available for CPU-side inspection while lazily creating and caching a GPU texture for rendering. When possible, `load()` also pre-decodes the source into an `ImageBitmap` so texture uploads preserve pixel-art alpha and color values more reliably. After calling `indexize()`, the sheet stores palette indices rather than RGBA data. The GPU texture becomes an `r8uint` format uploaded via `writeTexture`. The original RGBA bytes are retained so `reindexize()` can re-convert without reloading the image.
@since0.1.0
SpriteSheet
, class Timer
Fixed-tick interval helper for {@link IBTDemo.update } loops. Counts engine ticks ( {@link BT.ticks } ), which advance once per fixed update at {@link HardwareSettings.targetFPS } , not once per {@link IBTDemo.render } frame. Convert ticks to seconds with `intervalTicks / BT.targetFPS`. Tracks a "last fired" tick and reports when a configured interval has elapsed. Useful for periodic events such as particle spawning, score ticks, or palette swaps.
@since1.0.3
Timer
, 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 canvasToImagecanvasToImage, import registerCanvasColorsregisterCanvasColors } from './shared/canvas-sprites.js'; import { import applyThemeapplyTheme, import uiui, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js'; /** @typedef {import('blit386').Palette} Palette */ /** @typedef {import('blit386').SpriteSheet} SpriteSheet */ // AnimState names the three states the walker can be in. Object.freeze prevents these // values from being changed by accident. const const AnimState: anyAnimState = Object.freeze({ type Idle: stringIdle: 'Idle', // standing still type Walking: stringWalking: 'Walking', // sliding across the screen type Jumping: stringJumping: 'Jumping', // following a jump arc }); // How many particles can be alive at once. Each gets its own palette slot. A burst lives // for two spawn intervals, so at most about 64 are alive - 128 slots leave plenty of // room. const const MAX_PARTICLES: 128MAX_PARTICLES = 128; // Every SPAWN_INTERVAL ticks, BURST_SIZE particles shoot up from the walker's feet. const const SPAWN_INTERVAL: 45SPAWN_INTERVAL = 45; const const BURST_SIZE: 32BURST_SIZE = 32; // How many ticks one particle lives (90 ticks = 1.5 seconds at 60 FPS). const const PARTICLE_LIFETIME: 90PARTICLE_LIFETIME = 90; // How much faster a particle falls each tick, in pixels per tick. Small numbers make // floaty sparks, big numbers make them drop like stones. const const GRAVITY: 0.06GRAVITY = 0.06; // Where in the palette particle colors are stored (slots 50-177). const const PARTICLE_SLOT_START: 50PARTICLE_SLOT_START = 50; // The states in the order the State panel lists them (and the order they play in). const const STATE_ORDER: {}STATE_ORDER = [const AnimState: anyAnimState.Idle, const AnimState: anyAnimState.Walking, const AnimState: anyAnimState.Jumping]; // Where the walker's colors start (slot 20 and up; the canvas has three colors). const const SPRITE_BASE: 20SPRITE_BASE = 20; // Walk animation: four 18x18 frames in one horizontal strip (idle + three walk poses). const const WALK_FRAME_W: 18WALK_FRAME_W = 18; const const WALK_FRAME_COUNT: 4WALK_FRAME_COUNT = 4; // The walker is drawn at twice its size, so each canvas pixel becomes a 2x2 block. const const WALKER_SCALE: 2WALKER_SCALE = 2; // The y coordinate of the ground's top edge. The walker's feet rest on this line. const const GROUND_Y: 172GROUND_Y = 172; // The walker turns around when its left edge reaches one of these x positions. The // range is centered: 64 pixels of room on the left, 64 on the right of the 36-pixel-wide // walker. const const WALK_MIN_X: 64WALK_MIN_X = 64; const const WALK_MAX_X: 220WALK_MAX_X = 220; // Scene color slots (low palette slots - text and panels use the shared UI theme // instead). const const C_GROUND: 1C_GROUND = 1; // (40, 60, 40) dark green ground strip const const C_SHADOW: 2C_SHADOW = 2; // (0, 0, 0, 100) semi-transparent shadow under the walker const const C_GROUND_EDGE: 3C_GROUND_EDGE = 3; // (72, 104, 60) lighter green line along the ground's top edge // Palette slots of the shared UI theme. applyTheme() in init() writes the twelve UI kit // colors into slots 240-251 (its default start slot). configure() runs before init(), so // the overlay styles below cannot read this.theme yet - these constants spell out where // each theme color will land once init() runs. const const UI_BG: 240UI_BG = 240; // deep navy screen background const const UI_HEADER: 246UI_HEADER = 246; // warm amber (render bars, chart tags) const const UI_ACCENT: 247UI_ACCENT = 247; // phosphor green (update bars) const const UI_WARM: 248UI_WARM = 248; // orange (chart warning and error frames) const const UI_INFO: 249UI_INFO = 249; // light blue (overlay row text) // Draws one frame of the walker onto the canvas: a body square with one eye, plus two // legs whose positions change with `frameIndex` (0 idle, 1 left foot forward, 2 mid // stride, 3 right foot forward). The eye sits on the right, so the walker faces right; // renderCharacter() mirrors it to face left. function function drawWalkFrame(ctx: any, frameIndex: any): voiddrawWalkFrame(ctx: anyctx, frameIndex: anyframeIndex) { const const ox: numberox = frameIndex: anyframeIndex * const WALK_FRAME_W: 18WALK_FRAME_W; const const bodyColor: "#b0b0c8"bodyColor = '#b0b0c8'; const const legColor: "#8080a0"legColor = '#8080a0'; const const eyeColor: "#1a1c2c"eyeColor = '#1a1c2c'; ctx: anyctx.fillStyle = const bodyColor: "#b0b0c8"bodyColor; ctx: anyctx.fillRect(const ox: numberox + 5, 4, 8, 8); ctx: anyctx.fillStyle = const eyeColor: "#1a1c2c"eyeColor; ctx: anyctx.fillRect(const ox: numberox + 10, 6, 2, 2); ctx: anyctx.fillStyle = const legColor: "#8080a0"legColor; if (frameIndex: anyframeIndex === 0) { // Idle: feet apart under the body, lined up with the body edges. ctx: anyctx.fillRect(const ox: numberox + 5, 12, 3, 4); ctx: anyctx.fillRect(const ox: numberox + 10, 12, 3, 4); } else if (frameIndex: anyframeIndex === 1) { // Left foot forward. ctx: anyctx.fillRect(const ox: numberox + 4, 12, 3, 4); ctx: anyctx.fillRect(const ox: numberox + 10, 13, 3, 3); } else if (frameIndex: anyframeIndex === 2) { // Mid stride: feet under the hips. ctx: anyctx.fillRect(const ox: numberox + 6, 12, 3, 4); ctx: anyctx.fillRect(const ox: numberox + 9, 12, 3, 4); } else { // Right foot forward. ctx: anyctx.fillRect(const ox: numberox + 5, 13, 3, 3); ctx: anyctx.fillRect(const ox: numberox + 11, 12, 3, 4); } } // Builds the walk strip: an offscreen canvas with all four frames side by side. Returns // the canvas and its 2D drawing context. Throws if the browser cannot give us a 2D // context. function
function buildWalkSheet(): {
    canvas: any;
    ctx: any;
}
buildWalkSheet
() {
const const sheetW: numbersheetW = const WALK_FRAME_W: 18WALK_FRAME_W * const WALK_FRAME_COUNT: 4WALK_FRAME_COUNT; const const sheetH: 18sheetH = const WALK_FRAME_W: 18WALK_FRAME_W; const const canvas: anycanvas = new OffscreenCanvas(const sheetW: numbersheetW, const sheetH: 18sheetH); const const ctx: anyctx = const canvas: anycanvas.getContext('2d'); if (!const ctx: anyctx) { throw new Error('Could not create 2D context for walk sheet'); } const ctx: anyctx.clearRect(0, 0, const sheetW: numbersheetW, const sheetH: 18sheetH); for (let let f: numberf = 0; let f: numberf < const WALK_FRAME_COUNT: 4WALK_FRAME_COUNT; let f: numberf++) { function drawWalkFrame(ctx: any, frameIndex: any): voiddrawWalkFrame(const ctx: anyctx, let f: numberf); } return { canvas: anycanvas, ctx: anyctx }; } // Tick-based animation timing and state management: a state machine, a cooldown, periodic // particle spawns, and a jump arc, all driven by BT.ticks. class class DemoDemo { // The palette holds all colors used in this demo. /** @type {Palette | null} */ Demo.palette: Palette | null
@type{Palette | null}
palette
= null;
// The walk strip built in init() on an offscreen canvas. /** @type {SpriteSheet | null} */ Demo.spriteSheet: SpriteSheet | null
@type{SpriteSheet | null}
spriteSheet
= null;
// One Rect2i per walk-strip frame (idle + three walk poses). Demo.walkFrames: {}walkFrames = []; // Which state the walker is in right now. Demo.animState: anyanimState = const AnimState: anyAnimState.Idle; // The walker's position on screen (top-left corner of its frame). Its feet end 16 // canvas rows below the frame's top, so at 2x the frame's top sits 32 pixels above // the ground line. Demo.charPos: Vector2icharPos = 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
(80, const GROUND_Y: 172GROUND_Y - 16 * const WALKER_SCALE: 2WALKER_SCALE);
// Counts down how many ticks the ability is still unavailable. Demo.abilityCooldownTicks: numberabilityCooldownTicks = 0; // Total duration of the cooldown (2 seconds at 60 FPS = 120 ticks). Demo.abilityCooldownDuration: numberabilityCooldownDuration = 120; // Fires every 45 ticks (0.75 seconds at 60 FPS) to spawn the next burst. Demo.spawnTimer: TimerspawnTimer = new new Timer(intervalTicks: number): Timer
Creates a timer that fires once per fixed-tick interval.
@paramintervalTicks - Number of ticks required between firings; must be a positive integer.
Timer
(const SPAWN_INTERVAL: 45SPAWN_INTERVAL);
// The active particles: each has a position (x, y), a velocity (vx, vy), a // spawnTick, a hue, and a paletteSlot. Position and velocity are fractional numbers // so slow sparks still move smoothly; they are rounded to whole pixels only when // drawn. Demo.particles: {}particles = []; // Counts spawned particles so the palette slots are handed out in a circle. Demo.nextParticleSlot: numbernextParticleSlot = 0; // When the current jump started (used to calculate the arc height). Demo.jumpStartTick: numberjumpStartTick = 0; // How many ticks one hop takes from launch to landing (1 second = 60 ticks). The // Jumping state lasts 2 seconds, so the walker hops twice. Demo.jumpDuration: numberjumpDuration = 60; // The walker's horizontal direction (+1 = right, -1 = left). Demo.walkDir: numberwalkDir = 1; // Where the current walk started; the leg pose is picked from the distance walked. Demo.walkStartX: numberwalkStartX = 80; // Reused every frame for BT.drawSprite(): the scale stays at 2, and flags flips to // BT.FLIP_H (a mirror image) while the walker faces left.
Demo.spriteParams: {
    scale: number;
    flags: number;
}
spriteParams
= { scale: numberscale: const WALKER_SCALE: 2WALKER_SCALE, flags: numberflags: 0 };
// Slot map for the shared UI kit theme, filled in init() by applyTheme(). theme.bg, // theme.text, and friends are palette indices for our own drawing. Demo.theme: nulltheme = null; // Reused every frame for the engine overlay status row (state + ticks). Demo.overlayRowData: {}overlayRowData = [{ leftText: stringleftText: 'State: Idle', rightText: stringrightText: 'Ticks: 0', textPaletteIndex: numbertextPaletteIndex: const UI_INFO: 249UI_INFO }]; // Tells the engine which palette slots to use for the overlay bars and the timing // chart.
Demo.configure(): {
    overlayStyle: {
        barPaletteIndex: number;
        textPaletteIndex: number;
        gapPaletteIndex: number;
    };
    isOverlayTimingChartEnabled: boolean;
    overlayTimingChartStyle: {
        updateBarPaletteIndex: number;
        renderBarPaletteIndex: number;
        warningPaletteIndex: number;
        errorPaletteIndex: number;
        tagPaletteIndex: number;
    };
}
configure
() {
return {
overlayStyle: {
    barPaletteIndex: number;
    textPaletteIndex: number;
    gapPaletteIndex: number;
}
overlayStyle
: {
barPaletteIndex: numberbarPaletteIndex: const UI_BG: 240UI_BG, textPaletteIndex: numbertextPaletteIndex: const UI_INFO: 249UI_INFO, gapPaletteIndex: numbergapPaletteIndex: const UI_BG: 240UI_BG, }, // Show the scrolling timing chart in the overlay so each frame's // update/render cost is visible. isOverlayTimingChartEnabled: booleanisOverlayTimingChartEnabled: true,
overlayTimingChartStyle: {
    updateBarPaletteIndex: number;
    renderBarPaletteIndex: number;
    warningPaletteIndex: number;
    errorPaletteIndex: number;
    tagPaletteIndex: number;
}
overlayTimingChartStyle
: {
// Theme green for update() bars, matching the "ready" cooldown color. updateBarPaletteIndex: numberupdateBarPaletteIndex: const UI_ACCENT: 247UI_ACCENT, // Theme amber for render() bars. renderBarPaletteIndex: numberrenderBarPaletteIndex: const UI_HEADER: 246UI_HEADER, // Theme orange flags frames that are close to the frame budget, and the // same orange marks frames that went over it. warningPaletteIndex: numberwarningPaletteIndex: const UI_WARM: 248UI_WARM, errorPaletteIndex: numbererrorPaletteIndex: const UI_WARM: 248UI_WARM, // Theme amber for milestone labels such as BT.assignTag() calls. tagPaletteIndex: numbertagPaletteIndex: const UI_HEADER: 246UI_HEADER, }, }; } // Sets up the palette and builds the walk strip. async Demo.init(): unknowninit() { console.log('[AnimationDemo] Initializing...'); // Create the palette and fill the scene colors (ground, its edge, shadow). this.Demo.palette: Palette | null
@type{Palette | null}
palette
=
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.paletteCreate: (size?: number) => Palette
Creates a standalone palette instance.
@since1.0.3@paramsize - Palette size. Defaults to 256 colors.@returnsNew mutable palette.
paletteCreate
(256);
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_GROUND: 1C_GROUND, 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
(40, 60, 40));
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_SHADOW: 2C_SHADOW, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(0, 0, 0, 100));
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_GROUND_EDGE: 3C_GROUND_EDGE, 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
(72, 104, 60));
// Install the shared UI theme: applyTheme() writes the twelve UI kit colors into // high palette slots (240-251), far above this demo's sprite colors (slots 20-21) // and particle slots (50-177), and returns a map of friendly names to those slots // (this.theme.bg, .text, ...). All on-screen text and the cooldown meter use // them. this.Demo.theme: nulltheme = import applyThemeapplyTheme(this.Demo.palette: Palette
@type{Palette | null}
palette
);
// Particle slots (50-177) start fully transparent (alpha 0) so they stay // invisible until update() writes real colors into them when particles spawn. for (let let i: numberi = 0; let i: numberi < const MAX_PARTICLES: 128MAX_PARTICLES; let i: numberi++) { 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 PARTICLE_SLOT_START: 50PARTICLE_SLOT_START + let i: numberi, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(0, 0, 0, 0));
} // Build a four-frame walk strip on an offscreen canvas (idle + three walk poses). try { const { const canvas: anycanvas, const ctx: anyctx } =
function buildWalkSheet(): {
    canvas: any;
    ctx: any;
}
buildWalkSheet
();
// Read the canvas colors into palette slots starting at SPRITE_BASE. import registerCanvasColorsregisterCanvasColors(this.Demo.palette: Palette
@type{Palette | null}
palette
, const ctx: anyctx, const canvas: anycanvas.width, const canvas: anycanvas.height, const SPRITE_BASE: 20SPRITE_BASE);
// Turn the canvas into an image the engine can use, then convert its pixels // to palette slot numbers. const const image: anyimage = await import canvasToImagecanvasToImage(const canvas: anycanvas); this.Demo.spriteSheet: SpriteSheet | null
@type{SpriteSheet | null}
spriteSheet
= new new SpriteSheet(image: HTMLImageElement | null, size?: Vector2i): SpriteSheet
Creates a sprite sheet from a loaded image. Use the static load() method for easier loading from URL.
@paramimage - Pre-loaded HTMLImageElement, or null for raw indexed data sheets.@paramsize - Explicit dimensions (required when image is null).
SpriteSheet
(const image: anyimage);
this.Demo.spriteSheet: SpriteSheet
@type{SpriteSheet | null}
spriteSheet
.SpriteSheet.indexize(palette: Palette): void
Converts the sprite sheet's RGBA pixels to palette indices. Each non-transparent pixel is looked up in the provided palette via exact color matching. Index 0 is always transparent. The resulting indices are stored internally; an `r8uint` GPU texture is created lazily on the next `getTexture()` call. The original RGBA data is retained so `reindexize()` can re-convert after a palette swap without reloading the image.
@parampalette - Active palette used for color-to-index mapping.@throwsIf any opaque pixel's color is not present in the palette.
indexize
(this.Demo.palette: Palette
@type{Palette | null}
palette
);
// The strip is WALK_FRAME_COUNT square cells; tileRect(f) cuts frame f out of // it. this.Demo.spriteSheet: SpriteSheet
@type{SpriteSheet | null}
spriteSheet
.SpriteSheet.tileSize: number | Vector2i | null
Gets the size of one tile in this sheet's grid, or `null` when the sheet has no grid. The grid is what lets `BT.drawTile(sheet, index, destPos)`, `BT.drawTile(sheet, col, row, destPos)`, and {@link tileRect } find a tile without you computing a `Rect2i`. Tiles are counted in whole cells from the top-left; a partial cell at the right or bottom edge is not a tile. The returned vector is frozen - assign a new size instead of mutating it. The grid survives a hot-reload image swap; bounds are checked against the sheet's current size on every lookup. Sets the size of one tile in this sheet's grid.
@since1.8.0@returnsThe tile size, or `null`.@since1.8.0@paramvalue - A number for square tiles, a `Vector2i` for width x height, or `null` to remove the grid.@throwsRangeError if the size is not a positive integer on both axes (the grid is left unchanged).
tileSize
= const WALK_FRAME_W: 18WALK_FRAME_W;
this.Demo.walkFrames: {}walkFrames = Array.from({ length: numberlength: const WALK_FRAME_COUNT: 4WALK_FRAME_COUNT }, (_: any_, f: anyf) => this.Demo.spriteSheet: SpriteSheet | null
@type{SpriteSheet | null}
spriteSheet
.SpriteSheet.tileRect(index: number): Rect2i (+1 overload)
Returns the source rectangle of one tile in this sheet's grid. Allocates a new `Rect2i` per call - build frame lists with it in `init()`. For per-frame drawing, `BT.drawTile` does the same lookup without allocating.
@since1.8.0@paramindex - Row-major tile index: `0` is top-left, counting across each row then down.@returnsNew source rectangle for the tile.@throwsError if the sheet has no {@link tileSize}.@throwsRangeError if the index is negative, fractional, or past the last whole tile.
tileRect
(f: anyf));
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.paletteSet: (palette: Palette) => void
Stores the active engine palette. Use this to swap the **entire palette** (e.g. switch between a day and night theme). After this call the renderer uploads the new palette uniform on the next frame. **Palette-value swap (change what a slot looks like):** mutate the live {@link BT.palette } in place with `palette.set(slot, newColor)`. The renderer uploads dirty slots on the next frame; no `paletteSet()` or {@link BT.spritesRefresh } needed. **Palette-layout swap (same colors, different slot positions):** build a new palette with the same colors at new indices, call `paletteSet()`, then call {@link BT.spritesRefresh } so every sprite sheet re-maps its original RGBA pixels against the new slot layout.
@since1.0.3@parampalette - Palette to make active.
paletteSet
(this.Demo.palette: Palette
@type{Palette | null}
palette
);
console.log(`[AnimationDemo] Built walk sheet: ${const canvas: anycanvas.width}x${const canvas: anycanvas.height}px, 4 frames`); } catch (function (local var) error: unknownerror) { console.error('[AnimationDemo] Failed to build walk sheet:', function (local var) error: unknownerror); return false; } console.log('[AnimationDemo] Initialization complete!'); return true; } // Runs at a fixed rate (60 times per second). Advances the state machine, counts down // the cooldown, spawns and ages particles, and writes each particle's current color // into its palette slot. Demo.update(): voidupdate() { const const tick: numbertick =
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
;
// Auto-cycle through the states every 2 seconds (120 ticks at 60 FPS). this.Demo.autoCycleStates(tick: any): voidautoCycleStates(const tick: numbertick); // Count down the cooldown. It never goes below zero. if (this.Demo.abilityCooldownTicks: numberabilityCooldownTicks > 0) { this.Demo.abilityCooldownTicks: numberabilityCooldownTicks--; } // Spawn a burst of particles every SPAWN_INTERVAL ticks. if (this.Demo.spawnTimer: TimerspawnTimer.Timer.fireIfElapsed(currentTick?: number): boolean
Returns true once per interval and advances the internal last-fired tick.
@paramcurrentTick - Tick to evaluate against; defaults to engine tick counter.@returnsTrue when at least `intervalTicks` have elapsed since the last fire/reset.
fireIfElapsed
(const tick: numbertick)) {
this.Demo.spawnBurst(): voidspawnBurst(); } // Move every particle: add its velocity to its position, then let gravity pull a // little harder on it. Adding to vy each tick is what bends a straight line into // an arc - the spark slows as it rises, stops at the top, and falls faster and // faster on the way down. for (const const p: anyp of this.Demo.particles: {}particles) { const p: anyp.x += const p: anyp.vx; const p: anyp.y += const p: anyp.vy; const p: anyp.vy += const GRAVITY: 0.06GRAVITY; } // Remove dead particles: too old, or fallen back down to the ground's edge (a 2x2 // spark at y = GROUND_Y - 1 would already cover the edge line). Array.filter // returns a new array with only the entries that pass the test. this.Demo.particles: {}particles = this.Demo.particles: {}particles.filter((p: anyp) => const tick: numbertick - p: anyp.spawnTick < const PARTICLE_LIFETIME: 90PARTICLE_LIFETIME && p: anyp.y < const GROUND_Y: 172GROUND_Y - 1); // Update each particle's palette slot with its current color. The hue is fixed at // spawn time; only the alpha changes as the particle ages. for (const const p: anyp of this.Demo.particles: {}particles) { const const age: numberage = const tick: numbertick - const p: anyp.spawnTick; // t goes from 0 (just born) to 1 (fully aged, about to disappear). Think of // it like a candle burning down: 0 is a fresh candle, 1 is gone. const const t: numbert = const age: numberage / const PARTICLE_LIFETIME: 90PARTICLE_LIFETIME; // applyEasing(t, 'ease-in') starts slow and accelerates toward the end. // Subtracting from 1 flips it: alpha stays high for most of the particle's // life, then drops quickly right before it disappears - like a real spark // that glows brightly, then winks out all at once instead of fading evenly. const const alpha: anyalpha = Math.floor(255 * (1 - function applyEasing(t: number, easing: EasingFunction): number
Applies an easing curve to a normalized time value.
@since1.0.3@changed1.5.0 Added sine, cubic, quartic, quintic, expo, circ, back, elastic, and bounce families.@paramt - Normalized time in [0, 1]. Values outside this range are not clamped.@parameasing - Easing curve to apply.@returnsEased value. Guaranteed to return 0 for `t = 0` and 1 for `t = 1`.
applyEasing
(const t: numbert, 'ease-in')));
// Compute the color and write it into this particle's reserved palette slot. const const color: Color32color = class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.fromHSL(h: number, s: number, l: number, a?: number): Color32
Creates a color from HSL values.
@paramh - Hue in degrees (0-360).@params - Saturation as percentage (0-100).@paraml - Lightness as percentage (0-100).@parama - Alpha channel (0-255, defaults to 255).@returnsNew color converted from HSL values.
fromHSL
(const p: anyp.hue, 100, 60);
this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const p: anyp.paletteSlot, 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
(const color: Color32color.Color32.r: number
Red channel (0-255).
r
, const color: Color32color.Color32.g: number
Green channel (0-255).
g
, const color: Color32color.Color32.b: number
Blue channel (0-255).
b
, const alpha: anyalpha));
} // Move the walker sideways while in the Walking state. this.Demo.updateRockPosition(): voidupdateRockPosition(); } // Runs once per screen refresh to draw the walker, particles, and UI. Notice: no // Color32 objects appear in draw calls - only palette slot numbers. Demo.render(): voidrender() { // Clear the whole screen with the shared UI 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;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.clear: (paletteIndex: number) => void
Sets the frame clear color using a palette index. The renderer uses this color when clearing the full display at the start of the next frame.
@since0.1.0@parampaletteIndex - Palette index for the full-screen clear pass.
clear
(this.Demo.theme: nulltheme.bg);
// Green ground strip the walker stands on, from GROUND_Y down to the bottom of // the screen, with a 2-pixel lighter line on top so the edge reads clearly.
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawRectFill: (rect: Rect2i, paletteIndex: number) => void
Draws a filled rectangle.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRectFill
(new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2i
Creates an integer rectangle, truncating all inputs toward zero.
@paramx - Left-edge X coordinate (defaults to 0).@paramy - Top-edge Y coordinate (defaults to 0).@paramwidth - Width in pixels (defaults to 0).@paramheight - Height in pixels (defaults to 0).
Rect2i
(0, const GROUND_Y: 172GROUND_Y, 320, 240 - const GROUND_Y: 172GROUND_Y), const C_GROUND: 1C_GROUND);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawRectFill: (rect: Rect2i, paletteIndex: number) => void
Draws a filled rectangle.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRectFill
(new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2i
Creates an integer rectangle, truncating all inputs toward zero.
@paramx - Left-edge X coordinate (defaults to 0).@paramy - Top-edge Y coordinate (defaults to 0).@paramwidth - Width in pixels (defaults to 0).@paramheight - Height in pixels (defaults to 0).
Rect2i
(0, const GROUND_Y: 172GROUND_Y, 320, 2), const C_GROUND_EDGE: 3C_GROUND_EDGE);
// Draw the walker with a shadow below it. this.Demo.renderCharacter(): voidrenderCharacter(); // The State panel (the overlay row also shows state + ticks). this.Demo.renderStateIndicator(): voidrenderStateIndicator(); // Draw any active particles. this.Demo.renderParticles(): voidrenderParticles(); // Draw the timer readouts and the concept summary (shared UI kit groups). this.Demo.renderUI(): voidrenderUI(); } // Status row in the engine overlay: animation state (left) and tick count (right). Demo.overlayRows(): {}overlayRows() { const const row: anyrow = this.Demo.overlayRowData: {}overlayRowData[0]; const row: anyrow.leftText = `State: ${this.Demo.animState: anyanimState}`; const row: anyrow.rightText = `Ticks: ${
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
}`;
return this.Demo.overlayRowData: {}overlayRowData; } // Automatically cycles through Idle -> Walking -> Jumping, 2 seconds each. The full // cycle is 6 seconds (360 ticks at 60 FPS). Demo.autoCycleStates(tick: any): voidautoCycleStates(tick: anytick) { // cyclePos goes 0-359, repeating: `%` gives the remainder after dividing by 360. const const cyclePos: numbercyclePos = tick: anytick % 360; if (const cyclePos: numbercyclePos < 120) { // First 2 seconds: Idle. The walker stands still. if (this.Demo.animState: anyanimState !== const AnimState: anyAnimState.Idle) { this.Demo.animState: anyanimState = const AnimState: anyAnimState.Idle; } } else if (const cyclePos: numbercyclePos < 240) { // Second 2 seconds: Walking. The walker slides sideways. if (this.Demo.animState: anyanimState !== const AnimState: anyAnimState.Walking) { this.Demo.animState: anyanimState = const AnimState: anyAnimState.Walking; this.Demo.walkStartX: numberwalkStartX = this.Demo.charPos: Vector2icharPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
;
this.Demo.walkDir: numberwalkDir = 1; } } else { // Last 2 seconds: Jumping. The walker follows a sine arc. if (this.Demo.animState: anyanimState !== const AnimState: anyAnimState.Jumping) { this.Demo.animState: anyanimState = const AnimState: anyAnimState.Jumping; this.Demo.jumpStartTick: numberjumpStartTick = tick: anytick; // Trigger the ability cooldown at the start of a jump. if (this.Demo.abilityCooldownTicks: numberabilityCooldownTicks === 0) { this.Demo.abilityCooldownTicks: numberabilityCooldownTicks = this.Demo.abilityCooldownDuration: numberabilityCooldownDuration; } } } } // Moves the walker based on the current state. Only Walking moves it sideways; the // jump arc is a vertical offset computed in renderCharacter(). Demo.updateRockPosition(): voidupdateRockPosition() { if (this.Demo.animState: anyanimState === const AnimState: anyAnimState.Walking) { // Move 1 pixel per tick; turn around near the screen edges. this.Demo.charPos: Vector2icharPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
+= this.Demo.walkDir: numberwalkDir;
if (this.Demo.charPos: Vector2icharPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
>= const WALK_MAX_X: 220WALK_MAX_X) {
this.Demo.walkDir: numberwalkDir = -1; } if (this.Demo.charPos: Vector2icharPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
<= const WALK_MIN_X: 64WALK_MIN_X) {
this.Demo.walkDir: numberwalkDir = 1; } } } // Draws the walker at the correct position, with a shadow below. While Walking, the // frame cycles through the three walk poses based on the distance from walkStartX. // While Jumping, the sprite moves up in an arc while the shadow stays on the ground. Demo.renderCharacter(): voidrenderCharacter() { let let srcRect: anysrcRect = this.Demo.walkFrames: {}walkFrames[0]; if (this.Demo.animState: anyanimState === const AnimState: anyAnimState.Walking) { // walkStartX is where the walk began; every 3 pixels, advance to the next of // the three walk poses (frames 1, 2, 3). const const steps: anysteps = Math.abs(this.Demo.charPos: Vector2icharPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
- this.Demo.walkStartX: numberwalkStartX);
const const walkFrame: numberwalkFrame = 1 + (Math.floor(const steps: anysteps / 3) % 3); let srcRect: anysrcRect = this.Demo.walkFrames: {}walkFrames[const walkFrame: numberwalkFrame]; } // Calculate the vertical offset for the jump arc. let let yOffset: numberyOffset = 0; if (this.Demo.animState: anyanimState === const AnimState: anyAnimState.Jumping) { // jumpProgress runs from 0 at launch to 1 at landing (and on to 2 for the // second hop). Math.sin(progress * PI) traces a hill from 0 up to 1 and back // to 0; Math.abs keeps the second hop pointing up too. Negative y is up on // screen. const const jumpProgress: numberjumpProgress = (
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
- this.Demo.jumpStartTick: numberjumpStartTick) / this.Demo.jumpDuration: numberjumpDuration;
let yOffset: numberyOffset = -Math.abs(Math.sin(const jumpProgress: numberjumpProgress * Math.PI) * 40); } const const drawPos: Vector2idrawPos = 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
(this.Demo.charPos: Vector2icharPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
, this.Demo.charPos: Vector2icharPos.Vector2i.y: number
Vertical component (defaults to 0).
y
+ Math.floor(let yOffset: numberyOffset));
// Face the way we are walking: the frames face right, so mirror them with // BT.FLIP_H while walkDir is -1. The walker keeps its last facing while it idles // and jumps. this.
Demo.spriteParams: {
    scale: number;
    flags: number;
}
spriteParams
.flags: numberflags = this.Demo.walkDir: numberwalkDir < 0 ?
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.type FLIP_H: number
Horizontal flip flag for sprite rendering. Pass in {@link SpriteDrawParams.flags } ; combine with the other `FLIP_*` / `ROT_*` bits using `|`.
@since0.1.0@changed1.8.0 Accepted by `BT.drawSprite` through `SpriteDrawParams.flags`.
FLIP_H
: 0;
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawSprite: (spriteSheet: SpriteSheet, srcRect: Rect2i, destPos: Vector2i, paletteOffset?: number) => void (+1 overload)drawSprite(this.Demo.spriteSheet: SpriteSheet | null
@type{SpriteSheet | null}
spriteSheet
, let srcRect: anysrcRect, const drawPos: Vector2idrawPos, this.
Demo.spriteParams: {
    scale: number;
    flags: number;
}
spriteParams
);
// The shadow is a 1-pixel line on the ground's top edge, right under the feet, no // matter how high the jump is. It gets 2 pixels narrower on each side for every // 10 pixels of height, like a real shadow shrinking as the thing casting it // moves away from the ground. const const shrink: numbershrink = 2 * Math.floor(-let yOffset: numberyOffset / 10); const const shadowW: numbershadowW = 24 - 2 * const shrink: numbershrink;
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawRectFill: (rect: Rect2i, paletteIndex: number) => void
Draws a filled rectangle.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRectFill
(new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2i
Creates an integer rectangle, truncating all inputs toward zero.
@paramx - Left-edge X coordinate (defaults to 0).@paramy - Top-edge Y coordinate (defaults to 0).@paramwidth - Width in pixels (defaults to 0).@paramheight - Height in pixels (defaults to 0).
Rect2i
(this.Demo.charPos: Vector2icharPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
+ 6 + const shrink: numbershrink, const GROUND_Y: 172GROUND_Y, const shadowW: numbershadowW, 1), const C_SHADOW: 2C_SHADOW);
} // Draws the state machine as a kit panel in the top-right corner: all three states in // the order they play, with a lit pip (a small filled square) next to the current // one. Watching the lit pip step down the list makes the Idle -> Walking -> Jumping // loop obvious at a glance. Demo.renderStateIndicator(): voidrenderStateIndicator() { import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_RIGHT); import uiui.panel('State'); for (const const state: anystate of const STATE_ORDER: {}STATE_ORDER) { import uiui.pip(const state: anystate, const state: anystate === this.Demo.animState: anyanimState); } import uiui.end(); } // Spawns a burst of BURST_SIZE particles at the walker's feet, all flying upward at // slightly different speeds and angles, like a little fountain. Each particle gets // its own palette slot from the rotating pool. Demo.spawnBurst(): voidspawnBurst() { // The whole burst shares a base hue picked from the spawn tick, so each burst has // its own color. The % keeps the hue within 0-359 degrees of the color wheel. const const baseHue: numberbaseHue = (
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
* 3) % 360;
// The fountain starts at the middle of the walker's feet (it is 36 pixels wide, // so the middle is 18 pixels in from its left edge), just above the ground line. const const originX: numberoriginX = this.Demo.charPos: Vector2icharPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
+ 18;
const const originY: numberoriginY = const GROUND_Y: 172GROUND_Y - 2; for (let let i: numberi = 0; let i: numberi < const BURST_SIZE: 32BURST_SIZE; let i: numberi++) { // Rotate through the 128 slots in a circle: `%` wraps the count back to 0 // after 127, so the oldest particle's slot gets reused. const const slot: numberslot = const PARTICLE_SLOT_START: 50PARTICLE_SLOT_START + (this.Demo.nextParticleSlot: numbernextParticleSlot % const MAX_PARTICLES: 128MAX_PARTICLES); this.Demo.nextParticleSlot: numbernextParticleSlot++; // BT.random is the engine's shared random number generator. float(a, b) // returns a fractional number from a up to (but not including) b. vx spreads // the sparks left and right; vy is always negative, which on screen means // upward, so every spark starts by rising. int(0, 40) nudges each spark's hue // a little away from the burst's base hue, so the burst shimmers. this.Demo.particles: {}particles.push({ x: numberx: const originX: numberoriginX +
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.random: Random
Default engine PRNG (live reference - not a copy). Time-seeded when the engine singleton is created. Call {@link BT.randomSeed } for a reproducible run, or open the page with `?seed=N` - applied in `BT.init()` just before the demo's own `init()`, so a `randomSeed` call there still wins. Mutating the instance (for example `BT.random.int(10)`) advances the shared stream.
@since1.5.0@changed1.7.1 `?seed=N` URL parameter seeds the shared generator before the demo's `init()`.@returnsThe shared {@link Random} instance.@exampleBT.randomSeed(42); BT.random.int(150, 420); BT.random.pick(['a', 'b', 'c']);
random
.Random.float(min: number, max: number): number
Returns the next pseudo-random float in [min, max).
@parammin - Inclusive lower bound.@parammax - Exclusive upper bound.@returnsFloat in [min, max).@since1.5.0
float
(-4, 4),
y: numbery: const originY: numberoriginY, vx: numbervx:
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.random: Random
Default engine PRNG (live reference - not a copy). Time-seeded when the engine singleton is created. Call {@link BT.randomSeed } for a reproducible run, or open the page with `?seed=N` - applied in `BT.init()` just before the demo's own `init()`, so a `randomSeed` call there still wins. Mutating the instance (for example `BT.random.int(10)`) advances the shared stream.
@since1.5.0@changed1.7.1 `?seed=N` URL parameter seeds the shared generator before the demo's `init()`.@returnsThe shared {@link Random} instance.@exampleBT.randomSeed(42); BT.random.int(150, 420); BT.random.pick(['a', 'b', 'c']);
random
.Random.float(min: number, max: number): number
Returns the next pseudo-random float in [min, max).
@parammin - Inclusive lower bound.@parammax - Exclusive upper bound.@returnsFloat in [min, max).@since1.5.0
float
(-1.2, 1.2),
vy: numbervy:
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.random: Random
Default engine PRNG (live reference - not a copy). Time-seeded when the engine singleton is created. Call {@link BT.randomSeed } for a reproducible run, or open the page with `?seed=N` - applied in `BT.init()` just before the demo's own `init()`, so a `randomSeed` call there still wins. Mutating the instance (for example `BT.random.int(10)`) advances the shared stream.
@since1.5.0@changed1.7.1 `?seed=N` URL parameter seeds the shared generator before the demo's `init()`.@returnsThe shared {@link Random} instance.@exampleBT.randomSeed(42); BT.random.int(150, 420); BT.random.pick(['a', 'b', 'c']);
random
.Random.float(min: number, max: number): number
Returns the next pseudo-random float in [min, max).
@parammin - Inclusive lower bound.@parammax - Exclusive upper bound.@returnsFloat in [min, max).@since1.5.0
float
(-3, -1.2),
spawnTick: numberspawnTick:
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
,
hue: numberhue: (const baseHue: numberbaseHue +
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.random: Random
Default engine PRNG (live reference - not a copy). Time-seeded when the engine singleton is created. Call {@link BT.randomSeed } for a reproducible run, or open the page with `?seed=N` - applied in `BT.init()` just before the demo's own `init()`, so a `randomSeed` call there still wins. Mutating the instance (for example `BT.random.int(10)`) advances the shared stream.
@since1.5.0@changed1.7.1 `?seed=N` URL parameter seeds the shared generator before the demo's `init()`.@returnsThe shared {@link Random} instance.@exampleBT.randomSeed(42); BT.random.int(150, 420); BT.random.pick(['a', 'b', 'c']);
random
.Random.int(minOrMaxExclusive: number, maxExclusive?: number): number
Returns a pseudo-random integer in [0, maxExclusive) or [min, maxExclusive).
@paramminOrMaxExclusive - When alone, exclusive upper bound from 0; otherwise inclusive min.@parammaxExclusive - Exclusive upper bound when two arguments are passed.@returnsWhole number in the half-open range.@since1.5.0
int
(0, 40)) % 360,
paletteSlot: numberpaletteSlot: const slot: numberslot, // this particle "owns" this palette slot }); } } // Draws all active particles as tiny colored squares. The color for each particle // was already written in update() via palette.set(); here we just use the slot // number. Demo.renderParticles(): voidrenderParticles() { for (const const p: anyp of this.Demo.particles: {}particles) { // Draw a 2x2 square. Math.floor turns the fractional position into a whole // pixel, and the color is whatever update() put in p.paletteSlot.
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawRectFill: (rect: Rect2i, paletteIndex: number) => void
Draws a filled rectangle.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRectFill
(new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2i
Creates an integer rectangle, truncating all inputs toward zero.
@paramx - Left-edge X coordinate (defaults to 0).@paramy - Top-edge Y coordinate (defaults to 0).@paramwidth - Width in pixels (defaults to 0).@paramheight - Height in pixels (defaults to 0).
Rect2i
(Math.floor(const p: anyp.x), Math.floor(const p: anyp.y), 2, 2), const p: anyp.paletteSlot);
} } // Draws the timer readouts and the concept summary with the shared UI kit. State and // tick count are shown in overlayRows() above the bottom FPS bar. Demo.renderUI(): voidrenderUI() { // How much of the cooldown is still left, as a fraction from 0 (ready) to 1 // (full). const const cooldownPercent: anycooldownPercent = Math.max(0, this.Demo.abilityCooldownTicks: numberabilityCooldownTicks / this.Demo.abilityCooldownDuration: numberabilityCooldownDuration); // Ask the timer how many ticks are left until the next spawn event. const const ticksUntilSpawn: numberticksUntilSpawn = this.Demo.spawnTimer: TimerspawnTimer.Timer.remainingTicks(currentTick?: number): number
Returns ticks remaining until the timer will fire.
@paramcurrentTick - Tick to compare against; defaults to engine tick counter.@returnsRemaining ticks in `[0, intervalTicks]`.
remainingTicks
(
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
);
// Timer readouts: a kit panel in the top-left corner, level with the State panel // on the right. kvCols: 10 makes the key column of the ui.kv() rows wide enough // for "Particles" plus a space. import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { kvCols: numberkvCols: 10 }); import uiui.panel('Timers'); // Orange while counting down, green once the ability is ready again. Math.ceil // rounds up, so "1s" shows until the very last tick of the cooldown. const const cooldownSecs: anycooldownSecs = Math.ceil(this.Demo.abilityCooldownTicks: numberabilityCooldownTicks / 60); import uiui.label(`Cooldown: ${const cooldownSecs: anycooldownSecs}s`, { color: stringcolor: const cooldownPercent: anycooldownPercent > 0 ? 'warm' : 'accent' }); // A read-only kit meter shows the same fraction as a bar. import uiui.meter(null, const cooldownPercent: anycooldownPercent, { color: stringcolor: 'warm', width: numberwidth: 100 }); // Key-value rows: the key on the left in a dim color, the value lined up in a // column on the right. The next burst is under a second away, so it is counted // in ticks rather than seconds. padStart(2) adds a space in front of one-digit // numbers, so the text keeps the same width and the panel does not twitch. import uiui.kv('Spawn in', `${String(const ticksUntilSpawn: numberticksUntilSpawn).padStart(2)} ticks`); import uiui.kv('Particles', this.Demo.particles: {}particles.length); import uiui.end(); // Concept summary: a borderless kit group on the ground strip, a little below its // top edge so the walker's feet never touch the text. import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { y: numbery: const GROUND_Y: 172GROUND_Y + 10 }); import uiui.label('Tick-based timing', { color: stringcolor: 'header' }); import uiui.label('- Fixed-rate ticks (BT.ticks)', { color: stringcolor: 'dim' }); import uiui.label('- Cooldowns and Timer events', { color: stringcolor: 'dim' }); import uiui.label('- State machine transitions', { color: stringcolor: 'dim' }); import uiui.end(); } } // Hand the Demo class to BLIT386 to start the demo loop. function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>
One-liner bootstrap function for BLIT386 demos. Handles canvas retrieval and engine initialization. Backend selection (WebGPU or software fallback) is managed internally by BTAPI. This function provides a streamlined way to start a demo with sensible defaults while allowing customization through options.
@since0.2.0@changed1.4.0 Calling `bootstrap()` again while already initialized now routes to a hot swap (via {@link registerHotReload}) when a Vite HMR context is registered, or logs a double-bootstrap guard and returns `false` otherwise - previously it silently started a second, unstoppable `GameLoop`.@changed1.7.0 Exposes `BT` on `window.BT` after bootstrap finishes, gated by {@link BootstrapOptions.exposeGlobal} (default: {@link BT.isDevMode}).@paramDemoClass - Demo class constructor implementing `IBTDemo` (optional `configure()` for hardware settings).@paramoptions - Optional configuration for IDs and callbacks.@returns`true` when the demo boots successfully; otherwise `false`.@example// Simplest usage - uses default IDs. bootstrap(MyDemo);@example// With custom options. bootstrap(MyDemo, { canvasID: 'custom-canvas', containerID: 'custom-container', onSuccess: () => console.log('Demo started!'), onError: (err) => analytics.trackError(err), });@example// Await the result. const success = await bootstrap(MyDemo); if (success) { console.log('Demo is running'); }
bootstrap
(class DemoDemo);