// Palette Presets: six built-in color sets you can load instantly.
// @description Six built-in color sets, including VGA, CGA, and C64, you can load into the active palette instantly.
//
// Part of the BLIT386 series.
//
// Prerequisites:
//   Basics     https://demos.blit386.dev/basics
//   Primitives https://demos.blit386.dev/primitives
//   Colors     https://demos.blit386.dev/colors
//   Fonts      https://demos.blit386.dev/fonts
//     (text drawing basics with BT.systemPrint; guide: https://blit386.dev/docs/guides/bitmap-fonts)
//
// Live version: https://demos.blit386.dev/palette-presets
// Guide: https://blit386.dev/docs/guides/palette-presets
//
// WHAT IS A PALETTE PRESET?
//
// In all the earlier demos we built our palette by hand:
//   palette.set(1, new Color32(255, 0, 0)); // My red.
//   palette.set(2, new Color32(0, 255, 0)); // My green.
//
// BLIT386 ships with six "preset" palettes - ready-made color sets based on
// real hardware from the history of video games:
//
//   Game Boy    4 colors   (1989 Nintendo handheld - shades of green)
//   CGA        16 colors   (1981 IBM PC graphics card - loud, iconic)
//   C64        16 colors   (1982 Commodore 64 - earthy, distinctive)
//   PICO-8     16 colors   (2015 fantasy console - soft, retro feel)
//   NES        56 colors   (1983 Nintendo console - wide but limited)
//   VGA       256 colors   (1987 IBM PC graphics standard - rich range)
//
// A preset palette gives you instant authentic retro style.
//
// You can also NAME slots using setNamed() / getNamed() - like labeling paint cans
// instead of just numbering them. "live-swatch-0" is easier to remember than slot 200.
//
// The title strip, row captions, and the live-view panel are drawn with the shared demo
// UI kit (src/shared/ui.js); the swatch artwork itself stays plain BT.drawRectFill calls.
//
// WHAT YOU WILL SEE:
//   - A row of colored swatches for each preset.
//   - The preset name and slot count next to each row.
//   - A "live view" panel that auto-cycles through all six presets every 2 seconds.
//   - Named slots: the live view labels its swatch slots with palette.setNamed().
//   - Current preset name and color count = engine overlay row above the FPS bar.

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
,
type Palette = Palette
class Palette
Mutable palette of indexed {@link Color32 } entries. The palette is the central color authority for all rendering: - **Index 0 is always transparent.** It is initialized with `Color32.transparent` and cannot be set to an opaque color. The primitive and sprite shaders discard any fragment whose palette index resolves to alpha 0. - **Variable sizes:** valid sizes are `2, 4, 16, 32, 64, 128, 256`. The active size determines the range for `set()` / `get()` and named-color lookups. - **Fixed GPU layout:** `toFloat32Array()` always outputs `256 * 4` floats so the renderer can upload a stable 4 KB uniform block regardless of palette size. Slots beyond the active size are padded with transparent black. - **Named aliases:** optional string tags map human-readable names to indices, e.g. `setNamed('player', 3)`. They carry no runtime cost when unused. - **Mutable by design:** palette-effect features modify entries in place. Use `clone()` when a snapshot is needed before modification.
@since1.0.3@changed1.7.0 Added the `fillBlock` method, writing a transformed block of colors into contiguous slots via `set()` and returning the next free slot, so consecutive blocks chain.
Palette
, class Rect2i
Integer rectangle for pixel-perfect bounds and regions. Used throughout the engine for sprite regions, display-space bounds, and zero-allocation geometry helpers. Both convenience getters and allocation-free `*To()` helpers are provided so callers can choose between readability and hot-path efficiency.
@since0.1.0
Rect2i
, class Vector2i
Integer 2D vector for pixel-perfect positioning. Used for points, sizes, directions, and camera offsets throughout the engine. The API includes both allocation-free `*To()` / `*InPlace()` variants and convenience methods that return new vectors.
@since0.1.0
Vector2i
} from 'blit386';
import { import applyThemeapplyTheme, import uiui, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js'; /** @typedef {import('blit386').IBTDemo} IBTDemo */ /** @typedef {import('blit386').HardwareSettings} HardwareSettings */ /** @typedef {import('blit386').Palette} Palette */ // How many ticks to show each preset in the live view (2 seconds at 60 FPS = 120 ticks). const const LIVE_SWITCH_TICKS: 120LIVE_SWITCH_TICKS = 120; // Maximum number of swatches to show per row (to avoid running off screen). const const MAX_SWATCHES_PER_ROW: 32MAX_SWATCHES_PER_ROW = 32; // Swatch size in pixels. const const SWATCH_W: 7SWATCH_W = 7; const const SWATCH_H: 14SWATCH_H = 14; // First palette slot of the 16 live-view preview swatches (slots 200..215). const const LIVE_SWATCH_SLOT: 200LIVE_SWATCH_SLOT = 200; // Where the live-view panel sits on screen, in pixels from the top-left corner. const const LIVE_PANEL_X: 6LIVE_PANEL_X = 6; const const LIVE_PANEL_Y: 152LIVE_PANEL_Y = 152; // Shared UI theme slots used by configure(). // // applyTheme() (see init()) writes the twelve shared UI colors into palette slots // 240..251 - its default start slot, safely above this demo's swatch slots (10..181) // and live-view slots (200..215). configure() runs BEFORE init(), so the overlay // styles below have to name those slots as plain numbers instead of reading this.theme. const const THEME_BG: 240THEME_BG = 240; // Deep navy screen background. const const THEME_HEADER: 246THEME_HEADER = 246; // Warm amber header text. const const THEME_ACCENT: 247THEME_ACCENT = 247; // Phosphor green accent. const const THEME_WARM: 248THEME_WARM = 248; // Warm orange (warnings). const const THEME_INFO: 249THEME_INFO = 249; // Info blue. /** * Demonstrates the six built-in palette presets and named palette slots. * * @implements {IBTDemo} */ class class Demo
Demonstrates the six built-in palette presets and named palette slots.
@implementsIBTDemo
Demo
{
// The main palette used for UI and the live preview. /** @type {Palette | null} */ Demo.palette: Palette | null
@type{Palette | null}
palette
= null;
// Palette slots of the shared UI theme colors, filled by applyTheme() in init(). Demo.theme: nulltheme = null; // The six preset palette objects, loaded in init(). Demo.presets: {}presets = []; // Name strings for each preset (for display). Demo.presetNames: {}presetNames = ['Game Boy', 'CGA', 'C64', 'PICO-8', 'NES', 'VGA']; // Which preset index (0..5) is currently shown in the live view. Demo.currentPresetIndex: numbercurrentPresetIndex = 0; // Tick number when we last switched the live view. Demo.lastSwitchTick: numberlastSwitchTick = 0; // Palette slot offsets for each preset's swatch row (filled in init()). Demo.swatchOffsets: {}swatchOffsets = []; // Reused every frame for the overlay (current live-view preset). Demo.overlayRowData: {}overlayRowData = [{ leftText: stringleftText: 'Current Game Boy - 4 colors', textPaletteIndex: numbertextPaletteIndex: const THEME_HEADER: 246THEME_HEADER }]; /** * Wider canvas, overlay palette grid (64 columns), and timing chart colors. * * The overlay bar, text, and timing chart all borrow shared UI theme slots * (240..251, written by applyTheme() in init()) so the whole demo uses one * consistent color scheme. * * @returns {Partial<HardwareSettings>} */ Demo.configure(): Partial<HardwareSettings>
Wider canvas, overlay palette grid (64 columns), and timing chart colors. The overlay bar, text, and timing chart all borrow shared UI theme slots (240..251, written by applyTheme() in init()) so the whole demo uses one consistent color scheme.
@returns
configure
() {
return { displaySize: Vector2idisplaySize: new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(520, 390),
maxCanvasSize: Vector2imaxCanvasSize: new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(520 * 2, 390 * 2),
isOverlayPaletteEnabled: booleanisOverlayPaletteEnabled: true, overlayPaletteColumns: numberoverlayPaletteColumns: 64,
overlayStyle: {
    barPaletteIndex: number;
    textPaletteIndex: number;
    gapPaletteIndex: number;
}
overlayStyle
: {
barPaletteIndex: numberbarPaletteIndex: const THEME_BG: 240THEME_BG, textPaletteIndex: numbertextPaletteIndex: const THEME_HEADER: 246THEME_HEADER, gapPaletteIndex: numbergapPaletteIndex: const THEME_BG: 240THEME_BG, }, isOverlayTimingChartEnabled: booleanisOverlayTimingChartEnabled: true,
overlayTimingChartStyle: {
    updateBarPaletteIndex: number;
    renderBarPaletteIndex: number;
    warningPaletteIndex: number;
    errorPaletteIndex: number;
    tagPaletteIndex: number;
}
overlayTimingChartStyle
: {
updateBarPaletteIndex: numberupdateBarPaletteIndex: const THEME_ACCENT: 247THEME_ACCENT, renderBarPaletteIndex: numberrenderBarPaletteIndex: const THEME_INFO: 249THEME_INFO, warningPaletteIndex: numberwarningPaletteIndex: const THEME_HEADER: 246THEME_HEADER, errorPaletteIndex: numbererrorPaletteIndex: const THEME_WARM: 248THEME_WARM, tagPaletteIndex: numbertagPaletteIndex: const THEME_HEADER: 246THEME_HEADER, }, }; } /** * Loads all six factory presets, copies swatch colors into one UI palette, * installs the shared UI theme, and names the live-view swatch slots. * * @returns {Promise<boolean>} */ async Demo.init(): Promise<boolean>
Loads all six factory presets, copies swatch colors into one UI palette, installs the shared UI theme, and names the live-view swatch slots.
@returns
init
() {
console.log('[PalettePresetsDemo] Initializing...'); // Load all six preset palettes // These are static factory methods that return a ready-made Palette object. // Think of them as pre-sorted boxes of paint for specific retro styles. this.Demo.presets: {}presets = [ class Palette
Mutable palette of indexed {@link Color32 } entries. The palette is the central color authority for all rendering: - **Index 0 is always transparent.** It is initialized with `Color32.transparent` and cannot be set to an opaque color. The primitive and sprite shaders discard any fragment whose palette index resolves to alpha 0. - **Variable sizes:** valid sizes are `2, 4, 16, 32, 64, 128, 256`. The active size determines the range for `set()` / `get()` and named-color lookups. - **Fixed GPU layout:** `toFloat32Array()` always outputs `256 * 4` floats so the renderer can upload a stable 4 KB uniform block regardless of palette size. Slots beyond the active size are padded with transparent black. - **Named aliases:** optional string tags map human-readable names to indices, e.g. `setNamed('player', 3)`. They carry no runtime cost when unused. - **Mutable by design:** palette-effect features modify entries in place. Use `clone()` when a snapshot is needed before modification.
@since1.0.3@changed1.7.0 Added the `fillBlock` method, writing a transformed block of colors into contiguous slots via `set()` and returning the next free slot, so consecutive blocks chain.
Palette
.Palette.gameboy(): Palette
Creates a four-shade Game Boy palette.
@returnsNew Game Boy preset palette.
gameboy
(), // 4 shades of green-gray.
class Palette
Mutable palette of indexed {@link Color32 } entries. The palette is the central color authority for all rendering: - **Index 0 is always transparent.** It is initialized with `Color32.transparent` and cannot be set to an opaque color. The primitive and sprite shaders discard any fragment whose palette index resolves to alpha 0. - **Variable sizes:** valid sizes are `2, 4, 16, 32, 64, 128, 256`. The active size determines the range for `set()` / `get()` and named-color lookups. - **Fixed GPU layout:** `toFloat32Array()` always outputs `256 * 4` floats so the renderer can upload a stable 4 KB uniform block regardless of palette size. Slots beyond the active size are padded with transparent black. - **Named aliases:** optional string tags map human-readable names to indices, e.g. `setNamed('player', 3)`. They carry no runtime cost when unused. - **Mutable by design:** palette-effect features modify entries in place. Use `clone()` when a snapshot is needed before modification.
@since1.0.3@changed1.7.0 Added the `fillBlock` method, writing a transformed block of colors into contiguous slots via `set()` and returning the next free slot, so consecutive blocks chain.
Palette
.Palette.cga(): Palette
Creates the classic CGA 16-color palette.
@returnsNew CGA preset palette.
cga
(), // 16 loud IBM PC colors.
class Palette
Mutable palette of indexed {@link Color32 } entries. The palette is the central color authority for all rendering: - **Index 0 is always transparent.** It is initialized with `Color32.transparent` and cannot be set to an opaque color. The primitive and sprite shaders discard any fragment whose palette index resolves to alpha 0. - **Variable sizes:** valid sizes are `2, 4, 16, 32, 64, 128, 256`. The active size determines the range for `set()` / `get()` and named-color lookups. - **Fixed GPU layout:** `toFloat32Array()` always outputs `256 * 4` floats so the renderer can upload a stable 4 KB uniform block regardless of palette size. Slots beyond the active size are padded with transparent black. - **Named aliases:** optional string tags map human-readable names to indices, e.g. `setNamed('player', 3)`. They carry no runtime cost when unused. - **Mutable by design:** palette-effect features modify entries in place. Use `clone()` when a snapshot is needed before modification.
@since1.0.3@changed1.7.0 Added the `fillBlock` method, writing a transformed block of colors into contiguous slots via `set()` and returning the next free slot, so consecutive blocks chain.
Palette
.Palette.c64(): Palette
Creates the Commodore 64 16-color palette.
@returnsNew C64 preset palette.
c64
(), // 16 Commodore 64 colors.
class Palette
Mutable palette of indexed {@link Color32 } entries. The palette is the central color authority for all rendering: - **Index 0 is always transparent.** It is initialized with `Color32.transparent` and cannot be set to an opaque color. The primitive and sprite shaders discard any fragment whose palette index resolves to alpha 0. - **Variable sizes:** valid sizes are `2, 4, 16, 32, 64, 128, 256`. The active size determines the range for `set()` / `get()` and named-color lookups. - **Fixed GPU layout:** `toFloat32Array()` always outputs `256 * 4` floats so the renderer can upload a stable 4 KB uniform block regardless of palette size. Slots beyond the active size are padded with transparent black. - **Named aliases:** optional string tags map human-readable names to indices, e.g. `setNamed('player', 3)`. They carry no runtime cost when unused. - **Mutable by design:** palette-effect features modify entries in place. Use `clone()` when a snapshot is needed before modification.
@since1.0.3@changed1.7.0 Added the `fillBlock` method, writing a transformed block of colors into contiguous slots via `set()` and returning the next free slot, so consecutive blocks chain.
Palette
.Palette.pico8(): Palette
Creates the PICO-8 16-color palette.
@returnsNew PICO-8 preset palette.
pico8
(), // 16 soft fantasy console colors.
class Palette
Mutable palette of indexed {@link Color32 } entries. The palette is the central color authority for all rendering: - **Index 0 is always transparent.** It is initialized with `Color32.transparent` and cannot be set to an opaque color. The primitive and sprite shaders discard any fragment whose palette index resolves to alpha 0. - **Variable sizes:** valid sizes are `2, 4, 16, 32, 64, 128, 256`. The active size determines the range for `set()` / `get()` and named-color lookups. - **Fixed GPU layout:** `toFloat32Array()` always outputs `256 * 4` floats so the renderer can upload a stable 4 KB uniform block regardless of palette size. Slots beyond the active size are padded with transparent black. - **Named aliases:** optional string tags map human-readable names to indices, e.g. `setNamed('player', 3)`. They carry no runtime cost when unused. - **Mutable by design:** palette-effect features modify entries in place. Use `clone()` when a snapshot is needed before modification.
@since1.0.3@changed1.7.0 Added the `fillBlock` method, writing a transformed block of colors into contiguous slots via `set()` and returning the next free slot, so consecutive blocks chain.
Palette
.Palette.nes(): Palette
Creates a 64-slot NES palette.
@returnsNew NES preset palette.
nes
(), // 56 Nintendo console colors.
class Palette
Mutable palette of indexed {@link Color32 } entries. The palette is the central color authority for all rendering: - **Index 0 is always transparent.** It is initialized with `Color32.transparent` and cannot be set to an opaque color. The primitive and sprite shaders discard any fragment whose palette index resolves to alpha 0. - **Variable sizes:** valid sizes are `2, 4, 16, 32, 64, 128, 256`. The active size determines the range for `set()` / `get()` and named-color lookups. - **Fixed GPU layout:** `toFloat32Array()` always outputs `256 * 4` floats so the renderer can upload a stable 4 KB uniform block regardless of palette size. Slots beyond the active size are padded with transparent black. - **Named aliases:** optional string tags map human-readable names to indices, e.g. `setNamed('player', 3)`. They carry no runtime cost when unused. - **Mutable by design:** palette-effect features modify entries in place. Use `clone()` when a snapshot is needed before modification.
@since1.0.3@changed1.7.0 Added the `fillBlock` method, writing a transformed block of colors into contiguous slots via `set()` and returning the next free slot, so consecutive blocks chain.
Palette
.Palette.vga(): Palette
Creates the default VGA-style 256-color palette.
@returnsNew VGA preset palette.
vga
(), // 256 VGA standard colors.
]; // Build the main palette // This palette holds the swatch copies plus the shared UI colors. We keep it // separate from the preset palettes so the UI is always readable. this.Demo.palette: Palette | null
@type{Palette | null}
palette
=
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.paletteCreate: (size?: number) => Palette
Creates a standalone palette instance.
@since1.0.3@paramsize - Palette size. Defaults to 256 colors.@returnsNew mutable palette.
paletteCreate
(256);
// applyTheme() installs the twelve shared UI colors into slots 240..251 and // returns a map of where each color landed (this.theme.bg, .text, .dim, ...). // Every kit panel and label below draws with these colors automatically. this.Demo.theme: nulltheme = import applyThemeapplyTheme(this.Demo.palette: Palette
@type{Palette | null}
palette
);
// Slots 10..181 hold the swatch colors for the six static swatch rows. // We copy each preset's colors into this palette so we can draw swatches // without switching the active palette. // Layout: preset 0 at 10..13 (4 colors), preset 1 at 20..35 (16), etc. const const swatchOffsets: {}swatchOffsets = [10, 20, 40, 60, 80, 150]; for (let let p: numberp = 0; let p: numberp < this.Demo.presets: {}presets.length; let p: numberp++) { const const preset: anypreset = this.Demo.presets: {}presets[let p: numberp]; const const maxColors: anymaxColors = Math.min(const preset: anypreset.size, const MAX_SWATCHES_PER_ROW: 32MAX_SWATCHES_PER_ROW); const const offset: anyoffset = const swatchOffsets: {}swatchOffsets[let p: numberp]; for (let let c: numberc = 0; let c: numberc < const maxColors: anymaxColors; let c: numberc++) { // palette.get(index) returns the Color32 stored at that slot. const const color: anycolor = const preset: anypreset.get(let c: numberc); if (const color: anycolor) { this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const offset: anyoffset + let c: numberc, const color: anycolor);
} } } // Store offsets for use in render(). this.Demo.swatchOffsets: {}swatchOffsets = const swatchOffsets: {}swatchOffsets; // Slots 200..215 are reserved for the live view preview swatches. // These will be updated in update() when the active preset changes. for (let let i: numberi = 0; let i: numberi < 16; let i: numberi++) { this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const LIVE_SWATCH_SLOT: 200LIVE_SWATCH_SLOT + let i: numberi, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(0, 0, 0));
} // Use setNamed() for a semantic slot alias // setNamed() lets you refer to a slot by a descriptive word instead of a number. // This is like writing "live-swatch-0" on a label instead of "slot 200". // (applyTheme() above did the same trick for its UI colors: try // palette.getNamed('ui_bg') - it answers 240.) this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.setNamed(name: string, index: number): void
Associates a human-readable name with a palette index.
@paramname - Name to register.@paramindex - Palette index referenced by the name.@throwsError if the index is invalid.
setNamed
('live-swatch-0', const LIVE_SWATCH_SLOT: 200LIVE_SWATCH_SLOT);
// Activate palette
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.paletteSet: (palette: Palette) => void
Stores the active engine palette. Use this to swap the **entire palette** (e.g. switch between a day and night theme). After this call the renderer uploads the new palette uniform on the next frame. **Palette-value swap (change what a slot looks like):** mutate the live {@link BT.palette } in place with `palette.set(slot, newColor)`. The renderer uploads dirty slots on the next frame; no `paletteSet()` or {@link BT.spritesRefresh } needed. **Palette-layout swap (same colors, different slot positions):** build a new palette with the same colors at new indices, call `paletteSet()`, then call {@link BT.spritesRefresh } so every sprite sheet re-maps its original RGBA pixels against the new slot layout.
@since1.0.3@parampalette - Palette to make active.
paletteSet
(this.Demo.palette: Palette
@type{Palette | null}
palette
);
// Initialize the live view. this.Demo.updateLiveSwatches(): void
Copies the current preset's first 16 colors into the live-view palette slots (200..215). When update() calls this, render() automatically shows the new colors next frame.
updateLiveSwatches
();
console.log('[PalettePresetsDemo] Initialized'); return true; } /** * Advances the live view: switches to the next preset every LIVE_SWITCH_TICKS ticks. */ Demo.update(): void
Advances the live view: switches to the next preset every LIVE_SWITCH_TICKS ticks.
update
() {
const const tick: numbertick =
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
;
if (const tick: numbertick - this.Demo.lastSwitchTick: numberlastSwitchTick >= const LIVE_SWITCH_TICKS: 120LIVE_SWITCH_TICKS) { // Move to the next preset; wrap around after the last one. this.Demo.currentPresetIndex: numbercurrentPresetIndex = (this.Demo.currentPresetIndex: numbercurrentPresetIndex + 1) % this.Demo.presets: {}presets.length; this.Demo.lastSwitchTick: numberlastSwitchTick = const tick: numbertick;
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.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
(`Preset: ${this.Demo.presetNames: {}presetNames[this.Demo.currentPresetIndex: numbercurrentPresetIndex]}`);
// Copy the new preset's colors into the live view swatch slots (200..215). this.Demo.updateLiveSwatches(): void
Copies the current preset's first 16 colors into the live-view palette slots (200..215). When update() calls this, render() automatically shows the new colors next frame.
updateLiveSwatches
();
} } /** * Draws the title strip, the static swatch rows with captions, and the live * cycling preview panel. NO Color32 objects appear in draw calls here - only * palette index numbers (and the kit, which uses its own theme slots). */ Demo.render(): void
Draws the title strip, the static swatch rows with captions, and the live cycling preview panel. NO Color32 objects appear in draw calls here - only palette index numbers (and the kit, which uses its own theme slots).
render
() {
// Background - the shared theme's deep navy, so every demo looks alike.
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.clear: (paletteIndex: number) => void
Sets the frame clear color using a palette index. The renderer uses this color when clearing the full display at the start of the next frame.
@since0.1.0@parampaletteIndex - Palette index for the full-screen clear pass.
clear
(this.Demo.theme: nulltheme.bg);
// Full-width title strip across the top, drawn by the shared UI kit. import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_BAR); import uiui.panel('Palette Presets - six built-in retro color sets'); import uiui.end(); // Draw each preset as a row of colored swatches. this.Demo.renderSwatchRows(): void
Draws one row of colored rectangles per preset. Each rectangle's color comes directly from the swatch slots we copied in init(). The caption next to each row is a tiny borderless kit group, pinned exactly where the row's swatches end.
renderSwatchRows
();
// Draw the live cycling preview. this.Demo.renderLivePreview(): void
Draws the live cycling preview panel in the lower portion of the screen. The panel frame and text come from the shared UI kit; the 16 swatches are plain rectangle fills drawn on top, into space the panel reserves for them.
renderLivePreview
();
} /** * Current live-view preset name and slot count for the engine overlay. * * @returns {readonly { leftText: string }[]} */
Demo.overlayRows(): readonly {
    leftText: string;
}[]
Current live-view preset name and slot count for the engine overlay.
@returns
overlayRows
() {
// The overlay can ask for rows before init() has filled the six presets; // until then we answer with the prepared default row. After init(), // update() keeps currentPresetIndex in range with %, so no other check is needed. if (this.Demo.presets: {}presets.length === 0) { return this.Demo.overlayRowData: {}overlayRowData; } const const name: anyname = this.Demo.presetNames: {}presetNames[this.Demo.currentPresetIndex: numbercurrentPresetIndex]; const const size: anysize = this.Demo.presets: {}presets[this.Demo.currentPresetIndex: numbercurrentPresetIndex].size; this.Demo.overlayRowData: {}overlayRowData[0].leftText = `Current ${const name: anyname} - ${const size: anysize} colors`; return this.Demo.overlayRowData: {}overlayRowData; } /** * Copies the current preset's first 16 colors into the live-view palette slots (200..215). * When update() calls this, render() automatically shows the new colors next frame. */ Demo.updateLiveSwatches(): void
Copies the current preset's first 16 colors into the live-view palette slots (200..215). When update() calls this, render() automatically shows the new colors next frame.
updateLiveSwatches
() {
const const preset: anypreset = this.Demo.presets: {}presets[this.Demo.currentPresetIndex: numbercurrentPresetIndex]; const const maxColors: anymaxColors = Math.min(const preset: anypreset.size, 16); for (let let i: numberi = 0; let i: numberi < 16; let i: numberi++) { if (let i: numberi < const maxColors: anymaxColors) { const const color: anycolor = const preset: anypreset.get(let i: numberi); this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const LIVE_SWATCH_SLOT: 200LIVE_SWATCH_SLOT + let i: numberi, const color: anycolor ?? new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(0, 0, 0));
} else { this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const LIVE_SWATCH_SLOT: 200LIVE_SWATCH_SLOT + let i: numberi, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(0, 0, 0));
} } } /** * Draws one row of colored rectangles per preset. * Each rectangle's color comes directly from the swatch slots we copied in init(). * The caption next to each row is a tiny borderless kit group, pinned exactly * where the row's swatches end. */ Demo.renderSwatchRows(): void
Draws one row of colored rectangles per preset. Each rectangle's color comes directly from the swatch slots we copied in init(). The caption next to each row is a tiny borderless kit group, pinned exactly where the row's swatches end.
renderSwatchRows
() {
// Row positions: six presets spread across the upper portion of the screen. // y positions are spaced 20 pixels apart, starting below the 22px title strip. const const rowY: {}rowY = [30, 50, 70, 90, 110, 130]; for (let let p: numberp = 0; let p: numberp < this.Demo.presets: {}presets.length; let p: numberp++) { const const y: anyy = const rowY: {}rowY[let p: numberp]; // Same math as init(): show the preset's real color count, capped at the row limit. const const count: anycount = Math.min(this.Demo.presets: {}presets[let p: numberp].size, const MAX_SWATCHES_PER_ROW: 32MAX_SWATCHES_PER_ROW); const const offset: anyoffset = this.Demo.swatchOffsets: {}swatchOffsets[let p: numberp]; // Draw the colored swatches. for (let let c: numberc = 0; let c: numberc < const count: anycount; let c: numberc++) {
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawRectFill: (rect: Rect2i, paletteIndex: number) => void
Draws a filled rectangle.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRectFill
(new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2i
Creates an integer rectangle, truncating all inputs toward zero.
@paramx - Left-edge X coordinate (defaults to 0).@paramy - Top-edge Y coordinate (defaults to 0).@paramwidth - Width in pixels (defaults to 0).@paramheight - Height in pixels (defaults to 0).
Rect2i
(6 + let c: numberc * (const SWATCH_W: 7SWATCH_W + 1), const y: anyy, const SWATCH_W: 7SWATCH_W, const SWATCH_H: 14SWATCH_H), const offset: anyoffset + let c: numberc);
} // Caption: preset name and actual slot count. A one-label kit group with // pad 0 draws nothing but the text, pinned right after the last swatch. const const label: stringlabel = `${this.Demo.presetNames: {}presetNames[let p: numberp]} (${this.Demo.presets: {}presets[let p: numberp].size})`; import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: 6 + const count: anycount * (const SWATCH_W: 7SWATCH_W + 1) + 4, y: anyy: const y: anyy + 1, pad: numberpad: 0 }); import uiui.label(const label: stringlabel, { color: stringcolor: 'dim' }); import uiui.end(); } } /** * Draws the live cycling preview panel in the lower portion of the screen. * The panel frame and text come from the shared UI kit; the 16 swatches are * plain rectangle fills drawn on top, into space the panel reserves for them. */ Demo.renderLivePreview(): void
Draws the live cycling preview panel in the lower portion of the screen. The panel frame and text come from the shared UI kit; the 16 swatches are plain rectangle fills drawn on top, into space the panel reserves for them.
renderLivePreview
() {
// The kit panel: a pinned group with a fixed width so the swatches fit. // ui.spacer(28) reserves an empty band inside the panel where the 24px-tall // swatches will be drawn AFTER ui.end() - the kit paints its panel fill on // end(), so anything drawn later lands on top of it. import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { x: numberx: const LIVE_PANEL_X: 6LIVE_PANEL_X, y: numbery: const LIVE_PANEL_Y: 152LIVE_PANEL_Y, width: numberwidth: 298 }); import uiui.panel('Live view (cycles every 2s)'); import uiui.spacer(28); // Explain named slots - this is real code from init() above. import uiui.label("palette.setNamed('live-swatch-0', 200)", { color: stringcolor: 'dim' }); import uiui.label("palette.getNamed('live-swatch-0') => 200", { color: stringcolor: 'dim' }); import uiui.end(); // Show 16 large swatches from the live slots (200..215), inside the band the // spacer reserved: 20px down for the panel title, then 2px of breathing room. for (let let i: numberi = 0; let i: numberi < 16; let i: numberi++) {
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 LIVE_PANEL_X: 6LIVE_PANEL_X + 6 + let i: numberi * 18, const LIVE_PANEL_Y: 152LIVE_PANEL_Y + 22, 16, 24), const LIVE_SWATCH_SLOT: 200LIVE_SWATCH_SLOT + let i: numberi);
} // Current preset name and color count are shown in overlayRows() above the FPS bar. } } // Hand the Demo class to BLIT386 to start the demo loop. function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>
One-liner bootstrap function for BLIT386 demos. Handles canvas retrieval and engine initialization. Backend selection (WebGPU or software fallback) is managed internally by BTAPI. This function provides a streamlined way to start a demo with sensible defaults while allowing customization through options.
@since0.2.0@changed1.4.0 Calling `bootstrap()` again while already initialized now routes to a hot swap (via {@link registerHotReload}) when a Vite HMR context is registered, or logs a double-bootstrap guard and returns `false` otherwise - previously it silently started a second, unstoppable `GameLoop`.@changed1.7.0 Exposes `BT` on `window.BT` after bootstrap finishes, gated by {@link BootstrapOptions.exposeGlobal} (default: {@link BT.isDevMode}).@paramDemoClass - Demo class constructor implementing `IBTDemo` (optional `configure()` for hardware settings).@paramoptions - Optional configuration for IDs and callbacks.@returns`true` when the demo boots successfully; otherwise `false`.@example// Simplest usage - uses default IDs. bootstrap(MyDemo);@example// With custom options. bootstrap(MyDemo, { canvasID: 'custom-canvas', containerID: 'custom-container', onSuccess: () => console.log('Demo started!'), onError: (err) => analytics.trackError(err), });@example// Await the result. const success = await bootstrap(MyDemo); if (success) { console.log('Demo is running'); }
bootstrap
(class Demo
Demonstrates the six built-in palette presets and named palette slots.
@implementsIBTDemo
Demo
);