// @pageTitle BLIT386 Demo – CRT Toggle
// @description Turn a whole CRT post-process stack on and off at runtime, switching every two seconds as it runs.
//
// CRT Toggle: turn the post-process effects on and off in flight.
//
// Part of the BLIT386 demo series.
// Prerequisites:
//   Basics     https://demos.blit386.dev/basics
//   PipBoy CRT https://demos.blit386.dev/crt-pipboy
//
// Live version: https://demos.blit386.dev/crt-toggle
//
// Guide: https://blit386.dev/docs/guides/post-process-effects
//
// WHAT YOU WILL SEE
// A colorful, simple scene - bouncing squares and a few horizontal bars. Every two seconds
// the CRT preset flips on and off automatically. Status text sits in a small panel drawn
// with the shared UI kit. While it is on
// you see scanlines, the RGB shadow mask, smooth barrel curvature, and a soft phosphor
// glow; while it is off the pixels are exactly what the engine drew (no post-processing).
// The bouncing keeps going either way, so you can compare the two looks side by side.
//
// Notice that the lines stay STRAIGHT through the toggle: barrel distortion is display-tier,
// so it runs on RGBA after palette resolve + upscale to the canvas size - not on the
// 320x240 index buffer - which avoids stair-step artifacts on diagonals.
//
// WHAT YOU WILL LEARN
//   - How to add and remove a STACK of post-process effects at runtime.
//   - That the effect chain is "free" when nothing is registered: the engine renders straight
//     to the screen with zero extra cost. The first call to BT.effectAdd allocates an off-
//     screen texture; the last BT.effectRemove or BT.effectClear frees it again.
//   - That you keep the SAME effect instances across toggles. Demos that destroy and
//     recreate them on every toggle would also work, but they would re-create the GPU
//     pipeline on every toggle - wasteful when the look is the same.
//   - The convenience of `BT.preset.crtPipBoy()`: returns a fresh array of pre-configured
//     display-tier effects so you do not have to wire them up by hand.
//
// HOW THE TOGGLE WORKS
// We measure time in ticks (60 per second). Every TOGGLE_PERIOD_TICKS the demo flips a
// boolean and either adds or removes the entire preset stack. A small kit panel shows
// "CRT: ON" or "CRT: OFF" so you always know which side you are looking at.
//
// SOFTWARE FALLBACK
// In software renderer mode, post-process effects are unavailable. The bouncing
// squares and color bars still animate; CRT toggle is disabled and the status panel
// explains why.
//
// Why auto-toggle instead of a button? This page focuses on the effect API, not controls.
// Auto-toggling keeps the ON/OFF comparison hands-free. Pointer and keyboard input are
// covered in the pointer and keyboard demos.

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 isAvailableisAvailable, import SOFTWARE_FALLBACK_NOTESOFTWARE_FALLBACK_NOTE } from './shared/post-process-backend.js'; import { import applyThemeapplyTheme, import uiui, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js'; // Internal pixel resolution. const const DISPLAY_W: 320DISPLAY_W = 320; const const DISPLAY_H: 240DISPLAY_H = 240; // Output drawing-buffer resolution. Setting it 4x larger than the logical size unlocks // the display-tier effects (barrel, scanlines, mask, etc.) and gives them enough output // pixels to render smoothly. Each logical pixel maps to a 4x4 output block. const const OUTPUT_W: 1280OUTPUT_W = 1280; const const OUTPUT_H: 960OUTPUT_H = 960; const const TARGET_FPS: 60TARGET_FPS = 60; // Palette indices. Index 0 is always transparent. const const C_BG: 1C_BG = 1; // Dark navy: the background fill. const const C_LABEL: 2C_LABEL = 2; // White: the corner label. const const C_RED: 3C_RED = 3; const const C_GREEN: 4C_GREEN = 4; const const C_BLUE: 5C_BLUE = 5; const const C_YELLOW: 6C_YELLOW = 6; const const C_CYAN: 7C_CYAN = 7; const const C_MAGENTA: 8C_MAGENTA = 8; const const C_OVERLAY_BAR: 9C_OVERLAY_BAR = 9; // Overlay row background // The five colors used for the bouncing squares. We list them in order so each // square gets a distinct color from the palette. const const SQUARE_COLORS: {}SQUARE_COLORS = [const C_RED: 3C_RED, const C_GREEN: 4C_GREEN, const C_BLUE: 5C_BLUE, const C_YELLOW: 6C_YELLOW, const C_MAGENTA: 8C_MAGENTA]; // How big each bouncing square is (in pixels). const const SQUARE_SIZE: 24SQUARE_SIZE = 24; // How many bouncing squares to show. const const SQUARE_COUNT: 5SQUARE_COUNT = 5; // How fast each square moves (pixels per tick). One value per square so the // motion looks irregular - if all five moved at the same speed, they would // line up vertically and the demo would look duller. const const SQUARE_SPEEDS: {}SQUARE_SPEEDS = [ 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
(2, 1),
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, 2),
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
(3, 1),
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, 3),
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
(2, 2),
]; // How many ticks between toggles. 120 ticks at 60 FPS = 2 seconds per state. // You should be able to read the "CRT: ON / OFF" label and watch the scene // switch at a leisurely pace. const const TOGGLE_PERIOD_TICKS: 120TOGGLE_PERIOD_TICKS = 120; // Static horizontal bars across the middle of the screen. They give the CRT scanlines // something high-contrast to chew on so the difference between ON and OFF is obvious. const const BAR_HEIGHT: 18BAR_HEIGHT = 18; const const BAR_GAP: 6BAR_GAP = 6; const const BAR_TOP: 60BAR_TOP = 60; const const BAR_COLORS: {}BAR_COLORS = [const C_RED: 3C_RED, const C_YELLOW: 6C_YELLOW, const C_GREEN: 4C_GREEN, const C_CYAN: 7C_CYAN, const C_BLUE: 5C_BLUE, const C_MAGENTA: 8C_MAGENTA]; /** @typedef {import('blit386').IBTDemo} IBTDemo */ /** @typedef {import('blit386').HardwareSettings} HardwareSettings */ /** * Toggle demo: a small animated scene with the CRT effect stack flipping on and off * every two seconds. Demonstrates the dynamic add/remove path of the post-process chain. * * The effect stack comes from `BT.preset.crtPipBoy()`, a one-line helper that returns a * fresh array of display-tier effects (BarrelDistortion + ChromaticAberration + ... + * Bloom). We hold onto the array so we can re-add the SAME instances on each toggle - * that way the GPU pipelines stay alive across toggles instead of being torn down and * rebuilt every two seconds. * * @implements {IBTDemo} */ class class Demo
Toggle demo: a small animated scene with the CRT effect stack flipping on and off every two seconds. Demonstrates the dynamic add/remove path of the post-process chain. The effect stack comes from `BT.preset.crtPipBoy()`, a one-line helper that returns a fresh array of display-tier effects (BarrelDistortion + ChromaticAberration + ... + Bloom). We hold onto the array so we can re-add the SAME instances on each toggle - that way the GPU pipelines stay alive across toggles instead of being torn down and rebuilt every two seconds.
@implementsIBTDemo
Demo
{
/** * Same 320x240 logical / 1280x960 output setup as the PipBoy CRT demo for display-tier CRT presets. * * @returns {Partial<HardwareSettings>} */ Demo.configure(): Partial<HardwareSettings>
Same 320x240 logical / 1280x960 output setup as the PipBoy CRT demo for display-tier CRT presets.
@returns
configure
() {
return { // Internal pixel-art resolution. Game logic and draws operate at this size. displaySize: Vector2idisplaySize: new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(const DISPLAY_W: 320DISPLAY_W, const DISPLAY_H: 240DISPLAY_H),
// drawingBufferSize is required for display-tier effects (barrel, scanlines, // mask, bloom). Flow: draw palette indices at 320x240, optional pixel-tier on // that r8uint buffer, then the engine resolves indices through the palette LUT // and upscales to RGBA at this size, then the display chain runs on RGBA. // Without this field, BT.effectAdd would throw for display-tier effects. drawingBufferSize: Vector2idrawingBufferSize: 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 OUTPUT_W: 1280OUTPUT_W, const OUTPUT_H: 960OUTPUT_H),
// Match output buffer size so the CRT picture is not capped at 960x720 CSS. 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
(const OUTPUT_W: 1280OUTPUT_W, const OUTPUT_H: 960OUTPUT_H),
// 'nearest' keeps each source pixel as a crisp 4x4 block. 'linear' would // soften them into bilinear-blended squishes - a different look, also valid. outputUpscaleFilter: stringoutputUpscaleFilter: 'nearest', targetFPS: numbertargetFPS: const TARGET_FPS: 60TARGET_FPS,
overlayStyle: {
    barPaletteIndex: number;
    textPaletteIndex: number;
    gapPaletteIndex: number;
}
overlayStyle
: {
barPaletteIndex: numberbarPaletteIndex: const C_OVERLAY_BAR: 9C_OVERLAY_BAR, textPaletteIndex: numbertextPaletteIndex: const C_LABEL: 2C_LABEL, gapPaletteIndex: numbergapPaletteIndex: const C_OVERLAY_BAR: 9C_OVERLAY_BAR, }, isOverlayTimingChartEnabled: booleanisOverlayTimingChartEnabled: true,
overlayTimingChartStyle: {
    updateBarPaletteIndex: number;
    renderBarPaletteIndex: number;
    warningPaletteIndex: number;
    errorPaletteIndex: number;
    tagPaletteIndex: number;
}
overlayTimingChartStyle
: {
updateBarPaletteIndex: numberupdateBarPaletteIndex: const C_CYAN: 7C_CYAN, renderBarPaletteIndex: numberrenderBarPaletteIndex: const C_YELLOW: 6C_YELLOW, warningPaletteIndex: numberwarningPaletteIndex: const C_MAGENTA: 8C_MAGENTA, errorPaletteIndex: numbererrorPaletteIndex: const C_RED: 3C_RED, tagPaletteIndex: numbertagPaletteIndex: const C_GREEN: 4C_GREEN, }, }; } /** * @returns {Promise<boolean>} */ async Demo.init(): Promise<boolean>
Called once after the selected rendering backend has been initialized. Load assets and prepare a demo state here.
@returns
init
() {
// Step 1: build the palette // A small, colorful set of scene colors in the low slots. Bright primaries make // the CRT effect visually obvious - soft pastels would look the same with or // without. The palette is 256 entries long so the shared UI theme can live in // the high slots (240-251), far away from the scene colors. const const palette: Palettepalette =
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.paletteCreate: (size?: number) => Palette
Creates a standalone palette instance.
@since1.0.3@paramsize - Palette size. Defaults to 256 colors.@returnsNew mutable palette.
paletteCreate
(256);
const palette: Palettepalette.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
(20, 30, 50, 255));
const palette: Palettepalette.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_LABEL: 2C_LABEL, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.white: Color32
Pure white color (255, 255, 255, 255). Cached frozen singleton - do not modify.
@returnsThe shared white Color32 instance.
white
);
const palette: Palettepalette.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_RED: 3C_RED, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.red: Color32
Pure red color (255, 0, 0, 255). Cached frozen singleton - do not modify.
@returnsThe shared red Color32 instance.
red
);
const palette: Palettepalette.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_GREEN: 4C_GREEN, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.green: Color32
Pure green color (0, 255, 0, 255). Cached frozen singleton - do not modify.
@returnsThe shared green Color32 instance.
green
);
const palette: Palettepalette.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_BLUE: 5C_BLUE, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.blue: Color32
Pure blue color (0, 0, 255, 255). Cached frozen singleton - do not modify.
@returnsThe shared blue Color32 instance.
blue
);
const palette: Palettepalette.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_YELLOW: 6C_YELLOW, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.yellow: Color32
Yellow color (255, 255, 0, 255). Cached frozen singleton - do not modify.
@returnsThe shared yellow Color32 instance.
yellow
);
const palette: Palettepalette.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_CYAN: 7C_CYAN, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.cyan: Color32
Cyan color (0, 255, 255, 255). Cached frozen singleton - do not modify.
@returnsThe shared cyan Color32 instance.
cyan
);
const palette: Palettepalette.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_MAGENTA: 8C_MAGENTA, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.magenta: Color32
Magenta color (255, 0, 255, 255). Cached frozen singleton - do not modify.
@returnsThe shared magenta Color32 instance.
magenta
);
const palette: Palettepalette.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: 9C_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, 15, 25, 220));
// Install the shared UI kit colors into slots 240-251. The kit draws the small // CRT status panel; the animated scene keeps its own bright primaries above. import applyThemeapplyTheme(const palette: Palettepalette);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.paletteSet: (palette: Palette) => 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
(const palette: Palettepalette);
this.Demo.effectsAvailable: anyeffectsAvailable = import isAvailableisAvailable(); if (this.Demo.effectsAvailable: anyeffectsAvailable) { // Step 2: build the CRT preset ONCE up front // BT.preset.crtPipBoy() returns a fresh array of pre-configured display-tier // effects (BarrelDistortion + ChromaticAberration + Interference + RollLine + // Scanlines + RGBMask + Vignette + Noise + Flicker + Bloom). // // We hold onto the array so we can re-add the SAME instances on each toggle. // Re-creating them every toggle would also work, but it would re-allocate the // GPU pipelines and uniform buffers each time - wasteful when the look is the // same. this.Demo.stack: {} | undefinedstack =
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
.
preset: {
    crtPipBoy: typeof crtPipBoy;
    amber: typeof amber;
    green: typeof green;
}
Pre-configured display-tier effect stacks ("looks"). Each function returns a fresh array of effects. Add them to the engine via {@link BT.effectAdd } .
@since1.0.3@examplefor (const fx of BT.preset.crtPipBoy()) { BT.effectAdd(fx); }
preset
.crtPipBoy: () => Effect[]
Preset bundle that recreates the original "PipBoy" CRT look using the decomposed display-tier effects. Order matters: barrel curvature applies before scanlines/mask so the curve carries the rest of the effects with it; bloom comes last so the phosphor glow blends across the already-modulated output. The returned effects can be added in order to the engine's display chain.
@since1.0.3@returnsArray of pre-configured display-tier effects.@examplefor (const fx of BT.preset.crtPipBoy()) { BT.effectAdd(fx); }
crtPipBoy
();
// Step 3: pick out the time-driven effects so update() can animate them // Some effects (RollLine, Noise, Interference) animate using a `time` field; // we filter the array once and remember the references so we don't iterate // the whole stack on every frame. this.Demo.timedEffects: anytimedEffects = this.Demo.stack: {}stack.filter((fx: anyfx) => 'time' in fx: anyfx); } else { this.Demo.stack: {} | undefinedstack = []; this.Demo.timedEffects: anytimedEffects = []; } // Step 4: start in the OFF state // PipBoy CRT demo already shows what the CRT looks like straight away; here it's nicer // to begin clean and then have the effect arrive after the first toggle. this.Demo.enabled: anyenabled = false; this.Demo.lastToggleTick: number | undefinedlastToggleTick =
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
;
// Step 5: place the bouncing squares // Place them evenly across the bottom half so they don't all start in the same // spot. Each square keeps its own position (pos) and velocity (vel) as Vector2i // instances - the engine convention for all pixel-level coordinates. this.Demo.squares: {} | undefinedsquares = []; for (let let i: numberi = 0; let i: numberi < const SQUARE_COUNT: 5SQUARE_COUNT; let i: numberi++) { const const startPos: Vector2istartPos = 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
(20 + let i: numberi * 50, 150 + (let i: numberi % 2) * 30);
this.Demo.squares: {}squares.push({ pos: Vector2ipos: const startPos: Vector2istartPos, // prevPos remembers where the square was at the START of the most recent // update() tick. render() blends between prevPos and pos using // BT.renderAlpha so each square glides smoothly between physics ticks // instead of jumping - see "Interpolating render state with renderAlpha" // in the engine's docs/api-game-loop.md. prevPos: Vector2iprevPos: const startPos: Vector2istartPos, vel: Vector2ivel: 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 SQUARE_SPEEDS: {}SQUARE_SPEEDS[let i: numberi].x, const SQUARE_SPEEDS: {}SQUARE_SPEEDS[let i: numberi].y),
color: anycolor: const SQUARE_COLORS: {}SQUARE_COLORS[let i: numberi % const SQUARE_COLORS: {}SQUARE_COLORS.length], }); } return true; } Demo.update(): void
Called zero or more times per frame at the fixed timestep declared by `targetFPS`. The accumulator pattern ensures the target rate is met on average, but a single frame may invoke this multiple times (catch-up) or not at all. Update simulation, timers, and input-driven state here. This is a hot path. Minimize allocations, reuse objects, and prefer in-place vector operations where possible. Avoid rendering work here; draw in `render()` instead.
update
() {
// 1. Time-based toggle (WebGPU only - effectAdd throws in software mode) // Every TOGGLE_PERIOD_TICKS we flip the boolean and either add or remove the // entire preset stack. The engine handles the GPU pipeline lifecycle for us. if (this.Demo.effectsAvailable: anyeffectsAvailable &&
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.lastToggleTick: number | undefinedlastToggleTick >= const TOGGLE_PERIOD_TICKS: 120TOGGLE_PERIOD_TICKS) {
this.Demo.lastToggleTick: number | undefinedlastToggleTick =
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.enabled: anyenabled = !this.Demo.enabled: anyenabled; if (this.Demo.enabled: anyenabled) { // Add every effect from the preset to the chain. Each one declares its // own tier ('display' for the CRT effects), so the engine routes them // automatically. for (const const fx: anyfx of this.Demo.stack: {} | undefinedstack) {
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
.effectAdd: (effect: Effect) => void
Appends a fullscreen post-processing effect to whichever chain matches its declared {@link Effect.tier } . - `tier='pixel'` -> pixel chain (logical resolution). - `tier='display'` -> display chain (output resolution); requires `drawingBufferSize` in effective hardware settings (`configure()` or `defaultConfig()`). Effects run in registration order within each tier. The pixel chain runs first, followed by the upscale pass, followed by the display chain. Each {@link Effect } instance owns its own GPU resources and may be mutated each frame from demo code.
@since1.0.3@parameffect - Effect instance to append. When the engine is not ready, shows a canvas error instead of throwing.
effectAdd
(const fx: anyfx);
}
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.assignTag: (label?: string) => void
Places a labeled marker on the overlay timing chart at the current tick. Requires `isOverlayTimingChartEnabled: true` in `configure()`. Tags scroll with the chart history and are pruned when they leave the visible window. Empty labels become `"Untitled"`. Chart width resets add an automatic `"Start"` tag.
@since1.1.0@paramlabel - Short event name (for example `'Round start'`).
assignTag
('CRT: ON');
} else { // Remove them all. When the last effect is removed, the engine drops // the off-screen ping-pong textures and reverts to drawing straight // through the upscale pass to the swap chain. for (const const fx: anyfx of this.Demo.stack: {} | undefinedstack) {
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
.effectRemove: (effect: Effect) => void
Removes a previously registered post-processing effect. Searches both tiers and disposes the effect from whichever chain holds it. Removing an effect that was never added is a no-op.
@since1.0.3@parameffect - Effect instance to remove. When the engine is not ready, shows a canvas error instead of throwing.
effectRemove
(const fx: anyfx);
}
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.assignTag: (label?: string) => void
Places a labeled marker on the overlay timing chart at the current tick. Requires `isOverlayTimingChartEnabled: true` in `configure()`. Tags scroll with the chart history and are pruned when they leave the visible window. Empty labels become `"Untitled"`. Chart width resets add an automatic `"Start"` tag.
@since1.1.0@paramlabel - Short event name (for example `'Round start'`).
assignTag
('CRT: OFF');
} } if (this.Demo.effectsAvailable: anyeffectsAvailable) { // The CRT shaders use `time` for their rolling line and noise. Feed it seconds. // Safe to set even when the effects are not in the chain - the field is just a // number on the JS instance until the next encode pass reads it. const const seconds: numberseconds =
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
/ const TARGET_FPS: 60TARGET_FPS;
for (const const fx: anyfx of this.Demo.timedEffects: anytimedEffects) { const fx: anyfx.time = const seconds: numberseconds; } } // 2. Move each square and bounce off the screen edges // Vector2i is immutable, so we assign new instances rather than mutating components. // Reassigning sq.pos and sq.vel is allowed for per-frame demo state (see CLAUDE.md). for (const const sq: anysq of this.Demo.squares: {} | undefinedsquares) { // Snapshot "where was this square a moment ago" BEFORE moving it, so // render() can draw a smooth in-between position instead of a pop. const sq: anysq.prevPos = const sq: anysq.pos; const sq: anysq.pos = 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 sq: anysq.pos.x + const sq: anysq.vel.x, const sq: anysq.pos.y + const sq: anysq.vel.y);
// Bounce against the left/right walls. We compare against [0, DISPLAY_W - SQUARE_SIZE] // because the square's anchor is its top-left corner. if (const sq: anysq.pos.x <= 0 || const sq: anysq.pos.x >= const DISPLAY_W: 320DISPLAY_W - const SQUARE_SIZE: 24SQUARE_SIZE) { const sq: anysq.vel = 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 sq: anysq.vel.x, const sq: anysq.vel.y);
// Nudge the position back inside the playfield so we don't bounce twice. const sq: anysq.pos = 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
(Math.max(0, Math.min(const sq: anysq.pos.x, const DISPLAY_W: 320DISPLAY_W - const SQUARE_SIZE: 24SQUARE_SIZE)), const sq: anysq.pos.y);
} // Same for the top/bottom walls. We let the squares roam the full screen. if (const sq: anysq.pos.y <= 0 || const sq: anysq.pos.y >= const DISPLAY_H: 240DISPLAY_H - const SQUARE_SIZE: 24SQUARE_SIZE) { const sq: anysq.vel = 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 sq: anysq.vel.x, -const sq: anysq.vel.y);
const sq: anysq.pos = 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 sq: anysq.pos.x, Math.max(0, Math.min(const sq: anysq.pos.y, const DISPLAY_H: 240DISPLAY_H - const SQUARE_SIZE: 24SQUARE_SIZE)));
} } } Demo.render(): void
Called once per `requestAnimationFrame` tick (browser refresh rate). Issue all draw calls for the current frame here. When {@link HardwareSettings.isOverlayEnabled } is `true` (default), the engine draws a screen-space overlay HUD after this method returns (present FPS, target FPS, draw calls, frame/update()/render() timings, backend, demo title). Optional {@link overlayRows } adds stacked bars above the footer. Demos do not need to duplicate engine overlay text. Reserve about ~42 px at the top and space for the bottom palette grid (or ~13 px when {@link HardwareSettings.isOverlayPaletteEnabled } is `false`) at the bottom (plus ~14 px per custom overlay row) for overlay bars, or disable the overlay in `configure()` when using custom full-screen HUD layouts. This is a hot path. Batch draws by texture to reduce GPU state changes and reuse Color32/Vector2i instances instead of allocating per frame. Avoid mutating the simulation state here unless it is strictly visual.
render
() {
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
(const C_BG: 1C_BG);
// Draw the high-contrast horizontal bars across the middle. They're static - // the CRT scanlines and shadow mask interact strongly with bright horizontals. for (let let i: numberi = 0; let i: numberi < const BAR_COLORS: {}BAR_COLORS.length; let i: numberi++) { const const y: numbery = const BAR_TOP: 60BAR_TOP + let i: numberi * (const BAR_HEIGHT: 18BAR_HEIGHT + const BAR_GAP: 6BAR_GAP);
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
(20, const y: numbery, const DISPLAY_W: 320DISPLAY_W - 40, const BAR_HEIGHT: 18BAR_HEIGHT), const BAR_COLORS: {}BAR_COLORS[let i: numberi]);
} // Draw the bouncing squares on top of the bars. Vector2i.lerp() blends prevPos // toward pos by BT.renderAlpha, so a square's drawn position matches this exact // render moment instead of only its last-tick position - smoother motion when // render() runs at a different rate than update() (see docs/api-game-loop.md // in the engine repo for the full explanation). for (const const sq: anysq of this.Demo.squares: {} | undefinedsquares) { 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
(const sq: anysq.prevPos, const sq: anysq.pos,
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.renderAlpha: number
Fractional progress between the last completed fixed update and the next. Intended for interpolating render state between fixed-update steps.
@since1.3.0@returnsInterpolation alpha in `[0, 1)`.
renderAlpha
);
const 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 drawPos: Vector2idrawPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
, const drawPos: Vector2idrawPos.Vector2i.y: number
Vertical component (defaults to 0).
y
, const SQUARE_SIZE: 24SQUARE_SIZE, const SQUARE_SIZE: 24SQUARE_SIZE), const sq: anysq.color);
} // Status readout: a small panel from the shared UI kit, drawn last so it sits on // top of the moving squares. Like everything the demo draws, it lives on the // logical buffer, so the CRT effects warp and glow the panel too - a nice way to // read the toggle even when you cannot see the scanlines up close. import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT); import uiui.panel(); if (this.Demo.effectsAvailable: anyeffectsAvailable) { // 'accent' (phosphor green) while the stack is live, 'dim' gray while it rests. import uiui.label(this.Demo.enabled: anyenabled ? 'CRT: ON' : 'CRT: OFF', { color: stringcolor: this.Demo.enabled: anyenabled ? 'accent' : 'dim' }); import uiui.label('Auto-toggles every 2s', { color: stringcolor: 'dim' }); } else { // Software renderer: no post-processing at all, so explain the reduced mode. import uiui.label('CRT: N/A', { color: stringcolor: 'dim' }); import uiui.label(import SOFTWARE_FALLBACK_NOTESOFTWARE_FALLBACK_NOTE, { color: stringcolor: 'warm' }); } import uiui.end(); } } 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
Toggle demo: a small animated scene with the CRT effect stack flipping on and off every two seconds. Demonstrates the dynamic add/remove path of the post-process chain. The effect stack comes from `BT.preset.crtPipBoy()`, a one-line helper that returns a fresh array of display-tier effects (BarrelDistortion + ChromaticAberration + ... + Bloom). We hold onto the array so we can re-add the SAME instances on each toggle - that way the GPU pipelines stay alive across toggles instead of being torn down and rebuilt every two seconds.
@implementsIBTDemo
Demo
);