/*

CRT Toggle: turn the post-process effects on and off in flight.
@description Turn a whole CRT post-process stack on and off at runtime, switching every two seconds as it runs.
@pageTitle BLIT386 Demo – CRT Toggle

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

Guides:
  - Post-Process Effects: https://blit386.dev/docs/guides/post-process-effects#presets
  - Game Loop: https://blit386.dev/docs/guides/game-loop#smoothing-motion-between-updates
  - Overlay: https://blit386.dev/docs/guides/overlay#optional-bands

Reference:
  - Rendering: https://blit386.dev/docs/api/rendering#post-process-effects
  - Game Loop: https://blit386.dev/docs/api/game-loop#interpolating-render-state-with-renderalpha
  - Core: https://blit386.dev/docs/api/core#requested-vs-active-backend
  - Overlay: https://blit386.dev/docs/api/overlay#event-tags

---

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

A simple, colorful scene: six wide horizontal bars across the middle of a 320x240 screen
and five squares bouncing around the whole screen on top of them. Every two seconds the
CRT preset flips on or off by itself. While it is on you see scanlines, an RGB shadow
mask, a gentle barrel curve, a slight red/blue color fringe, a bright band slowly rolling
down, faint grain, and a soft phosphor glow; while it is off the picture is just the crisp
4x upscale of what the engine drew. The squares keep bouncing either way, so you can
compare the two looks back to back.

A small panel in the top-left corner says "CRT: ON" or "CRT: OFF" so you always know which
side you are looking at. Notice that the bars stay straight through the toggle: barrel
distortion is a display-tier effect, so it bends the 1280x960 RGBA picture after the
palette resolve and upscale - not the 320x240 index buffer - which keeps diagonals free of
stair-steps.

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

- How to add and remove a whole stack of post-process effects at runtime with
  BT.effectAdd(effect) and BT.effectRemove(effect).
- That an empty chain costs nothing extra: the first BT.effectAdd allocates the display
  chain's off-screen render targets, and removing the last effect (or BT.effectClear())
  frees them again, so the engine goes back to drawing straight through the upscale pass.
- That the same effect instances are kept across toggles. Creating new ones on every
  toggle would also work, but it would rebuild the GPU pipelines every two seconds for an
  identical look.
- BT.preset.crtPipBoy(): one call that returns a fresh array of nine pre-configured
  display-tier effects, so you do not have to wire them up by hand.
- BT.renderAlpha and Vector2i.lerp() for drawing the squares between two physics ticks.
- Checking BT.activeBackend (through the shared isAvailable() helper) before touching the
  effect API, because the software renderer has no post-processing.

HOW THE TOGGLE WORKS
--------------------

Time is measured in ticks (60 per second). Every TOGGLE_PERIOD_TICKS (120, so two seconds)
update() flips a boolean and either adds or removes every effect in the preset array. The
effects that animate over time (RollLine, Noise, Interference) get their `time` field fed
every tick whether or not they are in the chain, so they are never stale when they come
back.

SOFTWARE FALLBACK
-----------------

In software renderer mode (no WebGPU, or ?backend=software in the URL) post-process
effects are unavailable. The squares and bars still animate; the toggle is disabled and
the panel says "CRT: N/A" with a one-line explanation.

Why auto-toggle instead of a button? This demo is about the effect API, not controls.
Auto-toggling keeps the ON/OFF comparison hands-free. Pointer and keyboard input have
their own demos later in the series.

TRY THIS
--------

- Press the backquote key (`) to open the engine overlay. Its timing chart gets a "CRT:
  ON" or "CRT: OFF" tag at every toggle, so you can see whether the render bar changes
  while the effects are live. The overlay draws its bar and text with this demo's own
  palette slots.
- Add ?backend=software to the URL to see the fallback panel.

*/

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;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
, class Rect2i
Integer rectangle for pixel-perfect bounds and regions. Used throughout the engine for sprite regions, display-space bounds, and zero-allocation geometry helpers. Both convenience getters and allocation-free `*To()` helpers are provided so callers can choose between readability and hot-path efficiency.
@since0.1.0
Rect2i
, 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, ...) 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 engine overlay's text 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; // the engine overlay's bar and gap background // The five colors of the bouncing squares, in order, so each square gets its own. 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, in pixels per tick. One value per square so the motion // looks irregular - if all five moved at the same speed they would line up 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 ticks per second is 2 seconds per state // - long enough to read the "CRT: ON / OFF" label and watch the scene switch. 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 work 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]; // A small animated scene with the CRT effect stack flipping on and off every two seconds, // showing the dynamic add/remove path of the post-process chain. The stack comes from // BT.preset.crtPipBoy(), which returns a fresh array of display-tier effects // (BarrelDistortion, ChromaticAberration, Interference, RollLine, Scanlines, RGBMask, // Noise, Flicker, Bloom). The array is kept so the same instances are re-added on each // toggle; that way the GPU pipelines stay alive instead of being torn down and rebuilt // every two seconds. class class DemoDemo { // Same 320x240 logical / 1280x960 output setup as the PipBoy CRT demo, which the // display-tier CRT preset needs.
Demo.configure(): {
    displaySize: Vector2i;
    drawingBufferSize: Vector2i;
    maxCanvasSize: Vector2i;
    outputUpscaleFilter: string;
    targetFPS: number;
    overlayStyle: {
        barPaletteIndex: number;
        textPaletteIndex: number;
        gapPaletteIndex: number;
    };
    isOverlayTimingChartEnabled: boolean;
    overlayTimingChartStyle: {
        updateBarPaletteIndex: number;
        renderBarPaletteIndex: number;
        warningPaletteIndex: number;
        errorPaletteIndex: number;
        tagPaletteIndex: number;
    };
}
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, run any pixel-tier // effects on that r8uint buffer, then the engine resolves indices through the // palette and upscales to RGBA at this size, then the display chain runs on // the RGBA image. Without this field BT.effectAdd throws 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 the output buffer size so the CRT picture is not capped at the // default 960x720 CSS size. 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 a crisp 4x4 block. 'linear' would soften // them into bilinear-blended squares - a different look, also valid. outputUpscaleFilter: stringoutputUpscaleFilter: 'nearest', targetFPS: numbertargetFPS: const TARGET_FPS: 60TARGET_FPS, // The engine overlay draws with this demo's own palette slots, and its timing // chart is on so the "CRT: ON / OFF" tags have somewhere to land.
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, }, }; } async Demo.init(): unknowninit() { // Step 1: build the palette. A small, colorful set of scene colors in the low // slots. Bright primaries make the CRT effect obvious - soft pastels would look // the same with or without it. 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;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.paletteCreate: (size?: number) => Palette
Creates a standalone palette instance.
@since1.0.3@paramsize - Palette size. Defaults to 256 colors.@returnsNew mutable palette.
paletteCreate
(256);
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 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;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.paletteSet: (palette: Palette) => void
Stores the active engine palette. Use this to swap the **entire palette** (e.g. switch between a day and night theme). After this call the renderer uploads the new palette uniform on the next frame. **Palette-value swap (change what a slot looks like):** mutate the live {@link BT.palette } in place with `palette.set(slot, newColor)`. The renderer uploads dirty slots on the next frame; no `paletteSet()` or {@link BT.spritesRefresh } needed. **Palette-layout swap (same colors, different slot positions):** build a new palette with the same colors at new indices, call `paletteSet()`, then call {@link BT.spritesRefresh } so every sprite sheet re-maps its original RGBA pixels against the new slot layout.
@since1.0.3@parampalette - Palette to make active.
paletteSet
(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 nine pre-configured display-tier effects // (BarrelDistortion, ChromaticAberration, Interference, RollLine, Scanlines, // RGBMask, Noise, Flicker, Bloom). Keeping the array means the same instances // are re-added 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;
    ... 124 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 from a `time` field; filter // the array once and keep the references instead of walking the whole stack // every tick. 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. The PipBoy CRT demo shows the CRT look straight // away; here it is nicer to begin clean and let the effect arrive on 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;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
;
// Step 5: place the bouncing squares evenly across the bottom half so they do not // all start in the same spot. Each square keeps its position (pos) and velocity // (vel) as Vector2i instances - the engine convention for pixel 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 with // BT.renderAlpha so each square glides between physics ticks instead of // jumping - see the Game Loop guide linked in the header. 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(): voidupdate() { // 1. Time-based toggle (WebGPU only - effectAdd throws in software mode). Every // TOGGLE_PERIOD_TICKS flip the boolean and either add or remove the entire preset // stack. The engine handles the GPU pipeline lifecycle. 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;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
- this.Demo.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;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
;
this.Demo.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 it // 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;
    ... 124 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;
    ... 124 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 goes, 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;
    ... 124 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;
    ... 124 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 the rolling line, noise, and interference. // 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;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
/ 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 it off the screen edges. New Vector2i instances // are assigned rather than components mutated; reassigning sq.pos and sq.vel is // fine for per-frame demo state. for (const const sq: anysq of this.Demo.squares: {} | undefinedsquares) { // Snapshot where the square was 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 and right walls. The valid range is [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 it does not 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 and bottom walls. 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(): voidrender() {
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.clear: (paletteIndex: number) => void
Sets the frame clear color using a palette index. The renderer uses this color when clearing the full display at the start of the next frame.
@since0.1.0@parampaletteIndex - Palette index for the full-screen clear pass.
clear
(const C_BG: 1C_BG);
// The high-contrast horizontal bars across the middle. They are 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;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawRectFill: (rect: Rect2i, paletteIndex: number) => void
Draws a filled rectangle.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRectFill
(new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2i
Creates an integer rectangle, truncating all inputs toward zero.
@paramx - Left-edge X coordinate (defaults to 0).@paramy - Top-edge Y coordinate (defaults to 0).@paramwidth - Width in pixels (defaults to 0).@paramheight - Height in pixels (defaults to 0).
Rect2i
(20, const y: numbery, const DISPLAY_W: 320DISPLAY_W - 40, const BAR_HEIGHT: 18BAR_HEIGHT), const BAR_COLORS: {}BAR_COLORS[let i: numberi]);
} // 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(). 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;
    ... 124 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;
    ... 124 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawRectFill: (rect: Rect2i, paletteIndex: number) => void
Draws a filled rectangle.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRectFill
(new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2i
Creates an integer rectangle, truncating all inputs toward zero.
@paramx - Left-edge X coordinate (defaults to 0).@paramy - Top-edge Y coordinate (defaults to 0).@paramwidth - Width in pixels (defaults to 0).@paramheight - Height in pixels (defaults to 0).
Rect2i
(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 handy 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 DemoDemo);