// Sprite Effects: shows how to use palette offsets to create game effects.
// @description Palette offsets as game effects: damage flash, silhouette, ghost fade, team colors, and day or night.
//
// Prerequisites: Basics (https://demos.blit386.dev/basics),
// Sprites (https://demos.blit386.dev/sprites).
// Guide: https://blit386.dev/docs/guides/palette#per-draw-palette-offsets-zero-cost-color-variants
//
// In the palette-based rendering system, each sprite pixel stores a palette index.
// By drawing the same sprite with a different palette offset (the fourth argument
// to BT.drawSprite), every pixel shifts to a different color block in the palette.
// This replaces what older APIs called "tinting" (multiplying pixels by a color).
//
// Common uses in retro games:
//   - Damage flash: swap to a block of reds, then back to normal after 30 ticks
//   - Silhouette: use a block where all colors are black
//   - Ghost: a block where all colors have low alpha
//   - Team colors: separate pre-built blocks for red/blue/green teams
//   - Status effects: frozen (all shifted to cold blues), poisoned (pulsing greens)
//   - Day/night: a block dynamically updated every tick to reflect ambient light
//
// For STATIC effects (normal, silhouette, team red/blue/green, frozen): we build color
// blocks once in init() and never change them.
//
// For DYNAMIC effects (damage flash, ghost pulse, invincibility, poison, day/night):
// we update the color block in update() every tick and draw with that offset in render().
//
// We learned about palette offsets in Sprites demo:
// https://demos.blit386.dev/sprites
//
// FULLSCREEN CRT (Tesla Orava TV)
// The sprite grid is drawn at 640x400, then the engine resolves and upscales it and runs
// a display-tier stack tuned for 1970s Czechoslovak B/W CRT sets (Tesla Orava 131/226/229):
// curved tube, soft phosphor halation, scanlines, light bezel vignette, a roll band
// scrolling top-to-bottom, subtle offset-band wobble, and occasional TV faults (tear,
// snow, dim, ghost, roll). Same building blocks as basics-enhanced demo; chroma split is omitted (B/W).
// Post-process needs WebGPU; the software renderer still shows the sprite effects without CRT.
//
// We learned about composing effects in PipBoy CRT and Basics Enhanced.
//
// Captions, the day/night legend, and the software fallback note are drawn with the shared
// UI kit (src/shared/ui.js), which installs its twelve UI colors high in the palette
// (slots 240-251) via applyTheme().

import {
    
type BarrelDistortion = BarrelDistortion
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
,
type Bloom = Bloom
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 Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
,
type Flicker = Flicker
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
,
type Interference = Interference
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
,
type Noise = Noise
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
,
type PixelGlitch = PixelGlitch
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
,
type Rect2i = Rect2i
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
,
type RGBMask = RGBMask
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
,
type RollLine = RollLine
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
,
type Scanlines = Scanlines
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
,
type SpriteSheet = SpriteSheet
class SpriteSheet
Sprite-sheet wrapper around a loaded image asset. The class keeps the original image available for CPU-side inspection while lazily creating and caching a GPU texture for rendering. When possible, `load()` also pre-decodes the source into an `ImageBitmap` so texture uploads preserve pixel-art alpha and color values more reliably. After calling `indexize()`, the sheet stores palette indices rather than RGBA data. The GPU texture becomes an `r8uint` format uploaded via `writeTexture`. The original RGBA bytes are retained so `reindexize()` can re-convert without reloading the image.
@since0.1.0
SpriteSheet
,
class Vector2i
Integer 2D vector for pixel-perfect positioning. Used for points, sizes, directions, and camera offsets throughout the engine. The API includes both allocation-free `*To()` / `*InPlace()` variants and convenience methods that return new vectors.
@since0.1.0
Vector2i
,
type Vignette = Vignette
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 GLITCH_LABELSGLITCH_LABELS, import GLITCH_TYPES_VROLLGLITCH_TYPES_VROLL } 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 */ /** @typedef {import('blit386').SpriteSheet} SpriteSheet */ /** @typedef {import('blit386').Rect2i} Rect2i */ /** @typedef {import('blit386').PixelGlitch} PixelGlitch */ /** @typedef {import('blit386').BarrelDistortion} BarrelDistortion */ /** @typedef {import('blit386').Interference} Interference */ /** @typedef {import('blit386').RollLine} RollLine */ /** @typedef {import('blit386').Scanlines} Scanlines */ /** @typedef {import('blit386').RGBMask} RGBMask */ /** @typedef {import('blit386').Vignette} Vignette */ /** @typedef {import('blit386').Noise} Noise */ /** @typedef {import('blit386').Flicker} Flicker */ /** @typedef {import('blit386').Bloom} Bloom */ // Where sprite colors start in the palette. The 13 theme blocks stack upward from here: // with the 6-color test sprite the top block ends at slot 89, far below the shared UI // theme colors at slots 240-251. const const COLOR_BASE: 12COLOR_BASE = 12; // Palette slots of the shared UI theme. applyTheme() in init() writes the twelve UI kit // colors into slots 240-251 (its default start slot). configure() runs BEFORE init(), so // the overlay styles below cannot read this.theme yet - these constants spell out where // each theme color will land once init() runs. const const UI_BG: 240UI_BG = 240; // 'ui_bg' - deep navy screen background. const const UI_TEXT: 244UI_TEXT = 244; // 'ui_text' - off-white primary text. const const UI_DIM: 245UI_DIM = 245; // 'ui_text_dim' - secondary gray text. const const UI_GAP: 252UI_GAP = 252; // gray gap between UI elements; a static slot outside both the sprite // theme range (12-89) and the applyTheme() range (240-251), set directly in init(). const const UI_HEADER: 246UI_HEADER = 246; // 'ui_header' - warm amber (render bars, chart warnings). const const UI_ACCENT: 247UI_ACCENT = 247; // 'ui_accent' - phosphor green (update bars). const const UI_WARM: 248UI_WARM = 248; // 'ui_accent_warm' - orange (chart error frames). const const UI_INFO: 249UI_INFO = 249; // 'ui_info' - light blue (chart tags). // Theme block indices (as palette offsets from COLOR_BASE). // Each block contains one entry per unique sprite color - that count lives in // this.colorCount, extracted in init(). Offset = blockIndex * colorCount. // Blocks 0..7 are static; blocks 8..12 are dynamic (updated in update()). // // Block 0 (offset 0): Original stone colors. // Block 1 (offset colorCount): Silhouette - all colors near-black. // Block 2 (offset 2 * colorCount): Damage white - all colors bright white. // Block 3 (offset 3 * colorCount): Damage red - all colors shifted red. // Block 4 (offset 4 * colorCount): Team red. // Block 5 (offset 5 * colorCount): Team blue. // Block 6 (offset 6 * colorCount): Team green. // Block 7 (offset 7 * colorCount): Frozen (cool blue). // Block 8 (offset 8 * colorCount): Damage flash (dynamic: toggling white/red). // Block 9 (offset 9 * colorCount): Ghost (dynamic: pulsing low alpha). // Block 10 (offset 10 * colorCount): Invincibility (dynamic: hue rotation). // Block 11 (offset 11 * colorCount): Poison (dynamic: pulsing green brightness). // Block 12 (offset 12 * colorCount): Day/night ambient (dynamic: brightness cycle). const const BLOCK_ORIGINAL: 0BLOCK_ORIGINAL = 0; const const BLOCK_SILHOUETTE: 1BLOCK_SILHOUETTE = 1; const const BLOCK_DAMAGE_WHITE: 2BLOCK_DAMAGE_WHITE = 2; const const BLOCK_DAMAGE_RED: 3BLOCK_DAMAGE_RED = 3; const const BLOCK_TEAM_RED: 4BLOCK_TEAM_RED = 4; const const BLOCK_TEAM_BLUE: 5BLOCK_TEAM_BLUE = 5; const const BLOCK_TEAM_GREEN: 6BLOCK_TEAM_GREEN = 6; const const BLOCK_FROZEN: 7BLOCK_FROZEN = 7; const const BLOCK_DAMAGE_FLASH: 8BLOCK_DAMAGE_FLASH = 8; // Dynamic. const const BLOCK_GHOST: 9BLOCK_GHOST = 9; // Dynamic. const const BLOCK_INVINCIBLE: 10BLOCK_INVINCIBLE = 10; // Dynamic. const const BLOCK_POISON: 11BLOCK_POISON = 11; // Dynamic. const const BLOCK_DAYNIGHT: 12BLOCK_DAYNIGHT = 12; // Dynamic. // Logical game resolution (where render() draws). The demos page scales this up on screen. const const DISPLAY_W: 640DISPLAY_W = 640; const const DISPLAY_H: 400DISPLAY_H = 400; // How large the canvas may appear in the browser (3x logical, crisp CSS upscale). const const MAX_CANVAS_W: numberMAX_CANVAS_W = const DISPLAY_W: 640DISPLAY_W * 3; const const MAX_CANVAS_H: numberMAX_CANVAS_H = const DISPLAY_H: 400DISPLAY_H * 3; // Display-tier CRT runs on the upscaled RGBA buffer (3x logical, like basics-enhanced demo). const const OUTPUT_W: numberOUTPUT_W = const DISPLAY_W: 640DISPLAY_W * 3; const const OUTPUT_H: numberOUTPUT_H = const DISPLAY_H: 400DISPLAY_H * 3; // Analog-TV glitch bursts (ticks at default 60 FPS). const const GLITCH_COOLDOWN_MIN: 150GLITCH_COOLDOWN_MIN = 150; const const GLITCH_COOLDOWN_MAX: 420GLITCH_COOLDOWN_MAX = 420; const const GLITCH_ACTIVE_MIN: 4GLITCH_ACTIVE_MIN = 4; const const GLITCH_ACTIVE_MAX: 24GLITCH_ACTIVE_MAX = 24; const const GLITCH_INTENSITY_MIN: 0.3GLITCH_INTENSITY_MIN = 0.3; const const GLITCH_INTENSITY_MAX: 0.95GLITCH_INTENSITY_MAX = 0.95; // The shared fallback note is one long sentence. split('. ') cuts the string at the // sentence break, giving us an array of two shorter lines the UI kit can draw one under // the other when the software renderer is active (same pattern as basics-enhanced demo). const const FALLBACK_LINES: anyFALLBACK_LINES = import SOFTWARE_FALLBACK_NOTESOFTWARE_FALLBACK_NOTE.split('. '); const const FLICKER_BASE: 1FLICKER_BASE = 1.0; const const FLICKER_DIP: 0.78FLICKER_DIP = 0.78; const const NOISE_BASE: 0.038NOISE_BASE = 0.038; // Always-on bright band scrolling top to bottom (RollLine). const const ROLL_BASE: 0.26ROLL_BASE = 0.26; const const ROLL_SPEED: 0.92ROLL_SPEED = 0.92; const const INTERFERENCE_BASE: 0INTERFERENCE_BASE = 0; // Occasional subtle horizontal band offset (PixelGlitch), separate from TV fault bursts. const const BAND_WOBBLE_COOLDOWN_MIN: 100BAND_WOBBLE_COOLDOWN_MIN = 100; const const BAND_WOBBLE_COOLDOWN_MAX: 280BAND_WOBBLE_COOLDOWN_MAX = 280; const const BAND_WOBBLE_ACTIVE_MIN: 3BAND_WOBBLE_ACTIVE_MIN = 3; const const BAND_WOBBLE_ACTIVE_MAX: 10BAND_WOBBLE_ACTIVE_MAX = 10; const const BAND_WOBBLE_INTENSITY: 0.11BAND_WOBBLE_INTENSITY = 0.11; /** * Spins the CPU until roughly `ms` milliseconds have passed. * The timing chart maps bar height from real update()/render() time; this demo adds * a gentle pulse so the scrolling bars are easy to see while you watch the effects. * * @param {number} ms - Target delay in milliseconds. */ function function burnCpuMs(ms: number): void
Spins the CPU until roughly `ms` milliseconds have passed. The timing chart maps bar height from real update()/render() time; this demo adds a gentle pulse so the scrolling bars are easy to see while you watch the effects.
@paramms - Target delay in milliseconds.
burnCpuMs
(ms: number
- Target delay in milliseconds.
@paramms - Target delay in milliseconds.
ms
) {
if (ms: number
- Target delay in milliseconds.
@paramms - Target delay in milliseconds.
ms
<= 0) {
return; } // performance.now() returns a high-resolution clock in milliseconds. const const deadline: anydeadline = performance.now() + ms: number
- Target delay in milliseconds.
@paramms - Target delay in milliseconds.
ms
;
while (performance.now() < const deadline: anydeadline) { // Empty loop on purpose: we are waiting for the clock, not doing useful work. } } /** * Demonstrates palette-offset based sprite effects. * Static effects are pre-built in init(); dynamic effects update in update(). * * @implements {IBTDemo} */ class class Demo
Demonstrates palette-offset based sprite effects. Static effects are pre-built in init(); dynamic effects update in update().
@implementsIBTDemo
Demo
{
// The palette holds all colors for this demo. /** @type {Palette | null} */ Demo.palette: Palette | null
@type{Palette | null}
palette
= null;
// The sprite sheet loaded from /sprites/test.png. /** @type {SpriteSheet | null} */ Demo.sheet: SpriteSheet | null
@type{SpriteSheet | null}
sheet
= null;
// The source rectangle for the rock sprite. /** @type {Rect2i | null} */ Demo.charRect: Rect2i | null
@type{Rect2i | null}
charRect
= null;
// How many unique colors the sprite has - every theme block is this many // palette slots wide. Computed in init() after the colors are extracted. Demo.colorCount: numbercolorCount = 0; // Slot map for the shared UI kit theme, filled in init() by applyTheme(). // theme.bg, theme.panel, and friends are palette indices for our own drawing. Demo.theme: nulltheme = null; // The extracted original Color32 objects (used to build theme blocks). Demo.baseColors: {}baseColors = []; // animTime drives all dynamic effects. Demo.animTime: numberanimTime = 0; // Which tick the last "damage event" occurred on (for the damage flash). Demo.damageFlashTick: numberdamageFlashTick = 0; /** @type {PixelGlitch | null} */ Demo.pixelGlitch: PixelGlitch | null
@type{PixelGlitch | null}
pixelGlitch
= null;
/** @type {BarrelDistortion | null} */ Demo.barrel: BarrelDistortion | null
@type{BarrelDistortion | null}
barrel
= null;
/** @type {Interference | null} */ Demo.interference: Interference | null
@type{Interference | null}
interference
= null;
/** @type {RollLine | null} */ Demo.rollLine: RollLine | null
@type{RollLine | null}
rollLine
= null;
/** @type {Scanlines | null} */ Demo.scanlines: Scanlines | null
@type{Scanlines | null}
scanlines
= null;
/** @type {RGBMask | null} */ Demo.mask: RGBMask | null
@type{RGBMask | null}
mask
= null;
/** @type {Vignette | null} */ Demo.vignette: Vignette | null
@type{Vignette | null}
vignette
= null;
/** @type {Noise | null} */ Demo.noise: Noise | null
@type{Noise | null}
noise
= null;
/** @type {Flicker | null} */ Demo.flicker: Flicker | null
@type{Flicker | null}
flicker
= null;
/** @type {Bloom | null} */ Demo.bloom: Bloom | null
@type{Bloom | null}
bloom
= null;
Demo.effectsAvailable: booleaneffectsAvailable = false; Demo.glitchCooldown: numberglitchCooldown = 0; // How many ticks the current TV fault burst still has to run (0 = no burst active). Demo.glitchTicksLeft: numberglitchTicksLeft = 0; Demo.glitchDuration: numberglitchDuration = 0; Demo.glitchType: stringglitchType = 'none'; Demo.glitchPeak: numberglitchPeak = 0; Demo.bandWobbleCooldown: numberbandWobbleCooldown = 0; // How many ticks the current band wobble still has to run (0 = no wobble active). Demo.bandWobbleTicksLeft: numberbandWobbleTicksLeft = 0; Demo.bandWobbleDuration: numberbandWobbleDuration = 0; Demo.bandWobbleSeed: numberbandWobbleSeed = 0; Demo.overlayRowData: {}overlayRowData = [ { leftText: stringleftText: 'Tesla Orava CRT: OFF', textPaletteIndex: numbertextPaletteIndex: const UI_TEXT: 244UI_TEXT }, { leftText: stringleftText: 'TV fault: NONE', textPaletteIndex: numbertextPaletteIndex: const UI_HEADER: 246UI_HEADER }, ]; /** * Wider logical screen for the sprite grid; display-tier Tesla Orava runs at 3x upscale. * * @returns {Partial<HardwareSettings>} */ Demo.configure(): Partial<HardwareSettings>
Wider logical screen for the sprite grid; display-tier Tesla Orava runs at 3x upscale.
@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: 640DISPLAY_W, const DISPLAY_H: 400DISPLAY_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: numberOUTPUT_W, const OUTPUT_H: numberOUTPUT_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 MAX_CANVAS_W: numberMAX_CANVAS_W, const MAX_CANVAS_H: numberMAX_CANVAS_H),
outputUpscaleFilter: stringoutputUpscaleFilter: 'nearest', isOverlayPaletteEnabled: booleanisOverlayPaletteEnabled: true, isOverlayVisibleAtStart: booleanisOverlayVisibleAtStart: true, // Opt in to the engine timing chart band under the title row. // overlayTimingChartHeight sets band height in pixels (default 22). isOverlayTimingChartEnabled: booleanisOverlayTimingChartEnabled: true, overlayTimingChartHeight: numberoverlayTimingChartHeight: 64, overlayTimingChartDiagnostics: stringoverlayTimingChartDiagnostics: 'rich', isOverlayRendererDiagnosticsBarEnabled: booleanisOverlayRendererDiagnosticsBarEnabled: true,
overlayStyle: {
    barPaletteIndex: number;
    textPaletteIndex: number;
    gapPaletteIndex: number;
}
overlayStyle
: {
barPaletteIndex: numberbarPaletteIndex: const UI_BG: 240UI_BG, textPaletteIndex: numbertextPaletteIndex: const UI_DIM: 245UI_DIM, gapPaletteIndex: numbergapPaletteIndex: const UI_GAP: 252UI_GAP, },
overlayTimingChartStyle: {
    updateBarPaletteIndex: number;
    renderBarPaletteIndex: number;
    warningPaletteIndex: number;
    errorPaletteIndex: number;
    tagPaletteIndex: number;
}
overlayTimingChartStyle
: {
updateBarPaletteIndex: numberupdateBarPaletteIndex: const UI_ACCENT: 247UI_ACCENT, renderBarPaletteIndex: numberrenderBarPaletteIndex: const UI_HEADER: 246UI_HEADER, warningPaletteIndex: numberwarningPaletteIndex: const UI_HEADER: 246UI_HEADER, errorPaletteIndex: numbererrorPaletteIndex: const UI_WARM: 248UI_WARM, tagPaletteIndex: numbertagPaletteIndex: const UI_INFO: 249UI_INFO, }, }; } /** * Sets up the palette, builds all 13 theme blocks, and loads the sprite. * * @returns {Promise<boolean>} Returns true when everything is ready. */ async Demo.init(): Promise<boolean>
Sets up the palette, builds all 13 theme blocks, and loads the sprite.
@returnsReturns true when everything is ready.
init
() {
console.log('[SpriteEffectsDemo] Initializing...'); 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);
// Install the shared UI theme: applyTheme() writes the twelve UI kit colors into // high palette slots (240-251), far above the sprite theme blocks (slots 12-89 // with the 6-color test sprite), and returns a map of friendly names to those // slots (this.theme.bg, .panel, ...). All captions and legends draw with these. this.Demo.theme: nulltheme = import applyThemeapplyTheme(this.Demo.palette: Palette
@type{Palette | null}
palette
);
// The overlay gap color lives outside the theme's own range, so we set it directly. 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 UI_GAP: 252UI_GAP, 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
(60, 60, 70));
// Extract sprite colors // Ask the engine to scan the PNG and add every unique color it finds into our palette, // starting at COLOR_BASE. The returned array is the same colors in palette-write order // (sorted darkest-first by brightness). We keep them so the theme-block builders can // tint each base color and write the result into a higher slot. this.Demo.baseColors: {}baseColors = await class SpriteSheet
Sprite-sheet wrapper around a loaded image asset. The class keeps the original image available for CPU-side inspection while lazily creating and caching a GPU texture for rendering. When possible, `load()` also pre-decodes the source into an `ImageBitmap` so texture uploads preserve pixel-art alpha and color values more reliably. After calling `indexize()`, the sheet stores palette indices rather than RGBA data. The GPU texture becomes an `r8uint` format uploaded via `writeTexture`. The original RGBA bytes are retained so `reindexize()` can re-convert without reloading the image.
@since0.1.0
SpriteSheet
.
SpriteSheet.loadColorsIntoPalette(url: string, palette: Palette, startSlot: number, options?: {
    sort?: "luminance" | "none";
}): Promise<Color32[]>
Walks a PNG's pixels and registers every unique opaque color into the supplied palette starting at `startSlot`. Pixels with alpha 0 are skipped - they map to the engine's transparent sentinel slot 0 at draw time. Opaque pixels are deduplicated on RGB and stored with alpha forced to 255, matching the lookup performed by `indexize()` so a subsequent `sheet.indexize(palette)` call resolves without throwing on missing colors. By default colors are sorted darkest-first by perceived luminance ( {@link Color32.luminance } ); pass `{ sort: 'none' }` to keep the row-major scan order of the source image. Image loading goes through {@link AssetLoader.loadImage } , so the call shares cache and in-flight deduplication with {@link SpriteSheet.load } . The destination range is validated before any write, so the palette is never left partially mutated: if the collected colors would not fit (`startSlot < 1` or `startSlot + count > palette.size`), the method throws without touching any slot.
@paramurl - Path or URL to the PNG file.@parampalette - Target palette to populate.@paramstartSlot - First palette slot to write into.@paramoptions - Optional configuration.@paramoptions.sort - Color ordering. Defaults to `'luminance'`.@returnsRegistered colors in palette-write order.@throwsError if the image cannot be loaded.@throwsRangeError if the discovered colors do not fit in the palette starting at `startSlot`.
loadColorsIntoPalette
('/sprites/test.png', this.Demo.palette: Palette
@type{Palette | null}
palette
, const COLOR_BASE: 12COLOR_BASE);
const const colorCount: anycolorCount = this.Demo.baseColors: {}baseColors.length; this.Demo.colorCount: numbercolorCount = const colorCount: anycolorCount; // Build the 8 static theme blocks // Each block sits at COLOR_BASE + blockIndex * colorCount. this.Demo.buildStaticThemeBlocks(): void
Builds the 8 static theme blocks by transforming the base colors. Called once in init() - these never change after setup.
buildStaticThemeBlocks
();
// Dynamic blocks (8..12) start as copies of the original. // update() will replace them each tick. palette.fillBlock(start, source, transform) // writes transform(baseColor) into one slot per base color, starting at `start`. for (let let block: numberblock = const BLOCK_DAMAGE_FLASH: 8BLOCK_DAMAGE_FLASH; let block: numberblock <= const BLOCK_DAYNIGHT: 12BLOCK_DAYNIGHT; let block: numberblock++) { this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.fillBlock(start: number, source: readonly Color32[], transform: (color: Color32, index: number) => Color32): number
Writes a transformed block of colors into contiguous palette slots. Writes `transform(source[i], i)` into slot `start + i` for every `i` in `[0, source.length)`, delegating each write to {@link set } so it inherits {@link set } 's validation, including the rule that slot 0 must stay transparent. Collapses the common pattern of looping `palette.set(start + i, transform(baseColors[i]))` into one call.
@since1.7.0@paramstart - First palette index to write.@paramsource - Source colors to read from, in order. `source[i]` maps to slot `start + i`.@paramtransform - Called once per source color as `transform(color, i)`; its return value is written to slot `start + i`.@returnsThe next free slot after the written block (`start + source.length`), for chaining further writes.@throwsError if `start` is not a non-negative integer.@throwsError if the block would exceed the palette size.@throwsError if an individual slot write is invalid - see {@link set}.
fillBlock
(
const COLOR_BASE: 12COLOR_BASE + let block: numberblock * const colorCount: anycolorCount, this.Demo.baseColors: {}baseColors, (base: Color32base) => 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
(base: Color32base.Color32.r: number
Red channel (0-255).
r
, base: Color32base.Color32.g: number
Green channel (0-255).
g
, base: Color32base.Color32.b: number
Blue channel (0-255).
b
, base: Color32base.Color32.a: number
Alpha channel (0-255).
a
),
); } // Load and indexize sprite try { const const indexed: Promise<IndexedSpriteLoadResult>indexed = await class SpriteSheet
Sprite-sheet wrapper around a loaded image asset. The class keeps the original image available for CPU-side inspection while lazily creating and caching a GPU texture for rendering. When possible, `load()` also pre-decodes the source into an `ImageBitmap` so texture uploads preserve pixel-art alpha and color values more reliably. After calling `indexize()`, the sheet stores palette indices rather than RGBA data. The GPU texture becomes an `r8uint` format uploaded via `writeTexture`. The original RGBA bytes are retained so `reindexize()` can re-convert without reloading the image.
@since0.1.0
SpriteSheet
.
SpriteSheet.loadIndexed(url: string, palette: Palette, startSlot: number, options?: {
    sort?: "luminance" | "none";
}): Promise<IndexedSpriteLoadResult>
Convenience one-call path for palette-indexed sprite setup. This combines: 1) {@link SpriteSheet.loadColorsIntoPalette } 2) {@link SpriteSheet.load } 3) {@link SpriteSheet.indexize } It returns the indexized sheet plus a full-frame source rectangle and the colors that were written into the palette. Callers still control when to activate the palette via `BT.paletteSet(palette)`.
@paramurl - Path or URL to the PNG file.@parampalette - Target palette used for both registration and indexization.@paramstartSlot - First palette slot to write discovered colors into.@paramoptions - Optional color-sort behavior for registration.@paramoptions.sort - Color ordering for palette registration.@returnsObject with `sheet`, `srcRect`, and registered `colors`.
loadIndexed
('/sprites/test.png', this.Demo.palette: Palette
@type{Palette | null}
palette
, const COLOR_BASE: 12COLOR_BASE, {
sort?: "none" | "luminance" | undefinedsort: 'none', }); this.Demo.sheet: SpriteSheet | null
@type{SpriteSheet | null}
sheet
= const indexed: Promise<IndexedSpriteLoadResult>indexed.sheet;
this.Demo.charRect: Rect2i | null
@type{Rect2i | null}
charRect
= this.Demo.sheet: SpriteSheet | null
@type{SpriteSheet | null}
sheet
.SpriteSheet.fullRect(): Rect2i
Returns a source rectangle that covers the entire sprite sheet.
@returnsFull-sheet source rectangle.
fullRect
();
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
);
console.log(`[SpriteEffectsDemo] Loaded sprite: ${this.Demo.charRect: Rect2i
@type{Rect2i | null}
charRect
.Rect2i.width: number
Width in pixels (defaults to 0).
width
}x${this.Demo.charRect: Rect2i
@type{Rect2i | null}
charRect
.Rect2i.height: number
Height in pixels (defaults to 0).
height
}px`);
} catch (function (local var) error: unknownerror) { console.error('[SpriteEffectsDemo] Failed to load sprite:', function (local var) error: unknownerror); return false; } // Glitch state is shared by both backends - initialize once before the CRT check. // 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 waits a random number of ticks before the first 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
(const GLITCH_COOLDOWN_MIN: 150GLITCH_COOLDOWN_MIN, const GLITCH_COOLDOWN_MAX: 420GLITCH_COOLDOWN_MAX);
this.Demo.glitchTicksLeft: numberglitchTicksLeft = 0; this.Demo.glitchDuration: numberglitchDuration = 0; this.Demo.glitchType: stringglitchType = 'none'; this.Demo.glitchPeak: numberglitchPeak = 0; this.Demo.effectsAvailable: booleaneffectsAvailable = import isAvailableisAvailable(); if (!this.Demo.effectsAvailable: booleaneffectsAvailable) { console.log('[SpriteEffectsDemo] Initialization complete (no CRT stack).'); return true; } // Build the full Tesla Orava effect chain (see setupCrtStack() below render()). this.Demo.setupCrtStack(): void
Builds the Tesla Orava effect chain once in init(): the pixel-tier band tear plus the display-tier tube look, in back-to-front order.
setupCrtStack
();
this.Demo.bandWobbleCooldown: numberbandWobbleCooldown =
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 BAND_WOBBLE_COOLDOWN_MIN: 100BAND_WOBBLE_COOLDOWN_MIN, const BAND_WOBBLE_COOLDOWN_MAX: 280BAND_WOBBLE_COOLDOWN_MAX);
this.Demo.bandWobbleTicksLeft: numberbandWobbleTicksLeft = 0; this.Demo.bandWobbleDuration: numberbandWobbleDuration = 0; console.log('[SpriteEffectsDemo] Initialization complete!'); return true; } /** * Advances animTime and updates all five dynamic theme blocks. * * The damage flash toggles between Block 2 (white) and Block 3 (red) every 3 ticks. * Ghost pulses alpha between 40 and 180. * Invincibility rotates hue around the color wheel. * Poison pulses brightness up and down. * Day/night smoothly cycles brightness over 20 seconds. */ Demo.update(): void
Advances animTime and updates all five dynamic theme blocks. The damage flash toggles between Block 2 (white) and Block 3 (red) every 3 ticks. Ghost pulses alpha between 40 and 180. Invincibility rotates hue around the color wheel. Poison pulses brightness up and down. Day/night smoothly cycles brightness over 20 seconds.
update
() {
this.Demo.animTime: numberanimTime +=
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
.deltaSeconds: number
Fixed-step seconds per update tick. Equivalent to `1 / BT.targetFPS` when `BT.targetFPS` is finite and positive. Falls back to `1 / 60` when target FPS is non-finite or non-positive.
@since1.0.4@returnsSeconds advanced by one fixed update tick.
deltaSeconds
;
if (!this.Demo.colorCount: numbercolorCount) { return; } // Trigger a damage event every 3 seconds (180 ticks). if (
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
% 180 === 0) {
this.Demo.damageFlashTick: numberdamageFlashTick =
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
;
} this.Demo.updateDamageFlashBlock(): void
Damage flash: alternates between "all white" and "all red" every 3 ticks for the first 30 ticks after damage. Fades back to normal after that.
updateDamageFlashBlock
();
this.Demo.updateGhostBlock(): void
Ghost: the sprite appears semi-transparent with a blue-white tint. Alpha pulses between 40 and 180 using a sine wave.
updateGhostBlock
();
this.Demo.updateInvincibleBlock(): void
Invincibility: cycles the entire sprite through the rainbow. The hue rotates 200 degrees per second.
updateInvincibleBlock
();
this.Demo.updatePoisonBlock(): void
Poison: a green tint that pulses brighter and darker 5 times per second.
updatePoisonBlock
();
this.Demo.updateDayNightBlock(): void
Day/night: a brightness multiplier that cycles over 20 seconds (1200 ticks). At midday the multiplier is ~1.0; at midnight it drops to ~0.3.
updateDayNightBlock
();
if (this.Demo.effectsAvailable: booleaneffectsAvailable) { this.Demo.updateCrtEffects(): void
Drives RollLine scroll, TV fault bursts, and occasional subtle band offset wobble.
updateCrtEffects
();
} // Extra update() work so the overlay timing chart shows green scrolling bars. // Math.sin swings between -1 and 1; * 0.5 + 0.5 remaps that to 0..1 for a smooth pulse. // Keep the combined update+render busy-wait well under a 60 FPS frame (~16 ms) // so low-power devices stay responsive while the timing chart still shows a pulse. const const chartUpdateLoadMs: numberchartUpdateLoadMs = 0.5 + (Math.sin(
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
.timeSeconds: number
Fixed-step elapsed time in seconds (`BT.ticks * BT.deltaSeconds`).
@since1.0.4@returnsElapsed fixed-step time in seconds since initialization.
timeSeconds
* 1.5) * 0.5 + 0.5) * 2;
function burnCpuMs(ms: number): void
Spins the CPU until roughly `ms` milliseconds have passed. The timing chart maps bar height from real update()/render() time; this demo adds a gentle pulse so the scrolling bars are easy to see while you watch the effects.
@paramms - Target delay in milliseconds.
burnCpuMs
(const chartUpdateLoadMs: numberchartUpdateLoadMs);
} /** * Runs once per screen refresh to draw all the sprite effect demonstrations. * Notice: NO Color32 objects appear in draw calls - only palette indices and offsets. */ Demo.render(): void
Runs once per screen refresh to draw all the sprite effect demonstrations. Notice: NO Color32 objects appear in draw calls - only palette indices and offsets.
render
() {
// Clear the whole screen with the shared UI theme's background color.
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.clear: (paletteIndex: number) => void
Sets the frame clear color using a palette index. The renderer uses this color when clearing the full display at the start of the next frame.
@since0.1.0@parampaletteIndex - Palette index for the full-screen clear pass.
clear
(this.Demo.theme: nulltheme.bg);
// Extra render() work so the timing chart shows yellow scrolling bars. const const chartRenderLoadMs: numberchartRenderLoadMs = 0.3 + (Math.cos(
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
.timeSeconds: number
Fixed-step elapsed time in seconds (`BT.ticks * BT.deltaSeconds`).
@since1.0.4@returnsElapsed fixed-step time in seconds since initialization.
timeSeconds
* 2.2) * 0.5 + 0.5) * 1.5;
function burnCpuMs(ms: number): void
Spins the CPU until roughly `ms` milliseconds have passed. The timing chart maps bar height from real update()/render() time; this demo adds a gentle pulse so the scrolling bars are easy to see while you watch the effects.
@paramms - Target delay in milliseconds.
burnCpuMs
(const chartRenderLoadMs: numberchartRenderLoadMs);
// Draw both effect rows. this.Demo.renderStaticEffects(): void
Draws the first row: six static palette-offset effects. Normal, Silhouette, Team Red/Blue/Green, Frozen.
renderStaticEffects
();
this.Demo.renderDynamicEffects(): void
Draws the second row: four dynamic palette-offset effects (updated in update()). Damage Flash, Ghost, Invincibility, Poison.
renderDynamicEffects
();
// Day/night cycle at the bottom. this.Demo.renderDayNightCycle(): void
Shows a day/night cycle effect: the sprite dims at night and brightens at noon. A progress bar shows the current phase.
renderDayNightCycle
();
// Software renderer: warn on-canvas that the Tesla Orava look is missing. A small // borderless kit group in the top-left corner; the shared note was split into // two lines up top (FALLBACK_LINES) so it stays short and easy to read. if (!this.Demo.effectsAvailable: booleaneffectsAvailable) { import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT, { margin: numbermargin: 2, pad: numberpad: 2 }); for (const const line: anyline of const FALLBACK_LINES: anyFALLBACK_LINES) { import uiui.label(const line: anyline, { color: stringcolor: 'warm' }); } import uiui.end(); } } /** * Tesla Orava stack and current analog-TV fault (overlay custom rows). * * @returns {readonly { leftText: string }[]} */
Demo.overlayRows(): readonly {
    leftText: string;
}[]
Tesla Orava stack and current analog-TV fault (overlay custom rows).
@returns
overlayRows
() {
if (this.Demo.effectsAvailable: booleaneffectsAvailable) { this.Demo.overlayRowData: {}overlayRowData[0].leftText = 'Tesla Orava: ON'; const const faultLabel: anyfaultLabel = import GLITCH_LABELSGLITCH_LABELS[this.Demo.glitchType: stringglitchType] ?? 'NONE'; const const faultValue: anyfaultValue = this.Demo.glitchTicksLeft: numberglitchTicksLeft > 0 ? Math.round(this.Demo.glitchPeak: numberglitchPeak * 100) : 0; this.Demo.overlayRowData: {}overlayRowData[1].leftText = `TV fault: ${const faultLabel: anyfaultLabel} ${String(const faultValue: anyfaultValue).padStart(2, '0')}%`; } else { // Software renderer: no CRT stack, so the fault machine never fires. The full // explanation lives on the canvas itself (see render()), not in the overlay. this.Demo.overlayRowData: {}overlayRowData[0].leftText = 'Tesla Orava: CRT OFF (software)'; this.Demo.overlayRowData: {}overlayRowData[1].leftText = 'TV fault: NONE'; } return this.Demo.overlayRowData: {}overlayRowData; } /** * Builds the Tesla Orava effect chain once in init(): the pixel-tier band tear plus * the display-tier tube look, in back-to-front order. */ Demo.setupCrtStack(): void
Builds the Tesla Orava effect chain once in init(): the pixel-tier band tear plus the display-tier tube look, in back-to-front order.
setupCrtStack
() {
// Pixel tier: indexed-buffer horizontal band tear (V-hold style). this.Demo.pixelGlitch: PixelGlitch | null
@type{PixelGlitch | null}
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 | null}
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
= 4;
this.Demo.pixelGlitch: PixelGlitch
@type{PixelGlitch | null}
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 | null}
pixelGlitch
);
// Display tier: Tesla Orava B/W tube - curved glass, faint grille, soft halation. this.Demo.barrel: BarrelDistortion | null
@type{BarrelDistortion | null}
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 | null}
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.07;
this.Demo.interference: Interference | null
@type{Interference | null}
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 | null}
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
= const INTERFERENCE_BASE: 0INTERFERENCE_BASE;
this.Demo.rollLine: RollLine | null
@type{RollLine | null}
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 | null}
rollLine
.RollLine.amount: number
Roll line amplitude (mix factor onto a brightness boost).
amount
= const ROLL_BASE: 0.26ROLL_BASE;
this.Demo.rollLine: RollLine
@type{RollLine | null}
rollLine
.RollLine.speed: number
Scroll speed multiplier; final scroll velocity = `time * speed`.
speed
= const ROLL_SPEED: 0.92ROLL_SPEED;
this.Demo.scanlines: Scanlines | null
@type{Scanlines | null}
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 | null}
scanlines
.Scanlines.amount: number
Scanline mix amount in `[0, 1]`. 0 disables.
amount
= 0.42;
this.Demo.scanlines: Scanlines
@type{Scanlines | null}
scanlines
.Scanlines.strength: number
Negative gaussian falloff parameter for scanline brightness. More negative values produce sharper dark bands. PipBoy reference: `-8.0`.
strength
= -7;
this.Demo.scanlines: Scanlines
@type{Scanlines | null}
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: 400DISPLAY_H;
this.Demo.mask: RGBMask | null
@type{RGBMask | null}
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 | null}
mask
.RGBMask.intensity: number
Mask brightness mix amount in `[0, 1]`. 0 hides the mask.
intensity
= 0.07;
this.Demo.mask: RGBMask
@type{RGBMask | null}
mask
.RGBMask.size: number
Mask cell pitch in output (display-chain) pixels. Smaller = denser mask.
size
= 5;
this.Demo.mask: RGBMask
@type{RGBMask | null}
mask
.RGBMask.border: number
Border darkening within each mask cell. 0 disables, 1 strong.
border
= 0.45;
this.Demo.vignette: Vignette | null
@type{Vignette | null}
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 | null}
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.1;
this.Demo.noise: Noise | null
@type{Noise | null}
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 | null}
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
= const NOISE_BASE: 0.038NOISE_BASE;
this.Demo.flicker: Flicker | null
@type{Flicker | null}
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 | null}
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
= const FLICKER_BASE: 1FLICKER_BASE;
this.Demo.bloom: Bloom | null
@type{Bloom | null}
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 | null}
bloom
.Bloom.spread: number
Texel offset multiplier for the box-blur kernel.
spread
= 2.2;
this.Demo.bloom: Bloom
@type{Bloom | null}
bloom
.Bloom.glow: number
Mix factor between the original sample and the blurred neighborhood.
glow
= 0.09;
for (const const fx: anyfx of [ this.Demo.barrel: BarrelDistortion
@type{BarrelDistortion | null}
barrel
,
this.Demo.interference: Interference
@type{Interference | null}
interference
,
this.Demo.rollLine: RollLine
@type{RollLine | null}
rollLine
,
this.Demo.scanlines: Scanlines
@type{Scanlines | null}
scanlines
,
this.Demo.mask: RGBMask
@type{RGBMask | null}
mask
,
this.Demo.vignette: Vignette
@type{Vignette | null}
vignette
,
this.Demo.noise: Noise
@type{Noise | null}
noise
,
this.Demo.flicker: Flicker
@type{Flicker | null}
flicker
,
this.Demo.bloom: Bloom
@type{Bloom | null}
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);
} } /** * Drives RollLine scroll, TV fault bursts, and occasional subtle band offset wobble. */ Demo.updateCrtEffects(): void
Drives RollLine scroll, TV fault bursts, and occasional subtle band offset wobble.
updateCrtEffects
() {
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
.timeSeconds: number
Fixed-step elapsed time in seconds (`BT.ticks * BT.deltaSeconds`).
@since1.0.4@returnsElapsed fixed-step time in seconds since initialization.
timeSeconds
;
this.Demo.rollLine: RollLine | null
@type{RollLine | null}
rollLine
.RollLine.time: number
Wall-clock seconds; demos typically drive this each frame.
time
= const seconds: numberseconds;
this.Demo.noise: Noise | null
@type{Noise | null}
noise
.Noise.time: number
Wall-clock seconds; reseeds the noise each frame.
time
= const seconds: numberseconds;
this.Demo.interference: Interference | null
@type{Interference | null}
interference
.Interference.time: number
Wall-clock seconds; reseeds the row offsets each frame.
time
= const seconds: numberseconds;
if (this.Demo.glitchTicksLeft: numberglitchTicksLeft > 0) { const const t: numbert = 1 - (this.Demo.glitchTicksLeft: numberglitchTicksLeft - 1) / this.Demo.glitchDuration: numberglitchDuration; const const envelope: anyenvelope = Math.sin(const t: numbert * Math.PI); this.Demo.applyRestingCrtUniforms(): void
Resting Tesla Orava look: scrolling roll band plus calm noise/flicker.
applyRestingCrtUniforms
();
this.Demo.applyGlitchUniforms(envelope: number): void
@paramenvelope
applyGlitchUniforms
(const envelope: anyenvelope);
this.Demo.glitchTicksLeft: numberglitchTicksLeft--; if (this.Demo.glitchTicksLeft: numberglitchTicksLeft <= 0) { 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
(const GLITCH_COOLDOWN_MIN: 150GLITCH_COOLDOWN_MIN, const GLITCH_COOLDOWN_MAX: 420GLITCH_COOLDOWN_MAX);
this.Demo.bandWobbleCooldown: numberbandWobbleCooldown =
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 BAND_WOBBLE_COOLDOWN_MIN: 100BAND_WOBBLE_COOLDOWN_MIN, const BAND_WOBBLE_COOLDOWN_MAX: 280BAND_WOBBLE_COOLDOWN_MAX);
} return; } this.Demo.applyRestingCrtUniforms(): void
Resting Tesla Orava look: scrolling roll band plus calm noise/flicker.
applyRestingCrtUniforms
();
if (this.Demo.bandWobbleTicksLeft: numberbandWobbleTicksLeft > 0) { const const t: numbert = 1 - (this.Demo.bandWobbleTicksLeft: numberbandWobbleTicksLeft - 1) / this.Demo.bandWobbleDuration: numberbandWobbleDuration; const const envelope: anyenvelope = Math.sin(const t: numbert * Math.PI); this.Demo.pixelGlitch: PixelGlitch | null
@type{PixelGlitch | null}
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
= const BAND_WOBBLE_INTENSITY: 0.11BAND_WOBBLE_INTENSITY * const envelope: anyenvelope;
this.Demo.pixelGlitch: PixelGlitch | null
@type{PixelGlitch | null}
pixelGlitch
.PixelGlitch.seed: number
Per-glitch random seed. Change between glitches to vary the band noise pattern.
seed
= this.Demo.bandWobbleSeed: numberbandWobbleSeed;
this.Demo.bandWobbleTicksLeft: numberbandWobbleTicksLeft--; if (this.Demo.bandWobbleTicksLeft: numberbandWobbleTicksLeft <= 0) { this.Demo.pixelGlitch: PixelGlitch | null
@type{PixelGlitch | null}
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;
this.Demo.bandWobbleCooldown: numberbandWobbleCooldown =
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 BAND_WOBBLE_COOLDOWN_MIN: 100BAND_WOBBLE_COOLDOWN_MIN, const BAND_WOBBLE_COOLDOWN_MAX: 280BAND_WOBBLE_COOLDOWN_MAX);
} } else { this.Demo.bandWobbleCooldown: numberbandWobbleCooldown--; if (this.Demo.bandWobbleCooldown: numberbandWobbleCooldown <= 0) { this.Demo.bandWobbleDuration: numberbandWobbleDuration =
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 BAND_WOBBLE_ACTIVE_MIN: 3BAND_WOBBLE_ACTIVE_MIN, const BAND_WOBBLE_ACTIVE_MAX: 10BAND_WOBBLE_ACTIVE_MAX);
this.Demo.bandWobbleTicksLeft: numberbandWobbleTicksLeft = this.Demo.bandWobbleDuration: numberbandWobbleDuration; this.Demo.bandWobbleSeed: numberbandWobbleSeed =
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);
} } 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(). this.Demo.glitchType: stringglitchType =
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_VROLLGLITCH_TYPES_VROLL);
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
(const GLITCH_ACTIVE_MIN: 4GLITCH_ACTIVE_MIN, const GLITCH_ACTIVE_MAX: 24GLITCH_ACTIVE_MAX);
this.Demo.glitchTicksLeft: numberglitchTicksLeft = 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
(const GLITCH_INTENSITY_MIN: 0.3GLITCH_INTENSITY_MIN, const GLITCH_INTENSITY_MAX: 0.95GLITCH_INTENSITY_MAX);
this.Demo.pixelGlitch: PixelGlitch | null
@type{PixelGlitch | null}
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);
} } /** Resting Tesla Orava look: scrolling roll band plus calm noise/flicker. */ Demo.applyRestingCrtUniforms(): void
Resting Tesla Orava look: scrolling roll band plus calm noise/flicker.
applyRestingCrtUniforms
() {
this.Demo.pixelGlitch: PixelGlitch | null
@type{PixelGlitch | null}
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;
this.Demo.noise: Noise | null
@type{Noise | null}
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
= const NOISE_BASE: 0.038NOISE_BASE;
this.Demo.flicker: Flicker | null
@type{Flicker | null}
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
= const FLICKER_BASE: 1FLICKER_BASE;
this.Demo.interference: Interference | null
@type{Interference | null}
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
= const INTERFERENCE_BASE: 0INTERFERENCE_BASE;
this.Demo.rollLine: RollLine | null
@type{RollLine | null}
rollLine
.RollLine.amount: number
Roll line amplitude (mix factor onto a brightness boost).
amount
= const ROLL_BASE: 0.26ROLL_BASE;
this.Demo.rollLine: RollLine | null
@type{RollLine | null}
rollLine
.RollLine.speed: number
Scroll speed multiplier; final scroll velocity = `time * speed`.
speed
= const ROLL_SPEED: 0.92ROLL_SPEED;
} /** * @param {number} envelope */ Demo.applyGlitchUniforms(envelope: number): void
@paramenvelope
applyGlitchUniforms
(envelope: number
@paramenvelope
envelope
) {
const const peak: numberpeak = this.Demo.glitchPeak: numberglitchPeak * envelope: number
@paramenvelope
envelope
;
if (this.Demo.glitchType: stringglitchType === 'hshift') { this.Demo.pixelGlitch: PixelGlitch | null
@type{PixelGlitch | null}
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
= const peak: numberpeak;
} else if (this.Demo.glitchType: stringglitchType === 'noise') { this.Demo.noise: Noise | null
@type{Noise | null}
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
= const NOISE_BASE: 0.038NOISE_BASE + const peak: numberpeak * 0.1;
} else if (this.Demo.glitchType: stringglitchType === 'flicker') { this.Demo.flicker: Flicker | null
@type{Flicker | null}
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
= const FLICKER_BASE: 1FLICKER_BASE - (const FLICKER_BASE: 1FLICKER_BASE - const FLICKER_DIP: 0.78FLICKER_DIP) * envelope: number
@paramenvelope
envelope
;
} else if (this.Demo.glitchType: stringglitchType === 'interference') { this.Demo.interference: Interference | null
@type{Interference | null}
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
= const peak: numberpeak * 0.07;
} else if (this.Demo.glitchType: stringglitchType === 'vroll') { this.Demo.rollLine: RollLine | null
@type{RollLine | null}
rollLine
.RollLine.amount: number
Roll line amplitude (mix factor onto a brightness boost).
amount
= const ROLL_BASE: 0.26ROLL_BASE + const peak: numberpeak * 0.35;
this.Demo.rollLine: RollLine | null
@type{RollLine | null}
rollLine
.RollLine.speed: number
Scroll speed multiplier; final scroll velocity = `time * speed`.
speed
= const ROLL_SPEED: 0.92ROLL_SPEED + const peak: numberpeak * 1.8;
} } /** * Builds the 8 static theme blocks by transforming the base colors. * Called once in init() - these never change after setup. */ Demo.buildStaticThemeBlocks(): void
Builds the 8 static theme blocks by transforming the base colors. Called once in init() - these never change after setup.
buildStaticThemeBlocks
() {
// Block 0 (original) is already filled by SpriteSheet.loadColorsIntoPalette above. // n is how many unique colors the sprite has - every block is n slots wide. const const n: numbern = this.Demo.colorCount: numbercolorCount; // Each block below is a palette.fillBlock(start, source, transform) call: it walks // this.baseColors once and writes transform(baseColor) into one slot per color, // starting at `start`. That replaces a hand-written for loop over the same colors. // Block 1: Silhouette - near-black with slight variation to preserve depth cues. // Floor channels like blocks 4-7 so every recipe hands Color32 whole bytes. this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.fillBlock(start: number, source: readonly Color32[], transform: (color: Color32, index: number) => Color32): number
Writes a transformed block of colors into contiguous palette slots. Writes `transform(source[i], i)` into slot `start + i` for every `i` in `[0, source.length)`, delegating each write to {@link set } so it inherits {@link set } 's validation, including the rule that slot 0 must stay transparent. Collapses the common pattern of looping `palette.set(start + i, transform(baseColors[i]))` into one call.
@since1.7.0@paramstart - First palette index to write.@paramsource - Source colors to read from, in order. `source[i]` maps to slot `start + i`.@paramtransform - Called once per source color as `transform(color, i)`; its return value is written to slot `start + i`.@returnsThe next free slot after the written block (`start + source.length`), for chaining further writes.@throwsError if `start` is not a non-negative integer.@throwsError if the block would exceed the palette size.@throwsError if an individual slot write is invalid - see {@link set}.
fillBlock
(const COLOR_BASE: 12COLOR_BASE + const BLOCK_SILHOUETTE: 1BLOCK_SILHOUETTE * const n: numbern, this.Demo.baseColors: {}baseColors, (base: Color32base) => {
// The average brightness of this pixel (0..255 range). const const lum: anylum = Math.floor(base: Color32base.Color32.luminance: number
Perceived (Rec. 601) luminance of this color, ignoring alpha.
@returnsLuminance value in range 0-255 using 0.299*R + 0.587*G + 0.114*B.
luminance
);
return 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
(Math.floor(const lum: anylum * 0.08), Math.floor(const lum: anylum * 0.08), Math.floor(const lum: anylum * 0.1), base: Color32base.Color32.a: number
Alpha channel (0-255).
a
);
}); // Block 2: Damage white - everything shifted toward bright white. this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.fillBlock(start: number, source: readonly Color32[], transform: (color: Color32, index: number) => Color32): number
Writes a transformed block of colors into contiguous palette slots. Writes `transform(source[i], i)` into slot `start + i` for every `i` in `[0, source.length)`, delegating each write to {@link set } so it inherits {@link set } 's validation, including the rule that slot 0 must stay transparent. Collapses the common pattern of looping `palette.set(start + i, transform(baseColors[i]))` into one call.
@since1.7.0@paramstart - First palette index to write.@paramsource - Source colors to read from, in order. `source[i]` maps to slot `start + i`.@paramtransform - Called once per source color as `transform(color, i)`; its return value is written to slot `start + i`.@returnsThe next free slot after the written block (`start + source.length`), for chaining further writes.@throwsError if `start` is not a non-negative integer.@throwsError if the block would exceed the palette size.@throwsError if an individual slot write is invalid - see {@link set}.
fillBlock
(const COLOR_BASE: 12COLOR_BASE + const BLOCK_DAMAGE_WHITE: 2BLOCK_DAMAGE_WHITE * const n: numbern, this.Demo.baseColors: {}baseColors, (base: Color32base) => {
const const lum: anylum = Math.floor(base: Color32base.Color32.luminance: number
Perceived (Rec. 601) luminance of this color, ignoring alpha.
@returnsLuminance value in range 0-255 using 0.299*R + 0.587*G + 0.114*B.
luminance
);
const const whitened: anywhitened = Math.floor(128 + const lum: anylum * 0.5); return 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
(const whitened: anywhitened, const whitened: anywhitened, const whitened: anywhitened, base: Color32base.Color32.a: number
Alpha channel (0-255).
a
);
}); // Block 3: Damage red - everything shifted toward red. this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.fillBlock(start: number, source: readonly Color32[], transform: (color: Color32, index: number) => Color32): number
Writes a transformed block of colors into contiguous palette slots. Writes `transform(source[i], i)` into slot `start + i` for every `i` in `[0, source.length)`, delegating each write to {@link set } so it inherits {@link set } 's validation, including the rule that slot 0 must stay transparent. Collapses the common pattern of looping `palette.set(start + i, transform(baseColors[i]))` into one call.
@since1.7.0@paramstart - First palette index to write.@paramsource - Source colors to read from, in order. `source[i]` maps to slot `start + i`.@paramtransform - Called once per source color as `transform(color, i)`; its return value is written to slot `start + i`.@returnsThe next free slot after the written block (`start + source.length`), for chaining further writes.@throwsError if `start` is not a non-negative integer.@throwsError if the block would exceed the palette size.@throwsError if an individual slot write is invalid - see {@link set}.
fillBlock
(const COLOR_BASE: 12COLOR_BASE + const BLOCK_DAMAGE_RED: 3BLOCK_DAMAGE_RED * const n: numbern, this.Demo.baseColors: {}baseColors, (base: Color32base) => {
const const lum: anylum = Math.floor(base: Color32base.Color32.luminance: number
Perceived (Rec. 601) luminance of this color, ignoring alpha.
@returnsLuminance value in range 0-255 using 0.299*R + 0.587*G + 0.114*B.
luminance
);
return 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
(Math.min(255, const lum: anylum + 80), Math.floor(const lum: anylum * 0.3), Math.floor(const lum: anylum * 0.3), base: Color32base.Color32.a: number
Alpha channel (0-255).
a
);
}); // Block 4: Team red - multiply base colors with a red tint. this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.fillBlock(start: number, source: readonly Color32[], transform: (color: Color32, index: number) => Color32): number
Writes a transformed block of colors into contiguous palette slots. Writes `transform(source[i], i)` into slot `start + i` for every `i` in `[0, source.length)`, delegating each write to {@link set } so it inherits {@link set } 's validation, including the rule that slot 0 must stay transparent. Collapses the common pattern of looping `palette.set(start + i, transform(baseColors[i]))` into one call.
@since1.7.0@paramstart - First palette index to write.@paramsource - Source colors to read from, in order. `source[i]` maps to slot `start + i`.@paramtransform - Called once per source color as `transform(color, i)`; its return value is written to slot `start + i`.@returnsThe next free slot after the written block (`start + source.length`), for chaining further writes.@throwsError if `start` is not a non-negative integer.@throwsError if the block would exceed the palette size.@throwsError if an individual slot write is invalid - see {@link set}.
fillBlock
(
const COLOR_BASE: 12COLOR_BASE + const BLOCK_TEAM_RED: 4BLOCK_TEAM_RED * const n: numbern, this.Demo.baseColors: {}baseColors, (base: Color32base) => 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
(
Math.min(255, Math.floor(base: Color32base.Color32.r: number
Red channel (0-255).
r
* 1.4)),
Math.floor(base: Color32base.Color32.g: number
Green channel (0-255).
g
* 0.5),
Math.floor(base: Color32base.Color32.b: number
Blue channel (0-255).
b
* 0.5),
base: Color32base.Color32.a: number
Alpha channel (0-255).
a
,
), ); // Block 5: Team blue - multiply with a blue tint. this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.fillBlock(start: number, source: readonly Color32[], transform: (color: Color32, index: number) => Color32): number
Writes a transformed block of colors into contiguous palette slots. Writes `transform(source[i], i)` into slot `start + i` for every `i` in `[0, source.length)`, delegating each write to {@link set } so it inherits {@link set } 's validation, including the rule that slot 0 must stay transparent. Collapses the common pattern of looping `palette.set(start + i, transform(baseColors[i]))` into one call.
@since1.7.0@paramstart - First palette index to write.@paramsource - Source colors to read from, in order. `source[i]` maps to slot `start + i`.@paramtransform - Called once per source color as `transform(color, i)`; its return value is written to slot `start + i`.@returnsThe next free slot after the written block (`start + source.length`), for chaining further writes.@throwsError if `start` is not a non-negative integer.@throwsError if the block would exceed the palette size.@throwsError if an individual slot write is invalid - see {@link set}.
fillBlock
(
const COLOR_BASE: 12COLOR_BASE + const BLOCK_TEAM_BLUE: 5BLOCK_TEAM_BLUE * const n: numbern, this.Demo.baseColors: {}baseColors, (base: Color32base) => 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
(
Math.floor(base: Color32base.Color32.r: number
Red channel (0-255).
r
* 0.5),
Math.floor(base: Color32base.Color32.g: number
Green channel (0-255).
g
* 0.7),
Math.min(255, Math.floor(base: Color32base.Color32.b: number
Blue channel (0-255).
b
* 1.6)),
base: Color32base.Color32.a: number
Alpha channel (0-255).
a
,
), ); // Block 6: Team green - multiply with a green tint. this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.fillBlock(start: number, source: readonly Color32[], transform: (color: Color32, index: number) => Color32): number
Writes a transformed block of colors into contiguous palette slots. Writes `transform(source[i], i)` into slot `start + i` for every `i` in `[0, source.length)`, delegating each write to {@link set } so it inherits {@link set } 's validation, including the rule that slot 0 must stay transparent. Collapses the common pattern of looping `palette.set(start + i, transform(baseColors[i]))` into one call.
@since1.7.0@paramstart - First palette index to write.@paramsource - Source colors to read from, in order. `source[i]` maps to slot `start + i`.@paramtransform - Called once per source color as `transform(color, i)`; its return value is written to slot `start + i`.@returnsThe next free slot after the written block (`start + source.length`), for chaining further writes.@throwsError if `start` is not a non-negative integer.@throwsError if the block would exceed the palette size.@throwsError if an individual slot write is invalid - see {@link set}.
fillBlock
(
const COLOR_BASE: 12COLOR_BASE + const BLOCK_TEAM_GREEN: 6BLOCK_TEAM_GREEN * const n: numbern, this.Demo.baseColors: {}baseColors, (base: Color32base) => 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
(
Math.floor(base: Color32base.Color32.r: number
Red channel (0-255).
r
* 0.5),
Math.min(255, Math.floor(base: Color32base.Color32.g: number
Green channel (0-255).
g
* 1.4)),
Math.floor(base: Color32base.Color32.b: number
Blue channel (0-255).
b
* 0.5),
base: Color32base.Color32.a: number
Alpha channel (0-255).
a
,
), ); // Block 7: Frozen - push toward cold blue-white. this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.fillBlock(start: number, source: readonly Color32[], transform: (color: Color32, index: number) => Color32): number
Writes a transformed block of colors into contiguous palette slots. Writes `transform(source[i], i)` into slot `start + i` for every `i` in `[0, source.length)`, delegating each write to {@link set } so it inherits {@link set } 's validation, including the rule that slot 0 must stay transparent. Collapses the common pattern of looping `palette.set(start + i, transform(baseColors[i]))` into one call.
@since1.7.0@paramstart - First palette index to write.@paramsource - Source colors to read from, in order. `source[i]` maps to slot `start + i`.@paramtransform - Called once per source color as `transform(color, i)`; its return value is written to slot `start + i`.@returnsThe next free slot after the written block (`start + source.length`), for chaining further writes.@throwsError if `start` is not a non-negative integer.@throwsError if the block would exceed the palette size.@throwsError if an individual slot write is invalid - see {@link set}.
fillBlock
(const COLOR_BASE: 12COLOR_BASE + const BLOCK_FROZEN: 7BLOCK_FROZEN * const n: numbern, this.Demo.baseColors: {}baseColors, (base: Color32base) => {
const const lum: anylum = Math.floor(base: Color32base.Color32.luminance: number
Perceived (Rec. 601) luminance of this color, ignoring alpha.
@returnsLuminance value in range 0-255 using 0.299*R + 0.587*G + 0.114*B.
luminance
);
return 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
(Math.floor(const lum: anylum * 0.7 + 40), Math.floor(const lum: anylum * 0.8 + 40), Math.min(255, const lum: anylum + 80), base: Color32base.Color32.a: number
Alpha channel (0-255).
a
);
}); } // The 5 update*Block() methods below run every tick (called from update() above), unlike // buildStaticThemeBlocks() which only runs once in init(). They stay as plain for loops // instead of palette.fillBlock(start, source, transform) calls on purpose: fillBlock takes // a transform function, and writing `(base) => ...` inline at each call site would create a // brand-new function 60 times a second. A raw loop reuses the same code without allocating // anything extra per tick. /** * Damage flash: alternates between "all white" and "all red" every 3 ticks * for the first 30 ticks after damage. Fades back to normal after that. */ Demo.updateDamageFlashBlock(): void
Damage flash: alternates between "all white" and "all red" every 3 ticks for the first 30 ticks after damage. Fades back to normal after that.
updateDamageFlashBlock
() {
const const flashAge: numberflashAge =
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
- this.Demo.damageFlashTick: numberdamageFlashTick;
const const n: numbern = this.Demo.colorCount: numbercolorCount; for (let let i: numberi = 0; let i: numberi < const n: numbern; let i: numberi++) { const const base: anybase = this.Demo.baseColors: {}baseColors[let i: numberi]; const const lum: anylum = Math.floor(const base: anybase.luminance); let let color: anycolor; if (const flashAge: numberflashAge < 30) { // Math.floor(flashAge / 3) % 2 alternates 0 and 1 every 3 ticks. // 0 = show white flash; 1 = show red. const const phase: numberphase = Math.floor(const flashAge: numberflashAge / 3) % 2; const const whitened: anywhitened = Math.floor(128 + const lum: anylum * 0.5); const const redShift: anyredShift = Math.min(255, const lum: anylum + 80); let color: anycolor = const phase: numberphase === 0 ? 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
(const whitened: anywhitened, const whitened: anywhitened, const whitened: anywhitened, const base: anybase.a)
: 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
(const redShift: anyredShift, Math.floor(const lum: anylum * 0.3), Math.floor(const lum: anylum * 0.3), const base: anybase.a);
} else { // Outside the flash window: use the original color. let color: anycolor = new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(const base: anybase.r, const base: anybase.g, const base: anybase.b, const base: anybase.a);
} this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const COLOR_BASE: 12COLOR_BASE + const BLOCK_DAMAGE_FLASH: 8BLOCK_DAMAGE_FLASH * const n: numbern + let i: numberi, let color: Color32color);
} } /** * Ghost: the sprite appears semi-transparent with a blue-white tint. * Alpha pulses between 40 and 180 using a sine wave. */ Demo.updateGhostBlock(): void
Ghost: the sprite appears semi-transparent with a blue-white tint. Alpha pulses between 40 and 180 using a sine wave.
updateGhostBlock
() {
// Math.sin oscillates between -1 and 1; we shift it to 0..1. const const pulse: numberpulse = Math.sin(this.Demo.animTime: numberanimTime * 3) * 0.5 + 0.5; const const alpha: anyalpha = Math.floor(40 + const pulse: numberpulse * 140); // 40..180 const const n: numbern = this.Demo.colorCount: numbercolorCount; for (let let i: numberi = 0; let i: numberi < const n: numbern; let i: numberi++) { const const base: anybase = this.Demo.baseColors: {}baseColors[let i: numberi]; const const lum: anylum = Math.floor(const base: anybase.luminance); // Push toward a cool blue-white while reducing alpha. this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(
const COLOR_BASE: 12COLOR_BASE + const BLOCK_GHOST: 9BLOCK_GHOST * const n: numbern + let i: numberi, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(Math.floor(const lum: anylum * 0.8 + 40), Math.floor(const lum: anylum * 0.8 + 40), Math.min(255, const lum: anylum + 60), const alpha: anyalpha),
); } } /** * Invincibility: cycles the entire sprite through the rainbow. * The hue rotates 200 degrees per second. */ Demo.updateInvincibleBlock(): void
Invincibility: cycles the entire sprite through the rainbow. The hue rotates 200 degrees per second.
updateInvincibleBlock
() {
const const hue: numberhue = (this.Demo.animTime: numberanimTime * 200) % 360; const const n: numbern = this.Demo.colorCount: numbercolorCount; for (let let i: numberi = 0; let i: numberi < const n: numbern; let i: numberi++) { const const base: anybase = this.Demo.baseColors: {}baseColors[let i: numberi]; // fromHSL takes hue (0-360), saturation (0-100), lightness (0-100). // We use varying lightness so darker parts stay darker. const const lum: anylum = const base: anybase.luminance; const const lightness: numberlightness = 30 + (const lum: anylum / 255) * 40; // 30..70% const const rainbow: Color32rainbow = class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.fromHSL(h: number, s: number, l: number, a?: number): Color32
Creates a color from HSL values.
@paramh - Hue in degrees (0-360).@params - Saturation as percentage (0-100).@paraml - Lightness as percentage (0-100).@parama - Alpha channel (0-255, defaults to 255).@returnsNew color converted from HSL values.
fromHSL
(const hue: numberhue, 100, const lightness: numberlightness);
this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(
const COLOR_BASE: 12COLOR_BASE + const BLOCK_INVINCIBLE: 10BLOCK_INVINCIBLE * const n: numbern + let i: numberi, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(const rainbow: Color32rainbow.Color32.r: number
Red channel (0-255).
r
, const rainbow: Color32rainbow.Color32.g: number
Green channel (0-255).
g
, const rainbow: Color32rainbow.Color32.b: number
Blue channel (0-255).
b
, const base: anybase.a),
); } } /** * Poison: a green tint that pulses brighter and darker 5 times per second. */ Demo.updatePoisonBlock(): void
Poison: a green tint that pulses brighter and darker 5 times per second.
updatePoisonBlock
() {
const const pulse: numberpulse = Math.sin(this.Demo.animTime: numberanimTime * 5) * 0.2 + 0.8; // 0.6..1.0 const const n: numbern = this.Demo.colorCount: numbercolorCount; for (let let i: numberi = 0; let i: numberi < const n: numbern; let i: numberi++) { const const base: anybase = this.Demo.baseColors: {}baseColors[let i: numberi]; this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(
const COLOR_BASE: 12COLOR_BASE + const BLOCK_POISON: 11BLOCK_POISON * const n: numbern + let i: numberi, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(
Math.floor(const base: anybase.r * 0.5 * const pulse: numberpulse), Math.min(255, Math.floor(const base: anybase.g * 1.4 * const pulse: numberpulse)), Math.floor(const base: anybase.b * 0.5 * const pulse: numberpulse), const base: anybase.a, ), ); } } /** * Day/night: a brightness multiplier that cycles over 20 seconds (1200 ticks). * At midday the multiplier is ~1.0; at midnight it drops to ~0.3. */ Demo.updateDayNightBlock(): void
Day/night: a brightness multiplier that cycles over 20 seconds (1200 ticks). At midday the multiplier is ~1.0; at midnight it drops to ~0.3.
updateDayNightBlock
() {
const const cycle: numbercycle = (
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
% 1200) / 1200; // 0..1
const const brightness: numberbrightness = (Math.cos(const cycle: numbercycle * Math.PI * 2) + 1) * 0.35 + 0.3; // 0.3..1.0 const const n: numbern = this.Demo.colorCount: numbercolorCount; for (let let i: numberi = 0; let i: numberi < const n: numbern; let i: numberi++) { const const base: anybase = this.Demo.baseColors: {}baseColors[let i: numberi]; // Blend from a fixed cool night tint (slight blue) toward the sprite's daylight // colors as brightness approaches 1. // Color32.lerp(a, b, t): t=0 is all `a`, t=1 is all `b`; matches the old per-channel formula. const const nightTint: Color32nightTint = new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(0, 0, 30, const base: anybase.a);
this.Demo.palette: Palette | null
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const COLOR_BASE: 12COLOR_BASE + const BLOCK_DAYNIGHT: 12BLOCK_DAYNIGHT * const n: numbern + let i: numberi, class Color32
Mutable 32-bit RGBA color value with 8-bit channels.
@since0.1.0
Color32
.Color32.lerp(a: Color32, b: Color32, t: number): Color32
Linearly interpolates between two colors. Each RGBA channel is interpolated independently. `t` is clamped to [0, 1].
@parama - Color at t = 0.@paramb - Color at t = 1.@paramt - Interpolation factor (0.0 = `a`, 1.0 = `b`).@returnsNew color blended between `a` and `b`.
lerp
(const nightTint: Color32nightTint, const base: anybase, const brightness: numberbrightness));
} } /** * Draws the first row: six static palette-offset effects. * Normal, Silhouette, Team Red/Blue/Green, Frozen. */ Demo.renderStaticEffects(): void
Draws the first row: six static palette-offset effects. Normal, Silhouette, Team Red/Blue/Green, Frozen.
renderStaticEffects
() {
const const row1Y: 130row1Y = 130; const const spacing: 100spacing = 100; const const n: numbern = this.Demo.colorCount: numbercolorCount; // Each cell is the same sprite drawn with a different palette offset, with a // named caption and a one-line "when you would use this" note under it.
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
.drawSprite: (spriteSheet: SpriteSheet, srcRect: Rect2i, destPos: Vector2i, paletteOffset?: number) => void
Draws a sprite region from an indexed sprite sheet. Sprite draws are batched internally. Grouping draws from the same {@link SpriteSheet } minimizes batch flushes and reduces GPU state changes. The sprite sheet must have been converted to palette indices via `spriteSheet.indexize(palette)` before the first draw call. Prefer `SpriteSheet.loadIndexed(...)` for one-call setup. **Palette offset semantics:** Sprite pixels are stored as palette indices starting at 1. Index 0 is always transparent and is discarded by the fragment shader. The final palette lookup is `storedIndex + paletteOffset`, so: - `paletteOffset = 0` (default): a sprite pixel stored at index 1 renders as `palette[1]`. `palette[0]` is never reachable because stored indices start at 1. - `paletteOffset = N`: shifts the entire sprite's color range up by N slots. A pixel stored at index 1 renders as `palette[1 + N]`, a pixel at index 2 renders as `palette[2 + N]`, and so on. Use this for palette-swap effects such as team colors or damage flashes. **Out-of-range behavior:** No CPU-side validation is performed. `paletteOffset` is passed to the GPU as a `u32`. If `storedIndex + paletteOffset` exceeds the last palette index, WebGPU's robust buffer access returns 0 for every component; because the fragment shader forces alpha to 1.0, the affected pixels render as opaque black. Negative values are forbidden - a negative JS number written into a `u32` vertex attribute wraps to a large unsigned integer, which also produces out-of-bounds black pixels.
@since0.1.0@paramspriteSheet - Indexed sprite sheet.@paramsrcRect - Source rectangle within the sprite sheet, in pixels.@paramdestPos - Destination top-left position in display coordinates.@parampaletteOffset - Shift added to every stored pixel index before palette lookup (default 0).@exampleBT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10)); BT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10), 16); // blue team
drawSprite
(this.Demo.sheet: SpriteSheet | null
@type{SpriteSheet | null}
sheet
, this.Demo.charRect: Rect2i | null
@type{Rect2i | null}
charRect
, 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
(10, const row1Y: 130row1Y), const BLOCK_ORIGINAL: 0BLOCK_ORIGINAL * const n: numbern);
import uiui.caption(6, const row1Y: 130row1Y + 40, 'Normal', { color: stringcolor: 'text' }); import uiui.caption(6, const row1Y: 130row1Y + 50, 'Default look', { color: stringcolor: 'dim' });
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
.drawSprite: (spriteSheet: SpriteSheet, srcRect: Rect2i, destPos: Vector2i, paletteOffset?: number) => void
Draws a sprite region from an indexed sprite sheet. Sprite draws are batched internally. Grouping draws from the same {@link SpriteSheet } minimizes batch flushes and reduces GPU state changes. The sprite sheet must have been converted to palette indices via `spriteSheet.indexize(palette)` before the first draw call. Prefer `SpriteSheet.loadIndexed(...)` for one-call setup. **Palette offset semantics:** Sprite pixels are stored as palette indices starting at 1. Index 0 is always transparent and is discarded by the fragment shader. The final palette lookup is `storedIndex + paletteOffset`, so: - `paletteOffset = 0` (default): a sprite pixel stored at index 1 renders as `palette[1]`. `palette[0]` is never reachable because stored indices start at 1. - `paletteOffset = N`: shifts the entire sprite's color range up by N slots. A pixel stored at index 1 renders as `palette[1 + N]`, a pixel at index 2 renders as `palette[2 + N]`, and so on. Use this for palette-swap effects such as team colors or damage flashes. **Out-of-range behavior:** No CPU-side validation is performed. `paletteOffset` is passed to the GPU as a `u32`. If `storedIndex + paletteOffset` exceeds the last palette index, WebGPU's robust buffer access returns 0 for every component; because the fragment shader forces alpha to 1.0, the affected pixels render as opaque black. Negative values are forbidden - a negative JS number written into a `u32` vertex attribute wraps to a large unsigned integer, which also produces out-of-bounds black pixels.
@since0.1.0@paramspriteSheet - Indexed sprite sheet.@paramsrcRect - Source rectangle within the sprite sheet, in pixels.@paramdestPos - Destination top-left position in display coordinates.@parampaletteOffset - Shift added to every stored pixel index before palette lookup (default 0).@exampleBT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10)); BT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10), 16); // blue team
drawSprite
(this.Demo.sheet: SpriteSheet | null
@type{SpriteSheet | null}
sheet
, this.Demo.charRect: Rect2i | null
@type{Rect2i | null}
charRect
, 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
(10 + const spacing: 100spacing, const row1Y: 130row1Y), const BLOCK_SILHOUETTE: 1BLOCK_SILHOUETTE * const n: numbern);
import uiui.caption(6 + const spacing: 100spacing, const row1Y: 130row1Y + 40, 'Silhouette', { color: stringcolor: 'text' }); import uiui.caption(6 + const spacing: 100spacing, const row1Y: 130row1Y + 50, 'Stealth', { color: stringcolor: 'dim' });
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
.drawSprite: (spriteSheet: SpriteSheet, srcRect: Rect2i, destPos: Vector2i, paletteOffset?: number) => void
Draws a sprite region from an indexed sprite sheet. Sprite draws are batched internally. Grouping draws from the same {@link SpriteSheet } minimizes batch flushes and reduces GPU state changes. The sprite sheet must have been converted to palette indices via `spriteSheet.indexize(palette)` before the first draw call. Prefer `SpriteSheet.loadIndexed(...)` for one-call setup. **Palette offset semantics:** Sprite pixels are stored as palette indices starting at 1. Index 0 is always transparent and is discarded by the fragment shader. The final palette lookup is `storedIndex + paletteOffset`, so: - `paletteOffset = 0` (default): a sprite pixel stored at index 1 renders as `palette[1]`. `palette[0]` is never reachable because stored indices start at 1. - `paletteOffset = N`: shifts the entire sprite's color range up by N slots. A pixel stored at index 1 renders as `palette[1 + N]`, a pixel at index 2 renders as `palette[2 + N]`, and so on. Use this for palette-swap effects such as team colors or damage flashes. **Out-of-range behavior:** No CPU-side validation is performed. `paletteOffset` is passed to the GPU as a `u32`. If `storedIndex + paletteOffset` exceeds the last palette index, WebGPU's robust buffer access returns 0 for every component; because the fragment shader forces alpha to 1.0, the affected pixels render as opaque black. Negative values are forbidden - a negative JS number written into a `u32` vertex attribute wraps to a large unsigned integer, which also produces out-of-bounds black pixels.
@since0.1.0@paramspriteSheet - Indexed sprite sheet.@paramsrcRect - Source rectangle within the sprite sheet, in pixels.@paramdestPos - Destination top-left position in display coordinates.@parampaletteOffset - Shift added to every stored pixel index before palette lookup (default 0).@exampleBT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10)); BT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10), 16); // blue team
drawSprite
(this.Demo.sheet: SpriteSheet | null
@type{SpriteSheet | null}
sheet
, this.Demo.charRect: Rect2i | null
@type{Rect2i | null}
charRect
, 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
(10 + const spacing: 100spacing * 2, const row1Y: 130row1Y), const BLOCK_TEAM_RED: 4BLOCK_TEAM_RED * const n: numbern);
import uiui.caption(6 + const spacing: 100spacing * 2, const row1Y: 130row1Y + 40, 'Team Red', { color: stringcolor: 'warm' }); import uiui.caption(6 + const spacing: 100spacing * 2, const row1Y: 130row1Y + 50, 'Friendly team', { color: stringcolor: 'dim' });
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
.drawSprite: (spriteSheet: SpriteSheet, srcRect: Rect2i, destPos: Vector2i, paletteOffset?: number) => void
Draws a sprite region from an indexed sprite sheet. Sprite draws are batched internally. Grouping draws from the same {@link SpriteSheet } minimizes batch flushes and reduces GPU state changes. The sprite sheet must have been converted to palette indices via `spriteSheet.indexize(palette)` before the first draw call. Prefer `SpriteSheet.loadIndexed(...)` for one-call setup. **Palette offset semantics:** Sprite pixels are stored as palette indices starting at 1. Index 0 is always transparent and is discarded by the fragment shader. The final palette lookup is `storedIndex + paletteOffset`, so: - `paletteOffset = 0` (default): a sprite pixel stored at index 1 renders as `palette[1]`. `palette[0]` is never reachable because stored indices start at 1. - `paletteOffset = N`: shifts the entire sprite's color range up by N slots. A pixel stored at index 1 renders as `palette[1 + N]`, a pixel at index 2 renders as `palette[2 + N]`, and so on. Use this for palette-swap effects such as team colors or damage flashes. **Out-of-range behavior:** No CPU-side validation is performed. `paletteOffset` is passed to the GPU as a `u32`. If `storedIndex + paletteOffset` exceeds the last palette index, WebGPU's robust buffer access returns 0 for every component; because the fragment shader forces alpha to 1.0, the affected pixels render as opaque black. Negative values are forbidden - a negative JS number written into a `u32` vertex attribute wraps to a large unsigned integer, which also produces out-of-bounds black pixels.
@since0.1.0@paramspriteSheet - Indexed sprite sheet.@paramsrcRect - Source rectangle within the sprite sheet, in pixels.@paramdestPos - Destination top-left position in display coordinates.@parampaletteOffset - Shift added to every stored pixel index before palette lookup (default 0).@exampleBT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10)); BT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10), 16); // blue team
drawSprite
(this.Demo.sheet: SpriteSheet | null
@type{SpriteSheet | null}
sheet
, this.Demo.charRect: Rect2i | null
@type{Rect2i | null}
charRect
, 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
(10 + const spacing: 100spacing * 3, const row1Y: 130row1Y), const BLOCK_TEAM_BLUE: 5BLOCK_TEAM_BLUE * const n: numbern);
import uiui.caption(6 + const spacing: 100spacing * 3, const row1Y: 130row1Y + 40, 'Team Blue', { color: stringcolor: 'info' }); import uiui.caption(6 + const spacing: 100spacing * 3, const row1Y: 130row1Y + 50, 'Enemy team', { color: stringcolor: 'dim' });
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
.drawSprite: (spriteSheet: SpriteSheet, srcRect: Rect2i, destPos: Vector2i, paletteOffset?: number) => void
Draws a sprite region from an indexed sprite sheet. Sprite draws are batched internally. Grouping draws from the same {@link SpriteSheet } minimizes batch flushes and reduces GPU state changes. The sprite sheet must have been converted to palette indices via `spriteSheet.indexize(palette)` before the first draw call. Prefer `SpriteSheet.loadIndexed(...)` for one-call setup. **Palette offset semantics:** Sprite pixels are stored as palette indices starting at 1. Index 0 is always transparent and is discarded by the fragment shader. The final palette lookup is `storedIndex + paletteOffset`, so: - `paletteOffset = 0` (default): a sprite pixel stored at index 1 renders as `palette[1]`. `palette[0]` is never reachable because stored indices start at 1. - `paletteOffset = N`: shifts the entire sprite's color range up by N slots. A pixel stored at index 1 renders as `palette[1 + N]`, a pixel at index 2 renders as `palette[2 + N]`, and so on. Use this for palette-swap effects such as team colors or damage flashes. **Out-of-range behavior:** No CPU-side validation is performed. `paletteOffset` is passed to the GPU as a `u32`. If `storedIndex + paletteOffset` exceeds the last palette index, WebGPU's robust buffer access returns 0 for every component; because the fragment shader forces alpha to 1.0, the affected pixels render as opaque black. Negative values are forbidden - a negative JS number written into a `u32` vertex attribute wraps to a large unsigned integer, which also produces out-of-bounds black pixels.
@since0.1.0@paramspriteSheet - Indexed sprite sheet.@paramsrcRect - Source rectangle within the sprite sheet, in pixels.@paramdestPos - Destination top-left position in display coordinates.@parampaletteOffset - Shift added to every stored pixel index before palette lookup (default 0).@exampleBT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10)); BT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10), 16); // blue team
drawSprite
(this.Demo.sheet: SpriteSheet | null
@type{SpriteSheet | null}
sheet
, this.Demo.charRect: Rect2i | null
@type{Rect2i | null}
charRect
, 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
(10 + const spacing: 100spacing * 4, const row1Y: 130row1Y), const BLOCK_TEAM_GREEN: 6BLOCK_TEAM_GREEN * const n: numbern);
import uiui.caption(6 + const spacing: 100spacing * 4, const row1Y: 130row1Y + 40, 'Team Green', { color: stringcolor: 'accent' }); import uiui.caption(6 + const spacing: 100spacing * 4, const row1Y: 130row1Y + 50, 'Ally team', { color: stringcolor: 'dim' });
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
.drawSprite: (spriteSheet: SpriteSheet, srcRect: Rect2i, destPos: Vector2i, paletteOffset?: number) => void
Draws a sprite region from an indexed sprite sheet. Sprite draws are batched internally. Grouping draws from the same {@link SpriteSheet } minimizes batch flushes and reduces GPU state changes. The sprite sheet must have been converted to palette indices via `spriteSheet.indexize(palette)` before the first draw call. Prefer `SpriteSheet.loadIndexed(...)` for one-call setup. **Palette offset semantics:** Sprite pixels are stored as palette indices starting at 1. Index 0 is always transparent and is discarded by the fragment shader. The final palette lookup is `storedIndex + paletteOffset`, so: - `paletteOffset = 0` (default): a sprite pixel stored at index 1 renders as `palette[1]`. `palette[0]` is never reachable because stored indices start at 1. - `paletteOffset = N`: shifts the entire sprite's color range up by N slots. A pixel stored at index 1 renders as `palette[1 + N]`, a pixel at index 2 renders as `palette[2 + N]`, and so on. Use this for palette-swap effects such as team colors or damage flashes. **Out-of-range behavior:** No CPU-side validation is performed. `paletteOffset` is passed to the GPU as a `u32`. If `storedIndex + paletteOffset` exceeds the last palette index, WebGPU's robust buffer access returns 0 for every component; because the fragment shader forces alpha to 1.0, the affected pixels render as opaque black. Negative values are forbidden - a negative JS number written into a `u32` vertex attribute wraps to a large unsigned integer, which also produces out-of-bounds black pixels.
@since0.1.0@paramspriteSheet - Indexed sprite sheet.@paramsrcRect - Source rectangle within the sprite sheet, in pixels.@paramdestPos - Destination top-left position in display coordinates.@parampaletteOffset - Shift added to every stored pixel index before palette lookup (default 0).@exampleBT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10)); BT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10), 16); // blue team
drawSprite
(this.Demo.sheet: SpriteSheet | null
@type{SpriteSheet | null}
sheet
, this.Demo.charRect: Rect2i | null
@type{Rect2i | null}
charRect
, 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
(10 + const spacing: 100spacing * 5, const row1Y: 130row1Y), const BLOCK_FROZEN: 7BLOCK_FROZEN * const n: numbern);
import uiui.caption(6 + const spacing: 100spacing * 5, const row1Y: 130row1Y + 40, 'Frozen', { color: stringcolor: 'info' }); import uiui.caption(6 + const spacing: 100spacing * 5, const row1Y: 130row1Y + 50, 'Slow freeze status', { color: stringcolor: 'dim' }); } /** * Draws the second row: four dynamic palette-offset effects (updated in update()). * Damage Flash, Ghost, Invincibility, Poison. */ Demo.renderDynamicEffects(): void
Draws the second row: four dynamic palette-offset effects (updated in update()). Damage Flash, Ghost, Invincibility, Poison.
renderDynamicEffects
() {
const const row2Y: 190row2Y = 190; const const spacing: 100spacing = 100; const const n: numbern = this.Demo.colorCount: numbercolorCount;
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
.drawSprite: (spriteSheet: SpriteSheet, srcRect: Rect2i, destPos: Vector2i, paletteOffset?: number) => void
Draws a sprite region from an indexed sprite sheet. Sprite draws are batched internally. Grouping draws from the same {@link SpriteSheet } minimizes batch flushes and reduces GPU state changes. The sprite sheet must have been converted to palette indices via `spriteSheet.indexize(palette)` before the first draw call. Prefer `SpriteSheet.loadIndexed(...)` for one-call setup. **Palette offset semantics:** Sprite pixels are stored as palette indices starting at 1. Index 0 is always transparent and is discarded by the fragment shader. The final palette lookup is `storedIndex + paletteOffset`, so: - `paletteOffset = 0` (default): a sprite pixel stored at index 1 renders as `palette[1]`. `palette[0]` is never reachable because stored indices start at 1. - `paletteOffset = N`: shifts the entire sprite's color range up by N slots. A pixel stored at index 1 renders as `palette[1 + N]`, a pixel at index 2 renders as `palette[2 + N]`, and so on. Use this for palette-swap effects such as team colors or damage flashes. **Out-of-range behavior:** No CPU-side validation is performed. `paletteOffset` is passed to the GPU as a `u32`. If `storedIndex + paletteOffset` exceeds the last palette index, WebGPU's robust buffer access returns 0 for every component; because the fragment shader forces alpha to 1.0, the affected pixels render as opaque black. Negative values are forbidden - a negative JS number written into a `u32` vertex attribute wraps to a large unsigned integer, which also produces out-of-bounds black pixels.
@since0.1.0@paramspriteSheet - Indexed sprite sheet.@paramsrcRect - Source rectangle within the sprite sheet, in pixels.@paramdestPos - Destination top-left position in display coordinates.@parampaletteOffset - Shift added to every stored pixel index before palette lookup (default 0).@exampleBT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10)); BT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10), 16); // blue team
drawSprite
(this.Demo.sheet: SpriteSheet | null
@type{SpriteSheet | null}
sheet
, this.Demo.charRect: Rect2i | null
@type{Rect2i | null}
charRect
, 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
(10, const row2Y: 190row2Y), const BLOCK_DAMAGE_FLASH: 8BLOCK_DAMAGE_FLASH * const n: numbern);
import uiui.caption(6, const row2Y: 190row2Y + 40, 'Damage', { color: stringcolor: 'warm' }); import uiui.caption(6, const row2Y: 190row2Y + 50, 'Hit flash', { color: stringcolor: 'dim' });
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
.drawSprite: (spriteSheet: SpriteSheet, srcRect: Rect2i, destPos: Vector2i, paletteOffset?: number) => void
Draws a sprite region from an indexed sprite sheet. Sprite draws are batched internally. Grouping draws from the same {@link SpriteSheet } minimizes batch flushes and reduces GPU state changes. The sprite sheet must have been converted to palette indices via `spriteSheet.indexize(palette)` before the first draw call. Prefer `SpriteSheet.loadIndexed(...)` for one-call setup. **Palette offset semantics:** Sprite pixels are stored as palette indices starting at 1. Index 0 is always transparent and is discarded by the fragment shader. The final palette lookup is `storedIndex + paletteOffset`, so: - `paletteOffset = 0` (default): a sprite pixel stored at index 1 renders as `palette[1]`. `palette[0]` is never reachable because stored indices start at 1. - `paletteOffset = N`: shifts the entire sprite's color range up by N slots. A pixel stored at index 1 renders as `palette[1 + N]`, a pixel at index 2 renders as `palette[2 + N]`, and so on. Use this for palette-swap effects such as team colors or damage flashes. **Out-of-range behavior:** No CPU-side validation is performed. `paletteOffset` is passed to the GPU as a `u32`. If `storedIndex + paletteOffset` exceeds the last palette index, WebGPU's robust buffer access returns 0 for every component; because the fragment shader forces alpha to 1.0, the affected pixels render as opaque black. Negative values are forbidden - a negative JS number written into a `u32` vertex attribute wraps to a large unsigned integer, which also produces out-of-bounds black pixels.
@since0.1.0@paramspriteSheet - Indexed sprite sheet.@paramsrcRect - Source rectangle within the sprite sheet, in pixels.@paramdestPos - Destination top-left position in display coordinates.@parampaletteOffset - Shift added to every stored pixel index before palette lookup (default 0).@exampleBT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10)); BT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10), 16); // blue team
drawSprite
(this.Demo.sheet: SpriteSheet | null
@type{SpriteSheet | null}
sheet
, this.Demo.charRect: Rect2i | null
@type{Rect2i | null}
charRect
, 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
(10 + const spacing: 100spacing, const row2Y: 190row2Y), const BLOCK_GHOST: 9BLOCK_GHOST * const n: numbern);
import uiui.caption(6 + const spacing: 100spacing, const row2Y: 190row2Y + 40, 'Ghost', { color: stringcolor: 'info' }); import uiui.caption(6 + const spacing: 100spacing, const row2Y: 190row2Y + 50, 'Spirit', { color: stringcolor: 'dim' });
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
.drawSprite: (spriteSheet: SpriteSheet, srcRect: Rect2i, destPos: Vector2i, paletteOffset?: number) => void
Draws a sprite region from an indexed sprite sheet. Sprite draws are batched internally. Grouping draws from the same {@link SpriteSheet } minimizes batch flushes and reduces GPU state changes. The sprite sheet must have been converted to palette indices via `spriteSheet.indexize(palette)` before the first draw call. Prefer `SpriteSheet.loadIndexed(...)` for one-call setup. **Palette offset semantics:** Sprite pixels are stored as palette indices starting at 1. Index 0 is always transparent and is discarded by the fragment shader. The final palette lookup is `storedIndex + paletteOffset`, so: - `paletteOffset = 0` (default): a sprite pixel stored at index 1 renders as `palette[1]`. `palette[0]` is never reachable because stored indices start at 1. - `paletteOffset = N`: shifts the entire sprite's color range up by N slots. A pixel stored at index 1 renders as `palette[1 + N]`, a pixel at index 2 renders as `palette[2 + N]`, and so on. Use this for palette-swap effects such as team colors or damage flashes. **Out-of-range behavior:** No CPU-side validation is performed. `paletteOffset` is passed to the GPU as a `u32`. If `storedIndex + paletteOffset` exceeds the last palette index, WebGPU's robust buffer access returns 0 for every component; because the fragment shader forces alpha to 1.0, the affected pixels render as opaque black. Negative values are forbidden - a negative JS number written into a `u32` vertex attribute wraps to a large unsigned integer, which also produces out-of-bounds black pixels.
@since0.1.0@paramspriteSheet - Indexed sprite sheet.@paramsrcRect - Source rectangle within the sprite sheet, in pixels.@paramdestPos - Destination top-left position in display coordinates.@parampaletteOffset - Shift added to every stored pixel index before palette lookup (default 0).@exampleBT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10)); BT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10), 16); // blue team
drawSprite
(this.Demo.sheet: SpriteSheet | null
@type{SpriteSheet | null}
sheet
, this.Demo.charRect: Rect2i | null
@type{Rect2i | null}
charRect
, 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
(10 + const spacing: 100spacing * 2, const row2Y: 190row2Y), const BLOCK_INVINCIBLE: 10BLOCK_INVINCIBLE * const n: numbern);
import uiui.caption(6 + const spacing: 100spacing * 2, const row2Y: 190row2Y + 40, 'Invincible', { color: stringcolor: 'text' }); import uiui.caption(6 + const spacing: 100spacing * 2, const row2Y: 190row2Y + 50, 'Power-up mode', { color: stringcolor: 'dim' });
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
.drawSprite: (spriteSheet: SpriteSheet, srcRect: Rect2i, destPos: Vector2i, paletteOffset?: number) => void
Draws a sprite region from an indexed sprite sheet. Sprite draws are batched internally. Grouping draws from the same {@link SpriteSheet } minimizes batch flushes and reduces GPU state changes. The sprite sheet must have been converted to palette indices via `spriteSheet.indexize(palette)` before the first draw call. Prefer `SpriteSheet.loadIndexed(...)` for one-call setup. **Palette offset semantics:** Sprite pixels are stored as palette indices starting at 1. Index 0 is always transparent and is discarded by the fragment shader. The final palette lookup is `storedIndex + paletteOffset`, so: - `paletteOffset = 0` (default): a sprite pixel stored at index 1 renders as `palette[1]`. `palette[0]` is never reachable because stored indices start at 1. - `paletteOffset = N`: shifts the entire sprite's color range up by N slots. A pixel stored at index 1 renders as `palette[1 + N]`, a pixel at index 2 renders as `palette[2 + N]`, and so on. Use this for palette-swap effects such as team colors or damage flashes. **Out-of-range behavior:** No CPU-side validation is performed. `paletteOffset` is passed to the GPU as a `u32`. If `storedIndex + paletteOffset` exceeds the last palette index, WebGPU's robust buffer access returns 0 for every component; because the fragment shader forces alpha to 1.0, the affected pixels render as opaque black. Negative values are forbidden - a negative JS number written into a `u32` vertex attribute wraps to a large unsigned integer, which also produces out-of-bounds black pixels.
@since0.1.0@paramspriteSheet - Indexed sprite sheet.@paramsrcRect - Source rectangle within the sprite sheet, in pixels.@paramdestPos - Destination top-left position in display coordinates.@parampaletteOffset - Shift added to every stored pixel index before palette lookup (default 0).@exampleBT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10)); BT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10), 16); // blue team
drawSprite
(this.Demo.sheet: SpriteSheet | null
@type{SpriteSheet | null}
sheet
, this.Demo.charRect: Rect2i | null
@type{Rect2i | null}
charRect
, 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
(10 + const spacing: 100spacing * 3, const row2Y: 190row2Y), const BLOCK_POISON: 11BLOCK_POISON * const n: numbern);
import uiui.caption(6 + const spacing: 100spacing * 3, const row2Y: 190row2Y + 40, 'Poisoned', { color: stringcolor: 'accent' }); import uiui.caption(6 + const spacing: 100spacing * 3, const row2Y: 190row2Y + 50, 'Poison time', { color: stringcolor: 'dim' }); } /** * Shows a day/night cycle effect: the sprite dims at night and brightens at noon. * A progress bar shows the current phase. */ Demo.renderDayNightCycle(): void
Shows a day/night cycle effect: the sprite dims at night and brightens at noon. A progress bar shows the current phase.
renderDayNightCycle
() {
const const baseY: 260baseY = 260; const const n: numbern = this.Demo.colorCount: numbercolorCount; import uiui.caption(10, const baseY: 260baseY, 'Day/Night Cycle:', { color: stringcolor: 'header' }); import uiui.caption(10, const baseY: 260baseY + 10, 'Ambient light palette offset', { color: stringcolor: 'dim' }); // Draw the sprite with the day/night block.
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
.drawSprite: (spriteSheet: SpriteSheet, srcRect: Rect2i, destPos: Vector2i, paletteOffset?: number) => void
Draws a sprite region from an indexed sprite sheet. Sprite draws are batched internally. Grouping draws from the same {@link SpriteSheet } minimizes batch flushes and reduces GPU state changes. The sprite sheet must have been converted to palette indices via `spriteSheet.indexize(palette)` before the first draw call. Prefer `SpriteSheet.loadIndexed(...)` for one-call setup. **Palette offset semantics:** Sprite pixels are stored as palette indices starting at 1. Index 0 is always transparent and is discarded by the fragment shader. The final palette lookup is `storedIndex + paletteOffset`, so: - `paletteOffset = 0` (default): a sprite pixel stored at index 1 renders as `palette[1]`. `palette[0]` is never reachable because stored indices start at 1. - `paletteOffset = N`: shifts the entire sprite's color range up by N slots. A pixel stored at index 1 renders as `palette[1 + N]`, a pixel at index 2 renders as `palette[2 + N]`, and so on. Use this for palette-swap effects such as team colors or damage flashes. **Out-of-range behavior:** No CPU-side validation is performed. `paletteOffset` is passed to the GPU as a `u32`. If `storedIndex + paletteOffset` exceeds the last palette index, WebGPU's robust buffer access returns 0 for every component; because the fragment shader forces alpha to 1.0, the affected pixels render as opaque black. Negative values are forbidden - a negative JS number written into a `u32` vertex attribute wraps to a large unsigned integer, which also produces out-of-bounds black pixels.
@since0.1.0@paramspriteSheet - Indexed sprite sheet.@paramsrcRect - Source rectangle within the sprite sheet, in pixels.@paramdestPos - Destination top-left position in display coordinates.@parampaletteOffset - Shift added to every stored pixel index before palette lookup (default 0).@exampleBT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10)); BT.drawSprite(sheet, new Rect2i(0, 0, 16, 16), new Vector2i(10, 10), 16); // blue team
drawSprite
(this.Demo.sheet: SpriteSheet | null
@type{SpriteSheet | null}
sheet
, this.Demo.charRect: Rect2i | null
@type{Rect2i | null}
charRect
, 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
(10, const baseY: 260baseY + 18), const BLOCK_DAYNIGHT: 12BLOCK_DAYNIGHT * const n: numbern);
// Progress bar showing time of day. The bar stays hand-drawn (a kit meter shows a // 0..1 fill, not a moving marker) but its track and outline use theme colors. const const barX: 60barX = 60; const const barY: numberbarY = const baseY: 260baseY + 30; const const barWidth: 240barWidth = 240; const const barHeight: 10barHeight = 10;
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 barX: 60barX, const barY: numberbarY, const barWidth: 240barWidth, const barHeight: 10barHeight), this.Demo.theme: nulltheme.panel);
const const cycle: numbercycle = (
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
% 1200) / 1200;
const const indicatorX: anyindicatorX = const barX: 60barX + Math.floor(const barWidth: 240barWidth * const cycle: numbercycle); // The indicator rectangle uses the current day/night color (block 12, first color). // We compute the actual index: COLOR_BASE + 12 * n + 0 = first slot in the day/night block.
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 indicatorX: anyindicatorX - 2, const barY: numberbarY - 2, 4, const barHeight: 10barHeight + 4), const COLOR_BASE: 12COLOR_BASE + const BLOCK_DAYNIGHT: 12BLOCK_DAYNIGHT * const n: numbern);
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
.drawRect: (rect: Rect2i, paletteIndex: number) => void
Draws an unfilled rectangle outline.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRect
(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 barX: 60barX, const barY: numberbarY, const barWidth: 240barWidth, const barHeight: 10barHeight), this.Demo.theme: nulltheme.border);
// Phase labels. import uiui.caption(const barX: 60barX, const barY: numberbarY + 14, 'Day', { color: stringcolor: 'dim' }); import uiui.caption(const barX: 60barX + 60, const barY: numberbarY + 14, 'Sunset', { color: stringcolor: 'dim' }); import uiui.caption(const barX: 60barX + 120, const barY: numberbarY + 14, 'Night', { color: stringcolor: 'dim' }); import uiui.caption(const barX: 60barX + 180, const barY: numberbarY + 14, 'Dawn', { color: stringcolor: 'dim' }); } } // Hand the Demo class to BLIT386 to start the demo loop. function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>
One-liner bootstrap function for BLIT386 demos. Handles canvas retrieval and engine initialization. Backend selection (WebGPU or software fallback) is managed internally by BTAPI. This function provides a streamlined way to start a demo with sensible defaults while allowing customization through options.
@since0.2.0@changed1.4.0 Calling `bootstrap()` again while already initialized now routes to a hot swap (via {@link registerHotReload}) when a Vite HMR context is registered, or logs a double-bootstrap guard and returns `false` otherwise - previously it silently started a second, unstoppable `GameLoop`.@changed1.7.0 Exposes `BT` on `window.BT` after bootstrap finishes, gated by {@link BootstrapOptions.exposeGlobal} (default: {@link BT.isDevMode}).@paramDemoClass - Demo class constructor implementing `IBTDemo` (optional `configure()` for hardware settings).@paramoptions - Optional configuration for IDs and callbacks.@returns`true` when the demo boots successfully; otherwise `false`.@example// Simplest usage - uses default IDs. bootstrap(MyDemo);@example// With custom options. bootstrap(MyDemo, { canvasID: 'custom-canvas', containerID: 'custom-container', onSuccess: () => console.log('Demo started!'), onError: (err) => analytics.trackError(err), });@example// Await the result. const success = await bootstrap(MyDemo); if (success) { console.log('Demo is running'); }
bootstrap
(class Demo
Demonstrates palette-offset based sprite effects. Static effects are pre-built in init(); dynamic effects update in update().
@implementsIBTDemo
Demo
);