/**
 * Snake - grid snake with walls, food, keyboard steering, and PipBoy CRT post-processing.
 * @description Grid snake with walls, food, keyboard, D-pad, and swipe steering, plus PipBoy CRT post-processing.
 *
 * Part of the BLIT386 demo series.
 * Prerequisites:
 *   Basics         https://demos.blit386.dev/basics
 *   PipBoy CRT     https://demos.blit386.dev/crt-pipboy
 *   Keyboard Input https://demos.blit386.dev/keyboard-input
 *
 * Live version: https://demos.blit386.dev/snake-game
 *
 * Move with WASD or the arrow keys (both are mapped to player 0 face buttons). On a phone
 * or tablet, steer with the on-screen D-pad in the bottom-right corner (it appears at the
 * first touch) or simply swipe anywhere in the direction you want to go - both come from
 * the shared UI kit in src/shared/ui.js. Each food dot grows the snake and makes it
 * one step faster. Hitting the boundary wall or your own body ends the run; the game
 * restarts after two seconds.
 * Gameplay uses rectangles; a short systemPrint note appears in software mode.
 *
 * Post-processing matches the PipBoy CRT demo when WebGPU is active. In software fallback mode the
 * snake game still runs; CRT effects are skipped and a short note is shown on canvas.
 *
 * WebGPU path: PixelGlitch on the logical index buffer, then palette resolve + upscale,
 * then display-tier barrel distortion, chromatic aberration, interference, rolling scan
 * line, scanlines, RGB mask, vignette, noise, flicker, bloom, and the glitch state machine.
 *
 * Eating food and dying both play a synthesized sound effect (built with AudioClip.synth(),
 * the same technique Synth Toy explores in depth). An upbeat music loop plays in the
 * background; each food multiplies its playback rate so the beat climbs with snake length.
 */

import {
    class AudioClip
Decoded audio asset with its winning source URL and buffer-derived metadata. Construct instances with {@link AudioClip.load } , {@link AudioClip.loadAll } , or {@link AudioClip.synth } ; there is no public constructor.
@since1.3.0
AudioClip
,
class BarrelDistortion
Barrel distortion that warps UVs outward from the screen center. Display-tier: operates on the upscaled output. Applying this in the pixel tier (logical 320x240) discretizes the curve onto the source texel grid, which CSS upscale then magnifies into visible step artifacts. At output resolution the curve has enough resolution to express smoothly. The math comes from Timothy Lottes's public-domain `crt-lottes.glsl`: `warp(uv) = uv + delta * d2 * curvature` where `delta = uv - 0.5` and `d2 = dot(delta, delta)`.
@since1.0.3
BarrelDistortion
,
class Bloom
Single-pass box-blur bloom. Samples a 5x5 neighborhood (25 taps) around each fragment, averages, then mixes with the original color by {@link glow } . {@link spread } scales the texel offset so the bloom radius can be tuned independently of the source resolution. Display-tier: bloom mixes neighboring pixels into intermediate hues that are not in the active palette. Running it in pixel space would violate the palette-pixel aesthetic; running it on the upscaled output reads as the warm phosphor glow of an old monitor instead. The implementation matches the original PipBoy bloom shader. A future optimization would be a two-pass separable Gaussian (5 + 5 = 10 taps); add it once a GPU perf test demands it.
@since1.0.3
Bloom
,
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 ChromaticAberration
RGB channel offset that simulates lens chromatic aberration: red samples left of the fragment, blue samples right, green stays centered. Display-tier: spreads color along the lens axis. At logical resolution the single-pixel offset is too coarse and reads as a glitch instead of a soft fringe.
@since1.0.3
ChromaticAberration
,
class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
,
class Flicker
Brightness multiplier - the simplest CRT animation knob. Demos drive {@link amount } per frame to simulate flicker (e.g. with `0.95 + sin(t) * 0.05`). The effect is intentionally trivial so the demo controls the pattern; for procedural noise-driven flicker, combine with the {@link Noise } effect. Display-tier.
@since1.0.3
Flicker
,
class Interference
Per-row horizontal jitter that simulates analog signal interference. Each output row gets a deterministic random horizontal offset seeded by row index and time. Row offsets are stable for one frame and re-seed every frame, producing a buzzing-noise feel. Display-tier. Drives jitter from {@link time } ; demos typically pass `BT.ticks / BT.targetFPS`.
@since1.0.3
Interference
,
class Noise
Additive per-pixel pseudo-random noise. Reseeds each frame from {@link time } so the noise pattern animates. Display-tier.
@since1.0.3
Noise
,
class PixelGlitch
Chunky pixel-aligned horizontal glitch: every Nth row of source pixels gets a random horizontal shift. Shifts snap to integer source-pixel offsets so palette indices move whole-texel (no RGB resampling). Pixel-tier: runs on the logical `r8uint` framebuffer (palette indices).
@since1.0.3
PixelGlitch
,
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 RGBMask
CRT shadow mask: per-pixel R/G/B vertical-stripe pattern with darkened cell borders, simulating the phosphor grille of an aperture-grille CRT. Display-tier: at output resolution there are enough output pixels per mask cell to read as colored stripes. The cell pitch (in output pixels) is the {@link size } parameter. Math is a direct WGSL port of the libretro `crt-lottes.glsl` mask code.
@since1.0.3
RGBMask
,
class RollLine
Slowly scrolling vertical interference band that brightens a horizontal stripe of the image. Combination of three cosines + smoothstep gives the stripe a soft top/bottom edge. Display-tier. Demo drives {@link time } (typically `BT.ticks / BT.targetFPS`).
@since1.0.3
RollLine
,
class Scanlines
CRT scanlines: alternating bright/dark horizontal bands aligned to the source vertical resolution. Display-tier: at output resolution there is enough vertical pixels for scanlines to read as alternating bright/dark bands. At logical 320x240 the Gaussian weight quantizes to one of two values per source row and you lose the smooth fade.
@since1.0.3
Scanlines
,
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
,
class Vignette
Edge-darkening vignette: smooth radial fade from full brightness at the center to black at the corners. Display-tier: applies to the whole simulated screen, not the underlying pixel art.
@since1.0.3
Vignette
,
} from 'blit386'; import { import ABERRATION_BASEABERRATION_BASE, import applyGlitchUniformsapplyGlitchUniforms, import FLICKER_BASEFLICKER_BASE, import GLITCH_ACTIVE_MAXGLITCH_ACTIVE_MAX, import GLITCH_ACTIVE_MINGLITCH_ACTIVE_MIN, import GLITCH_COOLDOWN_MAXGLITCH_COOLDOWN_MAX, import GLITCH_COOLDOWN_MINGLITCH_COOLDOWN_MIN, import GLITCH_INTENSITY_MAXGLITCH_INTENSITY_MAX, import GLITCH_INTENSITY_MINGLITCH_INTENSITY_MIN, import GLITCH_TYPES_CHROMAGLITCH_TYPES_CHROMA, import NOISE_BASENOISE_BASE, import PIXEL_GLITCH_BAND_HEIGHTPIXEL_GLITCH_BAND_HEIGHT, import resetGlitchUniformsresetGlitchUniforms, } from './shared/crt-glitch.js'; 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'; /** @typedef {import('blit386').IBTDemo} IBTDemo */ /** @typedef {import('blit386').HardwareSettings} HardwareSettings */ /** @typedef {import('blit386').Palette} Palette */ // Palette indices (slot 0 reserved). const const C_BG: 1C_BG = 1; const const C_WALL: 2C_WALL = 2; const const C_SNAKE: 3C_SNAKE = 3; const const C_FOOD: 4C_FOOD = 4; const const C_FOOTER_DIM: 5C_FOOTER_DIM = 5; const const C_FOOTER_WHITE: 6C_FOOTER_WHITE = 6; // Logical resolution: small playfield as requested. const const DISPLAY_W: 160DISPLAY_W = 160; const const DISPLAY_H: 120DISPLAY_H = 120; // Canvas output size: 4x logical resolution so display-tier CRT effects have enough pixels. const const OUTPUT_W: 640OUTPUT_W = 640; const const OUTPUT_H: 480OUTPUT_H = 480; const const TARGET_FPS: 60TARGET_FPS = 60; // Wall thickness in pixels (also one grid cell tall/wide). const const WALL: 8WALL = 8; // Snake and food are drawn on a coarse grid so movement stays chunky and readable. const const CELL: 8CELL = 8; // Inner playable area after subtracting the wall strip from each side. const const INNER_X0: 8INNER_X0 = const WALL: 8WALL; const const INNER_Y0: 8INNER_Y0 = const WALL: 8WALL; const const INNER_W: numberINNER_W = const DISPLAY_W: 160DISPLAY_W - 2 * const WALL: 8WALL; const const INNER_H: numberINNER_H = const DISPLAY_H: 120DISPLAY_H - 2 * const WALL: 8WALL; // How many cells fit inside the inner rectangle (should divide evenly). const const CELLS_X: numberCELLS_X = const INNER_W: numberINNER_W / const CELL: 8CELL; const const CELLS_Y: numberCELLS_Y = const INNER_H: numberINNER_H / const CELL: 8CELL; // Ticks between snake steps (lower = faster). A new round starts slow, then each food // shortens the wait by MOVE_INTERVAL_STEP until MOVE_INTERVAL_MIN. At 60 FPS, 20 ticks // is three steps per second; 4 ticks is fifteen steps per second. const const MOVE_INTERVAL_START: 20MOVE_INTERVAL_START = 20; const const MOVE_INTERVAL_MIN: 4MOVE_INTERVAL_MIN = 4; const const MOVE_INTERVAL_STEP: 1MOVE_INTERVAL_STEP = 1; // Background-loop playback rate (Web Audio pitch). 1.0 is the file's natural tempo; // higher values play faster and higher, like speeding up a cassette. // MUSIC_PITCH_AT_START is the rate on a fresh round; each food multiplies that rate by // MUSIC_PITCH_SCALE_PER_GROWTH (for example 0.57, then 0.57 * 1.03, then 0.57 * 1.03^2, ...). const const MUSIC_PITCH_AT_START: 0.57MUSIC_PITCH_AT_START = 0.57; const const MUSIC_PITCH_SCALE_PER_GROWTH: 1.03MUSIC_PITCH_SCALE_PER_GROWTH = 1.03; // How many growths it takes for moveInterval to reach MOVE_INTERVAL_MIN. Music pitch uses // the same ceiling so the loop stops speeding up once the snake is already at top speed. const const MUSIC_PITCH_GROWTH_CAP: numberMUSIC_PITCH_GROWTH_CAP = (const MOVE_INTERVAL_START: 20MOVE_INTERVAL_START - const MOVE_INTERVAL_MIN: 4MOVE_INTERVAL_MIN) / const MOVE_INTERVAL_STEP: 1MOVE_INTERVAL_STEP; // High enough that short eat / death blips never steal the looping music voice from the // SFX pool (BT.soundPlay uses priority stealing when every voice is busy). const const MUSIC_VOICE_PRIORITY: 100MUSIC_VOICE_PRIORITY = 100; // Soft glide when the loop speeds up or slows down after a food eat or a new round. const const MUSIC_PITCH_FADE_MS: 120MUSIC_PITCH_FADE_MS = 120; // Two seconds at 60 ticks per second before a new round starts. const const RESTART_DELAY_TICKS: 120RESTART_DELAY_TICKS = 120; /** * Minimal snake with PipBoy CRT post-processing from crt-pipboy demo. * * @implements {IBTDemo} */ class class Demo
Minimal snake with PipBoy CRT post-processing from crt-pipboy demo.
@implementsIBTDemo
Demo
{
/** @type {Palette | null} */ Demo.palette: Palette | null
@type{Palette | null}
palette
= null;
/** @type {AudioClip | null} Sound played when the snake eats food. */ Demo.eatClip: AudioClip | null
@type{AudioClip | null} Sound played when the snake eats food.
eatClip
= null;
/** @type {AudioClip | null} Sound played when the snake dies. */ Demo.gameOverClip: AudioClip | null
@type{AudioClip | null} Sound played when the snake dies.
gameOverClip
= null;
/** @type {AudioClip | null} Looping background music. */ Demo.musicClip: AudioClip | null
@type{AudioClip | null} Looping background music.
musicClip
= null;
/** * Live handle for the looping music voice, or null before unlock / if music failed to * load. We use BT.soundPlay (not BT.musicPlay) so we can change pitch each time the * snake speeds up - the music player has volume and crossfade controls, but no pitch. * * @type {import('blit386').SoundRef | null} */ Demo.musicRef: SoundRef | null
Live handle for the looping music voice, or null before unlock / if music failed to load. We use BT.soundPlay (not BT.musicPlay) so we can change pitch each time the snake speeds up - the music player has volume and crossfade controls, but no pitch.
@type{import('blit386').SoundRef | null}
musicRef
= null;
/** @type {{ x: number; y: number }[]} Head first, tail last (grid coords). */ Demo.snake: {}
@type{{ x: number; y: number }[]} Head first, tail last (grid coords).
snake
= [];
/** @type {{ x: number; y: number }} Food cell in grid coords. */
Demo.food: {
    x: number;
    y: number;
}
@type{{ x: number; y: number }} Food cell in grid coords.
food
= { x: numberx: 0, y: numbery: 0 };
/** Current step direction (grid units per move). */ Demo.dx: number
Current step direction (grid units per move).
dx
= 1;
Demo.dy: numberdy = 0; /** Next direction chosen by the player (applied when the snake steps). */ Demo.pendingDx: number
Next direction chosen by the player (applied when the snake steps).
pendingDx
= 1;
Demo.pendingDy: numberpendingDy = 0; /** Counts ticks until the next snake step while the game is running. */ Demo.moveCooldown: number
Counts ticks until the next snake step while the game is running.
moveCooldown
= 0;
/** * Current ticks between steps. Starts at MOVE_INTERVAL_START each round and drops * toward MOVE_INTERVAL_MIN every time the snake eats food. */ Demo.moveInterval: number
Current ticks between steps. Starts at MOVE_INTERVAL_START each round and drops toward MOVE_INTERVAL_MIN every time the snake eats food.
moveInterval
= const MOVE_INTERVAL_START: 20MOVE_INTERVAL_START;
/** * How many food dots the snake has eaten this round. Music pitch uses this count * capped at MUSIC_PITCH_GROWTH_CAP so tempo plateaus with move speed. */ Demo.growthCount: number
How many food dots the snake has eaten this round. Music pitch uses this count capped at MUSIC_PITCH_GROWTH_CAP so tempo plateaus with move speed.
growthCount
= 0;
/** When true, the snake does not move until restart. */ Demo.gameOver: boolean
When true, the snake does not move until restart.
gameOver
= false;
/** @type {number | null} Tick index when the snake died (`BT.ticks`); null while playing. */ Demo.deathTick: number | null
@type{number | null} Tick index when the snake died (`BT.ticks`); null while playing.
deathTick
= null;
/** True when the WebGPU backend is active, so the CRT effect chain can run (set in init()). */ Demo.effectsAvailable: boolean
True when the WebGPU backend is active, so the CRT effect chain can run (set in init()).
effectsAvailable
= false;
// CRT effects (initialized in setupCrtEffects(), called from init()) /** @type {PixelGlitch} */ Demo.pixelGlitch: PixelGlitch
@type{PixelGlitch}
pixelGlitch
;
/** @type {BarrelDistortion} */ Demo.barrel: BarrelDistortion
@type{BarrelDistortion}
barrel
;
/** @type {ChromaticAberration} */ Demo.aberration: ChromaticAberration
@type{ChromaticAberration}
aberration
;
/** @type {Interference} */ Demo.interference: Interference
@type{Interference}
interference
;
/** @type {RollLine} */ Demo.rollLine: RollLine
@type{RollLine}
rollLine
;
/** @type {Scanlines} */ Demo.scanlines: Scanlines
@type{Scanlines}
scanlines
;
/** @type {RGBMask} */ Demo.mask: RGBMask
@type{RGBMask}
mask
;
/** @type {Vignette} */ Demo.vignette: Vignette
@type{Vignette}
vignette
;
/** @type {Noise} */ Demo.noise: Noise
@type{Noise}
noise
;
/** @type {Flicker} */ Demo.flicker: Flicker
@type{Flicker}
flicker
;
/** @type {Bloom} */ Demo.bloom: Bloom
@type{Bloom}
bloom
;
Demo.glitchCooldown: numberglitchCooldown = 0; /** How many ticks remain in the current glitch burst; 0 means no glitch is running. */ Demo.glitchTicksLeft: number
How many ticks remain in the current glitch burst; 0 means no glitch is running.
glitchTicksLeft
= 0;
Demo.glitchDuration: numberglitchDuration = 0; /** @type {string} */ Demo.glitchType: string
@type{string}
glitchType
= 'none';
Demo.glitchPeak: numberglitchPeak = 0; /** * 160x120 framebuffer, 4x upscale for CRT chain, 60 fixed updates per second. * * @returns {Partial<HardwareSettings>} */ Demo.configure(): Partial<HardwareSettings>
160x120 framebuffer, 4x upscale for CRT chain, 60 fixed updates per second.
@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
(const DISPLAY_W: 160DISPLAY_W, const DISPLAY_H: 120DISPLAY_H),
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: 640OUTPUT_W, const OUTPUT_H: 480OUTPUT_H),
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: 640OUTPUT_W, const OUTPUT_H: 480OUTPUT_H),
outputUpscaleFilter: stringoutputUpscaleFilter: 'nearest', targetFPS: numbertargetFPS: const TARGET_FPS: 60TARGET_FPS, // Phones and tablets dim, then lock, the screen after 30-60 seconds without a touch - // annoying mid-game when you are only pressing arrow keys or swiping every few seconds. // This asks the browser to keep the screen on while you are playing. Browsers that do // not support the request just ignore it, so this is safe everywhere. isWakeLockEnabled: booleanisWakeLockEnabled: true, // Arrow keys (and Space) normally scroll the page. This demo maps those keys to move // the snake, so opt in so the browser does not scroll the demo page while you play. isCapturingKeyboardScroll: booleanisCapturingKeyboardScroll: true, // Hide the small "~" toggle hint in the bottom-left corner so the game // board stays clean. Players who want the stats overlay can still press // the Backquote key (`) to show it and press ` again to hide it - hiding // the hint does not turn the overlay off. isOverlayToggleHintVisible: booleanisOverlayToggleHintVisible: false,
overlayStyle: {
    barPaletteIndex: number;
    textPaletteIndex: number;
    gapPaletteIndex: number;
}
overlayStyle
: {
barPaletteIndex: numberbarPaletteIndex: const C_BG: 1C_BG, textPaletteIndex: numbertextPaletteIndex: const C_FOOTER_WHITE: 6C_FOOTER_WHITE, gapPaletteIndex: numbergapPaletteIndex: const C_BG: 1C_BG, }, isOverlayTimingChartEnabled: booleanisOverlayTimingChartEnabled: true,
overlayTimingChartStyle: {
    updateBarPaletteIndex: number;
    renderBarPaletteIndex: number;
    warningPaletteIndex: number;
    errorPaletteIndex: number;
    tagPaletteIndex: number;
}
overlayTimingChartStyle
: {
updateBarPaletteIndex: numberupdateBarPaletteIndex: const C_SNAKE: 3C_SNAKE, renderBarPaletteIndex: numberrenderBarPaletteIndex: const C_FOOD: 4C_FOOD, warningPaletteIndex: numberwarningPaletteIndex: const C_FOOTER_DIM: 5C_FOOTER_DIM, errorPaletteIndex: numbererrorPaletteIndex: const C_WALL: 2C_WALL, tagPaletteIndex: numbertagPaletteIndex: const C_FOOTER_WHITE: 6C_FOOTER_WHITE, }, }; } /** * Palette, PipBoy CRT stack (crt-pipboy), glitch machine, then first round. * * @returns {Promise<boolean>} */ async Demo.init(): Promise<boolean>
Palette, PipBoy CRT stack (crt-pipboy), glitch machine, then first round.
@returns
init
() {
// Start from default keyboard maps so this demo stays independent from remapping demos.
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
.inputMapReset: () => void
Restores built-in default keyboard maps for players `0` and `1`. Same tables as `BT.DEFAULT_KEYBOARD_PLAYER1` and `BT.DEFAULT_KEYBOARD_PLAYER2`.
@since1.0.3
inputMapReset
();
// Engine defaults put WASD on player 0 and arrows on player 1. This is a single-player // game, so fold both layouts into player 0: either set of keys steers the same snake. // BT.inputMap replaces the whole key list for that button; listing both codes means // either key counts (logical OR), the same pattern input-map-remapping demo shows with Q|E for LEFT.
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
.inputMap: (player: number, button: number, ...keys: string[]) => void
Assigns one or more `KeyboardEvent.code` values to a face button for a keyboard player. Logical button state is the OR of all listed keys. Only players `0` and `1` support keyboard; other indices no-op. `button` must be one face-button bit flag (`BT.BTN_UP` … `BT.BTN_SELECT`). Pass an empty key list to clear keyboard bindings for that button until remapped again.
@since1.0.3@paramplayer - Zero-based player index (`0` or `1`).@parambutton - Face button constant.@paramkeys - DOM key codes (for example `'Space'`, `'KeyW'`).
inputMap
(0,
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
.type BTN_UP: number
Up button bit flag.
@since0.1.0
BTN_UP
, 'KeyW', 'ArrowUp');
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
.inputMap: (player: number, button: number, ...keys: string[]) => void
Assigns one or more `KeyboardEvent.code` values to a face button for a keyboard player. Logical button state is the OR of all listed keys. Only players `0` and `1` support keyboard; other indices no-op. `button` must be one face-button bit flag (`BT.BTN_UP` … `BT.BTN_SELECT`). Pass an empty key list to clear keyboard bindings for that button until remapped again.
@since1.0.3@paramplayer - Zero-based player index (`0` or `1`).@parambutton - Face button constant.@paramkeys - DOM key codes (for example `'Space'`, `'KeyW'`).
inputMap
(0,
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
.type BTN_DOWN: number
Down button bit flag.
@since0.1.0
BTN_DOWN
, 'KeyS', 'ArrowDown');
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
.inputMap: (player: number, button: number, ...keys: string[]) => void
Assigns one or more `KeyboardEvent.code` values to a face button for a keyboard player. Logical button state is the OR of all listed keys. Only players `0` and `1` support keyboard; other indices no-op. `button` must be one face-button bit flag (`BT.BTN_UP` … `BT.BTN_SELECT`). Pass an empty key list to clear keyboard bindings for that button until remapped again.
@since1.0.3@paramplayer - Zero-based player index (`0` or `1`).@parambutton - Face button constant.@paramkeys - DOM key codes (for example `'Space'`, `'KeyW'`).
inputMap
(0,
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
.type BTN_LEFT: number
Left button bit flag.
@since0.1.0
BTN_LEFT
, 'KeyA', 'ArrowLeft');
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
.inputMap: (player: number, button: number, ...keys: string[]) => void
Assigns one or more `KeyboardEvent.code` values to a face button for a keyboard player. Logical button state is the OR of all listed keys. Only players `0` and `1` support keyboard; other indices no-op. `button` must be one face-button bit flag (`BT.BTN_UP` … `BT.BTN_SELECT`). Pass an empty key list to clear keyboard bindings for that button until remapped again.
@since1.0.3@paramplayer - Zero-based player index (`0` or `1`).@parambutton - Face button constant.@paramkeys - DOM key codes (for example `'Space'`, `'KeyW'`).
inputMap
(0,
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
.type BTN_RIGHT: number
Right button bit flag.
@since0.1.0
BTN_RIGHT
, 'KeyD', 'ArrowRight');
// BT.synthPreset bundles ready-tuned sound recipes (Synth Toy explores all six). // Rendering them once here means eating and dying play back with zero delay later. this.Demo.eatClip: AudioClip | null
@type{AudioClip | null} Sound played when the snake eats food.
eatClip
= await class AudioClip
Decoded audio asset with its winning source URL and buffer-derived metadata. Construct instances with {@link AudioClip.load } , {@link AudioClip.loadAll } , or {@link AudioClip.synth } ; there is no public constructor.
@since1.3.0
AudioClip
.AudioClip.synth(params: SynthParams): Promise<AudioClip>
Synthesizes a clip from deterministic procedural parameters - no source file, no `OfflineAudioContext`, and no audio graph involved. Rendering happens entirely on the CPU via the pure {@link renderSynthSamples } function against an `AudioBuffer` allocated from the registered decode context. The returned clip flows through the same {@link buffer } getter and playback path as a loaded clip, but uses a synthetic, non-cached identifier (`synth:<waveform>`) - it is never added to the URL-keyed resolved cache and never deduplicated, so identical `params` still render a fresh, independent `AudioBuffer` on every call. See {@link SynthParams } for the full parameter set.
@paramparams - Deterministic synthesis parameters.@returnsA new clip wrapping the synthesized buffer.@throwsError if `params` fails validation, or the engine has not registered a decode context yet (see {@link audioClipNotReadyError }).
synth
(
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
.
synthPreset: {
    jump: (seed?: number) => SynthParams;
    pickup: (seed?: number) => SynthParams;
    explosion: (seed?: number) => SynthParams;
    laser: (seed?: number) => SynthParams;
    hit: (seed?: number) => SynthParams;
    blip: (seed?: number) => SynthParams;
}
Pre-configured `SynthParams` presets for common sound effects ("jump", "pickup", "explosion", "laser", "hit", "blip"). Each function returns a fresh {@link SynthParams } object; pass it to {@link AudioClip.synth } to render a clip, then play the result via {@link BT.soundPlay } . An optional `seed` argument applies small, bounded, deterministic jitter to a few hand-picked fields per preset, so repeated plays vary without losing reproducibility - the same seed always renders the exact same variant.
@since1.3.0@exampleconst jumpClip = await AudioClip.synth(BT.synthPreset.jump()); BT.soundPlay(jumpClip);
synthPreset
.pickup: (seed?: number) => SynthParams
Item pickup / coin: a short, bright square-wave blip that sweeps upward an octave. `seed` jitters the base frequency (+/-8%) and duration (+/-12%). Omit `seed` for a fixed baseline variant.
@paramseed - Seed for deterministic jitter. Defaults to {@link DEFAULT_PRESET_SEED }.@returnsA fresh `SynthParams` for a pickup sound effect.
pickup
());
this.Demo.gameOverClip: AudioClip | null
@type{AudioClip | null} Sound played when the snake dies.
gameOverClip
= await class AudioClip
Decoded audio asset with its winning source URL and buffer-derived metadata. Construct instances with {@link AudioClip.load } , {@link AudioClip.loadAll } , or {@link AudioClip.synth } ; there is no public constructor.
@since1.3.0
AudioClip
.AudioClip.synth(params: SynthParams): Promise<AudioClip>
Synthesizes a clip from deterministic procedural parameters - no source file, no `OfflineAudioContext`, and no audio graph involved. Rendering happens entirely on the CPU via the pure {@link renderSynthSamples } function against an `AudioBuffer` allocated from the registered decode context. The returned clip flows through the same {@link buffer } getter and playback path as a loaded clip, but uses a synthetic, non-cached identifier (`synth:<waveform>`) - it is never added to the URL-keyed resolved cache and never deduplicated, so identical `params` still render a fresh, independent `AudioBuffer` on every call. See {@link SynthParams } for the full parameter set.
@paramparams - Deterministic synthesis parameters.@returnsA new clip wrapping the synthesized buffer.@throwsError if `params` fails validation, or the engine has not registered a decode context yet (see {@link audioClipNotReadyError }).
synth
(
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
.
synthPreset: {
    jump: (seed?: number) => SynthParams;
    pickup: (seed?: number) => SynthParams;
    explosion: (seed?: number) => SynthParams;
    laser: (seed?: number) => SynthParams;
    hit: (seed?: number) => SynthParams;
    blip: (seed?: number) => SynthParams;
}
Pre-configured `SynthParams` presets for common sound effects ("jump", "pickup", "explosion", "laser", "hit", "blip"). Each function returns a fresh {@link SynthParams } object; pass it to {@link AudioClip.synth } to render a clip, then play the result via {@link BT.soundPlay } . An optional `seed` argument applies small, bounded, deterministic jitter to a few hand-picked fields per preset, so repeated plays vary without losing reproducibility - the same seed always renders the exact same variant.
@since1.3.0@exampleconst jumpClip = await AudioClip.synth(BT.synthPreset.jump()); BT.soundPlay(jumpClip);
synthPreset
.explosion: (seed?: number) => SynthParams
Explosion: a low sawtooth rumble mixed heavily with noise, with a slow decay and release for a boom that lingers. `seed` jitters the base frequency (+/-8%), duration (+/-12%), and `noiseMix` (+/-10%, clamped to `[0, 1]`) - the mix jitter alone gives every explosion a distinct noisy texture. Omit `seed` for a fixed baseline variant.
@paramseed - Seed for deterministic jitter. Defaults to {@link DEFAULT_PRESET_SEED }.@returnsA fresh `SynthParams` for an explosion sound effect.
explosion
());
// The background music file is loaded separately from the sound effects above, and // wrapped in its own try/catch. Unlike the effects (built on the spot by the synth // engine, so they cannot fail), this loads an actual audio file over the network - // if it is missing or the browser cannot decode it, we do not want that one file to // stop the whole game from starting. Catching the error here means Snake stays fully // playable with sound effects but no music, instead of getting stuck on a blank // screen. // // We only load here - we do not call BT.soundPlay yet. Browsers keep audio locked // until the first click or key press, and unlike BT.musicPlay (which remembers a // request while locked), BT.soundPlay would be dropped. update() starts the loop // the moment BT.isAudioUnlocked flips true. try { this.Demo.musicClip: AudioClip | null
@type{AudioClip | null} Looping background music.
musicClip
= await class AudioClip
Decoded audio asset with its winning source URL and buffer-derived metadata. Construct instances with {@link AudioClip.load } , {@link AudioClip.loadAll } , or {@link AudioClip.synth } ; there is no public constructor.
@since1.3.0
AudioClip
.AudioClip.load(url: string | string[], options?: AudioClipLoadOptions): Promise<AudioClip>
Loads an audio clip from a single URL, or from an ordered list of candidate URLs. A single URL runs the download+decode pipeline directly, sharing the per-URL cache and in-flight dedup described on {@link AudioClip } . A URL array tries each candidate in order and resolves with the first one that downloads and decodes successfully - useful for offering a browser-friendly fallback (for example `['music.ogg', 'music.mp3']`) when a container or codec isn't universally supported. If every candidate fails, the error from the last candidate is thrown.
@paramurl - Single audio URL, or an ordered list of fallback URLs.@paramoptions - Optional load options (progress reporting).@returnsLoaded clip, cached under its winning source URL.@throwsError if the URL (or every URL in the list) fails to load or decode.
load
('/audio/music-upbeat.wav');
} catch (function (local var) error: unknownerror) { console.warn('Snake Game: failed to load background music, continuing without it.', function (local var) error: unknownerror); } 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);
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_BG: 1C_BG, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(25, 35, 45));
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_WALL: 2C_WALL, 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
(180, 170, 140));
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_SNAKE: 3C_SNAKE, 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
(90, 220, 120));
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_FOOD: 4C_FOOD, 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
(240, 90, 70));
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_FOOTER_DIM: 5C_FOOTER_DIM, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(120, 130, 150));
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_FOOTER_WHITE: 6C_FOOTER_WHITE, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(220, 230, 240));
// The shared UI kit draws the touch D-pad, and it needs its theme colors in the // palette. applyTheme() writes them into high slots (240 and up), far away from the // six scene colors above, so the game's look does not change at all. import applyThemeapplyTheme(this.Demo.palette: Palette
@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
.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
);
// CRT post-processing needs WebGPU. In software fallback mode we skip the whole // effect chain - the snake game itself still runs fine without it. this.Demo.effectsAvailable: boolean
True when the WebGPU backend is active, so the CRT effect chain can run (set in init()).
effectsAvailable
= import isAvailableisAvailable();
if (this.Demo.effectsAvailable: boolean
True when the WebGPU backend is active, so the CRT effect chain can run (set in init()).
effectsAvailable
) {
this.Demo.setupCrtEffects(): void
Builds the WebGPU CRT effect chain (same stack and tuning as crt-pipboy demo) and seeds the glitch state machine. Called from init() only when WebGPU is active.
setupCrtEffects
();
} this.Demo.startRound(): void
Places a short snake in the middle, resets speed to the slow start, and spawns the first food dot.
startRound
();
return true; } /** * Drive CRT animation and glitch machine every tick; run snake logic when alive. */ Demo.update(): void
Drive CRT animation and glitch machine every tick; run snake logic when alive.
update
() {
// The UI kit's once-per-tick housekeeping: it tracks touch contacts for the D-pad // and watches for swipe gestures. Always the first line of update(). import uiui.tick(); // Start (or restart) the tempo-linked music loop once audio is unlocked. this.Demo.ensureBackgroundMusic(): void
Starts the looping music voice once audio is unlocked, or restarts it if the voice was somehow lost. Safe to call every tick - it no-ops while already playing. Why BT.soundPlay instead of BT.musicPlay: only the SFX path exposes pitch (playback rate), which is how we keep the beat tied to snake growth. The tradeoff is that soundPlay is dropped before unlock, so we wait for BT.isAudioUnlocked here.
ensureBackgroundMusic
();
// Did a swipe finish on this tick? ui.swipe() answers with a direction name // ('up', 'down', 'left', 'right') or null. We read it once and hand it to the // steering code below, next to the keyboard and D-pad checks. const const swipe: anyswipe = import uiui.swipe(); if (this.Demo.effectsAvailable: boolean
True when the WebGPU backend is active, so the CRT effect chain can run (set in init()).
effectsAvailable
) {
this.Demo.tickCrtClock(): void
Updates time-driven uniforms for rolling noise, interference, and roll line.
tickCrtClock
();
this.Demo.tickGlitchMachine(): void
Same state machine as crt-pipboy demo: cooldown, burst envelope, random glitch type.
tickGlitchMachine
();
} if (this.Demo.tickRestartAfterDeath(): boolean
While game over, waits for the restart delay then starts a new round.
@returnsTrue when the snake should not move this tick.
tickRestartAfterDeath
()) {
return; } this.Demo.pollDirectionInput(swipe: "up" | "down" | "left" | "right" | null): void
Reads all three steering inputs and updates pending direction (no instant reverse): player 0 face buttons (WASD, arrows, or gamepad), the on-screen touch D-pad, and swipes. We use BT.isPressed (edge: up -> down this tick), not BT.isDown (held every tick), and the D-pad's matching ui.dpad.isPressed. That way one tap per direction per move interval feels like a classic D-pad. The `this.dy !== 1` checks block a 180-degree turn: you cannot go straight back into your body on the next step.
@paramswipe - The swipe finished this tick, if any (from ui.swipe() in update()).
pollDirectionInput
(const swipe: anyswipe);
this.Demo.moveCooldown: number
Counts ticks until the next snake step while the game is running.
moveCooldown
-= 1;
if (this.Demo.moveCooldown: number
Counts ticks until the next snake step while the game is running.
moveCooldown
> 0) {
return; } // Use the live interval so food-driven speed-ups take effect on the next step. this.Demo.moveCooldown: number
Counts ticks until the next snake step while the game is running.
moveCooldown
= this.Demo.moveInterval: number
Current ticks between steps. Starts at MOVE_INTERVAL_START each round and drops toward MOVE_INTERVAL_MIN every time the snake eats food.
moveInterval
;
if (!(this.Demo.pendingDx: number
Next direction chosen by the player (applied when the snake steps).
pendingDx
=== -this.Demo.dx: number
Current step direction (grid units per move).
dx
&& this.Demo.pendingDy: numberpendingDy === -this.Demo.dy: numberdy)) {
this.Demo.dx: number
Current step direction (grid units per move).
dx
= this.Demo.pendingDx: number
Next direction chosen by the player (applied when the snake steps).
pendingDx
;
this.Demo.dy: numberdy = this.Demo.pendingDy: numberpendingDy; } this.Demo.step(): void
One grid step: wall check, self check, grow or shift tail.
step
();
} /** * Clear to background, draw walls, food, snake segments. */ Demo.render(): void
Clear to background, draw walls, food, snake segments.
render
() {
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.clear: (paletteIndex: number) => void
Sets the frame clear color using a palette index. The renderer uses this color when clearing the full display at the start of the next frame.
@since0.1.0@parampaletteIndex - Palette index for the full-screen clear pass.
clear
(const C_BG: 1C_BG);
this.Demo.renderWalls(): void
Four filled bars form the boundary the snake must not cross.
renderWalls
();
if (this.
Demo.food: {
    x: number;
    y: number;
}
@type{{ x: number; y: number }} Food cell in grid coords.
food
.x: numberx >= 0 && this.
Demo.food: {
    x: number;
    y: number;
}
@type{{ x: number; y: number }} Food cell in grid coords.
food
.y: numbery >= 0) {
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
(this.Demo.gridRect(gx: number, gy: number): Rect2i
Pixel rectangle for one grid cell at (gx, gy), inside the inner playfield.
@paramgx@paramgy@returns
gridRect
(this.
Demo.food: {
    x: number;
    y: number;
}
@type{{ x: number; y: number }} Food cell in grid coords.
food
.x: numberx, this.
Demo.food: {
    x: number;
    y: number;
}
@type{{ x: number; y: number }} Food cell in grid coords.
food
.y: numbery), const C_FOOD: 4C_FOOD);
} for (let let i: numberi = 0; let i: numberi < this.Demo.snake: {}
@type{{ x: number; y: number }[]} Head first, tail last (grid coords).
snake
.length; let i: numberi++) {
const const seg: anyseg = this.Demo.snake: {}
@type{{ x: number; y: number }[]} Head first, tail last (grid coords).
snake
[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
(this.Demo.gridRect(gx: number, gy: number): Rect2i
Pixel rectangle for one grid cell at (gx, gy), inside the inner playfield.
@paramgx@paramgy@returns
gridRect
(const seg: anyseg.x, const seg: anyseg.y), const C_SNAKE: 3C_SNAKE);
} if (!this.Demo.effectsAvailable: boolean
True when the WebGPU backend is active, so the CRT effect chain can run (set in init()).
effectsAvailable
) {
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.systemPrint: (pos: Vector2i, paletteIndex: number, text: string) => void
Draws text using the built-in 6x14 system font. The system font covers printable ASCII (characters 32-126). For custom bitmap fonts with proportional glyphs, use {@link BT.printFont } instead.
@since1.0.3@parampos - Text origin in display coordinates.@parampaletteIndex - Palette color index for the text.@paramtext - String to render.
systemPrint
(new new Vector2i(x?: number, y?: number): Vector2i
Creates an integer 2D vector, truncating inputs toward zero.
@paramx - Horizontal component (defaults to 0).@paramy - Vertical component (defaults to 0).
Vector2i
(const INNER_X0: 8INNER_X0, const DISPLAY_H: 120DISPLAY_H - 16), const C_FOOTER_DIM: 5C_FOOTER_DIM, import SOFTWARE_FALLBACK_NOTESOFTWARE_FALLBACK_NOTE);
} // Browsers keep all sound muted until the player clicks or presses a key. This // shared row shows a warm "enable sound" hint only while audio is still locked, // then disappears on its own the moment a first move unlocks it - which is also // the same gesture that starts the snake moving, so the hint is usually only // visible for a single frame. The default sentence is too long for this 160-wide // playfield, so we pass a short override. import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { margin: numbermargin: 3 }); import uiui.audioUnlockHint({ text: stringtext: 'Click for sound' }); import uiui.end(); // The touch D-pad, drawn over the playfield in the bottom-right corner. It stays // invisible until the first touch contact, so mouse-and-keyboard players never see // it. This playfield is only 160x120 logical pixels, so the keys are scaled down // from the kit's phone-sized default. import uiui.dpadWidget({ size: numbersize: 22, gap: numbergap: 3, margin: numbermargin: 4 }); } /** * Builds the WebGPU CRT effect chain (same stack and tuning as crt-pipboy demo) and seeds * the glitch state machine. Called from init() only when WebGPU is active. */ Demo.setupCrtEffects(): void
Builds the WebGPU CRT effect chain (same stack and tuning as crt-pipboy demo) and seeds the glitch state machine. Called from init() only when WebGPU is active.
setupCrtEffects
() {
// Pixel-tier glitch (same role as crt-pipboy demo). this.Demo.pixelGlitch: PixelGlitch
@type{PixelGlitch}
pixelGlitch
= new new PixelGlitch(): PixelGlitch
Chunky pixel-aligned horizontal glitch: every Nth row of source pixels gets a random horizontal shift. Shifts snap to integer source-pixel offsets so palette indices move whole-texel (no RGB resampling). Pixel-tier: runs on the logical `r8uint` framebuffer (palette indices).
@since1.0.3
PixelGlitch
();
this.Demo.pixelGlitch: PixelGlitch
@type{PixelGlitch}
pixelGlitch
.PixelGlitch.bandHeight: number
Height of each glitch band in source pixels. Each band gets a single shift value, so larger bands produce chunkier glitches.
bandHeight
= import PIXEL_GLITCH_BAND_HEIGHTPIXEL_GLITCH_BAND_HEIGHT;
this.Demo.pixelGlitch: PixelGlitch
@type{PixelGlitch}
pixelGlitch
.PixelGlitch.intensity: number
Glitch strength in `[0, 1]`. Scales the per-band horizontal shift magnitude. Bands are shifted when their hash exceeds ~0.85 (~15% of bands). `0` disables.
intensity
= 0;
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.effectAdd: (effect: Effect) => void
Appends a fullscreen post-processing effect to whichever chain matches its declared {@link Effect.tier } . - `tier='pixel'` -> pixel chain (logical resolution). - `tier='display'` -> display chain (output resolution); requires `drawingBufferSize` in effective hardware settings (`configure()` or `defaultConfig()`). Effects run in registration order within each tier. The pixel chain runs first, followed by the upscale pass, followed by the display chain. Each {@link Effect } instance owns its own GPU resources and may be mutated each frame from demo code.
@since1.0.3@parameffect - Effect instance to append. When the engine is not ready, shows a canvas error instead of throwing.
effectAdd
(this.Demo.pixelGlitch: PixelGlitch
@type{PixelGlitch}
pixelGlitch
);
this.Demo.barrel: BarrelDistortion
@type{BarrelDistortion}
barrel
= new new BarrelDistortion(): BarrelDistortion
Barrel distortion that warps UVs outward from the screen center. Display-tier: operates on the upscaled output. Applying this in the pixel tier (logical 320x240) discretizes the curve onto the source texel grid, which CSS upscale then magnifies into visible step artifacts. At output resolution the curve has enough resolution to express smoothly. The math comes from Timothy Lottes's public-domain `crt-lottes.glsl`: `warp(uv) = uv + delta * d2 * curvature` where `delta = uv - 0.5` and `d2 = dot(delta, delta)`.
@since1.0.3
BarrelDistortion
();
this.Demo.barrel: BarrelDistortion
@type{BarrelDistortion}
barrel
.BarrelDistortion.curvature: number
Curvature strength. Typical values: - `0.02` - subtle, like a flat-screen monitor. - `0.05` - moderate desktop CRT. - `0.10` - heavy small CRT or pocket TV.
curvature
= 0.2;
this.Demo.aberration: ChromaticAberration
@type{ChromaticAberration}
aberration
= new new ChromaticAberration(): ChromaticAberration
RGB channel offset that simulates lens chromatic aberration: red samples left of the fragment, blue samples right, green stays centered. Display-tier: spreads color along the lens axis. At logical resolution the single-pixel offset is too coarse and reads as a glitch instead of a soft fringe.
@since1.0.3
ChromaticAberration
();
this.Demo.aberration: ChromaticAberration
@type{ChromaticAberration}
aberration
.ChromaticAberration.aberration: number
Channel offset in display-chain (output) pixels. Reasonable values are `0.5` to `3.0`. Set to `0` to disable.
aberration
= import ABERRATION_BASEABERRATION_BASE;
this.Demo.interference: Interference
@type{Interference}
interference
= new new Interference(): Interference
Per-row horizontal jitter that simulates analog signal interference. Each output row gets a deterministic random horizontal offset seeded by row index and time. Row offsets are stable for one frame and re-seed every frame, producing a buzzing-noise feel. Display-tier. Drives jitter from {@link time } ; demos typically pass `BT.ticks / BT.targetFPS`.
@since1.0.3
Interference
();
this.Demo.interference: Interference
@type{Interference}
interference
.Interference.amount: number
Maximum horizontal offset as a UV fraction (e.g. `0.06` shifts the row by up to ~6% of the image width). Set to `0` to disable.
amount
= 0;
this.Demo.rollLine: RollLine
@type{RollLine}
rollLine
= new new RollLine(): RollLine
Slowly scrolling vertical interference band that brightens a horizontal stripe of the image. Combination of three cosines + smoothstep gives the stripe a soft top/bottom edge. Display-tier. Demo drives {@link time } (typically `BT.ticks / BT.targetFPS`).
@since1.0.3
RollLine
();
this.Demo.rollLine: RollLine
@type{RollLine}
rollLine
.RollLine.amount: number
Roll line amplitude (mix factor onto a brightness boost).
amount
= 0.1;
this.Demo.rollLine: RollLine
@type{RollLine}
rollLine
.RollLine.speed: number
Scroll speed multiplier; final scroll velocity = `time * speed`.
speed
= 1.0;
this.Demo.scanlines: Scanlines
@type{Scanlines}
scanlines
= new new Scanlines(): Scanlines
CRT scanlines: alternating bright/dark horizontal bands aligned to the source vertical resolution. Display-tier: at output resolution there is enough vertical pixels for scanlines to read as alternating bright/dark bands. At logical 320x240 the Gaussian weight quantizes to one of two values per source row and you lose the smooth fade.
@since1.0.3
Scanlines
();
this.Demo.scanlines: Scanlines
@type{Scanlines}
scanlines
.Scanlines.amount: number
Scanline mix amount in `[0, 1]`. 0 disables.
amount
= 0.55;
this.Demo.scanlines: Scanlines
@type{Scanlines}
scanlines
.Scanlines.strength: number
Negative gaussian falloff parameter for scanline brightness. More negative values produce sharper dark bands. PipBoy reference: `-8.0`.
strength
= -8;
this.Demo.scanlines: Scanlines
@type{Scanlines}
scanlines
.Scanlines.density: number
Number of scanline cycles vertically. Should match the demo's logical source vertical resolution so each "source pixel row" maps to one scanline cycle. Defaults to `240`, the most common pixel-art height. Set to e.g. `200` for VGA-style 320x200 games or `144` for Game Boy resolution.
density
= const DISPLAY_H: 120DISPLAY_H;
this.Demo.mask: RGBMask
@type{RGBMask}
mask
= new new RGBMask(): RGBMask
CRT shadow mask: per-pixel R/G/B vertical-stripe pattern with darkened cell borders, simulating the phosphor grille of an aperture-grille CRT. Display-tier: at output resolution there are enough output pixels per mask cell to read as colored stripes. The cell pitch (in output pixels) is the {@link size } parameter. Math is a direct WGSL port of the libretro `crt-lottes.glsl` mask code.
@since1.0.3
RGBMask
();
this.Demo.mask: RGBMask
@type{RGBMask}
mask
.RGBMask.intensity: number
Mask brightness mix amount in `[0, 1]`. 0 hides the mask.
intensity
= 0.18;
this.Demo.mask: RGBMask
@type{RGBMask}
mask
.RGBMask.size: number
Mask cell pitch in output (display-chain) pixels. Smaller = denser mask.
size
= 6;
this.Demo.mask: RGBMask
@type{RGBMask}
mask
.RGBMask.border: number
Border darkening within each mask cell. 0 disables, 1 strong.
border
= 0.5;
this.Demo.vignette: Vignette
@type{Vignette}
vignette
= new new Vignette(): Vignette
Edge-darkening vignette: smooth radial fade from full brightness at the center to black at the corners. Display-tier: applies to the whole simulated screen, not the underlying pixel art.
@since1.0.3
Vignette
();
this.Demo.vignette: Vignette
@type{Vignette}
vignette
.Vignette.amount: number
Vignette darkening exponent. Higher values produce a stronger vignette with a sharper falloff. PipBoy reference: `0.2`. Set to `0` to disable.
amount
= 0.35;
this.Demo.noise: Noise
@type{Noise}
noise
= new new Noise(): Noise
Additive per-pixel pseudo-random noise. Reseeds each frame from {@link time } so the noise pattern animates. Display-tier.
@since1.0.3
Noise
();
this.Demo.noise: Noise
@type{Noise}
noise
.Noise.amount: number
Noise amplitude as a `[-amount, +amount]` additive perturbation on each channel. Reasonable values are `0.005` to `0.05`. Set to `0` to disable.
amount
= import NOISE_BASENOISE_BASE;
this.Demo.flicker: Flicker
@type{Flicker}
flicker
= new new Flicker(): Flicker
Brightness multiplier - the simplest CRT animation knob. Demos drive {@link amount } per frame to simulate flicker (e.g. with `0.95 + sin(t) * 0.05`). The effect is intentionally trivial so the demo controls the pattern; for procedural noise-driven flicker, combine with the {@link Noise } effect. Display-tier.
@since1.0.3
Flicker
();
this.Demo.flicker: Flicker
@type{Flicker}
flicker
.Flicker.amount: number
Brightness multiplier. `1` is unmodulated; values below `1` darken the frame. The demo typically updates this each frame from a sin wave or random source.
amount
= import FLICKER_BASEFLICKER_BASE;
this.Demo.bloom: Bloom
@type{Bloom}
bloom
= new new Bloom(): Bloom
Single-pass box-blur bloom. Samples a 5x5 neighborhood (25 taps) around each fragment, averages, then mixes with the original color by {@link glow } . {@link spread } scales the texel offset so the bloom radius can be tuned independently of the source resolution. Display-tier: bloom mixes neighboring pixels into intermediate hues that are not in the active palette. Running it in pixel space would violate the palette-pixel aesthetic; running it on the upscaled output reads as the warm phosphor glow of an old monitor instead. The implementation matches the original PipBoy bloom shader. A future optimization would be a two-pass separable Gaussian (5 + 5 = 10 taps); add it once a GPU perf test demands it.
@since1.0.3
Bloom
();
this.Demo.bloom: Bloom
@type{Bloom}
bloom
.Bloom.spread: number
Texel offset multiplier for the box-blur kernel.
spread
= 3.0;
this.Demo.bloom: Bloom
@type{Bloom}
bloom
.Bloom.glow: number
Mix factor between the original sample and the blurred neighborhood.
glow
= 0.18;
for (const const fx: anyfx of [ this.Demo.barrel: BarrelDistortion
@type{BarrelDistortion}
barrel
,
this.Demo.aberration: ChromaticAberration
@type{ChromaticAberration}
aberration
,
this.Demo.interference: Interference
@type{Interference}
interference
,
this.Demo.rollLine: RollLine
@type{RollLine}
rollLine
,
this.Demo.scanlines: Scanlines
@type{Scanlines}
scanlines
,
this.Demo.mask: RGBMask
@type{RGBMask}
mask
,
this.Demo.vignette: Vignette
@type{Vignette}
vignette
,
this.Demo.noise: Noise
@type{Noise}
noise
,
this.Demo.flicker: Flicker
@type{Flicker}
flicker
,
this.Demo.bloom: Bloom
@type{Bloom}
bloom
,
]) {
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.effectAdd: (effect: Effect) => void
Appends a fullscreen post-processing effect to whichever chain matches its declared {@link Effect.tier } . - `tier='pixel'` -> pixel chain (logical resolution). - `tier='display'` -> display chain (output resolution); requires `drawingBufferSize` in effective hardware settings (`configure()` or `defaultConfig()`). Effects run in registration order within each tier. The pixel chain runs first, followed by the upscale pass, followed by the display chain. Each {@link Effect } instance owns its own GPU resources and may be mutated each frame from demo code.
@since1.0.3@parameffect - Effect instance to append. When the engine is not ready, shows a canvas error instead of throwing.
effectAdd
(const fx: anyfx);
} // BT.random is the engine's shared random number generator. // Its int() method returns a whole number from the first value up to (but not including) the second, // so this picks how many ticks to wait before the first glitch burst. this.Demo.glitchCooldown: numberglitchCooldown =
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
.random: Random
Default engine PRNG (live reference - not a copy). Time-seeded when the engine singleton is created. Call {@link BT.randomSeed } for a reproducible run. Mutating the instance (for example `BT.random.int(10)`) advances the shared stream.
@since1.5.0@returnsThe shared {@link Random} instance.@exampleBT.randomSeed(42); BT.random.int(150, 420); BT.random.pick(['a', 'b', 'c']);
random
.Random.int(minOrMaxExclusive: number, maxExclusive?: number): number
Returns a pseudo-random integer in [0, maxExclusive) or [min, maxExclusive).
@paramminOrMaxExclusive - When alone, exclusive upper bound from 0; otherwise inclusive min.@parammaxExclusive - Exclusive upper bound when two arguments are passed.@returnsWhole number in the half-open range.@since1.5.0
int
(import GLITCH_COOLDOWN_MINGLITCH_COOLDOWN_MIN, import GLITCH_COOLDOWN_MAXGLITCH_COOLDOWN_MAX);
this.Demo.glitchTicksLeft: number
How many ticks remain in the current glitch burst; 0 means no glitch is running.
glitchTicksLeft
= 0;
this.Demo.glitchDuration: numberglitchDuration = 0; this.Demo.glitchType: string
@type{string}
glitchType
= 'none';
this.Demo.glitchPeak: numberglitchPeak = 0; } /** * While game over, waits for the restart delay then starts a new round. * * @returns {boolean} True when the snake should not move this tick. */ Demo.tickRestartAfterDeath(): boolean
While game over, waits for the restart delay then starts a new round.
@returnsTrue when the snake should not move this tick.
tickRestartAfterDeath
() {
if (!this.Demo.gameOver: boolean
When true, the snake does not move until restart.
gameOver
) {
return false; } 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 (this.Demo.deathTick: number | null
@type{number | null} Tick index when the snake died (`BT.ticks`); null while playing.
deathTick
!== null && const tick: numbertick - this.Demo.deathTick: number
@type{number | null} Tick index when the snake died (`BT.ticks`); null while playing.
deathTick
>= const RESTART_DELAY_TICKS: 120RESTART_DELAY_TICKS) {
this.Demo.startRound(): void
Places a short snake in the middle, resets speed to the slow start, and spawns the first food dot.
startRound
();
} return true; } /** * Reads all three steering inputs and updates pending direction (no instant reverse): * player 0 face buttons (WASD, arrows, or gamepad), the on-screen touch D-pad, and swipes. * * We use BT.isPressed (edge: up -> down this tick), not BT.isDown (held every tick), * and the D-pad's matching ui.dpad.isPressed. That way one tap per direction per move * interval feels like a classic D-pad. The `this.dy !== 1` checks block a 180-degree * turn: you cannot go straight back into your body on the next step. * * @param {'up' | 'down' | 'left' | 'right' | null} swipe - The swipe finished this * tick, if any (from ui.swipe() in update()). */ Demo.pollDirectionInput(swipe: "up" | "down" | "left" | "right" | null): void
Reads all three steering inputs and updates pending direction (no instant reverse): player 0 face buttons (WASD, arrows, or gamepad), the on-screen touch D-pad, and swipes. We use BT.isPressed (edge: up -> down this tick), not BT.isDown (held every tick), and the D-pad's matching ui.dpad.isPressed. That way one tap per direction per move interval feels like a classic D-pad. The `this.dy !== 1` checks block a 180-degree turn: you cannot go straight back into your body on the next step.
@paramswipe - The swipe finished this tick, if any (from ui.swipe() in update()).
pollDirectionInput
(swipe: "up" | "down" | "left" | "right" | null
- The swipe finished this tick, if any (from ui.swipe() in update()).
@paramswipe - The swipe finished this tick, if any (from ui.swipe() in update()).
swipe
) {
// Up: only if we are not currently moving down (would reverse into the tail). if (this.Demo.isSteerPressed(button: number, dir: "up" | "down" | "left" | "right", swipe: "up" | "down" | "left" | "right" | null): boolean
Combines the three ways to steer in one direction: the engine face button (keyboard or gamepad), the on-screen touch D-pad key, and a swipe that way.
@parambutton - The engine face button mask (BT.BTN_UP and friends).@paramdir - The D-pad/swipe direction name.@paramswipe - The swipe finished this tick.@returnsTrue when any of the three pressed that direction this tick.
isSteerPressed
(
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
.type BTN_UP: number
Up button bit flag.
@since0.1.0
BTN_UP
, 'up', swipe: "up" | "down" | "left" | "right" | null
- The swipe finished this tick, if any (from ui.swipe() in update()).
@paramswipe - The swipe finished this tick, if any (from ui.swipe() in update()).
swipe
) && this.Demo.dy: numberdy !== 1) {
this.Demo.pendingDx: number
Next direction chosen by the player (applied when the snake steps).
pendingDx
= 0;
this.Demo.pendingDy: numberpendingDy = -1; } // Down: only if we are not currently moving up. if (this.Demo.isSteerPressed(button: number, dir: "up" | "down" | "left" | "right", swipe: "up" | "down" | "left" | "right" | null): boolean
Combines the three ways to steer in one direction: the engine face button (keyboard or gamepad), the on-screen touch D-pad key, and a swipe that way.
@parambutton - The engine face button mask (BT.BTN_UP and friends).@paramdir - The D-pad/swipe direction name.@paramswipe - The swipe finished this tick.@returnsTrue when any of the three pressed that direction this tick.
isSteerPressed
(
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
.type BTN_DOWN: number
Down button bit flag.
@since0.1.0
BTN_DOWN
, 'down', swipe: "up" | "down" | "left" | "right" | null
- The swipe finished this tick, if any (from ui.swipe() in update()).
@paramswipe - The swipe finished this tick, if any (from ui.swipe() in update()).
swipe
) && this.Demo.dy: numberdy !== -1) {
this.Demo.pendingDx: number
Next direction chosen by the player (applied when the snake steps).
pendingDx
= 0;
this.Demo.pendingDy: numberpendingDy = 1; } // Left: only if we are not currently moving right. if (this.Demo.isSteerPressed(button: number, dir: "up" | "down" | "left" | "right", swipe: "up" | "down" | "left" | "right" | null): boolean
Combines the three ways to steer in one direction: the engine face button (keyboard or gamepad), the on-screen touch D-pad key, and a swipe that way.
@parambutton - The engine face button mask (BT.BTN_UP and friends).@paramdir - The D-pad/swipe direction name.@paramswipe - The swipe finished this tick.@returnsTrue when any of the three pressed that direction this tick.
isSteerPressed
(
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
.type BTN_LEFT: number
Left button bit flag.
@since0.1.0
BTN_LEFT
, 'left', swipe: "up" | "down" | "left" | "right" | null
- The swipe finished this tick, if any (from ui.swipe() in update()).
@paramswipe - The swipe finished this tick, if any (from ui.swipe() in update()).
swipe
) && this.Demo.dx: number
Current step direction (grid units per move).
dx
!== 1) {
this.Demo.pendingDx: number
Next direction chosen by the player (applied when the snake steps).
pendingDx
= -1;
this.Demo.pendingDy: numberpendingDy = 0; } // Right: only if we are not currently moving left. if (this.Demo.isSteerPressed(button: number, dir: "up" | "down" | "left" | "right", swipe: "up" | "down" | "left" | "right" | null): boolean
Combines the three ways to steer in one direction: the engine face button (keyboard or gamepad), the on-screen touch D-pad key, and a swipe that way.
@parambutton - The engine face button mask (BT.BTN_UP and friends).@paramdir - The D-pad/swipe direction name.@paramswipe - The swipe finished this tick.@returnsTrue when any of the three pressed that direction this tick.
isSteerPressed
(
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
.type BTN_RIGHT: number
Right button bit flag.
@since0.1.0
BTN_RIGHT
, 'right', swipe: "up" | "down" | "left" | "right" | null
- The swipe finished this tick, if any (from ui.swipe() in update()).
@paramswipe - The swipe finished this tick, if any (from ui.swipe() in update()).
swipe
) && this.Demo.dx: number
Current step direction (grid units per move).
dx
!== -1) {
this.Demo.pendingDx: number
Next direction chosen by the player (applied when the snake steps).
pendingDx
= 1;
this.Demo.pendingDy: numberpendingDy = 0; } } /** * Combines the three ways to steer in one direction: the engine face button (keyboard * or gamepad), the on-screen touch D-pad key, and a swipe that way. * * @param {number} button - The engine face button mask (BT.BTN_UP and friends). * @param {'up' | 'down' | 'left' | 'right'} dir - The D-pad/swipe direction name. * @param {'up' | 'down' | 'left' | 'right' | null} swipe - The swipe finished this tick. * @returns {boolean} True when any of the three pressed that direction this tick. */ Demo.isSteerPressed(button: number, dir: "up" | "down" | "left" | "right", swipe: "up" | "down" | "left" | "right" | null): boolean
Combines the three ways to steer in one direction: the engine face button (keyboard or gamepad), the on-screen touch D-pad key, and a swipe that way.
@parambutton - The engine face button mask (BT.BTN_UP and friends).@paramdir - The D-pad/swipe direction name.@paramswipe - The swipe finished this tick.@returnsTrue when any of the three pressed that direction this tick.
isSteerPressed
(button: number
- The engine face button mask (BT.BTN_UP and friends).
@parambutton - The engine face button mask (BT.BTN_UP and friends).
button
, dir: "up" | "down" | "left" | "right"
- The D-pad/swipe direction name.
@paramdir - The D-pad/swipe direction name.
dir
, swipe: "up" | "down" | "left" | "right" | null
- The swipe finished this tick.
@paramswipe - The swipe finished this tick.
swipe
) {
return
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
.isPressed: (button: number, player?: number, repeatRate?: number) => boolean
Checks whether a button was pressed on the current frame. Same parameter semantics as {@link isDown } ; returns `true` only on the frame the button transitions from up to down. Call from `update()`, not `render()`, for reliable detection: for keyboard-mapped face buttons (players 0 and 1), the press edge clears once per fixed-update tick, which always runs before that frame's `render()`.
@since1.1.1@parambutton - Button constant from the `BTN_*` set.@paramplayer - Zero-based player index for gamepads, or pointer slot (0-3) for `BTN_POINTER_*`.@paramrepeatRate - Optional repeat interval in fixed ticks (`0`/omitted = edge only).@returns`true` on the transition frame.
isPressed
(button: number
- The engine face button mask (BT.BTN_UP and friends).
@parambutton - The engine face button mask (BT.BTN_UP and friends).
button
, 0) || import uiui.dpad.isPressed(dir: "up" | "down" | "left" | "right"
- The D-pad/swipe direction name.
@paramdir - The D-pad/swipe direction name.
dir
) || swipe: "up" | "down" | "left" | "right" | null
- The swipe finished this tick.
@paramswipe - The swipe finished this tick.
swipe
=== dir: "up" | "down" | "left" | "right"
- The D-pad/swipe direction name.
@paramdir - The D-pad/swipe direction name.
dir
;
} /** * Updates time-driven uniforms for rolling noise, interference, and roll line. */ Demo.tickCrtClock(): void
Updates time-driven uniforms for rolling noise, interference, and roll line.
tickCrtClock
() {
const const seconds: numberseconds =
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
/ const TARGET_FPS: 60TARGET_FPS;
this.Demo.rollLine: RollLine
@type{RollLine}
rollLine
.RollLine.time: number
Wall-clock seconds; demos typically drive this each frame.
time
= const seconds: numberseconds;
this.Demo.noise: Noise
@type{Noise}
noise
.Noise.time: number
Wall-clock seconds; reseeds the noise each frame.
time
= const seconds: numberseconds;
this.Demo.interference: Interference
@type{Interference}
interference
.Interference.time: number
Wall-clock seconds; reseeds the row offsets each frame.
time
= const seconds: numberseconds;
} /** * Same state machine as crt-pipboy demo: cooldown, burst envelope, random glitch type. */ Demo.tickGlitchMachine(): void
Same state machine as crt-pipboy demo: cooldown, burst envelope, random glitch type.
tickGlitchMachine
() {
// A burst is running while there are ticks left on its countdown. if (this.Demo.glitchTicksLeft: number
How many ticks remain in the current glitch burst; 0 means no glitch is running.
glitchTicksLeft
> 0) {
const const t: numbert = 1 - this.Demo.glitchTicksLeft: number
How many ticks remain in the current glitch burst; 0 means no glitch is running.
glitchTicksLeft
/ this.Demo.glitchDuration: numberglitchDuration;
const const envelope: anyenvelope = Math.sin(const t: numbert * Math.PI); import applyGlitchUniformsapplyGlitchUniforms(this, const envelope: anyenvelope); this.Demo.glitchTicksLeft: number
How many ticks remain in the current glitch burst; 0 means no glitch is running.
glitchTicksLeft
--;
// Countdown just hit zero: the burst is over, so calm the effects down // and roll a fresh cooldown until the next burst. if (this.Demo.glitchTicksLeft: number
How many ticks remain in the current glitch burst; 0 means no glitch is running.
glitchTicksLeft
=== 0) {
import resetGlitchUniformsresetGlitchUniforms(this); this.Demo.glitchCooldown: numberglitchCooldown =
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
.random: Random
Default engine PRNG (live reference - not a copy). Time-seeded when the engine singleton is created. Call {@link BT.randomSeed } for a reproducible run. Mutating the instance (for example `BT.random.int(10)`) advances the shared stream.
@since1.5.0@returnsThe shared {@link Random} instance.@exampleBT.randomSeed(42); BT.random.int(150, 420); BT.random.pick(['a', 'b', 'c']);
random
.Random.int(minOrMaxExclusive: number, maxExclusive?: number): number
Returns a pseudo-random integer in [0, maxExclusive) or [min, maxExclusive).
@paramminOrMaxExclusive - When alone, exclusive upper bound from 0; otherwise inclusive min.@parammaxExclusive - Exclusive upper bound when two arguments are passed.@returnsWhole number in the half-open range.@since1.5.0
int
(import GLITCH_COOLDOWN_MINGLITCH_COOLDOWN_MIN, import GLITCH_COOLDOWN_MAXGLITCH_COOLDOWN_MAX);
} return; } this.Demo.glitchCooldown: numberglitchCooldown--; if (this.Demo.glitchCooldown: numberglitchCooldown <= 0) { // pick() draws one item out of a list, like taking a card off the top of a shuffled deck. // float() is the decimal cousin of int(), for values that are not whole numbers. this.Demo.glitchType: string
@type{string}
glitchType
=
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
.random: Random
Default engine PRNG (live reference - not a copy). Time-seeded when the engine singleton is created. Call {@link BT.randomSeed } for a reproducible run. Mutating the instance (for example `BT.random.int(10)`) advances the shared stream.
@since1.5.0@returnsThe shared {@link Random} instance.@exampleBT.randomSeed(42); BT.random.int(150, 420); BT.random.pick(['a', 'b', 'c']);
random
.Random.pick<string>(arr: readonly string[]): string
Returns one element chosen uniformly from a non-empty array.
@paramarr - Array to pick from; must contain at least one element.@returnsChosen element.@since1.5.0
pick
(import GLITCH_TYPES_CHROMAGLITCH_TYPES_CHROMA);
this.Demo.glitchDuration: numberglitchDuration =
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
.random: Random
Default engine PRNG (live reference - not a copy). Time-seeded when the engine singleton is created. Call {@link BT.randomSeed } for a reproducible run. Mutating the instance (for example `BT.random.int(10)`) advances the shared stream.
@since1.5.0@returnsThe shared {@link Random} instance.@exampleBT.randomSeed(42); BT.random.int(150, 420); BT.random.pick(['a', 'b', 'c']);
random
.Random.int(minOrMaxExclusive: number, maxExclusive?: number): number
Returns a pseudo-random integer in [0, maxExclusive) or [min, maxExclusive).
@paramminOrMaxExclusive - When alone, exclusive upper bound from 0; otherwise inclusive min.@parammaxExclusive - Exclusive upper bound when two arguments are passed.@returnsWhole number in the half-open range.@since1.5.0
int
(import GLITCH_ACTIVE_MINGLITCH_ACTIVE_MIN, import GLITCH_ACTIVE_MAXGLITCH_ACTIVE_MAX);
this.Demo.glitchTicksLeft: number
How many ticks remain in the current glitch burst; 0 means no glitch is running.
glitchTicksLeft
= this.Demo.glitchDuration: numberglitchDuration;
this.Demo.glitchPeak: numberglitchPeak =
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
.random: Random
Default engine PRNG (live reference - not a copy). Time-seeded when the engine singleton is created. Call {@link BT.randomSeed } for a reproducible run. Mutating the instance (for example `BT.random.int(10)`) advances the shared stream.
@since1.5.0@returnsThe shared {@link Random} instance.@exampleBT.randomSeed(42); BT.random.int(150, 420); BT.random.pick(['a', 'b', 'c']);
random
.Random.float(min: number, max: number): number
Returns the next pseudo-random float in [min, max).
@parammin - Inclusive lower bound.@parammax - Exclusive upper bound.@returnsFloat in [min, max).@since1.5.0
float
(import GLITCH_INTENSITY_MINGLITCH_INTENSITY_MIN, import GLITCH_INTENSITY_MAXGLITCH_INTENSITY_MAX);
this.Demo.pixelGlitch: PixelGlitch
@type{PixelGlitch}
pixelGlitch
.PixelGlitch.seed: number
Per-glitch random seed. Change between glitches to vary the band noise pattern.
seed
=
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
.random: Random
Default engine PRNG (live reference - not a copy). Time-seeded when the engine singleton is created. Call {@link BT.randomSeed } for a reproducible run. Mutating the instance (for example `BT.random.int(10)`) advances the shared stream.
@since1.5.0@returnsThe shared {@link Random} instance.@exampleBT.randomSeed(42); BT.random.int(150, 420); BT.random.pick(['a', 'b', 'c']);
random
.Random.float(min: number, max: number): number
Returns the next pseudo-random float in [min, max).
@parammin - Inclusive lower bound.@parammax - Exclusive upper bound.@returnsFloat in [min, max).@since1.5.0
float
(0, 1000);
} } /** * Pixel rectangle for one grid cell at (gx, gy), inside the inner playfield. * * @param {number} gx * @param {number} gy * @returns {Rect2i} */ Demo.gridRect(gx: number, gy: number): Rect2i
Pixel rectangle for one grid cell at (gx, gy), inside the inner playfield.
@paramgx@paramgy@returns
gridRect
(gx: number
@paramgx
gx
, gy: number
@paramgy
gy
) {
const const px: numberpx = const INNER_X0: 8INNER_X0 + gx: number
@paramgx
gx
* const CELL: 8CELL;
const const py: numberpy = const INNER_Y0: 8INNER_Y0 + gy: number
@paramgy
gy
* const CELL: 8CELL;
return 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 px: numberpx, const py: numberpy, const CELL: 8CELL, const CELL: 8CELL);
} /** * Four filled bars form the boundary the snake must not cross. */ Demo.renderWalls(): void
Four filled bars form the boundary the snake must not cross.
renderWalls
() {
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
(0, 0, const DISPLAY_W: 160DISPLAY_W, const WALL: 8WALL), const C_WALL: 2C_WALL);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 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
(0, const DISPLAY_H: 120DISPLAY_H - const WALL: 8WALL, const DISPLAY_W: 160DISPLAY_W, const WALL: 8WALL), const C_WALL: 2C_WALL);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 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
(0, const WALL: 8WALL, const WALL: 8WALL, const DISPLAY_H: 120DISPLAY_H - 2 * const WALL: 8WALL), const C_WALL: 2C_WALL);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 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 DISPLAY_W: 160DISPLAY_W - const WALL: 8WALL, const WALL: 8WALL, const WALL: 8WALL, const DISPLAY_H: 120DISPLAY_H - 2 * const WALL: 8WALL), const C_WALL: 2C_WALL);
} /** * Playback rate for the background loop from the starting pitch and how many times * the snake has grown this round. One food is start * scale, two foods is * start * scale * scale, and so on; zero growths leaves the rate at the start pitch. * Growth above MUSIC_PITCH_GROWTH_CAP no longer raises the rate (matches top speed). * * @returns {number} Playback rate to pass to BT.soundPlay / BT.soundPitchSet. */ Demo.currentMusicPitch(): number
Playback rate for the background loop from the starting pitch and how many times the snake has grown this round. One food is start * scale, two foods is start * scale * scale, and so on; zero growths leaves the rate at the start pitch. Growth above MUSIC_PITCH_GROWTH_CAP no longer raises the rate (matches top speed).
@returnsPlayback rate to pass to BT.soundPlay / BT.soundPitchSet.
currentMusicPitch
() {
// The ** operator is "to the power of": a ** b means a multiplied by itself b times. // growthCount 0 gives 1, so the rate stays at MUSIC_PITCH_AT_START on a fresh round. // Cap at MUSIC_PITCH_GROWTH_CAP so pitch plateaus with moveInterval at top speed. const const growthForPitch: anygrowthForPitch = Math.min(this.Demo.growthCount: number
How many food dots the snake has eaten this round. Music pitch uses this count capped at MUSIC_PITCH_GROWTH_CAP so tempo plateaus with move speed.
growthCount
, const MUSIC_PITCH_GROWTH_CAP: numberMUSIC_PITCH_GROWTH_CAP);
return const MUSIC_PITCH_AT_START: 0.57MUSIC_PITCH_AT_START * const MUSIC_PITCH_SCALE_PER_GROWTH: 1.03MUSIC_PITCH_SCALE_PER_GROWTH ** const growthForPitch: anygrowthForPitch; } /** * Starts the looping music voice once audio is unlocked, or restarts it if the voice * was somehow lost. Safe to call every tick - it no-ops while already playing. * * Why BT.soundPlay instead of BT.musicPlay: only the SFX path exposes pitch (playback * rate), which is how we keep the beat tied to snake growth. The tradeoff is that * soundPlay is dropped before unlock, so we wait for BT.isAudioUnlocked here. */ Demo.ensureBackgroundMusic(): void
Starts the looping music voice once audio is unlocked, or restarts it if the voice was somehow lost. Safe to call every tick - it no-ops while already playing. Why BT.soundPlay instead of BT.musicPlay: only the SFX path exposes pitch (playback rate), which is how we keep the beat tied to snake growth. The tradeoff is that soundPlay is dropped before unlock, so we wait for BT.isAudioUnlocked here.
ensureBackgroundMusic
() {
if (this.Demo.musicClip: AudioClip | null
@type{AudioClip | null} Looping background music.
musicClip
=== null || !
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
.isAudioUnlocked: boolean
Whether the audio context has been unlocked by a user gesture. Browsers require a user gesture (pointer, key, or touch press) before allowing audio playback. Starts `false`; flips to `true` for the rest of the session after the first gesture successfully resumes the audio context.
@since1.3.0@returns`true` once unlocked; `false` when locked or before initialization.
isAudioUnlocked
) {
return; } // Already have a live looping voice - nothing to do. if (this.Demo.musicRef: SoundRef | null
Live handle for the looping music voice, or null before unlock / if music failed to load. We use BT.soundPlay (not BT.musicPlay) so we can change pitch each time the snake speeds up - the music player has volume and crossfade controls, but no pitch.
@type{import('blit386').SoundRef | null}
musicRef
!== null &&
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
.isSoundPlaying: (ref: SoundRef) => boolean
Reports whether a sound is still playing.
@since1.3.0@paramref - Sound to query.@returns`true` when still playing; `false` once it has stopped, been stolen, or completed.
isSoundPlaying
(this.Demo.musicRef: SoundRef
Live handle for the looping music voice, or null before unlock / if music failed to load. We use BT.soundPlay (not BT.musicPlay) so we can change pitch each time the snake speeds up - the music player has volume and crossfade controls, but no pitch.
@type{import('blit386').SoundRef | null}
musicRef
)) {
return; } // Start (or restart) at the pitch for the current growth count. this.Demo.musicRef: SoundRef | null
Live handle for the looping music voice, or null before unlock / if music failed to load. We use BT.soundPlay (not BT.musicPlay) so we can change pitch each time the snake speeds up - the music player has volume and crossfade controls, but no pitch.
@type{import('blit386').SoundRef | null}
musicRef
=
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
.soundPlay: (clip: AudioClip, options?: SoundPlayOptions) => SoundRef
Plays a loaded audio clip through the SFX voice pool. Returns an inert {@link SoundRef } without allocating a voice when the clip hasn't finished loading yet (or was already unloaded), when the pool has no free or stealable voice at this priority, or before the engine has unlocked audio playback.
@since1.3.0@paramclip - Loaded audio clip to play.@paramoptions - Playback options.@returnsA handle identifying the new voice; pass it to {@link BT.soundStop} and the other per-sound controls. Safe to use even when playback was silently dropped - every accessor on an inert handle is a no-op.
soundPlay
(this.Demo.musicClip: AudioClip
@type{AudioClip | null} Looping background music.
musicClip
, {
VoicePlayOptions.loop?: boolean | undefined
Whether the buffer loops. Defaults to `false`.
loop
: true,
VoicePlayOptions.volume?: number | undefined
Initial gain in `[0, 1]` (unclamped). Defaults to `1`.
volume
: 0.65,
VoicePlayOptions.pitch?: number | undefined
Initial `playbackRate`. Defaults to `1`.
pitch
: this.Demo.currentMusicPitch(): number
Playback rate for the background loop from the starting pitch and how many times the snake has grown this round. One food is start * scale, two foods is start * scale * scale, and so on; zero growths leaves the rate at the start pitch. Growth above MUSIC_PITCH_GROWTH_CAP no longer raises the rate (matches top speed).
@returnsPlayback rate to pass to BT.soundPlay / BT.soundPitchSet.
currentMusicPitch
(),
VoicePlayOptions.priority?: number | undefined
Allocation priority; higher survives stealing longer. Defaults to `0`.
priority
: const MUSIC_VOICE_PRIORITY: 100MUSIC_VOICE_PRIORITY,
}); } /** * Glides the live music loop to the playback rate for the current growth count. * No-op before the loop has started (still locked, or music failed to load). */ Demo.syncMusicTempo(): void
Glides the live music loop to the playback rate for the current growth count. No-op before the loop has started (still locked, or music failed to load).
syncMusicTempo
() {
if (this.Demo.musicRef: SoundRef | null
Live handle for the looping music voice, or null before unlock / if music failed to load. We use BT.soundPlay (not BT.musicPlay) so we can change pitch each time the snake speeds up - the music player has volume and crossfade controls, but no pitch.
@type{import('blit386').SoundRef | null}
musicRef
=== null || !
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
.isSoundPlaying: (ref: SoundRef) => boolean
Reports whether a sound is still playing.
@since1.3.0@paramref - Sound to query.@returns`true` when still playing; `false` once it has stopped, been stolen, or completed.
isSoundPlaying
(this.Demo.musicRef: SoundRef
Live handle for the looping music voice, or null before unlock / if music failed to load. We use BT.soundPlay (not BT.musicPlay) so we can change pitch each time the snake speeds up - the music player has volume and crossfade controls, but no pitch.
@type{import('blit386').SoundRef | null}
musicRef
)) {
return; }
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
.soundPitchSet: (ref: SoundRef, value: number, options?: SoundParamSetOptions) => void
Sets a sound's playback rate, optionally fading to it.
@since1.3.0@paramref - Sound to update.@paramvalue - Target playback rate.@paramoptions - Optional fade behavior.@paramoptions.fadeMs - Fade duration in milliseconds. Omit for an immediate change.
soundPitchSet
(this.Demo.musicRef: SoundRef
Live handle for the looping music voice, or null before unlock / if music failed to load. We use BT.soundPlay (not BT.musicPlay) so we can change pitch each time the snake speeds up - the music player has volume and crossfade controls, but no pitch.
@type{import('blit386').SoundRef | null}
musicRef
, this.Demo.currentMusicPitch(): number
Playback rate for the background loop from the starting pitch and how many times the snake has grown this round. One food is start * scale, two foods is start * scale * scale, and so on; zero growths leaves the rate at the start pitch. Growth above MUSIC_PITCH_GROWTH_CAP no longer raises the rate (matches top speed).
@returnsPlayback rate to pass to BT.soundPlay / BT.soundPitchSet.
currentMusicPitch
(), {
SoundParamSetOptions.fadeMs?: number | undefined
Fade duration in milliseconds. Omit for an immediate change.
fadeMs
: const MUSIC_PITCH_FADE_MS: 120MUSIC_PITCH_FADE_MS,
}); } /** * Places a short snake in the middle, resets speed to the slow start, and spawns * the first food dot. */ Demo.startRound(): void
Places a short snake in the middle, resets speed to the slow start, and spawns the first food dot.
startRound
() {
this.Demo.gameOver: boolean
When true, the snake does not move until restart.
gameOver
= false;
this.Demo.deathTick: number | null
@type{number | null} Tick index when the snake died (`BT.ticks`); null while playing.
deathTick
= null;
// Every new round begins at the slow pace; eating food will speed things up again. this.Demo.moveInterval: number
Current ticks between steps. Starts at MOVE_INTERVAL_START each round and drops toward MOVE_INTERVAL_MIN every time the snake eats food.
moveInterval
= const MOVE_INTERVAL_START: 20MOVE_INTERVAL_START;
this.Demo.moveCooldown: number
Counts ticks until the next snake step while the game is running.
moveCooldown
= this.Demo.moveInterval: number
Current ticks between steps. Starts at MOVE_INTERVAL_START each round and drops toward MOVE_INTERVAL_MIN every time the snake eats food.
moveInterval
;
// Zero foods eaten - music returns to MUSIC_PITCH_AT_START for the new round. this.Demo.growthCount: number
How many food dots the snake has eaten this round. Music pitch uses this count capped at MUSIC_PITCH_GROWTH_CAP so tempo plateaus with move speed.
growthCount
= 0;
this.Demo.syncMusicTempo(): void
Glides the live music loop to the playback rate for the current growth count. No-op before the loop has started (still locked, or music failed to load).
syncMusicTempo
();
const const midX: anymidX = Math.floor(const CELLS_X: numberCELLS_X / 2); const const midY: anymidY = Math.floor(const CELLS_Y: numberCELLS_Y / 2); this.Demo.snake: {}
@type{{ x: number; y: number }[]} Head first, tail last (grid coords).
snake
= [
{ x: anyx: const midX: anymidX, y: anyy: const midY: anymidY }, { x: numberx: const midX: anymidX - 1, y: anyy: const midY: anymidY }, { x: numberx: const midX: anymidX - 2, y: anyy: const midY: anymidY }, ]; this.Demo.dx: number
Current step direction (grid units per move).
dx
= 1;
this.Demo.dy: numberdy = 0; this.Demo.pendingDx: number
Next direction chosen by the player (applied when the snake steps).
pendingDx
= 1;
this.Demo.pendingDy: numberpendingDy = 0; this.Demo.placeFood(): void
Picks a random empty grid cell for food.
placeFood
();
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
('Round start');
} /** * Picks a random empty grid cell for food. */ Demo.placeFood(): void
Picks a random empty grid cell for food.
placeFood
() {
const const occupied: anyoccupied = new Set(); for (let let i: numberi = 0; let i: numberi < this.Demo.snake: {}
@type{{ x: number; y: number }[]} Head first, tail last (grid coords).
snake
.length; let i: numberi++) {
const const s: anys = this.Demo.snake: {}
@type{{ x: number; y: number }[]} Head first, tail last (grid coords).
snake
[let i: numberi];
const occupied: anyoccupied.add(`${const s: anys.x},${const s: anys.y}`); } for (let let attempt: numberattempt = 0; let attempt: numberattempt < 4000; let attempt: numberattempt++) { // int() with a single argument counts from 0, so int(CELLS_X) lands on any column from 0 to CELLS_X - 1 // - exactly the valid grid positions. const const x: numberx =
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
.random: Random
Default engine PRNG (live reference - not a copy). Time-seeded when the engine singleton is created. Call {@link BT.randomSeed } for a reproducible run. Mutating the instance (for example `BT.random.int(10)`) advances the shared stream.
@since1.5.0@returnsThe shared {@link Random} instance.@exampleBT.randomSeed(42); BT.random.int(150, 420); BT.random.pick(['a', 'b', 'c']);
random
.Random.int(minOrMaxExclusive: number, maxExclusive?: number): number
Returns a pseudo-random integer in [0, maxExclusive) or [min, maxExclusive).
@paramminOrMaxExclusive - When alone, exclusive upper bound from 0; otherwise inclusive min.@parammaxExclusive - Exclusive upper bound when two arguments are passed.@returnsWhole number in the half-open range.@since1.5.0
int
(const CELLS_X: numberCELLS_X);
const const y: numbery =
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
.random: Random
Default engine PRNG (live reference - not a copy). Time-seeded when the engine singleton is created. Call {@link BT.randomSeed } for a reproducible run. Mutating the instance (for example `BT.random.int(10)`) advances the shared stream.
@since1.5.0@returnsThe shared {@link Random} instance.@exampleBT.randomSeed(42); BT.random.int(150, 420); BT.random.pick(['a', 'b', 'c']);
random
.Random.int(minOrMaxExclusive: number, maxExclusive?: number): number
Returns a pseudo-random integer in [0, maxExclusive) or [min, maxExclusive).
@paramminOrMaxExclusive - When alone, exclusive upper bound from 0; otherwise inclusive min.@parammaxExclusive - Exclusive upper bound when two arguments are passed.@returnsWhole number in the half-open range.@since1.5.0
int
(const CELLS_Y: numberCELLS_Y);
const const key: stringkey = `${const x: numberx},${const y: numbery}`; if (!const occupied: anyoccupied.has(const key: stringkey)) { this.
Demo.food: {
    x: number;
    y: number;
}
@type{{ x: number; y: number }} Food cell in grid coords.
food
= { x: numberx, y: numbery };
return; } } // Board full - no free cell found; (-1, -1) is a sentinel that render() sees and skips drawing the food dot. this.
Demo.food: {
    x: number;
    y: number;
}
@type{{ x: number; y: number }} Food cell in grid coords.
food
= { x: numberx: -1, y: numbery: -1 };
} /** * One grid step: wall check, self check, grow or shift tail. */ Demo.step(): void
One grid step: wall check, self check, grow or shift tail.
step
() {
const const head: anyhead = this.Demo.snake: {}
@type{{ x: number; y: number }[]} Head first, tail last (grid coords).
snake
[0];
const const nx: anynx = const head: anyhead.x + this.Demo.dx: number
Current step direction (grid units per move).
dx
;
const const ny: anyny = const head: anyhead.y + this.Demo.dy: numberdy; if (const nx: anynx < 0 || const nx: anynx >= const CELLS_X: numberCELLS_X || const ny: anyny < 0 || const ny: anyny >= const CELLS_Y: numberCELLS_Y) { this.Demo.endGame(): void
Freeze movement and remember when to restart.
endGame
();
return; } const const eating: booleaneating = const nx: anynx === this.
Demo.food: {
    x: number;
    y: number;
}
@type{{ x: number; y: number }} Food cell in grid coords.
food
.x: numberx && const ny: anyny === this.
Demo.food: {
    x: number;
    y: number;
}
@type{{ x: number; y: number }} Food cell in grid coords.
food
.y: numbery;
const const limit: anylimit = const eating: booleaneating ? this.Demo.snake: {}
@type{{ x: number; y: number }[]} Head first, tail last (grid coords).
snake
.length : this.Demo.snake: {}
@type{{ x: number; y: number }[]} Head first, tail last (grid coords).
snake
.length - 1;
for (let let i: numberi = 0; let i: numberi < const limit: anylimit; let i: numberi++) { const const s: anys = this.Demo.snake: {}
@type{{ x: number; y: number }[]} Head first, tail last (grid coords).
snake
[let i: numberi];
if (const s: anys.x === const nx: anynx && const s: anys.y === const ny: anyny) { this.Demo.endGame(): void
Freeze movement and remember when to restart.
endGame
();
return; } } this.Demo.snake: {}
@type{{ x: number; y: number }[]} Head first, tail last (grid coords).
snake
.unshift({ x: anyx: const nx: anynx, y: anyy: const ny: anyny });
if (const eating: booleaneating) {
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
.soundPlay: (clip: AudioClip, options?: SoundPlayOptions) => SoundRef
Plays a loaded audio clip through the SFX voice pool. Returns an inert {@link SoundRef } without allocating a voice when the clip hasn't finished loading yet (or was already unloaded), when the pool has no free or stealable voice at this priority, or before the engine has unlocked audio playback.
@since1.3.0@paramclip - Loaded audio clip to play.@paramoptions - Playback options.@returnsA handle identifying the new voice; pass it to {@link BT.soundStop} and the other per-sound controls. Safe to use even when playback was silently dropped - every accessor on an inert handle is a no-op.
soundPlay
(this.Demo.eatClip: AudioClip | null
@type{AudioClip | null} Sound played when the snake eats food.
eatClip
);
// Shorter wait between steps = faster snake. Clamp so it never goes below // MOVE_INTERVAL_MIN; Math.max picks the larger of the floor and the stepped-down // value, which is how we stop the interval from dropping into the negatives. this.Demo.moveInterval: number
Current ticks between steps. Starts at MOVE_INTERVAL_START each round and drops toward MOVE_INTERVAL_MIN every time the snake eats food.
moveInterval
= Math.max(const MOVE_INTERVAL_MIN: 4MOVE_INTERVAL_MIN, this.Demo.moveInterval: number
Current ticks between steps. Starts at MOVE_INTERVAL_START each round and drops toward MOVE_INTERVAL_MIN every time the snake eats food.
moveInterval
- const MOVE_INTERVAL_STEP: 1MOVE_INTERVAL_STEP);
// Keep counting every food for length / UI; currentMusicPitch() caps the exponent // at MUSIC_PITCH_GROWTH_CAP so tempo stops climbing once speed has already maxed out. this.Demo.growthCount: number
How many food dots the snake has eaten this round. Music pitch uses this count capped at MUSIC_PITCH_GROWTH_CAP so tempo plateaus with move speed.
growthCount
+= 1;
this.Demo.syncMusicTempo(): void
Glides the live music loop to the playback rate for the current growth count. No-op before the loop has started (still locked, or music failed to load).
syncMusicTempo
();
this.Demo.placeFood(): void
Picks a random empty grid cell for food.
placeFood
();
} else { this.Demo.snake: {}
@type{{ x: number; y: number }[]} Head first, tail last (grid coords).
snake
.pop();
} } /** * Freeze movement and remember when to restart. */ Demo.endGame(): void
Freeze movement and remember when to restart.
endGame
() {
this.Demo.gameOver: boolean
When true, the snake does not move until restart.
gameOver
= true;
this.Demo.deathTick: number | null
@type{number | null} Tick index when the snake died (`BT.ticks`); null while playing.
deathTick
=
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
;
const 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
.soundPlay: (clip: AudioClip, options?: SoundPlayOptions) => SoundRef
Plays a loaded audio clip through the SFX voice pool. Returns an inert {@link SoundRef } without allocating a voice when the clip hasn't finished loading yet (or was already unloaded), when the pool has no free or stealable voice at this priority, or before the engine has unlocked audio playback.
@since1.3.0@paramclip - Loaded audio clip to play.@paramoptions - Playback options.@returnsA handle identifying the new voice; pass it to {@link BT.soundStop} and the other per-sound controls. Safe to use even when playback was silently dropped - every accessor on an inert handle is a no-op.
soundPlay
(this.Demo.gameOverClip: AudioClip | null
@type{AudioClip | null} Sound played when the snake dies.
gameOverClip
);
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
('Game over');
} } 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
Minimal snake with PipBoy CRT post-processing from crt-pipboy demo.
@implementsIBTDemo
Demo
);