// Palette Animation: change palette entries every tick for instant visual effects.
// @description Change palette entries every tick to animate a whole scene at once, without redrawing any pixels.
//
// Part of the BLIT386 series.
//
// Prerequisites:
//   Basics          https://demos.blit386.dev/basics
//   Primitives      https://demos.blit386.dev/primitives
//   Colors          https://demos.blit386.dev/colors
//   Palette Presets https://demos.blit386.dev/palette-presets
//     (guide: https://blit386.dev/docs/guides/palette-presets)
//
// Live version: https://demos.blit386.dev/palette-animation
// Guide: https://blit386.dev/docs/guides/palette#runtime-palette-effects
//
// WHAT IS PALETTE ANIMATION?
//
// Old game hardware (Super Nintendo, Sega Genesis, Commodore 64) had strict rules:
// each pixel only stored a small number - a "palette index" pointing to one color slot.
// To animate colors, programmers changed what color was IN the slot, not what was on screen.
//
// Imagine 16 buckets of paint, each numbered. A painting only records the bucket number
// for every spot, not the actual color. To change the sky from blue to red, you just
// repaint bucket 5. Every sky-colored spot changes instantly - without touching the painting!
//
// That trick is called "palette animation". Modern engines don't need it, but it's a
// beautiful technique to understand, and BLIT386 lets you do it the same way.
//
// THE KEY RULE:
//   render() writes palette indices (numbers) - never Color32 objects.
//   update() computes new Color32 values and stores them in palette slots.
//
// The section headings and panels are drawn with the shared UI kit (src/shared/ui.js);
// its theme colors live in high palette slots (240 and up), far away from every slot
// this demo animates.
//
// WHAT YOU WILL SEE (four panels):
//   1. Scrolling gradient bar  - 32 color slots hold a rainbow; the base hue rotates.
//   2. Fire column             - colors stack up from black to red to yellow to white.
//   3. Flashing health bar     - one slot alternates red / white every 8 ticks.
//   4. Cycling water strip     - three blue-green slots ripple in sequence.

import { function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>
One-liner bootstrap function for BLIT386 demos. Handles canvas retrieval and engine initialization. Backend selection (WebGPU or software fallback) is managed internally by BTAPI. This function provides a streamlined way to start a demo with sensible defaults while allowing customization through options.
@since0.2.0@changed1.4.0 Calling `bootstrap()` again while already initialized now routes to a hot swap (via {@link registerHotReload}) when a Vite HMR context is registered, or logs a double-bootstrap guard and returns `false` otherwise - previously it silently started a second, unstoppable `GameLoop`.@changed1.7.0 Exposes `BT` on `window.BT` after bootstrap finishes, gated by {@link BootstrapOptions.exposeGlobal} (default: {@link BT.isDevMode}).@paramDemoClass - Demo class constructor implementing `IBTDemo` (optional `configure()` for hardware settings).@paramoptions - Optional configuration for IDs and callbacks.@returns`true` when the demo boots successfully; otherwise `false`.@example// Simplest usage - uses default IDs. bootstrap(MyDemo);@example// With custom options. bootstrap(MyDemo, { canvasID: 'custom-canvas', containerID: 'custom-container', onSuccess: () => console.log('Demo started!'), onError: (err) => analytics.trackError(err), });@example// Await the result. const success = await bootstrap(MyDemo); if (success) { console.log('Demo is running'); }
bootstrap
,
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
, class 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
, class Vector2i
Integer 2D vector for pixel-perfect positioning. Used for points, sizes, directions, and camera offsets throughout the engine. The API includes both allocation-free `*To()` / `*InPlace()` variants and convenience methods that return new vectors.
@since0.1.0
Vector2i
} from 'blit386';
import { import applyThemeapplyTheme, import uiui, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js'; /** @typedef {import('blit386').IBTDemo} IBTDemo */ /** @typedef {import('blit386').HardwareSettings} HardwareSettings */ /** @typedef {import('blit386').Palette} Palette */ // Gradient bar // How many color slots the scrolling gradient uses. // More slots = smoother rainbow; fewer slots = more "chunky". const const GRAD_SLOTS: 32GRAD_SLOTS = 32; // Width of each gradient swatch rectangle in pixels. The swatches sit inside a UI kit // panel, so they share the panel's 308-pixel content width (320 minus the borders). const const GRAD_SWATCH_W: anyGRAD_SWATCH_W = Math.floor(308 / const GRAD_SLOTS: 32GRAD_SLOTS); // ~9 px each // Fire column // How many color slots make up the fire gradient. // The bottom of the fire is dark/black; the top is bright yellow-white. const const FIRE_SLOTS: 20FIRE_SLOTS = 20; // Height of each fire "band" rectangle in pixels. const const FIRE_BAND_H: 4FIRE_BAND_H = 4; // Width of the fire column in pixels. const const FIRE_COL_W: 60FIRE_COL_W = 60; // Water strip // Three slots cycle in sequence to create a ripple shimmer. const const WATER_SLOTS: 3WATER_SLOTS = 3; // Health bar // The health bar flashes every N ticks (8 ticks = about 0.13 seconds at 60 FPS). const const FLASH_PERIOD: 8FLASH_PERIOD = 8; // How low "health" must fall before the bar starts flashing. // (Simulated health: counts down from HEALTH_MAX to 0, then loops.) const const HEALTH_LOW: 30HEALTH_LOW = 30; const const HEALTH_MAX: 100HEALTH_MAX = 100; // How many ticks for one full health drain cycle. const const HEALTH_DRAIN_TICKS: 360HEALTH_DRAIN_TICKS = 360; // ~6 seconds to drain completely. // Palette slot constants - we group our palette like compartments in a paint box. // Each animated section owns a range of slots that it fills in update() every tick. // The shared UI kit adds its own 12 theme colors in slots 240..251 (see init()), // safely above everything listed here. // Slot 0: always transparent - reserved by the engine. // Engine overlay style slots. configure() runs BEFORE init() installs the shared UI // theme, so the overlay style cannot use theme slots - instead it points at these four // low slots, which init() fills by hand with fixed colors. const const C_OVERLAY_BAR: 2C_OVERLAY_BAR = 2; // Overlay bar background (very dark navy). const const C_OVERLAY_ERR: 3C_OVERLAY_ERR = 3; // Timing chart error bars (dark blue). const const C_OVERLAY_TEXT: 4C_OVERLAY_TEXT = 4; // Overlay text and chart update bars (golden yellow). const const C_OVERLAY_DIM: 5C_OVERLAY_DIM = 5; // Chart render/warning bars (cool gray-blue). // Gradient section: 32 slots, one per swatch column. // We update all 32 every tick to scroll the hue. const const C_GRAD_BASE: 10C_GRAD_BASE = 10; // Slots 10..41. // Fire section: 20 slots, one per horizontal band. // Slot 10+GRAD_SLOTS = 42 might overlap, so we start fire at 50. const const C_FIRE_BASE: 50C_FIRE_BASE = 50; // Slots 50..69. // Health bar: one slot. We toggle it between red and white. const const C_HEALTH_BAR: 80C_HEALTH_BAR = 80; // Slot 80. // Water strip: three slots that cycle. const const C_WATER_BASE: 90C_WATER_BASE = 90; // Slots 90..92. /** * Demonstrates the "palette animation" technique: change palette entries every tick * to create scrolling gradients, fire, flashing effects, and rippling water * all without touching the geometry drawn in render(). * * @implements {IBTDemo} */ class class Demo
Demonstrates the "palette animation" technique: change palette entries every tick to create scrolling gradients, fire, flashing effects, and rippling water all without touching the geometry drawn in render().
@implementsIBTDemo
Demo
{
// The single palette used for all drawing. /** @type {Palette | null} */ Demo.palette: Palette | null
@type{Palette | null}
palette
= null;
// Palette slot map for the shared UI kit theme, filled in init() by applyTheme(). // theme.bg, theme.border, theme.header, ... are palette indices ready for BT calls. Demo.theme: nulltheme = null; // Counts up by 1/60 every frame (in seconds). // Used to drive continuous animation in update(). Demo.animTime: numberanimTime = 0; // Simulated health value (0..100), drained over time. Demo.health: numberhealth = const HEALTH_MAX: 100HEALTH_MAX; // Which water slot is currently "brightest" (0, 1, or 2). Demo.waterPhase: numberwaterPhase = 0; // How many ticks since the water last advanced one step. Demo.waterTick: numberwaterTick = 0; /** * Wider canvas, overlay palette grid (64 columns), and overlay style colors from * the dedicated low slots (plus the animated health slot as a playful gap color). * * @returns {Partial<HardwareSettings>} */ Demo.configure(): Partial<HardwareSettings>
Wider canvas, overlay palette grid (64 columns), and overlay style colors from the dedicated low slots (plus the animated health slot as a playful gap color).
@returns
configure
() {
return { displaySize: Vector2idisplaySize: new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(520, 390),
maxCanvasSize: Vector2imaxCanvasSize: new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(520 * 2, 390 * 2),
isOverlayPaletteEnabled: booleanisOverlayPaletteEnabled: true, overlayPaletteColumns: numberoverlayPaletteColumns: 64,
overlayStyle: {
    barPaletteIndex: number;
    textPaletteIndex: number;
    gapPaletteIndex: number;
}
overlayStyle
: {
barPaletteIndex: numberbarPaletteIndex: const C_OVERLAY_BAR: 2C_OVERLAY_BAR, textPaletteIndex: numbertextPaletteIndex: const C_OVERLAY_TEXT: 4C_OVERLAY_TEXT, // The gap reuses the animated health slot, so it flashes with the demo. gapPaletteIndex: numbergapPaletteIndex: const C_HEALTH_BAR: 80C_HEALTH_BAR, }, isOverlayTimingChartEnabled: booleanisOverlayTimingChartEnabled: true,
overlayTimingChartStyle: {
    updateBarPaletteIndex: number;
    renderBarPaletteIndex: number;
    warningPaletteIndex: number;
    errorPaletteIndex: number;
    tagPaletteIndex: number;
}
overlayTimingChartStyle
: {
updateBarPaletteIndex: numberupdateBarPaletteIndex: const C_OVERLAY_TEXT: 4C_OVERLAY_TEXT, renderBarPaletteIndex: numberrenderBarPaletteIndex: const C_OVERLAY_DIM: 5C_OVERLAY_DIM, warningPaletteIndex: numberwarningPaletteIndex: const C_OVERLAY_DIM: 5C_OVERLAY_DIM, errorPaletteIndex: numbererrorPaletteIndex: const C_OVERLAY_ERR: 3C_OVERLAY_ERR, tagPaletteIndex: numbertagPaletteIndex: const C_OVERLAY_TEXT: 4C_OVERLAY_TEXT, }, }; } /** * Builds the palette with overlay slots, zeroed dynamic slots, and the shared UI * theme, then primes one update() so every animated slot has real colors before * the first render(). * * @returns {Promise<boolean>} */ async Demo.init(): Promise<boolean>
Builds the palette with overlay slots, zeroed dynamic slots, and the shared UI theme, then primes one update() so every animated slot has real colors before the first render().
@returns
init
() {
console.log('[PaletteAnimationDemo] Initializing...'); // Build the main palette // Think of this like setting up your paint box before you start painting. // Static entries (overlay colors, UI theme) go in now and never change. // Dynamic entries (gradient, fire, health, water) start as black and get // overwritten in update() every tick. this.Demo.palette: Palette | null
@type{Palette | null}
palette
=
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.paletteCreate: (size?: number) => Palette
Creates a standalone palette instance.
@since1.0.3@paramsize - Palette size. Defaults to 256 colors.@returnsNew mutable palette.
paletteCreate
(256);
// Colors for the engine overlay (the stats HUD). These match the shared UI // theme's look but live in low slots so configure() could reference them. 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_OVERLAY_BAR: 2C_OVERLAY_BAR, 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
(10, 12, 20)); // Very dark navy.
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_OVERLAY_ERR: 3C_OVERLAY_ERR, 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
(20, 24, 36)); // Dark blue.
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_OVERLAY_TEXT: 4C_OVERLAY_TEXT, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(255, 210, 80)); // Golden yellow.
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_OVERLAY_DIM: 5C_OVERLAY_DIM, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(120, 130, 160)); // Cool gray-blue.
// Initialize all dynamic slots to black (invisible for now). // They will be filled with real colors on the very first update() call. for (let let i: numberi = 0; let i: numberi < const GRAD_SLOTS: 32GRAD_SLOTS; 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 C_GRAD_BASE: 10C_GRAD_BASE + 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));
} for (let let i: numberi = 0; let i: numberi < const FIRE_SLOTS: 20FIRE_SLOTS; 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 C_FIRE_BASE: 50C_FIRE_BASE + 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));
} 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_HEALTH_BAR: 80C_HEALTH_BAR, 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
(220, 40, 40));
for (let let i: numberi = 0; let i: numberi < const WATER_SLOTS: 3WATER_SLOTS; 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 C_WATER_BASE: 90C_WATER_BASE + 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, 80, 160));
} // Install the shared UI kit colors (panel fills, borders, headings, dim text). // applyTheme() writes 12 colors into slots 240..251 - far above every range this // demo animates (gradient 10..41, fire 50..69, health 80, water 90..92), so the // palette animation can never overwrite the UI theme. The returned map remembers // where each color landed (this.theme.bg, this.theme.header, ...). this.Demo.theme: nulltheme = import applyThemeapplyTheme(this.Demo.palette: Palette
@type{Palette | null}
palette
);
// Activate palette // Tell the engine to use our palette from this point forward.
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.paletteSet: (palette: Palette) => void
Stores the active engine palette. Use this to swap the **entire palette** (e.g. switch between a day and night theme). After this call the renderer uploads the new palette uniform on the next frame. **Palette-value swap (change what a slot looks like):** mutate the live {@link BT.palette } in place with `palette.set(slot, newColor)`. The renderer uploads dirty slots on the next frame; no `paletteSet()` or {@link BT.spritesRefresh } needed. **Palette-layout swap (same colors, different slot positions):** build a new palette with the same colors at new indices, call `paletteSet()`, then call {@link BT.spritesRefresh } so every sprite sheet re-maps its original RGBA pixels against the new slot layout.
@since1.0.3@parampalette - Palette to make active.
paletteSet
(this.Demo.palette: Palette
@type{Palette | null}
palette
);
// Run one update cycle so all dynamic slots have real colors before the first render. // update() takes no arguments - this priming call still advances animTime by one tick, // same as every regular call from the engine's game loop. this.Demo.update(): void
Called 60 times per second. Computes new Color32 values for every dynamic slot. render() will never see Color32 - it only reads slot indices we set here.
update
();
console.log('[PaletteAnimationDemo] Initialized'); return true; } /** * Called 60 times per second. Computes new Color32 values for every dynamic slot. * render() will never see Color32 - it only reads slot indices we set here. */ Demo.update(): void
Called 60 times per second. Computes new Color32 values for every dynamic slot. render() will never see Color32 - it only reads slot indices we set here.
update
() {
// Advance the clock. animTime grows by 1/60 each frame. this.Demo.animTime: numberanimTime +=
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.deltaSeconds: number
Fixed-step seconds per update tick. Equivalent to `1 / BT.targetFPS` when `BT.targetFPS` is finite and positive. Falls back to `1 / 60` when target FPS is non-finite or non-positive.
@since1.0.4@returnsSeconds advanced by one fixed update tick.
deltaSeconds
;
// Advance health drain. // We simulate a health bar that empties over HEALTH_DRAIN_TICKS ticks, // then resets to full so the demo loops forever. 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;
    ... 106 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.health: numberhealth = const HEALTH_MAX: 100HEALTH_MAX - Math.floor((const tick: numbertick % const HEALTH_DRAIN_TICKS: 360HEALTH_DRAIN_TICKS) * (const HEALTH_MAX: 100HEALTH_MAX / const HEALTH_DRAIN_TICKS: 360HEALTH_DRAIN_TICKS)); // Panel 1: Scrolling gradient // We rotate a base hue forward every frame so the gradient appears to scroll. // animTime * 60 gives us degrees per second (one full rotation per ~6 seconds). this.Demo.updateGradient(): void
Scrolling gradient: rotates 32 hue slots so the rainbow appears to slide across. The "base hue" advances each frame; each slot gets a hue slightly ahead of the previous.
updateGradient
();
// Panel 2: Fire column // Each slot maps to a position along the fire column. // Lower slots = closer to the bottom = darker/cooler colors. this.Demo.updateFire(): void
Fire column: each slot represents a horizontal band of flame. The bottom is black/dark red; moving up transitions through orange to bright yellow-white. We shift the transition point over time so the flame flickers.
updateFire
();
// Panel 3: Flashing health bar // The slot alternates between red and white based on the tick count. this.Demo.updateHealthBar(tick: number): void
Health bar: toggles one slot between red and near-white every FLASH_PERIOD ticks. Only flashes when health is critically low.
@paramtick - Current tick count from BT.ticks.
updateHealthBar
(const tick: numbertick);
// Panel 4: Cycling water // Three slots take turns being the bright highlight. this.Demo.updateWater(tick: number): void
Water strip: three slots take turns being the brightest highlight. Each slot cycles: bright -> medium -> dim -> bright -> ... The phases are offset by one slot so the bright spot appears to travel.
@paramtick - Current tick count from BT.ticks.
updateWater
(const tick: numbertick);
} /** * Draws all four panels. Only palette indices appear here - no Color32 objects. */ Demo.render(): void
Draws all four panels. Only palette indices appear here - no Color32 objects.
render
() {
// Clear the screen with the UI theme's dark background.
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.clear: (paletteIndex: number) => void
Sets the frame clear color using a palette index. The renderer uses this color when clearing the full display at the start of the next frame.
@since0.1.0@parampaletteIndex - Palette index for the full-screen clear pass.
clear
(this.Demo.theme: nulltheme.bg);
// Draw each of the four panels. this.Demo.renderGradientPanel(): void
Panel 1: Scrolling gradient bar. A UI kit panel holds the heading; 32 thin rectangles sit inside it, each using a different palette slot. Because update() rotates the hues in those slots, the bar appears to scroll.
renderGradientPanel
();
this.Demo.renderFirePanel(): void
Panel 2: Fire column. FIRE_SLOTS horizontal bands stacked vertically; the colors change in update() to flicker.
renderFirePanel
();
this.Demo.renderHealthPanel(): void
Panel 3: Flashing health bar. One palette slot toggles between red and white when health is critically low.
renderHealthPanel
();
this.Demo.renderWaterPanel(): void
Panel 4: Cycling water strip. Three adjacent rectangles each use one of the three water palette slots. The brightness cycles across them to look like a ripple.
renderWaterPanel
();
} /** * Scrolling gradient: rotates 32 hue slots so the rainbow appears to slide across. * The "base hue" advances each frame; each slot gets a hue slightly ahead of the previous. */ Demo.updateGradient(): void
Scrolling gradient: rotates 32 hue slots so the rainbow appears to slide across. The "base hue" advances each frame; each slot gets a hue slightly ahead of the previous.
updateGradient
() {
// Base hue grows over time. Math.floor() converts to a whole number of degrees. // % 360 keeps hue in the 0..359 range (a full color wheel). const const baseHue: numberbaseHue = Math.floor(this.Demo.animTime: numberanimTime * 60) % 360; for (let let i: numberi = 0; let i: numberi < const GRAD_SLOTS: 32GRAD_SLOTS; let i: numberi++) { // Each slot is spread evenly around the color wheel. // (i / GRAD_SLOTS) * 360 spaces 32 hues evenly over 360 degrees. const const hue: numberhue = (const baseHue: numberbaseHue + (let i: numberi / const GRAD_SLOTS: 32GRAD_SLOTS) * 360) % 360; // Color32.fromHSL(hue, saturation, lightness): // hue 0..360 = position on the color wheel (0=red, 120=green, 240=blue) // saturation 0..100 = how vivid the color is (0=gray, 100=pure rainbow) // lightness 0..100 = how bright (0=black, 50=pure color, 100=white) 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 C_GRAD_BASE: 10C_GRAD_BASE + let i: numberi, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.fromHSL(h: number, s: number, l: number, a?: number): Color32
Creates a color from HSL values.
@paramh - Hue in degrees (0-360).@params - Saturation as percentage (0-100).@paraml - Lightness as percentage (0-100).@parama - Alpha channel (0-255, defaults to 255).@returnsNew color converted from HSL values.
fromHSL
(const hue: numberhue, 90, 55));
} } /** * Fire column: each slot represents a horizontal band of flame. * The bottom is black/dark red; moving up transitions through orange to bright yellow-white. * We shift the transition point over time so the flame flickers. */ Demo.updateFire(): void
Fire column: each slot represents a horizontal band of flame. The bottom is black/dark red; moving up transitions through orange to bright yellow-white. We shift the transition point over time so the flame flickers.
updateFire
() {
for (let let i: numberi = 0; let i: numberi < const FIRE_SLOTS: 20FIRE_SLOTS; let i: numberi++) { // t is 0 at the bottom slot, 1 at the top slot. const const t: numbert = let i: numberi / (const FIRE_SLOTS: 20FIRE_SLOTS - 1); // Add a gentle flicker by modulating the transition with a sine wave. // Math.sin() returns -1..1; we scale and shift it to 0..0.15 for a subtle wobble. const const flicker: numberflicker = (Math.sin(this.Demo.animTime: numberanimTime * 7 + let i: numberi * 0.8) + 1) * 0.075; // Apply flicker to t, clamped between 0 and 1. const const ft: anyft = Math.min(1, Math.max(0, const t: numbert + const flicker: numberflicker)); 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 C_FIRE_BASE: 50C_FIRE_BASE + let i: numberi, this.Demo.fireColor(ft: number): Color32
Maps one band position ft (0..1) to a fire color. We blend through a four-color gradient: 0.0 = black (cold, no flame) 0.3 = dark red (embers just starting) 0.6 = bright orange (active flame) 1.0 = pale yellow (hottest, near the tip)
@paramft - Position along the flame, 0 at the bottom, 1 at the top.@returnsThe blended color for that position.
fireColor
(const ft: anyft));
} } /** * Maps one band position ft (0..1) to a fire color. * We blend through a four-color gradient: * 0.0 = black (cold, no flame) * 0.3 = dark red (embers just starting) * 0.6 = bright orange (active flame) * 1.0 = pale yellow (hottest, near the tip) * * @param {number} ft - Position along the flame, 0 at the bottom, 1 at the top. * @returns {Color32} The blended color for that position. */ Demo.fireColor(ft: number): Color32
Maps one band position ft (0..1) to a fire color. We blend through a four-color gradient: 0.0 = black (cold, no flame) 0.3 = dark red (embers just starting) 0.6 = bright orange (active flame) 1.0 = pale yellow (hottest, near the tip)
@paramft - Position along the flame, 0 at the bottom, 1 at the top.@returnsThe blended color for that position.
fireColor
(ft: number
- Position along the flame, 0 at the bottom, 1 at the top.
@paramft - Position along the flame, 0 at the bottom, 1 at the top.
ft
) {
if (ft: number
- Position along the flame, 0 at the bottom, 1 at the top.
@paramft - Position along the flame, 0 at the bottom, 1 at the top.
ft
< 0.3) {
// Black to dark red. const const s: numbers = ft: number
- Position along the flame, 0 at the bottom, 1 at the top.
@paramft - Position along the flame, 0 at the bottom, 1 at the top.
ft
/ 0.3; // Rescales 0..0.3 to 0..1.
return 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
(Math.floor(const s: numbers * 160), 0, 0);
} if (ft: number
- Position along the flame, 0 at the bottom, 1 at the top.
@paramft - Position along the flame, 0 at the bottom, 1 at the top.
ft
< 0.6) {
// Dark red to bright orange. const const s: numbers = (ft: number
- Position along the flame, 0 at the bottom, 1 at the top.
@paramft - Position along the flame, 0 at the bottom, 1 at the top.
ft
- 0.3) / 0.3;
return 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
(160 + Math.floor(const s: numbers * 95), Math.floor(const s: numbers * 100), 0);
} // Orange to pale yellow-white. const const s: numbers = (ft: number
- Position along the flame, 0 at the bottom, 1 at the top.
@paramft - Position along the flame, 0 at the bottom, 1 at the top.
ft
- 0.6) / 0.4;
return new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(255, 100 + Math.floor(const s: numbers * 155), Math.floor(const s: numbers * 120));
} /** * Health bar: toggles one slot between red and near-white every FLASH_PERIOD ticks. * Only flashes when health is critically low. * * @param {number} tick - Current tick count from BT.ticks. */ Demo.updateHealthBar(tick: number): void
Health bar: toggles one slot between red and near-white every FLASH_PERIOD ticks. Only flashes when health is critically low.
@paramtick - Current tick count from BT.ticks.
updateHealthBar
(tick: number
- Current tick count from BT.ticks.
@paramtick - Current tick count from BT.ticks.
tick
) {
if (this.Demo.health: numberhealth <= const HEALTH_LOW: 30HEALTH_LOW) { // Flash! % is the remainder operator: tick % FLASH_PERIOD gives 0..(FLASH_PERIOD-1). // Math.floor(tick / FLASH_PERIOD) % 2 alternates between 0 and 1 every FLASH_PERIOD ticks. const const flashOn: booleanflashOn = Math.floor(tick: number
- Current tick count from BT.ticks.
@paramtick - Current tick count from BT.ticks.
tick
/ const FLASH_PERIOD: 8FLASH_PERIOD) % 2 === 0;
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 C_HEALTH_BAR: 80C_HEALTH_BAR, const flashOn: booleanflashOn ? new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(255, 50, 50) : new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(255, 255, 255));
} else { // Healthy: steady red. 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 C_HEALTH_BAR: 80C_HEALTH_BAR, 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
(200, 40, 40));
} } /** * Water strip: three slots take turns being the brightest highlight. * Each slot cycles: bright -> medium -> dim -> bright -> ... * The phases are offset by one slot so the bright spot appears to travel. * * @param {number} tick - Current tick count from BT.ticks. */ Demo.updateWater(tick: number): void
Water strip: three slots take turns being the brightest highlight. Each slot cycles: bright -> medium -> dim -> bright -> ... The phases are offset by one slot so the bright spot appears to travel.
@paramtick - Current tick count from BT.ticks.
updateWater
(tick: number
- Current tick count from BT.ticks.
@paramtick - Current tick count from BT.ticks.
tick
) {
// Advance the ripple every 8 ticks (about 7 ripples per second). if (tick: number
- Current tick count from BT.ticks.
@paramtick - Current tick count from BT.ticks.
tick
- this.Demo.waterTick: numberwaterTick >= 8) {
this.Demo.waterPhase: numberwaterPhase = (this.Demo.waterPhase: numberwaterPhase + 1) % const WATER_SLOTS: 3WATER_SLOTS; this.Demo.waterTick: numberwaterTick = tick: number
- Current tick count from BT.ticks.
@paramtick - Current tick count from BT.ticks.
tick
;
} for (let let i: numberi = 0; let i: numberi < const WATER_SLOTS: 3WATER_SLOTS; let i: numberi++) { // The ripple highlights one slot at a time. // phase distance: how far is slot i from the current bright spot? const const dist: numberdist = (let i: numberi - this.Demo.waterPhase: numberwaterPhase + const WATER_SLOTS: 3WATER_SLOTS) % const WATER_SLOTS: 3WATER_SLOTS; // dist == 0 -> brightest, dist == 1 -> medium, dist == 2 -> darkest let let color: anycolor; if (const dist: numberdist === 0) { // Bright highlight - the "wave crest". let color: anycolor = 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
(80, 200, 255);
} else if (const dist: numberdist === 1) { // Medium shade - just before or after the crest. let color: anycolor = 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
(30, 100, 200);
} else { // Dark trough - between ripples. let color: anycolor = 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
(10, 40, 120);
} 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 C_WATER_BASE: 90C_WATER_BASE + let i: numberi, let color: Color32color);
} } /** * Panel 1: Scrolling gradient bar. * A UI kit panel holds the heading; 32 thin rectangles sit inside it, each using a * different palette slot. Because update() rotates the hues in those slots, the bar * appears to scroll. */ Demo.renderGradientPanel(): void
Panel 1: Scrolling gradient bar. A UI kit panel holds the heading; 32 thin rectangles sit inside it, each using a different palette slot. Because update() rotates the hues in those slots, the bar appears to scroll.
renderGradientPanel
() {
const const bandY: 6bandY = 6; // The heading and subtitle live in a kit panel pinned to the band position. // ui.end() draws the panel right away, so the swatches drawn after it land ON TOP // of the panel background. ui.spacer() reserves empty rows for that artwork. import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: 0, y: numbery: const bandY: 6bandY, width: numberwidth: 320 }); import uiui.panel('Scrolling Gradient'); import uiui.label('Hues rotate in update() each tick', { color: stringcolor: 'dim' }); import uiui.spacer(18); import uiui.end(); // Draw one rectangle per gradient slot, side by side inside the panel. for (let let i: numberi = 0; let i: numberi < const GRAD_SLOTS: 32GRAD_SLOTS; let i: numberi++) { // Each swatch is GRAD_SWATCH_W pixels wide and 14 pixels tall, starting // 6 pixels in so the strip clears the panel border. const const x: numberx = 6 + let i: numberi * const GRAD_SWATCH_W: anyGRAD_SWATCH_W;
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
.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
(const x: numberx, const bandY: 6bandY + 38, const GRAD_SWATCH_W: anyGRAD_SWATCH_W, 14), const C_GRAD_BASE: 10C_GRAD_BASE + let i: numberi);
} } /** * Panel 2: Fire column. * FIRE_SLOTS horizontal bands stacked vertically; the colors change in update() to flicker. */ Demo.renderFirePanel(): void
Panel 2: Fire column. FIRE_SLOTS horizontal bands stacked vertically; the colors change in update() to flicker.
renderFirePanel
() {
const const bandY: 70bandY = 70; // Kit panel with the heading; the spacer reserves room for the 80-pixel column. import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: 0, y: numbery: const bandY: 70bandY, width: numberwidth: 320 }); import uiui.panel('Fire Column'); import uiui.label('Color stack shifts upward in update()', { color: stringcolor: 'dim' }); import uiui.spacer(86); import uiui.end(); // Fire bands, from bottom (slot 0 = darkest) to top (slot FIRE_SLOTS-1 = brightest). // We draw them from bottom up so slot 0 is at the base of the column. const const colX: 6colX = 6; const const colBottom: numbercolBottom = const bandY: 70bandY + 118; for (let let i: numberi = 0; let i: numberi < const FIRE_SLOTS: 20FIRE_SLOTS; let i: numberi++) { // i=0 -> bottom of column, i=FIRE_SLOTS-1 -> top. // Each band is FIRE_BAND_H pixels tall. const const y: numbery = const colBottom: numbercolBottom - (let i: numberi + 1) * const FIRE_BAND_H: 4FIRE_BAND_H;
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
.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
(const colX: 6colX, const y: numbery, const FIRE_COL_W: 60FIRE_COL_W, const FIRE_BAND_H: 4FIRE_BAND_H), const C_FIRE_BASE: 50C_FIRE_BASE + let i: numberi);
} // Explanatory notes beside the column, lined up with the parts they describe: // the brightest band is at the top of the column, the darkest at the bottom. // "Band" here means the position in the fire stack (0..19), not the palette // slot number - the actual slots are C_FIRE_BASE + band, i.e. 50..69. const const noteX: numbernoteX = const colX: 6colX + const FIRE_COL_W: 60FIRE_COL_W + 6;
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
.systemPrint: (pos: Vector2i, paletteIndex: number, text: string) => void
Draws text using the built-in 6x14 system font. The system font covers printable ASCII (characters 32-126). For custom bitmap fonts with proportional glyphs, use {@link BT.printFont } instead.
@since1.0.3@parampos - Text origin in display coordinates.@parampaletteIndex - Palette color index for the text.@paramtext - String to render.
systemPrint
(new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(const noteX: numbernoteX, const bandY: 70bandY + 44), this.Demo.theme: nulltheme.dim, 'band 19 = white');
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
.systemPrint: (pos: Vector2i, paletteIndex: number, text: string) => void
Draws text using the built-in 6x14 system font. The system font covers printable ASCII (characters 32-126). For custom bitmap fonts with proportional glyphs, use {@link BT.printFont } instead.
@since1.0.3@parampos - Text origin in display coordinates.@parampaletteIndex - Palette color index for the text.@paramtext - String to render.
systemPrint
(new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(const noteX: numbernoteX, const bandY: 70bandY + 74), this.Demo.theme: nulltheme.dim, '... = red');
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
.systemPrint: (pos: Vector2i, paletteIndex: number, text: string) => void
Draws text using the built-in 6x14 system font. The system font covers printable ASCII (characters 32-126). For custom bitmap fonts with proportional glyphs, use {@link BT.printFont } instead.
@since1.0.3@parampos - Text origin in display coordinates.@parampaletteIndex - Palette color index for the text.@paramtext - String to render.
systemPrint
(new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(const noteX: numbernoteX, const bandY: 70bandY + 104), this.Demo.theme: nulltheme.dim, 'band 0 = black');
} /** * Panel 3: Flashing health bar. * One palette slot toggles between red and white when health is critically low. */ Demo.renderHealthPanel(): void
Panel 3: Flashing health bar. One palette slot toggles between red and white when health is critically low.
renderHealthPanel
() {
const const bandY: 202bandY = 202; // Kit panel: heading, a spacer for the bar artwork, then the live status line. // The status text turns warm orange when health is critical, dim gray otherwise. const const isCritical: booleanisCritical = this.Demo.health: numberhealth <= const HEALTH_LOW: 30HEALTH_LOW; import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: 0, y: numbery: const bandY: 202bandY, width: numberwidth: 320 }); import uiui.panel('Flashing Health Bar'); import uiui.spacer(18); import uiui.label(const isCritical: booleanisCritical ? 'CRITICAL! slot 80 flashes red/white' : 'Healthy: slot 80 = steady red', { color: stringcolor: const isCritical: booleanisCritical ? 'warm' : 'dim', }); import uiui.end(); // Compute the width of the filled portion from the current health value. // health / HEALTH_MAX is a fraction from 0 to 1; multiply by max bar width (200 px). const const barMaxW: 200barMaxW = 200; const const barW: anybarW = Math.max(1, Math.floor((this.Demo.health: numberhealth / const HEALTH_MAX: 100HEALTH_MAX) * const barMaxW: 200barMaxW)); // Background trough (dark, always full width) with a theme-colored outline.
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
.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
(6, const bandY: 202bandY + 22, const barMaxW: 200barMaxW, 12), this.Demo.theme: nulltheme.bg);
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
.drawRect: (rect: Rect2i, paletteIndex: number) => void
Draws an unfilled rectangle outline.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRect
(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
(6, const bandY: 202bandY + 22, const barMaxW: 200barMaxW, 12), this.Demo.theme: nulltheme.border);
// Filled bar - uses C_HEALTH_BAR, which flashes in update() when health is low.
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
.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
(6, const bandY: 202bandY + 22, const barW: anybarW, 12), const C_HEALTH_BAR: 80C_HEALTH_BAR);
// Health value as text, in the theme's amber heading 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
.systemPrint: (pos: Vector2i, paletteIndex: number, text: string) => void
Draws text using the built-in 6x14 system font. The system font covers printable ASCII (characters 32-126). For custom bitmap fonts with proportional glyphs, use {@link BT.printFont } instead.
@since1.0.3@parampos - Text origin in display coordinates.@parampaletteIndex - Palette color index for the text.@paramtext - String to render.
systemPrint
(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
(212, const bandY: 202bandY + 21), this.Demo.theme: nulltheme.header, `HP: ${this.Demo.health: numberhealth}`);
} /** * Panel 4: Cycling water strip. * Three adjacent rectangles each use one of the three water palette slots. * The brightness cycles across them to look like a ripple. */ Demo.renderWaterPanel(): void
Panel 4: Cycling water strip. Three adjacent rectangles each use one of the three water palette slots. The brightness cycles across them to look like a ripple.
renderWaterPanel
() {
const const bandY: 266bandY = 266; // Kit panel with the heading; the spacer reserves room for the tile strip. import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: 0, y: numbery: const bandY: 266bandY, width: numberwidth: 320 }); import uiui.panel('Cycling Water Strip'); import uiui.label('3 slots (90..92) ripple in sequence', { color: stringcolor: 'dim' }); import uiui.spacer(18); import uiui.end(); // Draw 15 water tiles (5 repetitions of the 3-slot cycle) to make a wide strip. const const tileW: 18tileW = 18; const const tileH: 14tileH = 14; const const totalTiles: 15totalTiles = 15; for (let let i: numberi = 0; let i: numberi < const totalTiles: 15totalTiles; let i: numberi++) { // Map tile index to one of the 3 water slots using remainder (%). const const slot: numberslot = const C_WATER_BASE: 90C_WATER_BASE + (let i: numberi % const WATER_SLOTS: 3WATER_SLOTS);
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
.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
(6 + let i: numberi * const tileW: 18tileW, const bandY: 266bandY + 38, const tileW: 18tileW - 1, const tileH: 14tileH), const slot: numberslot);
} } } // 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 Demo
Demonstrates the "palette animation" technique: change palette entries every tick to create scrolling gradients, fire, flashing effects, and rippling water all without touching the geometry drawn in render().
@implementsIBTDemo
Demo
);