/**
 * Basics Demo - Your very first BLIT386 program!
 *
 * Welcome! This demo teaches you the absolute basics of making things appear
 * on screen with the BLIT386 engine. You will learn:
 *   - How a demo is structured (configure, init, update, render, overlayRows)
 *   - How to pick colors with a palette and clear the screen
 *   - How to load a sprite (a tiny picture) and draw it
 *   - How to make that sprite move and bounce off the screen edges
 *   - How smooth motion works when update() and render() run at different speeds
 *     (BT.renderAlpha + Vector2i.lerp)
 *   - How to show a text hint with the shared UI kit (ui.label)
 *   - How to show live stats and a timing chart in the engine overlay
 *     (overlayRows, BT.assignTag)
 *
 * If you are new to BLIT386, read this file carefully from top to bottom.
 * Almost every block has a comment explaining what it does and why.
 *
 * This demo sets targetFPS to 30 in configure() (slower than the engine default of 60)
 * so motion is easy to follow. update() therefore runs about 30 times per second.
 *
 * IMPORTANT - update() vs. render():
 *
 * update() runs at a FIXED rate (targetFPS from configure()). It is where you do
 * all game logic: move things, check wall collisions, count bounces. It may run
 * multiple times per screen refresh if the computer needs to catch up, but never
 * more than 8 times in a row.
 *
 * render() runs ONCE per screen refresh (often 60 times per second on a laptop
 * screen, but it can be faster on 120 or 144 times-per-second monitors). It is
 * where you draw everything. NEVER put game logic here - only drawing code.
 *
 * When you switch to a different browser tab, BOTH update() and render() pause
 * completely. The browser stops calling them to save battery. When you come
 * back, the engine catches up with a few extra update() calls (up to 8) so
 * your game does not jump forward in time by a huge amount.
 *
 * Live version: https://demos.blit386.dev/basics
 */

/**
 * "import" loads tools from the BLIT386 engine library.
 * Think of it like opening a toolbox before you start building.
 *   - bootstrap: a helper that starts the engine and connects your demo to it
 *   - BT: the main engine object - you call BT.clear(), BT.drawSprite(), etc.
 *   - Color32: represents a color with Red, Green, Blue (and optional Alpha)
 *   - SpriteSheet: a loaded image you can draw pieces of on screen (a "sprite")
 *   - Vector2i: a 2D point or direction using whole numbers (x, y)
 */
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`.@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;
    ... 98 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
,
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 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';
// The shared demo UI kit - every demo in this series uses it for on-screen text and panels // so they all look the same. applyTheme() installs the kit's colors into our palette, and // ui.* draws things like the hint label you see in the top-left corner. import { import applyThemeapplyTheme, import uiui } from './shared/ui.js'; /** * These @typedef lines tell code editors what types we mean when we write * Palette, SpriteSheet, and so on. They do not change how the demo runs. * IBTDemo is the contract that says a demo needs init, update, and render. * configure() and overlayRows() are optional extras (this demo uses both). * If you skip configure(), the engine uses defaults: 320x240 logical pixels, * 640x480 canvas, 60 updates per second. */ /** @typedef {import('blit386').IBTDemo} IBTDemo */ /** @typedef {import('blit386').HardwareSettings} HardwareSettings */ /** @typedef {import('blit386').Palette} Palette */ /** @typedef {import('blit386').SpriteSheet} SpriteSheet */ /** @typedef {import('blit386').Rect2i} Rect2i */ // BLIT386 uses a "palette" - a numbered list of colors you choose BEFORE drawing. // Think of it like an artist picking paint colors and laying them on a palette tray // before starting a painting. Each color gets a number (an "index"). // When we draw, we say "use color number 1" instead of spelling out the color each time. // // Index 0 is always transparent (completely invisible). Our custom colors start at 1. const const C_BG: 1C_BG = 1; // Almost-black with a faint green tint - our screen background. const const C_OVERLAY_BAR: 2C_OVERLAY_BAR = 2; // Slightly lighter bar behind overlay text (easy to read on dark green). const const C_OVERLAY_GREEN: 3C_OVERLAY_GREEN = 3; // Bright green for the position line in the overlay. const const C_OVERLAY_AMBER: 4C_OVERLAY_AMBER = 4; // Amber (warm yellow) for the bounce count in the overlay. const const C_OVERLAY_ERROR: 5C_OVERLAY_ERROR = 5; // Red the timing chart uses when a frame is badly late. // Where the sprite's own colors begin in the palette. // We already use slots 1-5 for background and overlay colors. Starting the sprite // colors at 10 leaves a little empty room (slots 6-9) so we can add more scene // colors later without having to renumber the sprite slots. const const SPRITE_BASE: 10SPRITE_BASE = 10; // Path to the sprite image. The "public" folder contents are served at the // site root, so /sprites/logo-1.png maps to public/sprites/logo-1.png on disk. const const SPRITE_URL: "/sprites/logo-1.png"SPRITE_URL = '/sprites/logo-1.png'; /** * Bouncing-sprite demo - a friendly first BLIT386 demo. * * Every BLIT386 demo is a class the engine drives. Three methods are required; * configure() and overlayRows() are optional extras this file also uses: * * 1. configure() - optional. If you define it, the engine calls it once at * the very start so you can change settings (FPS, overlay options, and more). * If you skip it, you get sensible defaults (320x240, 640x480 output, 60 FPS). * * 2. init() - called once after hardware settings are ready. This is where you * load images, set up colors, and pick starting positions. It uses "async" * because loading files takes time, and we need to wait for them to finish * (like waiting for a web page to load). * * 3. update() - called at the targetFPS rate (30 per second in this demo). * This is where you move the sprite, flip speed when it hits a wall, and * count bounces. It runs at a FIXED pace so motion matches on fast and slow * computers. See the file header for the full explanation. * * 4. overlayRows() - optional. Feeds extra text lines into the engine overlay * (the HUD you toggle with the ~ key). Not required, but handy for live stats. * * 5. render() - called once per screen refresh to draw everything. Clear the * screen, draw shapes, print text - all drawing goes here. * * @implements {IBTDemo} */ class class Demo
Bouncing-sprite demo - a friendly first BLIT386 demo. Every BLIT386 demo is a class the engine drives. Three methods are required; configure() and overlayRows() are optional extras this file also uses: 1. configure() - optional. If you define it, the engine calls it once at the very start so you can change settings (FPS, overlay options, and more). If you skip it, you get sensible defaults (320x240, 640x480 output, 60 FPS). 2. init() - called once after hardware settings are ready. This is where you load images, set up colors, and pick starting positions. It uses "async" because loading files takes time, and we need to wait for them to finish (like waiting for a web page to load). 3. update() - called at the targetFPS rate (30 per second in this demo). This is where you move the sprite, flip speed when it hits a wall, and count bounces. It runs at a FIXED pace so motion matches on fast and slow computers. See the file header for the full explanation. 4. overlayRows() - optional. Feeds extra text lines into the engine overlay (the HUD you toggle with the ~ key). Not required, but handy for live stats. 5. render() - called once per screen refresh to draw everything. Clear the screen, draw shapes, print text - all drawing goes here.
@implementsIBTDemo
Demo
{
// - Instance properties - // These are values that belong to this demo. They keep track of where // things are and how they are moving. We set them up here at the top // so they are easy to find. // "pos" is short for "position". It stores where the sprite is on screen. // Vector2i holds two whole numbers: x (horizontal) and y (vertical). // (0, 0) is the top-left corner of the screen. x increases going right, // y increases going DOWN (this is different from math class where y goes up!). // We start near the screen center as a placeholder; init() will // overwrite this with the exact center calculated from the real display size. Demo.pos: Vector2ipos = 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
(160, 120);
// "speed" is how many pixels the sprite moves each update(). // x=1 means it moves 1 pixel to the right each tick. // y=1 means it moves 1 pixel downward each tick. // When we make a number negative (like -1), the sprite moves in the // opposite direction (left instead of right, or up instead of down). Demo.speed: Vector2ispeed = 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
(1, 1);
// "prevPos" remembers where the sprite was at the START of the most recent // update() tick, before that tick moved it. render() uses this together with // BT.renderAlpha to draw the sprite smoothly between ticks - see the big comment // above render() below for the full explanation. It starts equal to pos (both // point at the same placeholder above) so the very first render() has nothing // to blend between; init() updates both again once the real starting position // is known. Demo.prevPos: Vector2iprevPos = this.Demo.pos: Vector2ipos; // "size" is how big the sprite is: we start with 16x16 as a guess. // We update this from the loaded image in init() so the bounce // checks stay correct even if you swap the PNG for a bigger one. Demo.size: Vector2isize = 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
(16, 16);
// "bounces" counts how many times the sprite has hit a wall. // We show this in the overlay so you can see it going up. Demo.bounces: numberbounces = 0; // "palette" holds the list of colors the engine will use for drawing. // We create it in init() once we know what colors we need. /** @type {Palette | null} */ Demo.palette: Palette | null
@type{Palette | null}
palette
= null;
// "spriteSheet" is the loaded image we will draw on screen. // It stays null until init() finishes loading the PNG file. /** @type {SpriteSheet | null} */ Demo.spriteSheet: SpriteSheet | null
@type{SpriteSheet | null}
spriteSheet
= null;
// "spriteRect" tells the engine WHICH rectangular piece of the image to draw. // A sprite sheet can hold many sprites in one big picture. Our PNG only has // one sprite, so the rectangle covers the whole image: (x=0, y=0, full width, full height). /** @type {Rect2i | null} */ Demo.spriteRect: Rect2i | null
@type{Rect2i | null}
spriteRect
= null;
// Reused every frame for the engine overlay (position + bounces). // We keep one array and update the text strings in place so we do not // create brand-new objects on every screen refresh. Demo.overlayRowData: {}overlayRowData = [ { leftText: stringleftText: 'Position 0, 0', textPaletteIndex: numbertextPaletteIndex: const C_OVERLAY_GREEN: 3C_OVERLAY_GREEN }, { leftText: stringleftText: 'Bounces 0', textPaletteIndex: numbertextPaletteIndex: const C_OVERLAY_AMBER: 4C_OVERLAY_AMBER }, ]; /** * Called once at the very start. Returns settings the engine should use. * This demo does not change the screen size (the engine keeps its defaults: * 320x240 logical pixels, 640x480 on the web page). We mainly slow down * update() and turn on helpful overlay tools for learning. * * @returns {Partial<HardwareSettings>} */ Demo.configure(): Partial<HardwareSettings>
Called once at the very start. Returns settings the engine should use. This demo does not change the screen size (the engine keeps its defaults: 320x240 logical pixels, 640x480 on the web page). We mainly slow down update() and turn on helpful overlay tools for learning.
@returns
configure
() {
// We only change the settings listed below. Everything else (display size, // canvas size, and more) comes from the engine's defaultConfig(). return { // How often update() should run. 30 times per second is slower than the // engine default (60), so the bounce is easier to watch. targetFPS: numbertargetFPS: 30, // Live palette grid at the bottom: every palette slot as a tiny color chip. // Slots your demo draws this frame show their color; unused slots look dim. isOverlayPaletteEnabled: booleanisOverlayPaletteEnabled: true, // Show 16 color chips per row and only 1 row at a time // (scroll the rest with the mouse wheel or by dragging). overlayPaletteColumns: numberoverlayPaletteColumns: 16, overlayPaletteRowsVisible: numberoverlayPaletteRowsVisible: 1, // Scrolling timing chart under the title row. // Green marks show update() time; amber marks show render() time. // One mark per screen refresh - handy for seeing when work spikes. isOverlayTimingChartEnabled: booleanisOverlayTimingChartEnabled: true, overlayTimingChartHeight: numberoverlayTimingChartHeight: 32, // Which palette slots to use for the overlay bars // (top FPS strip, bottom title strip, and the bar behind our custom rows).
overlayStyle: {
    barPaletteIndex: number;
    textPaletteIndex: number;
    gapPaletteIndex: number;
}
overlayStyle
: {
barPaletteIndex: numberbarPaletteIndex: const C_OVERLAY_BAR: 2C_OVERLAY_BAR, textPaletteIndex: numbertextPaletteIndex: const C_OVERLAY_GREEN: 3C_OVERLAY_GREEN, gapPaletteIndex: numbergapPaletteIndex: const C_BG: 1C_BG, }, // Colors for the timing chart: green = update, amber = render, // amber again for "a bit late", red for "badly late", green for our H/V tags.
overlayTimingChartStyle: {
    updateBarPaletteIndex: number;
    renderBarPaletteIndex: number;
    warningPaletteIndex: number;
    errorPaletteIndex: number;
    tagPaletteIndex: number;
}
overlayTimingChartStyle
: {
updateBarPaletteIndex: numberupdateBarPaletteIndex: const C_OVERLAY_GREEN: 3C_OVERLAY_GREEN, renderBarPaletteIndex: numberrenderBarPaletteIndex: const C_OVERLAY_AMBER: 4C_OVERLAY_AMBER, warningPaletteIndex: numberwarningPaletteIndex: const C_OVERLAY_AMBER: 4C_OVERLAY_AMBER, errorPaletteIndex: numbererrorPaletteIndex: const C_OVERLAY_ERROR: 5C_OVERLAY_ERROR, tagPaletteIndex: numbertagPaletteIndex: const C_OVERLAY_GREEN: 3C_OVERLAY_GREEN, }, }; } /** * Called once after hardware settings are ready (from configure() mixed with * engine defaults, or defaults alone if you skip configure). * Sets up the palette, loads the sprite image, and places the sprite * in the center of the screen. * * The "async" keyword lets us use "await" inside this method. "await" pauses * until something slow finishes (like loading an image from the server) and * then continues with the result. Without "async", we could not use "await". * * @returns {Promise<boolean>} Return true when everything is ready. * Returning false tells the engine that something went wrong. */ async Demo.init(): Promise<boolean>
Called once after hardware settings are ready (from configure() mixed with engine defaults, or defaults alone if you skip configure). Sets up the palette, loads the sprite image, and places the sprite in the center of the screen. The "async" keyword lets us use "await" inside this method. "await" pauses until something slow finishes (like loading an image from the server) and then continues with the result. Without "async", we could not use "await".
@returnsReturn true when everything is ready. Returning false tells the engine that something went wrong.
init
() {
// Step 1: set up the color palette // A palette is like an artist's tray of paint colors laid out before painting. // We pick every color we need here so the engine knows about them in advance. // BT.paletteCreate(256) makes a new empty palette with room for 256 colors. // 256 is a common size in retro-style games - plenty of slots for this demo. 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;
    ... 98 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);
// Fill in the colors we need for background and overlay text. // palette.set(number, color) stores one color in a numbered slot. // Color32(Red, Green, Blue) - each value is 0 to 255. // 0 = none of that color, 255 = maximum of that color. 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_BG: 1C_BG, 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
(16, 28, 16)); // Almost-black, faint green tint.
// Overlay colors (must match overlayStyle and overlayRowData above). 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
(24, 44, 28)); // Dark bar, slightly lighter than C_BG.
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_GREEN: 3C_OVERLAY_GREEN, 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, 110)); // Bright green for overlay text.
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_AMBER: 4C_OVERLAY_AMBER, 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, 180, 60)); // Amber accent for the bounce row.
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_ERROR: 5C_OVERLAY_ERROR, 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, 70, 70)); // Red when the timing chart marks a bad spike.
// Step 2: load the sprite AND put its colors into the palette // The engine draws sprites using palette numbers, not raw Red/Green/Blue. // So every color in the PNG must live in a palette slot first. // // SpriteSheet.loadIndexed() does the whole job in one call: // 1. Opens the PNG and writes each unique color into the palette // starting at SPRITE_BASE. // 2. Loads the image as a sprite sheet. // 3. "Indexizes" it: turns every pixel into a palette number // (like labeling each paint blob with the slot it matches). // 4. Returns the sheet plus a rectangle that covers the whole image. // // { sort: 'none' } keeps colors in the order they appear in the file // (left to right, top to bottom). The default would sort them darkest-first // instead - fine for many games, but we keep file order here so the slots // match the PNG layout if you peek at the overlay palette grid. // // We "await" because reading the PNG takes a moment. const const indexed: Promise<IndexedSpriteLoadResult>indexed = await 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
.
SpriteSheet.loadIndexed(url: string, palette: Palette, startSlot: number, options?: {
    sort?: "luminance" | "none";
}): Promise<IndexedSpriteLoadResult>
Convenience one-call path for palette-indexed sprite setup. This combines: 1) {@link SpriteSheet.loadColorsIntoPalette } 2) {@link SpriteSheet.load } 3) {@link SpriteSheet.indexize } It returns the indexized sheet plus a full-frame source rectangle and the colors that were written into the palette. Callers still control when to activate the palette via `BT.paletteSet(palette)`.
@paramurl - Path or URL to the PNG file.@parampalette - Target palette used for both registration and indexization.@paramstartSlot - First palette slot to write discovered colors into.@paramoptions - Optional color-sort behavior for registration.@paramoptions.sort - Color ordering for palette registration.@returnsObject with `sheet`, `srcRect`, and registered `colors`.
loadIndexed
(const SPRITE_URL: "/sprites/logo-1.png"SPRITE_URL, this.Demo.palette: Palette
@type{Palette | null}
palette
, const SPRITE_BASE: 10SPRITE_BASE, { sort?: "luminance" | "none" | undefinedsort: 'none' });
this.Demo.spriteSheet: SpriteSheet | null
@type{SpriteSheet | null}
spriteSheet
= const indexed: Promise<IndexedSpriteLoadResult>indexed.sheet;
this.Demo.spriteRect: Rect2i | null
@type{Rect2i | null}
spriteRect
= const indexed: Promise<IndexedSpriteLoadResult>indexed.srcRect;
// Step 3: install the shared UI theme // applyTheme() writes the demo series' twelve standard UI colors into high // palette slots (240-251 by default, far away from our low scene slots), so the // kit's hint label in render() has colors to draw with. It must run BEFORE // BT.paletteSet() below so those colors are included when the palette goes live. // (It also returns a map of slot names, but we do not need that map in this demo.) import applyThemeapplyTheme(this.Demo.palette: Palette
@type{Palette | null}
palette
);
// Step 4: activate the palette // Tell the engine "use this palette from now on." // Before this call, the engine does not know what colors are available. // We do this AFTER adding the sprite and UI colors so they are included.
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;
    ... 98 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
);
// Step 5: remember the sprite's pixel size // The sprite sheet exposes its dimensions through the .size property. // We copy them into our own size vector so the bounce checks below use // the real image size (not our 16x16 guess from above). this.Demo.size: Vector2isize = 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.spriteSheet: SpriteSheet | null
@type{SpriteSheet | null}
spriteSheet
.SpriteSheet.size: Vector2i
Gets the sprite-sheet dimensions in pixels.
@returnsSheet dimensions. Changes after a hot-replace image swap with different dimensions – any `srcRect` a demo holds onto is the demo's own responsibility to reconcile.
size
.Vector2i.x: number
Horizontal component (defaults to 0).
x
, this.Demo.spriteSheet: SpriteSheet | null
@type{SpriteSheet | null}
spriteSheet
.SpriteSheet.size: Vector2i
Gets the sprite-sheet dimensions in pixels.
@returnsSheet dimensions. Changes after a hot-replace image swap with different dimensions – any `srcRect` a demo holds onto is the demo's own responsibility to reconcile.
size
.Vector2i.y: number
Vertical component (defaults to 0).
y
);
// Step 6: position the sprite in the center of the screen // BT.displaySize is how big the screen is (320x240 with the defaults). // We subtract the sprite's size so the CENTER of the sprite is centered, // not its top-left corner. // Math.floor() rounds down to a whole number - we need whole pixels // because you cannot draw at position 160.5 on a pixel screen. const const screen: Vector2iscreen =
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;
    ... 98 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.displaySize: Vector2i
Active logical render resolution in pixels. This is the game/simulation coordinate space configured by the demo, not the canvas element's CSS size. Each read returns a clone.
@since1.0.4@returnsConfigured logical size, or `Vector2i.zero()` before initialization.
displaySize
;
const const x: anyx = Math.floor(const screen: Vector2iscreen.Vector2i.x: number
Horizontal component (defaults to 0).
x
/ 2 - this.Demo.size: Vector2isize.Vector2i.x: number
Horizontal component (defaults to 0).
x
/ 2);
const const y: anyy = Math.floor(const screen: Vector2iscreen.Vector2i.y: number
Vertical component (defaults to 0).
y
/ 2 - this.Demo.size: Vector2isize.Vector2i.y: number
Vertical component (defaults to 0).
y
/ 2);
this.Demo.pos: Vector2ipos = 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 x: anyx, const y: anyy);
// Keep prevPos in sync with the real starting position so the very first // render() does not try to smoothly slide in from the (160, 120) placeholder. this.Demo.prevPos: Vector2iprevPos = this.Demo.pos: Vector2ipos; // Return true to tell the engine: "Everything loaded fine, start the demo!" return true; } /** * Called at a fixed rate (30 times per second in this demo). * * This is where ALL game logic goes: moving things, checking collisions, * counting scores, etc. Never draw anything here - that belongs in render(). * * update() may be called 0 to 8 times between screen refreshes: * - Usually it runs about 30 times per second (our targetFPS). * On a 60-times-per-second monitor, that is about one update every * two screen refreshes. * - If the computer is slow, it may run multiple times to catch up. * - If you switch to another browser tab, it pauses completely. * - When you come back, it runs up to 8 times to catch up. * * Each call to update() is called a "tick". You can check how many ticks * have happened since the demo started with BT.ticks. */ Demo.update(): void
Called at a fixed rate (30 times per second in this demo). This is where ALL game logic goes: moving things, checking collisions, counting scores, etc. Never draw anything here - that belongs in render(). update() may be called 0 to 8 times between screen refreshes: - Usually it runs about 30 times per second (our targetFPS). On a 60-times-per-second monitor, that is about one update every two screen refreshes. - If the computer is slow, it may run multiple times to catch up. - If you switch to another browser tab, it pauses completely. - When you come back, it runs up to 8 times to catch up. Each call to update() is called a "tick". You can check how many ticks have happened since the demo started with BT.ticks.
update
() {
// --- Bounce logic (game rules live only in update(), never in render()) --- // Remember where the sprite was BEFORE this tick moves it. render() will use // this a moment from now to draw a smooth in-between position instead of a // pop - see the big comment above render() below. this.Demo.prevPos: Vector2iprevPos = this.Demo.pos: Vector2ipos; // Move the sprite by adding its speed to its position. // Think of it like taking steps: if you are standing at position 160 // and your speed is 1, after one step you are at 161. // .add() creates a new Vector2i with both numbers added together. this.Demo.pos: Vector2ipos = this.Demo.pos: Vector2ipos.Vector2i.add(other: Vector2i): Vector2i
Adds another vector and returns the result as a new vector.
@paramother - Vector to add.@returnsNew vector with summed components.
add
(this.Demo.speed: Vector2ispeed);
// Wall test for left/right: pos is the sprite's TOP-LEFT corner. // The right edge of the sprite is at pos.x + size.x, so we compare against // displaySize.x - size.x (the farthest right the top-left corner may go // while the whole sprite still fits on screen). // // We only flip the speed here - we do not push the sprite back onto the // edge. So for one tick it can sit one pixel past the wall, then travel // inward again. That is normal for this simple bounce. if (this.Demo.pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
<= 0 || this.Demo.pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
>=
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;
    ... 98 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.displaySize: Vector2i
Active logical render resolution in pixels. This is the game/simulation coordinate space configured by the demo, not the canvas element's CSS size. Each read returns a clone.
@since1.0.4@returnsConfigured logical size, or `Vector2i.zero()` before initialization.
displaySize
.Vector2i.x: number
Horizontal component (defaults to 0).
x
- this.Demo.size: Vector2isize.Vector2i.x: number
Horizontal component (defaults to 0).
x
) {
// Bounce: multiply speed.x by -1 to reverse horizontal direction. // If speed.x was 1 (going right), it becomes -1 (going left). this.Demo.speed: Vector2ispeed.Vector2i.x: number
Horizontal component (defaults to 0).
x
= -this.Demo.speed: Vector2ispeed.Vector2i.x: number
Horizontal component (defaults to 0).
x
;
// Count this as a bounce. this.Demo.bounces: numberbounces++; // Mark the moment on the timing chart (press ~ / Backquote to show the overlay). // Each tag scrolls left with the green/amber marks so you can line up // spikes in update/render time with when the logo hit a wall.
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;
    ... 98 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.assignTag: (label?: string) => void
Places a labeled marker on the overlay timing chart at the current tick. Requires `isOverlayTimingChartEnabled: true` in `configure()`. Tags scroll with the chart history and are pruned when they leave the visible window. Empty labels become `"Untitled"`. Chart width resets add an automatic `"Start"` tag.
@since1.1.0@paramlabel - Short event name (for example `'Round start'`).
assignTag
('H');
} // Same check for the top and bottom edges. if (this.Demo.pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
<= 0 || this.Demo.pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
>=
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;
    ... 98 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.displaySize: Vector2i
Active logical render resolution in pixels. This is the game/simulation coordinate space configured by the demo, not the canvas element's CSS size. Each read returns a clone.
@since1.0.4@returnsConfigured logical size, or `Vector2i.zero()` before initialization.
displaySize
.Vector2i.y: number
Vertical component (defaults to 0).
y
- this.Demo.size: Vector2isize.Vector2i.y: number
Vertical component (defaults to 0).
y
) {
// Flip the vertical speed. this.Demo.speed: Vector2ispeed.Vector2i.y: number
Vertical component (defaults to 0).
y
= -this.Demo.speed: Vector2ispeed.Vector2i.y: number
Vertical component (defaults to 0).
y
;
this.Demo.bounces: numberbounces++; // Same timing-chart marker as the left/right bounce above.
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;
    ... 98 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.assignTag: (label?: string) => void
Places a labeled marker on the overlay timing chart at the current tick. Requires `isOverlayTimingChartEnabled: true` in `configure()`. Tags scroll with the chart history and are pruned when they leave the visible window. Empty labels become `"Untitled"`. Chart width resets add an automatic `"Start"` tag.
@since1.1.0@paramlabel - Short event name (for example `'Round start'`).
assignTag
('V');
} } /** * Called once per screen refresh to draw everything. * * render() runs AFTER update(). By the time render() is called, all * positions and scores are already calculated. render() just reads * those values and draws the picture. * * IMPORTANT: render() runs once per screen refresh (your monitor's refresh * rate - often 60, but 120 or 144 on gaming displays). Do NOT put game logic * here because it would run at different speeds on different monitors. * * Every frame you must clear the screen and redraw everything from * scratch. If you skip clearing, the old frame stays and new drawings * pile up on top of it (which can look cool, but is usually a bug!). * * WHY THE SPRITE MIGHT LOOK LIKE IT STUTTERS WITHOUT THE FIX BELOW: * This demo sets targetFPS to 30 (see configure() above), but render() still * runs at your monitor's full refresh rate - 60, 120, whatever your screen * supports. That means update() (which moves the sprite) and render() * (which draws it) run at DIFFERENT speeds. On a 60-times-per-second monitor, * render() runs roughly twice for every one update() - so if render() just * drew this.pos every time, the sprite would sit frozen for one refresh, then * hop forward, then sit frozen again. That hop-hop-hop motion is "stutter." * * THE FIX: BT.renderAlpha. Think of it like a movie: update() ticks are the * individual film frames (say, one every two screen refreshes), and render() * is a projector that can run faster than the film advances. BT.renderAlpha * tells the projector how far along we are between "the last film frame" and * "the next one" - a fraction from 0 (the last update() tick just finished) up * to just under 1 (the next update() tick is about to happen). We use it below * to blend prevPos (where the sprite WAS) toward pos (where it IS) so every * single render() draws the sprite at its true in-between position instead of * only where it was as of the last tick. */ Demo.render(): void
Called once per screen refresh to draw everything. render() runs AFTER update(). By the time render() is called, all positions and scores are already calculated. render() just reads those values and draws the picture. IMPORTANT: render() runs once per screen refresh (your monitor's refresh rate - often 60, but 120 or 144 on gaming displays). Do NOT put game logic here because it would run at different speeds on different monitors. Every frame you must clear the screen and redraw everything from scratch. If you skip clearing, the old frame stays and new drawings pile up on top of it (which can look cool, but is usually a bug!). WHY THE SPRITE MIGHT LOOK LIKE IT STUTTERS WITHOUT THE FIX BELOW: This demo sets targetFPS to 30 (see configure() above), but render() still runs at your monitor's full refresh rate - 60, 120, whatever your screen supports. That means update() (which moves the sprite) and render() (which draws it) run at DIFFERENT speeds. On a 60-times-per-second monitor, render() runs roughly twice for every one update() - so if render() just drew this.pos every time, the sprite would sit frozen for one refresh, then hop forward, then sit frozen again. That hop-hop-hop motion is "stutter." THE FIX: BT.renderAlpha. Think of it like a movie: update() ticks are the individual film frames (say, one every two screen refreshes), and render() is a projector that can run faster than the film advances. BT.renderAlpha tells the projector how far along we are between "the last film frame" and "the next one" - a fraction from 0 (the last update() tick just finished) up to just under 1 (the next update() tick is about to happen). We use it below to blend prevPos (where the sprite WAS) toward pos (where it IS) so every single render() draws the sprite at its true in-between position instead of only where it was as of the last tick.
render
() {
// Clear the entire screen to the background color. This erases the previous frame. // C_BG is palette index 1, which we set to (16, 28, 16) in init() - almost // black with a faint green tint.
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;
    ... 98 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
(const C_BG: 1C_BG);
// Blend prevPos toward pos by BT.renderAlpha to get the sprite's true position // at this exact render moment. Vector2i.lerp(a, b, t) walks a fraction t of the // way from a to b: t=0 gives a (prevPos), t=1 gives b (pos), and anything in // between gives a smooth blend - exactly what BT.renderAlpha provides each frame. const const drawPos: Vector2idrawPos = 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
.Vector2i.lerp(a: Vector2i, b: Vector2i, t: number): Vector2i
Linearly interpolates between two vectors. Result is truncated to integers. t is clamped to [0, 1].
@parama - Start vector.@paramb - End vector.@paramt - Interpolation factor, clamped to [0, 1] (0 = a, 1 = b).@returnsNew interpolated vector.
lerp
(this.Demo.prevPos: Vector2iprevPos, this.Demo.pos: Vector2ipos,
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 98 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.renderAlpha: number
Fractional progress between the last completed fixed update and the next. Intended for interpolating render state between fixed-update steps.
@since1.3.0@returnsInterpolation alpha in `[0, 1)`.
renderAlpha
);
// Draw the bouncing sprite at its smoothed position. // BT.drawSprite takes (sheet, sourceRect, destinationPosition, paletteOffset). // - sheet: the loaded image we want to draw from. // - sourceRect: WHICH part of the image to draw. Our sprite fills the // whole image, so spriteRect is the full image bounds. // - destinationPosition: WHERE on screen to draw it (the top-left corner). // - paletteOffset: a number added to every pixel's palette index. We // pass 0 here to use the original colors. Bigger numbers can swap to // alternate "team colors" - you will see this trick in a future demo.
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;
    ... 98 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawSprite: (spriteSheet: SpriteSheet, srcRect: Rect2i, destPos: Vector2i, paletteOffset?: number) => void
Draws a sprite region from an indexed sprite sheet. Sprite draws are batched internally. Grouping draws from the same {@link SpriteSheet } minimizes batch flushes and reduces GPU state changes. The sprite sheet must have been converted to palette indices via `spriteSheet.indexize(palette)` before the first draw call. Prefer `SpriteSheet.loadIndexed(...)` for one-call setup. **Palette offset semantics:** Sprite pixels are stored as palette indices starting at 1. Index 0 is always transparent and is discarded by the fragment shader. The final palette lookup is `storedIndex + paletteOffset`, so: - `paletteOffset = 0` (default): a sprite pixel stored at index 1 renders as `palette[1]`. `palette[0]` is never reachable because stored indices start at 1. - `paletteOffset = N`: shifts the entire sprite's color range up by N slots. A pixel stored at index 1 renders as `palette[1 + N]`, a pixel at index 2 renders as `palette[2 + N]`, and so on. Use this for palette-swap effects such as team colors or damage flashes. **Out-of-range behavior:** No CPU-side validation is performed. `paletteOffset` is passed to the GPU as a `u32`. If `storedIndex + paletteOffset` exceeds the last palette index, WebGPU's robust buffer access returns 0 for every component; because the fragment shader forces alpha to 1.0, the affected pixels render as opaque black. Negative values are forbidden - a negative JS number written into a `u32` vertex attribute wraps to a large unsigned integer, which also produces out-of-bounds black pixels.
@since0.1.0@paramspriteSheet - Indexed sprite sheet.@paramsrcRect - Source rectangle within the sprite sheet, in pixels.@paramdestPos - Destination top-left position in display coordinates.@parampaletteOffset - Shift added to every stored pixel index before palette lookup (default 0).@exampleBT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10)); BT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10), 16); // blue team
drawSprite
(this.Demo.spriteSheet: SpriteSheet | null
@type{SpriteSheet | null}
spriteSheet
, this.Demo.spriteRect: Rect2i | null
@type{Rect2i | null}
spriteRect
, const drawPos: Vector2idrawPos, 0);
// On-canvas hint drawn with the shared UI kit. ui.begin()/ui.end() open and close // a small group of UI rows; with no ui.panel() call the group is just floating // text with no box around it. 'topLeft' anchors it to the top-left corner, and // { color: 'dim' } picks the kit's muted gray so the hint stays out of the way. // (Under the hood the kit prints with the same built-in 6x14 system font.) // "~" is the Backquote key (usually under Esc, left of the 1 key). import uiui.begin('topLeft'); import uiui.label('Press ~ or click/tap the symbol below', { color: stringcolor: 'dim' }); import uiui.label('to toggle the overlay', { color: stringcolor: 'dim' }); import uiui.end(); // Live stats (position, bounce count) come from overlayRows() in the engine HUD. // The overlay also shows FPS, backend, and this demo's page title - we do not // duplicate those strings here. } /** * Optional hook: feeds extra text rows into the engine overlay (not the game canvas). * * The overlay is the HUD the engine draws after render(): FPS, demo title, timing chart, * palette grid, and these custom rows. Toggle it with the ~ key (Backquote on the * keyboard) or by clicking/tapping the symbol in the bottom-left corner. * Each row here is plain text plus a palette index for its color. * We reuse overlayRowData every frame and only rewrite the strings - no new arrays. * * @returns {readonly { leftText: string, textPaletteIndex: number }[]} */
Demo.overlayRows(): readonly {
    leftText: string;
    textPaletteIndex: number;
}[]
Optional hook: feeds extra text rows into the engine overlay (not the game canvas). The overlay is the HUD the engine draws after render(): FPS, demo title, timing chart, palette grid, and these custom rows. Toggle it with the ~ key (Backquote on the keyboard) or by clicking/tapping the symbol in the bottom-left corner. Each row here is plain text plus a palette index for its color. We reuse overlayRowData every frame and only rewrite the strings - no new arrays.
@returns
overlayRows
() {
this.Demo.overlayRowData: {}overlayRowData[0].leftText = `Position (${this.Demo.pos: Vector2ipos.Vector2i.x: number
Horizontal component (defaults to 0).
x
}, ${this.Demo.pos: Vector2ipos.Vector2i.y: number
Vertical component (defaults to 0).
y
})`;
this.Demo.overlayRowData: {}overlayRowData[1].leftText = `Bounces ${this.Demo.bounces: numberbounces}`; return this.Demo.overlayRowData: {}overlayRowData; } } // bootstrap() is the function that starts everything. You pass it your Demo // class, and it takes care of: // 1. Setting up the HTML canvas on the page // 2. Picking a backend: WebGPU when the browser supports it, otherwise Canvas 2D software mode (see README) // 3. Creating a new instance of your Demo class // 4. Calling configure() when you define it, then init(), then the update/render loop // // After this line runs, your demo is alive and running! 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`.@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
Bouncing-sprite demo - a friendly first BLIT386 demo. Every BLIT386 demo is a class the engine drives. Three methods are required; configure() and overlayRows() are optional extras this file also uses: 1. configure() - optional. If you define it, the engine calls it once at the very start so you can change settings (FPS, overlay options, and more). If you skip it, you get sensible defaults (320x240, 640x480 output, 60 FPS). 2. init() - called once after hardware settings are ready. This is where you load images, set up colors, and pick starting positions. It uses "async" because loading files takes time, and we need to wait for them to finish (like waiting for a web page to load). 3. update() - called at the targetFPS rate (30 per second in this demo). This is where you move the sprite, flip speed when it hits a wall, and count bounces. It runs at a FIXED pace so motion matches on fast and slow computers. See the file header for the full explanation. 4. overlayRows() - optional. Feeds extra text lines into the engine overlay (the HUD you toggle with the ~ key). Not required, but handy for live stats. 5. render() - called once per screen refresh to draw everything. Clear the screen, draw shapes, print text - all drawing goes here.
@implementsIBTDemo
Demo
);