// Game Scene (CAPSTONE): one small world that uses almost everything from the series.
// @description The capstone: tilemap ground, patterns, sprites, camera, animation, and looping music in one scene.
//
// This demo brings together everything you have learned!
//
// Prerequisites (do these first):
//   Basics       https://demos.blit386.dev/basics
//   Primitives   https://demos.blit386.dev/primitives
//   Colors       https://demos.blit386.dev/colors
//   Fonts        https://demos.blit386.dev/fonts
//   Pixel Art    https://demos.blit386.dev/pixel-art
//   Patterns     https://demos.blit386.dev/patterns
//   Camera       https://demos.blit386.dev/camera
//   Sprites      https://demos.blit386.dev/sprites
//   Animation    https://demos.blit386.dev/animation
//   Sprite Effects    https://demos.blit386.dev/sprite-effects
//   Starfield    https://demos.blit386.dev/starfield
//   Tilemap      https://demos.blit386.dev/tilemap
//   Image Output https://demos.blit386.dev/image-output
//
// Guide: https://blit386.dev/docs
//
// WHAT YOU SEE (how the pieces connect):
//   - Sky gradient and slow-moving clouds = colors + parallax idea from starfield.
//   - Scrolling ground, tile-ID sidewalk strip (tilemap), checker buildings (patterns) = camera.
//   - Moving rock hero = sprites and timing (animation).
//   - Sparkles near the rock = small fading squares, like the particles in animation.
//   - Day and night = palette-based ambient lighting (sprite-effects); the world dims at night.
//   - Background music with a real intro-then-loop point (music), plus a chime on every
//     day/night phase change and a blip on a successful PNG capture.
//   - Score, rock position, and day phase = engine overlay rows (fonts + built-in FPS bar).
//   - A legend panel built with the shared UI kit (src/shared/ui.js) explains the mix and
//     holds a Save PNG button (image-output): click it, tap it on a touchscreen, or press Space.
//
// HOW THE DAY/NIGHT PALETTE WORKS:
//
// Instead of computing `base.multiply(ambient)` in render() and passing a Color32 to draw
// calls, we pre-compute those multiplied colors in update() and store them in dedicated
// palette slots. render() only ever uses palette index numbers.
//
// `Color32.multiply()` is a built-in engine method: it scales each channel of `base` by
// the matching channel of `ambient` and returns a new Color32. Think of it as shining a
// colored flashlight on a surface - a blue light on a red wall gives you a darker,
// purple-ish result.
//
// Example: the grass fill has a base color (50, 140, 70) and a reserved slot C_GRASS.
// Every tick in update(), we compute `grassBase.multiply(ambient)` and write it to C_GRASS.
// render() calls `BT.drawRectFill(rect, C_GRASS)` - no Color32 needed there.
//
// Think of it as updating the paint cans before the painter starts working.

import { function applyEasing(t: number, easing: EasingFunction): number
Applies an easing curve to a normalized time value.
@since1.0.3@changed1.5.0 Added sine, cubic, quartic, quintic, expo, circ, back, elastic, and bounce families.@paramt - Normalized time in [0, 1]. Values outside this range are not clamped.@parameasing - Easing curve to apply.@returnsEased value. Guaranteed to return 0 for `t = 0` and 1 for `t = 1`.
applyEasing
, class AudioClip
Decoded audio asset with its winning source URL and buffer-derived metadata. Construct instances with {@link AudioClip.load } , {@link AudioClip.loadAll } , or {@link AudioClip.synth } ; there is no public constructor.
@since1.3.0
AudioClip
, 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 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 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 Timer
Fixed-tick interval helper for {@link IBTDemo.update } loops. Counts engine ticks ( {@link BT.ticks } ), which advance once per fixed update at {@link HardwareSettings.targetFPS } , not once per {@link IBTDemo.render } frame. Convert ticks to seconds with `intervalTicks / BT.targetFPS`. Tracks a "last fired" tick and reports when a configured interval has elapsed. Useful for periodic events such as particle spawning, score ticks, or palette swaps.
@since1.0.3
Timer
, class Vector2i
Integer 2D vector for pixel-perfect positioning. Used for points, sizes, directions, and camera offsets throughout the engine. The API includes both allocation-free `*To()` / `*InPlace()` variants and convenience methods that return new vectors.
@since0.1.0
Vector2i
} from 'blit386';
import { import applyThemeapplyTheme, import uiui, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js'; /** @typedef {import('blit386').IBTDemo} IBTDemo */ /** @typedef {import('blit386').HardwareSettings} HardwareSettings */ /** @typedef {import('blit386').Palette} Palette */ /** @typedef {import('blit386').SpriteSheet} SpriteSheet */ /** @typedef {import('blit386').Rect2i} Rect2i */ // Internal game resolution. const const DISPLAY_W: 320DISPLAY_W = 320; // The level is wider than the screen so the camera can scroll (Camera). const const WORLD_W: 640WORLD_W = 640; const const WORLD_H: 240WORLD_H = 240; // Where the sidewalk / grass starts. const const GROUND_Y: 188GROUND_Y = 188; // One row of 16 px tiles along the sidewalk (Tilemap idea: small tile IDs in an array). const const GROUND_TILE_SIZE: 16GROUND_TILE_SIZE = 16; const const TILE_GRASS_ID: 1TILE_GRASS_ID = 1; const const TILE_DIRT_ID: 2TILE_DIRT_ID = 2; // Checker squares inside buildings (Patterns idea: repeating blocks, no images). const const BUILDING_PATTERN_CELL: 4BUILDING_PATTERN_CELL = 4; // How fast the rock moves along X each update tick. const const HERO_SPEED: 1HERO_SPEED = 1; // Walk frame timer: advance the "step" counter every 8 ticks. const const WALK_FRAME_TICKS: 8WALK_FRAME_TICKS = 8; // Score +1 every 60 ticks (~1 second). const const SCORE_INTERVAL_TICKS: 60SCORE_INTERVAL_TICKS = 60; // Sparkle particle batch every 30 ticks. const const PARTICLE_SPAWN_INTERVAL: 30PARTICLE_SPAWN_INTERVAL = 30; // Full day/night loop = 1200 ticks (~20 seconds at 60 FPS). const const DAY_NIGHT_CYCLE_TICKS: 1200DAY_NIGHT_CYCLE_TICKS = 1200; // Camera smooth factor: each tick we step 14% closer to the target. const const CAMERA_LERP: 0.14CAMERA_LERP = 0.14; // These two numbers come straight out of public/audio/music-intro-loop.loop.json, generated // by scripts/generate-audio-loops.mjs (see Music for the same track used in isolation). const const MUSIC_LOOP_START_SECONDS: 1.5MUSIC_LOOP_START_SECONDS = 1.5; const const MUSIC_LOOP_END_SECONDS: 7.9MUSIC_LOOP_END_SECONDS = 7.9; // Parallax: clouds move at 22% of the real camera speed (parallax illusion). const const SKY_PARALLAX: 0.22SKY_PARALLAX = 0.22; // How many sky bands cover the height of the sky (from 0 to GROUND_Y). const const SKY_BANDS: 20SKY_BANDS = 20; // Maximum live particles at once. const const MAX_PARTICLES: 20MAX_PARTICLES = 20; // Static scene slots (never change after init). // UI text and panel colors now come from the shared UI kit theme (slots 240 and up); the // slots below belong to the scene itself. const const C_BLACK: 2C_BLACK = 2; // Black for BT.clear. // Dynamic world slots (updated every tick in update()). // Sky bands: 10..10+SKY_BANDS-1 (20 slots). const const C_SKY_BASE: 10C_SKY_BASE = 10; // Ground: 30..31. const const C_GRASS: 30C_GRASS = 30; const const C_DIRTLINE: 31C_DIRTLINE = 31; // Buildings: 4 buildings × 2 slots (fill + outline) = 8 slots at 32..39. const const C_BUILDING_BASE: 32C_BUILDING_BASE = 32; // Clouds: 1 slot at 40. const const C_CLOUD: 40C_CLOUD = 40; // HUD text: 41..44. const const C_HUD_TITLE: 41C_HUD_TITLE = 41; const const C_HUD_SCORE: 42C_HUD_SCORE = 42; const const C_HUD_POS: 43C_HUD_POS = 43; const const C_HUD_PHASE: 44C_HUD_PHASE = 44; // Colors the day-phase overlay row (Day / Night / ...). // Hero shadow: 45. const const C_HERO_SHADOW: 45C_HERO_SHADOW = 45; // Overlay bar fill (text slots reuse C_HUD_SCORE / C_HUD_POS / C_HUD_PHASE below). const const C_OVERLAY_BAR: 46C_OVERLAY_BAR = 46; // Particle slots: 50..69 (MAX_PARTICLES=20). const const PARTICLE_SLOT_START: 50PARTICLE_SLOT_START = 50; // Sprite base colors extracted from test.png: 70..70+N-1. // The ambient (lit) version of sprite colors: 70+N..70+2N-1. const const SPRITE_BASE: 70SPRITE_BASE = 70; /** * One self-running mini scene: walking rock, following camera, HUD, day/night, sparkles. * All color computation happens in update(); render() uses only palette indices. * * @implements {IBTDemo} */ class class Demo
One self-running mini scene: walking rock, following camera, HUD, day/night, sparkles. All color computation happens in update(); render() uses only palette indices.
@implementsIBTDemo
Demo
{
// The palette holds all colors used in this demo. /** @type {Palette | null} */ Demo.palette: Palette | null
@type{Palette | null}
palette
= null;
// Sprite sheet for the rock hero, loaded from /sprites/test.png. /** @type {SpriteSheet | null} */ Demo.heroSheet: SpriteSheet | null
@type{SpriteSheet | null}
heroSheet
= null;
/** @type {AudioClip | null} Looping background music with a distinct intro section. */ Demo.musicClip: AudioClip | null
@type{AudioClip | null} Looping background music with a distinct intro section.
musicClip
= null;
/** @type {AudioClip | null} Short chime played on every day/night phase change. */ Demo.dayPhaseChimeClip: AudioClip | null
@type{AudioClip | null} Short chime played on every day/night phase change.
dayPhaseChimeClip
= null;
/** @type {AudioClip | null} Confirmation blip played after a successful PNG capture. */ Demo.captureBlipClip: AudioClip | null
@type{AudioClip | null} Confirmation blip played after a successful PNG capture.
captureBlipClip
= null;
/** @type {string | null} Day phase label as of the previous tick, used to detect changes. */ Demo.lastDayPhaseLabel: string | null
@type{string | null} Day phase label as of the previous tick, used to detect changes.
lastDayPhaseLabel
= null;
// The full source rectangle for the hero sprite. /** @type {Rect2i | null} */ Demo.heroSprite: Rect2i | null
@type{Rect2i | null}
heroSprite
= null;
// Hero sprite size in world pixels, read from the loaded sheet in init() // (test.png is 44x44). Deriving it from the real image - instead of guessing a // number here - keeps movement bounds, camera centering, and particle spawns // matching what is actually drawn on screen. /** @type {Vector2i} */ Demo.heroSize: Vector2i
@type{Vector2i}
heroSize
= 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
(0, 0);
// How many unique colors the sprite has (N). Demo.spriteColorCount: numberspriteColorCount = 0; // Original Color32 objects for the sprite's colors (for ambient multiplication). Demo.spriteBaseColors: {}spriteBaseColors = []; // Rock position in world pixels. The Y here is a placeholder: init() sets the // real Y once the sprite has loaded and its true height is known, so the rock // stands exactly on the ground line. Demo.heroPos: Vector2iheroPos = 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
(120, 0);
// Rock position at the START of the most recent update() tick, before this tick's // walk step moved it. render() blends between heroPrevPos and heroPos using // BT.renderAlpha so the rock glides smoothly between physics ticks instead of // jumping - see "Interpolating render state with renderAlpha" in the engine's // docs/api-game-loop.md. init() snaps this to match heroPos so the very first // frame does not blend in from a stale position. Demo.heroPrevPos: Vector2iheroPrevPos = 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
(120, 0);
// +1 = moving right, -1 = moving left. Demo.heroFacing: numberheroFacing = 1; // Walk "step" counter (not a frame index - just bobs the rock position slightly). Demo.walkStep: numberwalkStep = 0; Demo.walkFrameTimer: TimerwalkFrameTimer = new new Timer(intervalTicks: number): Timer
Creates a timer that fires once per fixed-tick interval.
@paramintervalTicks - Number of ticks required between firings; must be a positive integer.
Timer
(const WALK_FRAME_TICKS: 8WALK_FRAME_TICKS);
// Camera top-left in world coordinates. Demo.cameraPos: Vector2icameraPos = 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
(0, 0);
// Camera position at the START of the most recent update() tick, before this tick's // follow-lerp moved it. render() blends between this and cameraPos using // BT.renderAlpha for the same reason as heroPrevPos above. Demo.cameraPrevPos: Vector2icameraPrevPos = 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
(0, 0);
// Reused every render() call for the render-time (interpolated) camera and hero // positions, so we do not allocate new Vector2i instances every frame. Demo.cameraRenderPos: Vector2icameraRenderPos = 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
(0, 0);
Demo.heroRenderPos: Vector2iheroRenderPos = 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
(0, 0);
// Float version for smooth lerp without pixel jitter. Demo.cameraXFloat: numbercameraXFloat = 0; // Simple score counter. Demo.score: numberscore = 0; Demo.scoreTimer: TimerscoreTimer = new new Timer(intervalTicks: number): Timer
Creates a timer that fires once per fixed-tick interval.
@paramintervalTicks - Number of ticks required between firings; must be a positive integer.
Timer
(const SCORE_INTERVAL_TICKS: 60SCORE_INTERVAL_TICKS);
// Particle spawn timer. Demo.particleSpawnTimer: TimerparticleSpawnTimer = new new Timer(intervalTicks: number): Timer
Creates a timer that fires once per fixed-tick interval.
@paramintervalTicks - Number of ticks required between firings; must be a positive integer.
Timer
(const PARTICLE_SPAWN_INTERVAL: 30PARTICLE_SPAWN_INTERVAL);
// Active particle objects: { pos, spawnTick, paletteSlot }. Demo.particles: {}particles = []; // Rotating pool index for particle palette slots. Demo.nextParticleSlot: numbernextParticleSlot = 0; // World decoration: buildings and clouds (built once in init). Demo.buildings: {}buildings = []; Demo.clouds: {}clouds = []; // Tile IDs for one sidewalk row (tilemap): each entry is TILE_GRASS_ID or TILE_DIRT_ID. Demo.groundTileIds: {}groundTileIds = []; // PNG capture state (image-output): the legend's Save button (click, tap, or Space) // triggers BT.downloadFrame once per press. Demo.capturing: booleancapturing = false; Demo.lastCaptureMessage: stringlastCaptureMessage = ''; Demo.messageTimer: numbermessageTimer = 0; // Base colors for buildings and clouds (used in update() for ambient multiplication). Demo.buildingFills: {}buildingFills = []; Demo.buildingOutlines: {}buildingOutlines = []; Demo.cloudBaseColor: Color32cloudBaseColor = 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
(230, 240, 255, 200);
Demo.grassBaseColor: Color32grassBaseColor = 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
(50, 140, 70);
Demo.dirtBaseColor: Color32dirtBaseColor = 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
(40, 100, 55);
// Reused rectangle and vector to avoid creating new objects every frame. Demo.tempRect: Rect2itempRect = new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2i
Creates an integer rectangle, truncating all inputs toward zero.
@paramx - Left-edge X coordinate (defaults to 0).@paramy - Top-edge Y coordinate (defaults to 0).@paramwidth - Width in pixels (defaults to 0).@paramheight - Height in pixels (defaults to 0).
Rect2i
(0, 0, 0, 0);
Demo.tempVec: Vector2itempVec = 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
(0, 0);
Demo.worldSize: Vector2iworldSize = 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 WORLD_W: 640WORLD_W, const WORLD_H: 240WORLD_H); // pre-allocated for cameraClamp calls
// Sky band colors: top and horizon base values for the gradient. Demo.skyTop: Color32skyTop = 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
(40, 70, 140);
Demo.skyHorizon: Color32skyHorizon = new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(120, 170, 220);
// HUD text colors (base values before ambient is applied). Demo.hudTitleBase: Color32hudTitleBase = 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
(255, 230, 180);
Demo.hudScoreBase: Color32hudScoreBase = 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
(200, 220, 255);
Demo.hudPosBase: Color32hudPosBase = new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(180, 200, 180);
Demo.hudFpsBase: Color32hudFpsBase = 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
(150, 150, 160);
// Reused every frame for overlay rows (score, rock position, day phase). Demo.overlayRowData: {}overlayRowData = [ { leftText: stringleftText: 'Score 0', textPaletteIndex: numbertextPaletteIndex: const C_HUD_SCORE: 42C_HUD_SCORE }, { leftText: stringleftText: 'Rock (0, 0)', textPaletteIndex: numbertextPaletteIndex: const C_HUD_POS: 43C_HUD_POS }, { leftText: stringleftText: 'Dawn/Day', textPaletteIndex: numbertextPaletteIndex: const C_HUD_PHASE: 44C_HUD_PHASE }, ]; /** * Palette slots for the engine overlay bars (FPS strip uses the engine defaults). * * The live palette grid at the bottom shows which slots this frame's draw calls * use (helpful for day/night tinting and sprite palette blocks). Thirty-two swatches * per row, three visible rows; scroll to browse the full 256-slot palette. * * @returns {Partial<HardwareSettings>} */ Demo.configure(): Partial<HardwareSettings>
Palette slots for the engine overlay bars (FPS strip uses the engine defaults). The live palette grid at the bottom shows which slots this frame's draw calls use (helpful for day/night tinting and sprite palette blocks). Thirty-two swatches per row, three visible rows; scroll to browse the full 256-slot palette.
@returns
configure
() {
return { // The engine normally shows a tiny "~" toggle hint in the bottom-left // corner so people know they can press the Backquote key (`) to open the // stats overlay. This is an immersive game scene, so we hide that hint to // keep the picture clean. The overlay still works: press ` to reveal the // full dev HUD (timing chart and palette grid) on demand, then ` again to // hide it. Teaching demos leave this hint visible (the default) so newcomers // can find it. isOverlayToggleHintVisible: booleanisOverlayToggleHintVisible: false, isOverlayPaletteEnabled: booleanisOverlayPaletteEnabled: true, overlayPaletteColumns: numberoverlayPaletteColumns: 32, overlayPaletteRowsVisible: numberoverlayPaletteRowsVisible: 3,
overlayStyle: {
    barPaletteIndex: number;
    textPaletteIndex: number;
    gapPaletteIndex: number;
}
overlayStyle
: {
barPaletteIndex: numberbarPaletteIndex: const C_OVERLAY_BAR: 46C_OVERLAY_BAR, textPaletteIndex: numbertextPaletteIndex: const C_HUD_SCORE: 42C_HUD_SCORE, gapPaletteIndex: numbergapPaletteIndex: const C_OVERLAY_BAR: 46C_OVERLAY_BAR, }, isOverlayTimingChartEnabled: booleanisOverlayTimingChartEnabled: true,
overlayTimingChartStyle: {
    updateBarPaletteIndex: number;
    renderBarPaletteIndex: number;
    warningPaletteIndex: number;
    errorPaletteIndex: number;
    tagPaletteIndex: number;
}
overlayTimingChartStyle
: {
updateBarPaletteIndex: numberupdateBarPaletteIndex: const C_HUD_POS: 43C_HUD_POS, renderBarPaletteIndex: numberrenderBarPaletteIndex: const C_HUD_SCORE: 42C_HUD_SCORE, warningPaletteIndex: numberwarningPaletteIndex: const C_HUD_PHASE: 44C_HUD_PHASE, errorPaletteIndex: numbererrorPaletteIndex: const C_HUD_TITLE: 41C_HUD_TITLE, tagPaletteIndex: numbertagPaletteIndex: const C_HUD_POS: 43C_HUD_POS, }, }; } /** * Loads the hero sprite sheet, builds the palette, and places buildings and clouds. * * @returns {Promise<boolean>} */ async Demo.init(): Promise<boolean>
Loads the hero sprite sheet, builds the palette, and places buildings and clouds.
@returns
init
() {
console.log('[GameSceneDemo] Initializing...'); // Create palette and set static slots // The shared UI kit needs its twelve theme colors in the palette before any widget // draws. applyTheme() writes them into high slots (240 and up), far above this // demo's scene slots (which top out around slot 110 at the sprite ambient block). // This demo never needs the returned slot map, so the call is side effect only. 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);
import applyThemeapplyTheme(this.Demo.palette: Palette
@type{Palette | null}
palette
);
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_BLACK: 2C_BLACK, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(0, 0, 0));
this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const C_OVERLAY_BAR: 46C_OVERLAY_BAR, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(0, 0, 0, 180)); // overlay row backgrounds
// Pre-fill particle slots as transparent. for (let let i: numberi = 0; let i: numberi < const MAX_PARTICLES: 20MAX_PARTICLES; let i: numberi++) { this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const PARTICLE_SLOT_START: 50PARTICLE_SLOT_START + let i: numberi, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(0, 0, 0, 0));
} // Build world decoration and the sidewalk tile-ID row (Tilemap demo). this.Demo.buildWorldDecor(): void
Places buildings and clouds once at startup. Stores their base colors in separate arrays so update() can apply ambient.
buildWorldDecor
();
this.Demo.buildGroundTileStrip(): void
Fills groundTileIds with alternating grass/dirt tile IDs for one 16 px row (Tilemap demo).
buildGroundTileStrip
();
// Extract sprite colors and register in palette // Ask the engine to scan the PNG and add every unique color it finds into our palette, // starting at SPRITE_BASE. The returned array is the same colors in palette-write order // (sorted darkest-first by brightness). We keep them so updateWorldPalette() can // multiply each base color by the current ambient tint and write the lit version into // the higher "ambient block" (SPRITE_BASE+N..SPRITE_BASE+2N-1). this.Demo.spriteBaseColors: {}spriteBaseColors = 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 SPRITE_BASE: 70SPRITE_BASE);
const const colorCount: anycolorCount = this.Demo.spriteBaseColors: {}spriteBaseColors.length; this.Demo.spriteColorCount: numberspriteColorCount = const colorCount: anycolorCount; // Pre-fill the "ambient sprite" block (SPRITE_BASE+N..SPRITE_BASE+2N-1). // update() will recalculate these every tick based on the current ambient light. for (let let i: numberi = 0; let i: numberi < const colorCount: anycolorCount; let i: numberi++) { this.Demo.palette: Palette
@type{Palette | null}
palette
.Palette.set(index: number, color: Color32): void
Writes a color into a palette slot.
@paramindex - Palette index to overwrite.@paramcolor - Color to store.@throwsError if the index is invalid or if index `0` is set opaque.
set
(const SPRITE_BASE: 70SPRITE_BASE + const colorCount: anycolorCount + let i: numberi, this.Demo.spriteBaseColors: {}spriteBaseColors[let i: numberi]);
} // Load hero sprite 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 SPRITE_BASE: 70SPRITE_BASE, {
sort?: "luminance" | "none" | undefinedsort: 'none', }); this.Demo.heroSheet: SpriteSheet | null
@type{SpriteSheet | null}
heroSheet
= const indexed: Promise<IndexedSpriteLoadResult>indexed.sheet;
this.Demo.heroSprite: Rect2i | null
@type{Rect2i | null}
heroSprite
= this.Demo.heroSheet: SpriteSheet | null
@type{SpriteSheet | null}
heroSheet
.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(`[GameSceneDemo] Loaded sprite: ${this.Demo.heroSprite: Rect2i
@type{Rect2i | null}
heroSprite
.Rect2i.width: number
Width in pixels (defaults to 0).
width
}x${this.Demo.heroSprite: Rect2i
@type{Rect2i | null}
heroSprite
.Rect2i.height: number
Height in pixels (defaults to 0).
height
}px`);
// Read the hero's real size from the loaded sheet (44x44 for test.png), the // same way basics-enhanced and logo-lowres do. Every bit of math below - movement bounds, // camera centering, particle spawns - uses this size, so the logic always // matches the picture on screen. this.Demo.heroSize: Vector2i
@type{Vector2i}
heroSize
.Vector2i.set(x: number, y: number): Vector2i
Sets both components of this vector. Modifies this vector directly for maximum performance. WARNING: Mutates this vector. Don't use on frozen/cached singletons.
@paramx - New x component.@paramy - New y component.@returnsThis vector for chaining.
set
(this.Demo.heroSheet: SpriteSheet | null
@type{SpriteSheet | null}
heroSheet
.SpriteSheet.size: Vector2i
Gets the sprite-sheet dimensions in pixels.
@returnsSheet dimensions. Changes after a hot-replace image swap with different dimensions - any `srcRect` a demo holds onto is the demo's own responsibility to reconcile.
size
.Vector2i.x: number
Horizontal component (defaults to 0).
x
, this.Demo.heroSheet: SpriteSheet | null
@type{SpriteSheet | null}
heroSheet
.SpriteSheet.size: Vector2i
Gets the sprite-sheet dimensions in pixels.
@returnsSheet dimensions. Changes after a hot-replace image swap with different dimensions - any `srcRect` a demo holds onto is the demo's own responsibility to reconcile.
size
.Vector2i.y: number
Vertical component (defaults to 0).
y
);
// Stand the rock on the ground line: the sprite's top-left Y is the ground // minus the sprite's height, so its bottom edge touches GROUND_Y exactly. this.Demo.heroPos: Vector2iheroPos.Vector2i.y: number
Vertical component (defaults to 0).
y
= const GROUND_Y: 188GROUND_Y - this.Demo.heroSize: Vector2i
@type{Vector2i}
heroSize
.Vector2i.y: number
Vertical component (defaults to 0).
y
;
// Snap heroPrevPos to match so the very first render does not blend in from // the placeholder position the class fields started with. this.Demo.heroPrevPos: Vector2iheroPrevPos.Vector2i.set(x: number, y: number): Vector2i
Sets both components of this vector. Modifies this vector directly for maximum performance. WARNING: Mutates this vector. Don't use on frozen/cached singletons.
@paramx - New x component.@paramy - New y component.@returnsThis vector for chaining.
set
(this.Demo.heroPos: Vector2iheroPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
, this.Demo.heroPos: Vector2iheroPos.Vector2i.y: number
Vertical component (defaults to 0).
y
);
// Place camera on the hero to start. this.Demo.cameraXFloat: numbercameraXFloat = this.Demo.heroPos: Vector2iheroPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
- const DISPLAY_W: 320DISPLAY_W / 2 + this.Demo.heroSize: Vector2i
@type{Vector2i}
heroSize
.Vector2i.x: number
Horizontal component (defaults to 0).
x
/ 2;
this.Demo.cameraPos: Vector2icameraPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
= Math.floor(this.Demo.cameraXFloat: numbercameraXFloat);
this.Demo.cameraPos: Vector2icameraPos.Vector2i.y: number
Vertical component (defaults to 0).
y
= 0;
this.Demo.clampCamera(): void
Keeps cameraPos.x between 0 and WORLD_W - DISPLAY_W.
clampCamera
();
// Snap cameraPrevPos to match the starting camera position so the very first // render does not blend in from (0, 0). this.Demo.cameraPrevPos: Vector2icameraPrevPos.Vector2i.set(x: number, y: number): Vector2i
Sets both components of this vector. Modifies this vector directly for maximum performance. WARNING: Mutates this vector. Don't use on frozen/cached singletons.
@paramx - New x component.@paramy - New y component.@returnsThis vector for chaining.
set
(this.Demo.cameraPos: Vector2icameraPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
, this.Demo.cameraPos: Vector2icameraPos.Vector2i.y: number
Vertical component (defaults to 0).
y
);
// Load and start all sound: music, the day/night chime, and the capture blip. await this.Demo.initAudio(): any
Loads the music track, synthesizes the two sound effects, and starts the music. Called once from init(); split out so the audio setup reads as one clear step.
initAudio
();
console.log('[GameSceneDemo] Ready.'); return true; } /** * Loads the music track, synthesizes the two sound effects, and starts the music. * Called once from init(); split out so the audio setup reads as one clear step. */ async Demo.initAudio(): any
Loads the music track, synthesizes the two sound effects, and starts the music. Called once from init(); split out so the audio setup reads as one clear step.
initAudio
() {
// Background music: a real intro-then-loop track, the same one Music // demonstrates in isolation. BT.musicPlay() called before the page is unlocked is // "remembered" and starts for real the instant the player clicks or presses a key. // // Wrap load + play like snake-game: a missing or undecodable file must not // abort the whole scene - the capstone still renders with SFX only. try { this.Demo.musicClip: AudioClip | null
@type{AudioClip | null} Looping background music with a distinct intro section.
musicClip
= await class AudioClip
Decoded audio asset with its winning source URL and buffer-derived metadata. Construct instances with {@link AudioClip.load } , {@link AudioClip.loadAll } , or {@link AudioClip.synth } ; there is no public constructor.
@since1.3.0
AudioClip
.AudioClip.load(url: string | string[], options?: AudioClipLoadOptions): Promise<AudioClip>
Loads an audio clip from a single URL, or from an ordered list of candidate URLs. A single URL runs the download+decode pipeline directly, sharing the per-URL cache and in-flight dedup described on {@link AudioClip } . A URL array tries each candidate in order and resolves with the first one that downloads and decodes successfully - useful for offering a browser-friendly fallback (for example `['music.ogg', 'music.mp3']`) when a container or codec isn't universally supported. If every candidate fails, the error from the last candidate is thrown.
@paramurl - Single audio URL, or an ordered list of fallback URLs.@paramoptions - Optional load options (progress reporting).@returnsLoaded clip, cached under its winning source URL.@throwsError if the URL (or every URL in the list) fails to load or decode.
load
('/audio/music-intro-loop.wav');
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
.musicPlay: (clip: AudioClip, options?: MusicPlayOptions) => void
Plays a loaded audio clip through the music player, crossfading out whatever is currently playing. Silently does nothing when the clip hasn't finished loading yet (or was already unloaded with `clip.unload()`), or before the engine has initialized. While the audio context is still locked (before the first unlock gesture), the request is remembered instead of dropped - it starts automatically the instant the context unlocks, unlike {@link BT.soundPlay } .
@since1.3.0@paramclip - Loaded audio clip to play.@paramoptions - Crossfade, volume, and loop options; see {@link MusicPlayOptions}.
musicPlay
(this.Demo.musicClip: AudioClip | null
@type{AudioClip | null} Looping background music with a distinct intro section.
musicClip
, {
MusicPlayOptions.loop?: boolean | undefined
Whether the whole track loops. Ignored when `loopStart`/`loopEnd` are given. Defaults to `true`.
loop
: true,
MusicPlayOptions.loopStart?: number | undefined
Loop region start in seconds. Requires `loopEnd`; see {@link MusicPlayer.play } .
loopStart
: const MUSIC_LOOP_START_SECONDS: 1.5MUSIC_LOOP_START_SECONDS,
MusicPlayOptions.loopEnd?: number | undefined
Loop region end in seconds. Requires `loopStart`; see {@link MusicPlayer.play } .
loopEnd
: const MUSIC_LOOP_END_SECONDS: 7.9MUSIC_LOOP_END_SECONDS,
}); } catch (function (local var) error: unknownerror) { console.warn('[GameSceneDemo] Failed to load background music, continuing without it.', function (local var) error: unknownerror); this.Demo.musicClip: AudioClip | null
@type{AudioClip | null} Looping background music with a distinct intro section.
musicClip
= null;
} // A soft rising chime for day/night transitions, and BT.synthPreset.blip() (the // same UI blip Synth Toy uses) for a successful capture. this.Demo.dayPhaseChimeClip: AudioClip | null
@type{AudioClip | null} Short chime played on every day/night phase change.
dayPhaseChimeClip
= await class AudioClip
Decoded audio asset with its winning source URL and buffer-derived metadata. Construct instances with {@link AudioClip.load } , {@link AudioClip.loadAll } , or {@link AudioClip.synth } ; there is no public constructor.
@since1.3.0
AudioClip
.AudioClip.synth(params: SynthParams): Promise<AudioClip>
Synthesizes a clip from deterministic procedural parameters - no source file, no `OfflineAudioContext`, and no audio graph involved. Rendering happens entirely on the CPU via the pure {@link renderSynthSamples } function against an `AudioBuffer` allocated from the registered decode context. The returned clip flows through the same {@link buffer } getter and playback path as a loaded clip, but uses a synthetic, non-cached identifier (`synth:<waveform>`) - it is never added to the URL-keyed resolved cache and never deduplicated, so identical `params` still render a fresh, independent `AudioBuffer` on every call. See {@link SynthParams } for the full parameter set.
@paramparams - Deterministic synthesis parameters.@returnsA new clip wrapping the synthesized buffer.@throwsError if `params` fails validation, or the engine has not registered a decode context yet (see {@link audioClipNotReadyError }).
synth
({
SynthParams.waveform: any
Oscillator waveform shape.
waveform
: 'sine',
SynthParams.frequency: number
Base carrier frequency in Hz at the start of the clip (before any pitch sweep or vibrato).
frequency
: 660,
SynthParams.duration: number
Total clip duration in seconds. Must be greater than 0 and no more than {@link MAX_SYNTH_DURATION_SECONDS } .
duration
: 0.6,
SynthParams.volume?: number | undefined
Overall output amplitude in [0, 1] (unclamped on the high end; final output is always clamped to avoid clipping). Defaults to {@link DEFAULT_VOLUME } .
volume
: 0.5,
SynthParams.envelope?: SynthEnvelope | undefined
Optional attack/decay/sustain/release envelope. Defaults to a full ADSR envelope; see {@link SynthEnvelope } .
envelope
: { SynthEnvelope.attack?: number | undefined
Time in seconds to ramp from silence to full amplitude. Defaults to {@link DEFAULT_ATTACK } .
attack
: 0.02, SynthEnvelope.decay?: number | undefined
Time in seconds to fall from full amplitude to the `sustain` level. Defaults to {@link DEFAULT_DECAY } .
decay
: 0.15, SynthEnvelope.sustain?: number | undefined
Gain level in [0, 1] held between the decay and release phases. Defaults to {@link DEFAULT_SUSTAIN } .
sustain
: 0.3, SynthEnvelope.release?: number | undefined
Time in seconds to fall from the sustain level to silence at the end of the clip. Defaults to {@link DEFAULT_RELEASE } .
release
: 0.4 },
SynthParams.seed: number
Seed for the deterministic PRNG driving noise generation - identical seeds render identical output.
seed
: 1,
}); this.Demo.captureBlipClip: AudioClip | null
@type{AudioClip | null} Confirmation blip played after a successful PNG capture.
captureBlipClip
= await class AudioClip
Decoded audio asset with its winning source URL and buffer-derived metadata. Construct instances with {@link AudioClip.load } , {@link AudioClip.loadAll } , or {@link AudioClip.synth } ; there is no public constructor.
@since1.3.0
AudioClip
.AudioClip.synth(params: SynthParams): Promise<AudioClip>
Synthesizes a clip from deterministic procedural parameters - no source file, no `OfflineAudioContext`, and no audio graph involved. Rendering happens entirely on the CPU via the pure {@link renderSynthSamples } function against an `AudioBuffer` allocated from the registered decode context. The returned clip flows through the same {@link buffer } getter and playback path as a loaded clip, but uses a synthetic, non-cached identifier (`synth:<waveform>`) - it is never added to the URL-keyed resolved cache and never deduplicated, so identical `params` still render a fresh, independent `AudioBuffer` on every call. See {@link SynthParams } for the full parameter set.
@paramparams - Deterministic synthesis parameters.@returnsA new clip wrapping the synthesized buffer.@throwsError if `params` fails validation, or the engine has not registered a decode context yet (see {@link audioClipNotReadyError }).
synth
(
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.
synthPreset: {
    jump: (seed?: number) => SynthParams;
    pickup: (seed?: number) => SynthParams;
    explosion: (seed?: number) => SynthParams;
    laser: (seed?: number) => SynthParams;
    hit: (seed?: number) => SynthParams;
    blip: (seed?: number) => SynthParams;
}
Pre-configured `SynthParams` presets for common sound effects ("jump", "pickup", "explosion", "laser", "hit", "blip"). Each function returns a fresh {@link SynthParams } object; pass it to {@link AudioClip.synth } to render a clip, then play the result via {@link BT.soundPlay } . An optional `seed` argument applies small, bounded, deterministic jitter to a few hand-picked fields per preset, so repeated plays vary without losing reproducibility - the same seed always renders the exact same variant.
@since1.3.0@exampleconst jumpClip = await AudioClip.synth(BT.synthPreset.jump()); BT.soundPlay(jumpClip);
synthPreset
.blip: (seed?: number) => SynthParams
UI blip / menu select: a very short, clean sine tone. `seed` jitters only the base frequency, and only slightly (+/-3%) - UI feedback should stay recognizably consistent rather than vary as much as a gameplay sound effect. Omit `seed` for a fixed baseline variant.
@paramseed - Seed for deterministic jitter. Defaults to {@link DEFAULT_PRESET_SEED }.@returnsA fresh `SynthParams` for a UI blip sound effect.
blip
());
// Remember the starting day phase so updateDayPhaseSound() only chimes when // the phase actually changes, not on the very first tick. this.Demo.lastDayPhaseLabel: string | null
@type{string | null} Day phase label as of the previous tick, used to detect changes.
lastDayPhaseLabel
= this.Demo.getDayPhaseLabel(tick?: number): string
Human-readable label for where we are in the day/night cycle.
@paramtick - Tick to evaluate (defaults to {@link BT.ticks}).@returns
getDayPhaseLabel
();
} /** * Fixed-step logic: moves the rock, camera, score, and particles, * then recalculates all ambient-lit palette colors. */ Demo.update(): void
Fixed-step logic: moves the rock, camera, score, and particles, then recalculates all ambient-lit palette colors.
update
() {
// Let the shared UI kit latch keyboard shortcuts and touch contacts for this tick. // This must run before anything else so the Save button's Space binding works. import uiui.tick(); const const tick: numbertick =
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
;
this.Demo.updateDayPhaseSound(tick: number): void
Plays a short chime the instant the day/night phase label changes (Day -> Toward dusk -> Night -> Toward dawn -> Day...). Comparing this tick's label against last tick's label is the same "edge detection" idea Keyboard Input uses for key presses - we only care about the moment something changes, not every tick it stays the same.
@paramtick - Current engine tick (BT.ticks).
updateDayPhaseSound
(const tick: numbertick);
// The PNG capture itself now starts from the kit's Save button in renderLegend(). // Here we only count down the "Saved: ..." message so it disappears after a while. if (this.Demo.messageTimer: numbermessageTimer > 0) { this.Demo.messageTimer: numbermessageTimer--; } this.Demo.updateHeroMovement(): void
Moves the rock left/right automatically, bouncing off world edges.
updateHeroMovement
();
this.Demo.updateWalkStep(tick: number): void
Advances a step counter every WALK_FRAME_TICKS ticks. Used to add a subtle bob to the rock as it moves.
@paramtick - Current tick.
updateWalkStep
(const tick: numbertick);
this.Demo.updateCameraFollow(): void
Smoothly follows the hero, then clamps so the view never leaves the world.
updateCameraFollow
();
this.Demo.updateScore(tick: number): void
+1 score every SCORE_INTERVAL_TICKS.
@paramtick - Current tick.
updateScore
(const tick: numbertick);
this.Demo.updateParticlesSpawn(tick: number): void
Spawns a handful of sparkles near the rock on a fixed schedule.
@paramtick - Current tick.
updateParticlesSpawn
(const tick: numbertick);
this.Demo.cleanupParticles(tick: number): void
Removes particles older than 72 ticks (~1.2 seconds).
@paramtick - Current tick.
cleanupParticles
(const tick: numbertick);
this.Demo.updateParticleColors(tick: number): void
Updates each particle's palette slot with its current color. Hue comes from spawnTick; alpha fades out as the particle ages. The ambient tint is also applied so sparkles dim at night.
@paramtick - Current tick.
updateParticleColors
(const tick: numbertick);
// Recalculate all ambient-lit palette colors. // This is the core of the day/night system. this.Demo.updateWorldPalette(): void
Recalculates all ambient-dependent palette entries. Called every tick in update() so render() always has up-to-date slot values.
updateWorldPalette
();
} /** * Plays a short chime the instant the day/night phase label changes (Day -> Toward dusk * -> Night -> Toward dawn -> Day...). Comparing this tick's label against last tick's * label is the same "edge detection" idea Keyboard Input uses for key presses - we * only care about the moment something changes, not every tick it stays the same. * * @param {number} tick - Current engine tick (BT.ticks). */ Demo.updateDayPhaseSound(tick: number): void
Plays a short chime the instant the day/night phase label changes (Day -> Toward dusk -> Night -> Toward dawn -> Day...). Comparing this tick's label against last tick's label is the same "edge detection" idea Keyboard Input uses for key presses - we only care about the moment something changes, not every tick it stays the same.
@paramtick - Current engine tick (BT.ticks).
updateDayPhaseSound
(tick: number
- Current engine tick (BT.ticks).
@paramtick - Current engine tick (BT.ticks).
tick
) {
const const currentPhase: stringcurrentPhase = this.Demo.getDayPhaseLabel(tick?: number): string
Human-readable label for where we are in the day/night cycle.
@paramtick - Tick to evaluate (defaults to {@link BT.ticks}).@returns
getDayPhaseLabel
(tick: number
- Current engine tick (BT.ticks).
@paramtick - Current engine tick (BT.ticks).
tick
);
if (this.Demo.lastDayPhaseLabel: string | null
@type{string | null} Day phase label as of the previous tick, used to detect changes.
lastDayPhaseLabel
!== null && const currentPhase: stringcurrentPhase !== this.Demo.lastDayPhaseLabel: string
@type{string | null} Day phase label as of the previous tick, used to detect changes.
lastDayPhaseLabel
) {
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.soundPlay: (clip: AudioClip, options?: SoundPlayOptions) => SoundRef
Plays a loaded audio clip through the SFX voice pool. Returns an inert {@link SoundRef } without allocating a voice when the clip hasn't finished loading yet (or was already unloaded), when the pool has no free or stealable voice at this priority, or before the engine has unlocked audio playback.
@since1.3.0@paramclip - Loaded audio clip to play.@paramoptions - Playback options.@returnsA handle identifying the new voice; pass it to {@link BT.soundStop} and the other per-sound controls. Safe to use even when playback was silently dropped - every accessor on an inert handle is a no-op.
soundPlay
(this.Demo.dayPhaseChimeClip: AudioClip | null
@type{AudioClip | null} Short chime played on every day/night phase change.
dayPhaseChimeClip
);
} this.Demo.lastDayPhaseLabel: string | null
@type{string | null} Day phase label as of the previous tick, used to detect changes.
lastDayPhaseLabel
= const currentPhase: stringcurrentPhase;
} /** * Draws world layers back-to-front. HUD text is handled by the engine overlay. * Every draw call uses only palette index numbers - no Color32 objects. */ Demo.render(): void
Draws world layers back-to-front. HUD text is handled by the engine overlay. Every draw call uses only palette index numbers - no Color32 objects.
render
() {
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.clear: (paletteIndex: number) => void
Sets the frame clear color using a palette index. The renderer uses this color when clearing the full display at the start of the next frame.
@since0.1.0@parampaletteIndex - Palette index for the full-screen clear pass.
clear
(const C_BLACK: 2C_BLACK);
// Blend cameraPrevPos toward cameraPos by BT.renderAlpha - a fraction from 0 // (a tick just finished) to just under 1 (the next tick is about to happen) - // so the camera's on-screen position matches this exact render moment instead // of only its last-tick position. Both the sky parallax below and the full // camera offset use this same smoothed value, so the layers never drift apart. this.Demo.cameraRenderPos: Vector2icameraRenderPos.Vector2i.set(x: number, y: number): Vector2i
Sets both components of this vector. Modifies this vector directly for maximum performance. WARNING: Mutates this vector. Don't use on frozen/cached singletons.
@paramx - New x component.@paramy - New y component.@returnsThis vector for chaining.
set
(
Math.floor(this.Demo.cameraPrevPos: Vector2icameraPrevPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
+ (this.Demo.cameraPos: Vector2icameraPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
- this.Demo.cameraPrevPos: Vector2icameraPrevPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
) *
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.renderAlpha: number
Fractional progress between the last completed fixed update and the next. Intended for interpolating render state between fixed-update steps.
@since1.3.0@returnsInterpolation alpha in `[0, 1)`.
renderAlpha
),
Math.floor(this.Demo.cameraPrevPos: Vector2icameraPrevPos.Vector2i.y: number
Vertical component (defaults to 0).
y
+ (this.Demo.cameraPos: Vector2icameraPos.Vector2i.y: number
Vertical component (defaults to 0).
y
- this.Demo.cameraPrevPos: Vector2icameraPrevPos.Vector2i.y: number
Vertical component (defaults to 0).
y
) *
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.renderAlpha: number
Fractional progress between the last completed fixed update and the next. Intended for interpolating render state between fixed-update steps.
@since1.3.0@returnsInterpolation alpha in `[0, 1)`.
renderAlpha
),
); // Layer 1: sky and clouds with parallax. this.Demo.renderSkyLayer(): void
Draws the sky gradient and clouds using a slower fake camera for parallax depth.
renderSkyLayer
();
// Layer 2: world with full camera offset.
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
.cameraSet: (offset: Vector2i) => void
Sets the global camera offset applied to subsequent draw calls.
@since0.1.0@paramoffset - Camera translation in display pixels.
cameraSet
(this.Demo.cameraRenderPos: Vector2icameraRenderPos);
this.Demo.renderGroundAndBuildings(): void
Draws the grass strip and building blocks in world space.
renderGroundAndBuildings
();
this.Demo.renderParticles(): void
Draws active particles as 3x3 colored squares. Colors were already computed in update() - render() just reads the slot.
renderParticles
();
this.Demo.renderHero(): void
Draws the rock sprite with a shadow underfoot. The sprite is drawn with paletteOffset = spriteColorCount, which shifts each pixel index into the "ambient block" (SPRITE_BASE+N..SPRITE_BASE+2N-1). Those slots are updated every tick in updateWorldPalette() to reflect the current ambient light, so the rock automatically dims at night.
renderHero
();
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
.cameraReset: () => void
Resets the global camera offset to `(0, 0)`.
@since0.1.0
cameraReset
();
// First-time legend and capture hint (screen space, not scrolled with the world). this.Demo.renderLegend(): void
Short legend for first-time viewers (screen space, top-left), built with the shared UI kit. The Save button also listens for the Space key, so keyboard, mouse, and touch all trigger the same PNG capture (Image Output demo).
renderLegend
();
// Score, rock position, and day phase are drawn in overlayRows() above the FPS bar. } /** * Score, rock position, and day/night phase for the engine overlay. * Text colors are updated each tick in updateWorldPalette() so they dim at night. * * @returns {readonly { leftText: string, rightText?: string }[]} */
Demo.overlayRows(): readonly {
    leftText: string;
    rightText?: string;
}[]
Score, rock position, and day/night phase for the engine overlay. Text colors are updated each tick in updateWorldPalette() so they dim at night.
@returns
overlayRows
() {
this.Demo.overlayRowData: {}overlayRowData[0].leftText = `Score ${this.Demo.score: numberscore}`; this.Demo.overlayRowData: {}overlayRowData[1].leftText = `Rock (${this.Demo.heroPos: Vector2iheroPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
}, ${this.Demo.heroPos: Vector2iheroPos.Vector2i.y: number
Vertical component (defaults to 0).
y
})`;
this.Demo.overlayRowData: {}overlayRowData[2].leftText = this.Demo.getDayPhaseLabel(tick?: number): string
Human-readable label for where we are in the day/night cycle.
@paramtick - Tick to evaluate (defaults to {@link BT.ticks}).@returns
getDayPhaseLabel
();
return this.Demo.overlayRowData: {}overlayRowData; } /** * Fills groundTileIds with alternating grass/dirt tile IDs for one 16 px row (Tilemap demo). */ Demo.buildGroundTileStrip(): void
Fills groundTileIds with alternating grass/dirt tile IDs for one 16 px row (Tilemap demo).
buildGroundTileStrip
() {
const const cols: anycols = Math.ceil(const WORLD_W: 640WORLD_W / const GROUND_TILE_SIZE: 16GROUND_TILE_SIZE); this.Demo.groundTileIds: {}groundTileIds = []; for (let let col: numbercol = 0; let col: numbercol < const cols: anycols; let col: numbercol++) { // Simple pattern: two grass tiles, then two dirt tiles, repeat. this.Demo.groundTileIds: {}groundTileIds.push(let col: numbercol % 4 < 2 ? const TILE_GRASS_ID: 1TILE_GRASS_ID : const TILE_DIRT_ID: 2TILE_DIRT_ID); } } /** * Places buildings and clouds once at startup. * Stores their base colors in separate arrays so update() can apply ambient. */ Demo.buildWorldDecor(): void
Places buildings and clouds once at startup. Stores their base colors in separate arrays so update() can apply ambient.
buildWorldDecor
() {
const const rawBuildings: {}rawBuildings = [ { x: numberx: 40, y: numbery: const GROUND_Y: 188GROUND_Y - 52, w: numberw: 36, h: numberh: 52, fill: Color32fill: 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
(140, 90, 70), outline: Color32outline: 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
(80, 50, 40) },
{ x: numberx: 200, y: numbery: const GROUND_Y: 188GROUND_Y - 70, w: numberw: 44, h: numberh: 70, fill: Color32fill: 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
(110, 120, 140),
outline: Color32outline: 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, 70, 90),
}, { x: numberx: 380, y: numbery: const GROUND_Y: 188GROUND_Y - 46, w: numberw: 32, h: numberh: 46, fill: Color32fill: 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
(160, 100, 100),
outline: Color32outline: 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
(100, 50, 50),
}, { x: numberx: 500, y: numbery: const GROUND_Y: 188GROUND_Y - 60, w: numberw: 40, h: numberh: 60, fill: Color32fill: new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(120, 130, 100),
outline: Color32outline: 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
(70, 80, 60),
}, ]; for (const const b: anyb of const rawBuildings: {}rawBuildings) { this.Demo.buildings: {}buildings.push({ x: anyx: const b: anyb.x, y: anyy: const b: anyb.y, w: anyw: const b: anyb.w, h: anyh: const b: anyb.h }); this.Demo.buildingFills: {}buildingFills.push(const b: anyb.fill); this.Demo.buildingOutlines: {}buildingOutlines.push(const b: anyb.outline); } this.Demo.clouds: {}clouds = [ { x: numberx: 30, y: numbery: 24, w: numberw: 42, h: numberh: 14 }, { x: numberx: 160, y: numbery: 40, w: numberw: 56, h: numberh: 18 }, { x: numberx: 320, y: numbery: 18, w: numberw: 48, h: numberh: 16 }, { x: numberx: 480, y: numbery: 36, w: numberw: 50, h: numberh: 15 }, { x: numberx: 600, y: numbery: 22, w: numberw: 44, h: numberh: 17 }, ]; } /** * Computes the current ambient tint based on the day/night cycle. * Returns a Color32 that represents the current "color of the light". * Bright white = midday; dark blue = midnight. * * This is called in update() to drive the palette, not in render(). * * @returns {Color32} The ambient light color. */ Demo.getAmbientTint(): Color32
Computes the current ambient tint based on the day/night cycle. Returns a Color32 that represents the current "color of the light". Bright white = midday; dark blue = midnight. This is called in update() to drive the palette, not in render().
@returnsThe ambient light color.
getAmbientTint
() {
const const tick: numbertick =
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
;
const const cycle: numbercycle = (const tick: numbertick % const DAY_NIGHT_CYCLE_TICKS: 1200DAY_NIGHT_CYCLE_TICKS) / const DAY_NIGHT_CYCLE_TICKS: 1200DAY_NIGHT_CYCLE_TICKS; // Math.cos returns -1..1. (cos + 1) / 2 gives 0..1 for "how bright is the sun". const const dayAmount: numberdayAmount = (Math.cos(const cycle: numbercycle * Math.PI * 2) + 1) * 0.5; // Interpolate from a cool night color to a warm day color. const const r: anyr = Math.floor(70 + const dayAmount: numberdayAmount * 185); const const g: anyg = Math.floor(75 + const dayAmount: numberdayAmount * 180); const const b: anyb = Math.floor(120 + const dayAmount: numberdayAmount * 135); 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 r: anyr, const g: anyg, const b: anyb);
} /** * Recalculates all ambient-dependent palette entries. * Called every tick in update() so render() always has up-to-date slot values. */ Demo.updateWorldPalette(): void
Recalculates all ambient-dependent palette entries. Called every tick in update() so render() always has up-to-date slot values.
updateWorldPalette
() {
const const ambient: Color32ambient = this.Demo.getAmbientTint(): Color32
Computes the current ambient tint based on the day/night cycle. Returns a Color32 that represents the current "color of the light". Bright white = midday; dark blue = midnight. This is called in update() to drive the palette, not in render().
@returnsThe ambient light color.
getAmbientTint
();
// Sky gradient bands for (let let band: numberband = 0; let band: numberband < const SKY_BANDS: 20SKY_BANDS; let band: numberband++) { // t goes 0 at the top to 1 near the horizon. const const t: numbert = let band: numberband / const SKY_BANDS: 20SKY_BANDS; // applyEasing(t, 'ease-in-out') squishes the transition so it moves slowly // near the top, speeds up through the middle, then slows again near the horizon. // Real skies work the same way: a deep uniform color at the zenith, a quick // color shift through the middle bands, then a flatter wash near the horizon. // lerp(other, t) blends smoothly between two Color32 values at position t, // where t=0 is all skyTop and t=1 is all skyHorizon. const const bandBase: Color32bandBase = this.Demo.skyTop: Color32skyTop.Color32.lerp(other: Color32, t: number): Color32
Linearly interpolates between this color and another. Useful for color transitions and gradients.
@paramother - Target color to blend toward.@paramt - Interpolation factor (0.0 = this color, 1.0 = other color).@returnsNew color blended between this and other.
lerp
(this.Demo.skyHorizon: Color32skyHorizon, function applyEasing(t: number, easing: EasingFunction): number
Applies an easing curve to a normalized time value.
@since1.0.3@changed1.5.0 Added sine, cubic, quartic, quintic, expo, circ, back, elastic, and bounce families.@paramt - Normalized time in [0, 1]. Values outside this range are not clamped.@parameasing - Easing curve to apply.@returnsEased value. Guaranteed to return 0 for `t = 0` and 1 for `t = 1`.
applyEasing
(const t: numbert, 'ease-in-out'));
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 C_SKY_BASE: 10C_SKY_BASE + let band: numberband, const bandBase: Color32bandBase.Color32.multiply(other: Color32): Color32
Multiplies this color by another color component-wise. Useful for tinting and color modulation.
@paramother - Color to multiply with.@returnsNew color with each channel multiplied and normalized.
multiply
(const ambient: Color32ambient));
} // Ground 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 C_GRASS: 30C_GRASS, this.Demo.grassBaseColor: Color32grassBaseColor.Color32.multiply(other: Color32): Color32
Multiplies this color by another color component-wise. Useful for tinting and color modulation.
@paramother - Color to multiply with.@returnsNew color with each channel multiplied and normalized.
multiply
(const ambient: Color32ambient));
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 C_DIRTLINE: 31C_DIRTLINE, this.Demo.dirtBaseColor: Color32dirtBaseColor.Color32.multiply(other: Color32): Color32
Multiplies this color by another color component-wise. Useful for tinting and color modulation.
@paramother - Color to multiply with.@returnsNew color with each channel multiplied and normalized.
multiply
(const ambient: Color32ambient));
// Buildings for (let let i: numberi = 0; let i: numberi < this.Demo.buildings: {}buildings.length; 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 C_BUILDING_BASE: 32C_BUILDING_BASE + let i: numberi * 2, this.Demo.buildingFills: {}buildingFills[let i: numberi].multiply(const ambient: Color32ambient));
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 C_BUILDING_BASE: 32C_BUILDING_BASE + let i: numberi * 2 + 1, this.Demo.buildingOutlines: {}buildingOutlines[let i: numberi].multiply(const ambient: Color32ambient));
} // Clouds 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 C_CLOUD: 40C_CLOUD, this.Demo.cloudBaseColor: Color32cloudBaseColor.Color32.multiply(other: Color32): Color32
Multiplies this color by another color component-wise. Useful for tinting and color modulation.
@paramother - Color to multiply with.@returnsNew color with each channel multiplied and normalized.
multiply
(const ambient: Color32ambient));
// Engine overlay text (subtle night tint on the score / position / phase rows). // The kit legend panel keeps its fixed theme colors and does not dim at night. 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 C_HUD_TITLE: 41C_HUD_TITLE, this.Demo.hudTitleBase: Color32hudTitleBase.Color32.multiply(other: Color32): Color32
Multiplies this color by another color component-wise. Useful for tinting and color modulation.
@paramother - Color to multiply with.@returnsNew color with each channel multiplied and normalized.
multiply
(const ambient: Color32ambient));
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 C_HUD_SCORE: 42C_HUD_SCORE, this.Demo.hudScoreBase: Color32hudScoreBase.Color32.multiply(other: Color32): Color32
Multiplies this color by another color component-wise. Useful for tinting and color modulation.
@paramother - Color to multiply with.@returnsNew color with each channel multiplied and normalized.
multiply
(const ambient: Color32ambient));
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 C_HUD_POS: 43C_HUD_POS, this.Demo.hudPosBase: Color32hudPosBase.Color32.multiply(other: Color32): Color32
Multiplies this color by another color component-wise. Useful for tinting and color modulation.
@paramother - Color to multiply with.@returnsNew color with each channel multiplied and normalized.
multiply
(const ambient: Color32ambient));
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 C_HUD_PHASE: 44C_HUD_PHASE, this.Demo.hudFpsBase: Color32hudFpsBase.Color32.multiply(other: Color32): Color32
Multiplies this color by another color component-wise. Useful for tinting and color modulation.
@paramother - Color to multiply with.@returnsNew color with each channel multiplied and normalized.
multiply
(const ambient: Color32ambient));
// Hero shadow const const shadowAlpha: anyshadowAlpha = Math.floor(60 + (const ambient: Color32ambient.Color32.r: number
Red channel (0-255).
r
/ 255) * 60); // Softer at night.
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 C_HERO_SHADOW: 45C_HERO_SHADOW, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32
Creates a clamped 8-bit RGBA color.
@paramr - Red channel (0-255, defaults to 255).@paramg - Green channel (0-255, defaults to 255).@paramb - Blue channel (0-255, defaults to 255).@parama - Alpha channel (0-255, defaults to 255 = opaque).
Color32
(0, 0, 0, const shadowAlpha: anyshadowAlpha));
// Sprite ambient block // Each base stone color is multiplied by the current ambient to get the lit version. // drawSprite uses offset = spriteColorCount so it reads from this "ambient block". for (let let i: numberi = 0; let i: numberi < this.Demo.spriteColorCount: numberspriteColorCount; let i: numberi++) { const const base: anybase = this.Demo.spriteBaseColors: {}spriteBaseColors[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 SPRITE_BASE: 70SPRITE_BASE + this.Demo.spriteColorCount: numberspriteColorCount + let i: numberi, const base: anybase.multiply(const ambient: Color32ambient));
} } /** * Draws the sky gradient and clouds using a slower fake camera for parallax depth. */ Demo.renderSkyLayer(): void
Draws the sky gradient and clouds using a slower fake camera for parallax depth.
renderSkyLayer
() {
// Fake camera X is only a fraction of the real one: clouds drift slower than ground. // Uses the same render-time (interpolated) camera position as the ground layer // below, so the two never drift apart from each other frame to frame. const const paraX: anyparaX = Math.floor(this.Demo.cameraRenderPos: Vector2icameraRenderPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
* const SKY_PARALLAX: 0.22SKY_PARALLAX);
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
.cameraSet: (offset: Vector2i) => void
Sets the global camera offset applied to subsequent draw calls.
@since0.1.0@paramoffset - Camera translation in display pixels.
cameraSet
(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 paraX: anyparaX, 0));
// Each band is GROUND_Y/SKY_BANDS pixels tall. const const bandH: anybandH = Math.ceil(const GROUND_Y: 188GROUND_Y / const SKY_BANDS: 20SKY_BANDS); for (let let band: numberband = 0; let band: numberband < const SKY_BANDS: 20SKY_BANDS; let band: numberband++) { this.Demo.tempRect: Rect2itempRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2i
Sets all components of this rectangle. Modifies this rectangle directly for maximum performance. WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.
@paramx - New x position.@paramy - New y position.@paramwidth - New width.@paramheight - New height.@returnsThis rectangle for chaining.
set
(0, let band: numberband * const bandH: anybandH, const WORLD_W: 640WORLD_W, const bandH: anybandH);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawRectFill: (rect: Rect2i, paletteIndex: number) => void
Draws a filled rectangle.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRectFill
(this.Demo.tempRect: Rect2itempRect, const C_SKY_BASE: 10C_SKY_BASE + let band: numberband);
} // Clouds: two overlapping rectangles for a puffy look. for (const const c: anyc of this.Demo.clouds: {}clouds) { this.Demo.tempRect: Rect2itempRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2i
Sets all components of this rectangle. Modifies this rectangle directly for maximum performance. WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.
@paramx - New x position.@paramy - New y position.@paramwidth - New width.@paramheight - New height.@returnsThis rectangle for chaining.
set
(const c: anyc.x, const c: anyc.y, const c: anyc.w, const c: anyc.h);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawRectFill: (rect: Rect2i, paletteIndex: number) => void
Draws a filled rectangle.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRectFill
(this.Demo.tempRect: Rect2itempRect, const C_CLOUD: 40C_CLOUD);
this.Demo.tempRect: Rect2itempRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2i
Sets all components of this rectangle. Modifies this rectangle directly for maximum performance. WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.
@paramx - New x position.@paramy - New y position.@paramwidth - New width.@paramheight - New height.@returnsThis rectangle for chaining.
set
(const c: anyc.x + 8, const c: anyc.y - 6, const c: anyc.w - 16, const c: anyc.h - 4);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawRectFill: (rect: Rect2i, paletteIndex: number) => void
Draws a filled rectangle.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRectFill
(this.Demo.tempRect: Rect2itempRect, const C_CLOUD: 40C_CLOUD);
}
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
.cameraReset: () => void
Resets the global camera offset to `(0, 0)`.
@since0.1.0
cameraReset
();
} /** * Draws the grass strip and building blocks in world space. */ Demo.renderGroundAndBuildings(): void
Draws the grass strip and building blocks in world space.
renderGroundAndBuildings
() {
// Grass. this.Demo.tempRect: Rect2itempRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2i
Sets all components of this rectangle. Modifies this rectangle directly for maximum performance. WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.
@paramx - New x position.@paramy - New y position.@paramwidth - New width.@paramheight - New height.@returnsThis rectangle for chaining.
set
(0, const GROUND_Y: 188GROUND_Y, const WORLD_W: 640WORLD_W, const WORLD_H: 240WORLD_H - const GROUND_Y: 188GROUND_Y);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawRectFill: (rect: Rect2i, paletteIndex: number) => void
Draws a filled rectangle.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRectFill
(this.Demo.tempRect: Rect2itempRect, const C_GRASS: 30C_GRASS);
// Thin dark line at the top of the grass for depth. this.Demo.tempRect: Rect2itempRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2i
Sets all components of this rectangle. Modifies this rectangle directly for maximum performance. WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.
@paramx - New x position.@paramy - New y position.@paramwidth - New width.@paramheight - New height.@returnsThis rectangle for chaining.
set
(0, const GROUND_Y: 188GROUND_Y, const WORLD_W: 640WORLD_W, 3);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawRectFill: (rect: Rect2i, paletteIndex: number) => void
Draws a filled rectangle.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRectFill
(this.Demo.tempRect: Rect2itempRect, const C_DIRTLINE: 31C_DIRTLINE);
// Sidewalk tile row: each cell is a tile ID mapped to a palette color (Tilemap demo). this.Demo.renderGroundTileStrip(): void
Draws one 16 px tall row of tiles above the grass using tile IDs from groundTileIds.
renderGroundTileStrip
();
// Buildings: checker fill (Patterns demo) plus outline frame. for (let let i: numberi = 0; let i: numberi < this.Demo.buildings: {}buildings.length; let i: numberi++) { const const b: anyb = this.Demo.buildings: {}buildings[let i: numberi]; const const fillIdx: numberfillIdx = const C_BUILDING_BASE: 32C_BUILDING_BASE + let i: numberi * 2; const const outlineIdx: numberoutlineIdx = const fillIdx: numberfillIdx + 1; this.
Demo.renderBuildingChecker(building: {
    x: number;
    y: number;
    w: number;
    h: number;
}, fillIdx: number, outlineIdx: number): void
Fills a building with alternating 4x4 blocks (checker pattern from Patterns demo).
@parambuilding@paramfillIdx palette index for "light" squares@paramoutlineIdx palette index for "dark" squares
renderBuildingChecker
(const b: anyb, const fillIdx: numberfillIdx, const outlineIdx: numberoutlineIdx);
this.Demo.tempRect: Rect2itempRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2i
Sets all components of this rectangle. Modifies this rectangle directly for maximum performance. WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.
@paramx - New x position.@paramy - New y position.@paramwidth - New width.@paramheight - New height.@returnsThis rectangle for chaining.
set
(const b: anyb.x, const b: anyb.y, const b: anyb.w, const b: anyb.h);
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
(this.Demo.tempRect: Rect2itempRect, const outlineIdx: numberoutlineIdx);
} } /** * Draws one 16 px tall row of tiles above the grass using tile IDs from groundTileIds. */ Demo.renderGroundTileStrip(): void
Draws one 16 px tall row of tiles above the grass using tile IDs from groundTileIds.
renderGroundTileStrip
() {
const const rowY: numberrowY = const GROUND_Y: 188GROUND_Y - const GROUND_TILE_SIZE: 16GROUND_TILE_SIZE; for (let let col: numbercol = 0; let col: numbercol < this.Demo.groundTileIds: {}groundTileIds.length; let col: numbercol++) { const const tileId: anytileId = this.Demo.groundTileIds: {}groundTileIds[let col: numbercol]; const const colorIndex: 30 | 31colorIndex = const tileId: anytileId === const TILE_GRASS_ID: 1TILE_GRASS_ID ? const C_GRASS: 30C_GRASS : const C_DIRTLINE: 31C_DIRTLINE; const const worldX: numberworldX = let col: numbercol * const GROUND_TILE_SIZE: 16GROUND_TILE_SIZE; this.Demo.tempRect: Rect2itempRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2i
Sets all components of this rectangle. Modifies this rectangle directly for maximum performance. WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.
@paramx - New x position.@paramy - New y position.@paramwidth - New width.@paramheight - New height.@returnsThis rectangle for chaining.
set
(const worldX: numberworldX, const rowY: numberrowY, const GROUND_TILE_SIZE: 16GROUND_TILE_SIZE, const GROUND_TILE_SIZE: 16GROUND_TILE_SIZE);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawRectFill: (rect: Rect2i, paletteIndex: number) => void
Draws a filled rectangle.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRectFill
(this.Demo.tempRect: Rect2itempRect, const colorIndex: 30 | 31colorIndex);
} } /** * Fills a building with alternating 4x4 blocks (checker pattern from Patterns demo). * * @param {{ x: number, y: number, w: number, h: number }} building * @param {number} fillIdx palette index for "light" squares * @param {number} outlineIdx palette index for "dark" squares */
Demo.renderBuildingChecker(building: {
    x: number;
    y: number;
    w: number;
    h: number;
}, fillIdx: number, outlineIdx: number): void
Fills a building with alternating 4x4 blocks (checker pattern from Patterns demo).
@parambuilding@paramfillIdx palette index for "light" squares@paramoutlineIdx palette index for "dark" squares
renderBuildingChecker
(
building: {
    x: number;
    y: number;
    w: number;
    h: number;
}
@parambuilding
building
, fillIdx: number
palette index for "light" squares
@paramfillIdx palette index for "light" squares
fillIdx
, outlineIdx: number
palette index for "dark" squares
@paramoutlineIdx palette index for "dark" squares
outlineIdx
) {
const const cell: 4cell = const BUILDING_PATTERN_CELL: 4BUILDING_PATTERN_CELL; for (let let py: numberpy =
building: {
    x: number;
    y: number;
    w: number;
    h: number;
}
@parambuilding
building
.y: numbery; let py: numberpy <
building: {
    x: number;
    y: number;
    w: number;
    h: number;
}
@parambuilding
building
.y: numbery +
building: {
    x: number;
    y: number;
    w: number;
    h: number;
}
@parambuilding
building
.h: numberh; let py: numberpy += const cell: 4cell) {
for (let let px: numberpx =
building: {
    x: number;
    y: number;
    w: number;
    h: number;
}
@parambuilding
building
.x: numberx; let px: numberpx <
building: {
    x: number;
    y: number;
    w: number;
    h: number;
}
@parambuilding
building
.x: numberx +
building: {
    x: number;
    y: number;
    w: number;
    h: number;
}
@parambuilding
building
.w: numberw; let px: numberpx += const cell: 4cell) {
const const useFill: booleanuseFill = ((let px: numberpx >> 2) + (let py: numberpy >> 2)) % 2 === 0; const const colorIndex: numbercolorIndex = const useFill: booleanuseFill ? fillIdx: number
palette index for "light" squares
@paramfillIdx palette index for "light" squares
fillIdx
: outlineIdx: number
palette index for "dark" squares
@paramoutlineIdx palette index for "dark" squares
outlineIdx
;
this.Demo.tempRect: Rect2itempRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2i
Sets all components of this rectangle. Modifies this rectangle directly for maximum performance. WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.
@paramx - New x position.@paramy - New y position.@paramwidth - New width.@paramheight - New height.@returnsThis rectangle for chaining.
set
(let px: numberpx, let py: numberpy, const cell: 4cell, const cell: 4cell);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawRectFill: (rect: Rect2i, paletteIndex: number) => void
Draws a filled rectangle.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRectFill
(this.Demo.tempRect: Rect2itempRect, const colorIndex: numbercolorIndex);
} } } /** * Moves the rock left/right automatically, bouncing off world edges. */ Demo.updateHeroMovement(): void
Moves the rock left/right automatically, bouncing off world edges.
updateHeroMovement
() {
// Remember where the rock was before this tick moves it, so render() can // draw a smooth in-between position instead of a pop. this.Demo.heroPrevPos: Vector2iheroPrevPos.Vector2i.set(x: number, y: number): Vector2i
Sets both components of this vector. Modifies this vector directly for maximum performance. WARNING: Mutates this vector. Don't use on frozen/cached singletons.
@paramx - New x component.@paramy - New y component.@returnsThis vector for chaining.
set
(this.Demo.heroPos: Vector2iheroPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
, this.Demo.heroPos: Vector2iheroPos.Vector2i.y: number
Vertical component (defaults to 0).
y
);
let let nextX: numbernextX = this.Demo.heroPos: Vector2iheroPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
+ const HERO_SPEED: 1HERO_SPEED * this.Demo.heroFacing: numberheroFacing;
const const margin: 2margin = 2; if (let nextX: numbernextX <= const margin: 2margin) { let nextX: numbernextX = const margin: 2margin; this.Demo.heroFacing: numberheroFacing = 1; } else if (let nextX: numbernextX + this.Demo.heroSize: Vector2i
@type{Vector2i}
heroSize
.Vector2i.x: number
Horizontal component (defaults to 0).
x
>= const WORLD_W: 640WORLD_W - const margin: 2margin) {
let nextX: numbernextX = const WORLD_W: 640WORLD_W - this.Demo.heroSize: Vector2i
@type{Vector2i}
heroSize
.Vector2i.x: number
Horizontal component (defaults to 0).
x
- const margin: 2margin;
this.Demo.heroFacing: numberheroFacing = -1; } this.Demo.heroPos: Vector2iheroPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
= let nextX: numbernextX;
} /** * Advances a step counter every WALK_FRAME_TICKS ticks. * Used to add a subtle bob to the rock as it moves. * * @param {number} tick - Current tick. */ Demo.updateWalkStep(tick: number): void
Advances a step counter every WALK_FRAME_TICKS ticks. Used to add a subtle bob to the rock as it moves.
@paramtick - Current tick.
updateWalkStep
(tick: number
- Current tick.
@paramtick - Current tick.
tick
) {
if (this.Demo.walkFrameTimer: TimerwalkFrameTimer.Timer.fireIfElapsed(currentTick?: number): boolean
Returns true once per interval and advances the internal last-fired tick.
@paramcurrentTick - Tick to evaluate against; defaults to engine tick counter.@returnsTrue when at least `intervalTicks` have elapsed since the last fire/reset.
fireIfElapsed
(tick: number
- Current tick.
@paramtick - Current tick.
tick
)) {
this.Demo.walkStep: numberwalkStep = (this.Demo.walkStep: numberwalkStep + 1) % 4; } } /** * Draws the rock sprite with a shadow underfoot. * The sprite is drawn with paletteOffset = spriteColorCount, which shifts each pixel * index into the "ambient block" (SPRITE_BASE+N..SPRITE_BASE+2N-1). * Those slots are updated every tick in updateWorldPalette() to reflect the current * ambient light, so the rock automatically dims at night. */ Demo.renderHero(): void
Draws the rock sprite with a shadow underfoot. The sprite is drawn with paletteOffset = spriteColorCount, which shifts each pixel index into the "ambient block" (SPRITE_BASE+N..SPRITE_BASE+2N-1). Those slots are updated every tick in updateWorldPalette() to reflect the current ambient light, so the rock automatically dims at night.
renderHero
() {
if (!this.Demo.heroSheet: SpriteSheet | null
@type{SpriteSheet | null}
heroSheet
|| !this.Demo.heroSprite: Rect2i | null
@type{Rect2i | null}
heroSprite
) {
return; } // Blend heroPrevPos toward heroPos by BT.renderAlpha so the rock's drawn // position matches this exact render moment instead of only its last-tick // position (same idea as the camera blend in render() above). this.Demo.heroRenderPos: Vector2iheroRenderPos.Vector2i.set(x: number, y: number): Vector2i
Sets both components of this vector. Modifies this vector directly for maximum performance. WARNING: Mutates this vector. Don't use on frozen/cached singletons.
@paramx - New x component.@paramy - New y component.@returnsThis vector for chaining.
set
(
Math.round(this.Demo.heroPrevPos: Vector2iheroPrevPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
+ (this.Demo.heroPos: Vector2iheroPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
- this.Demo.heroPrevPos: Vector2iheroPrevPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
) *
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.renderAlpha: number
Fractional progress between the last completed fixed update and the next. Intended for interpolating render state between fixed-update steps.
@since1.3.0@returnsInterpolation alpha in `[0, 1)`.
renderAlpha
),
Math.round(this.Demo.heroPrevPos: Vector2iheroPrevPos.Vector2i.y: number
Vertical component (defaults to 0).
y
+ (this.Demo.heroPos: Vector2iheroPos.Vector2i.y: number
Vertical component (defaults to 0).
y
- this.Demo.heroPrevPos: Vector2iheroPrevPos.Vector2i.y: number
Vertical component (defaults to 0).
y
) *
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.renderAlpha: number
Fractional progress between the last completed fixed update and the next. Intended for interpolating render state between fixed-update steps.
@since1.3.0@returnsInterpolation alpha in `[0, 1)`.
renderAlpha
),
); // Tiny vertical bob based on walkStep (steps 0,2 are up; 1,3 are at rest). const const bob: 0 | -1bob = this.Demo.walkStep: numberwalkStep % 2 === 0 ? -1 : 0; this.Demo.tempVec: Vector2itempVec.Vector2i.set(x: number, y: number): Vector2i
Sets both components of this vector. Modifies this vector directly for maximum performance. WARNING: Mutates this vector. Don't use on frozen/cached singletons.
@paramx - New x component.@paramy - New y component.@returnsThis vector for chaining.
set
(this.Demo.heroRenderPos: Vector2iheroRenderPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
, this.Demo.heroRenderPos: Vector2iheroRenderPos.Vector2i.y: number
Vertical component (defaults to 0).
y
+ const bob: 0 | -1bob);
// The ambient offset shifts all pixel indices into the pre-lit 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.heroSheet: SpriteSheet
@type{SpriteSheet | null}
heroSheet
, this.Demo.heroSprite: Rect2i
@type{Rect2i | null}
heroSprite
, this.Demo.tempVec: Vector2itempVec, this.Demo.spriteColorCount: numberspriteColorCount);
// Shadow underfoot. const const shadowY: numbershadowY = this.Demo.heroRenderPos: Vector2iheroRenderPos.Vector2i.y: number
Vertical component (defaults to 0).
y
+ this.Demo.heroSprite: Rect2i
@type{Rect2i | null}
heroSprite
.Rect2i.height: number
Height in pixels (defaults to 0).
height
- 2;
this.Demo.tempRect: Rect2itempRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2i
Sets all components of this rectangle. Modifies this rectangle directly for maximum performance. WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.
@paramx - New x position.@paramy - New y position.@paramwidth - New width.@paramheight - New height.@returnsThis rectangle for chaining.
set
(this.Demo.heroRenderPos: Vector2iheroRenderPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
+ 2, const shadowY: numbershadowY, this.Demo.heroSprite: Rect2i
@type{Rect2i | null}
heroSprite
.Rect2i.width: number
Width in pixels (defaults to 0).
width
- 4, 3);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawRectFill: (rect: Rect2i, paletteIndex: number) => void
Draws a filled rectangle.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRectFill
(this.Demo.tempRect: Rect2itempRect, const C_HERO_SHADOW: 45C_HERO_SHADOW);
} /** * Smoothly follows the hero, then clamps so the view never leaves the world. */ Demo.updateCameraFollow(): void
Smoothly follows the hero, then clamps so the view never leaves the world.
updateCameraFollow
() {
// Remember where the camera was before this tick's follow-lerp moves it. this.Demo.cameraPrevPos: Vector2icameraPrevPos.Vector2i.set(x: number, y: number): Vector2i
Sets both components of this vector. Modifies this vector directly for maximum performance. WARNING: Mutates this vector. Don't use on frozen/cached singletons.
@paramx - New x component.@paramy - New y component.@returnsThis vector for chaining.
set
(this.Demo.cameraPos: Vector2icameraPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
, this.Demo.cameraPos: Vector2icameraPos.Vector2i.y: number
Vertical component (defaults to 0).
y
);
const const targetCamX: numbertargetCamX = this.Demo.heroPos: Vector2iheroPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
- const DISPLAY_W: 320DISPLAY_W / 2 + this.Demo.heroSize: Vector2i
@type{Vector2i}
heroSize
.Vector2i.x: number
Horizontal component (defaults to 0).
x
/ 2;
this.Demo.cameraXFloat: numbercameraXFloat += (const targetCamX: numbertargetCamX - this.Demo.cameraXFloat: numbercameraXFloat) * const CAMERA_LERP: 0.14CAMERA_LERP; this.Demo.cameraPos: Vector2icameraPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
= Math.floor(this.Demo.cameraXFloat: numbercameraXFloat);
this.Demo.clampCamera(): void
Keeps cameraPos.x between 0 and WORLD_W - DISPLAY_W.
clampCamera
();
} /** * Keeps cameraPos.x between 0 and WORLD_W - DISPLAY_W. */ Demo.clampCamera(): void
Keeps cameraPos.x between 0 and WORLD_W - DISPLAY_W.
clampCamera
() {
const const clamped: Vector2iclamped =
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
.cameraClamp: (camera: Vector2i, worldSize: Vector2i, viewSize?: Vector2i) => Vector2i
Clamps a camera origin so the viewport stays within world bounds. Uses integer clamping per axis: `[0, worldSize - viewSize]`. If `viewSize` is omitted, the active {@link BT.displaySize } is used.
@since1.0.3@paramcamera - Desired camera origin in world coordinates.@paramworldSize - Full world size in pixels.@paramviewSize - Viewport size in pixels (defaults to {@link BT.displaySize}).@returnsClamped camera origin.
cameraClamp
(this.Demo.cameraPos: Vector2icameraPos, this.Demo.worldSize: Vector2iworldSize,
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
.displaySize: Vector2i
Active logical render resolution in pixels. This is the game/simulation coordinate space configured by the demo, not the canvas element's CSS size. Each read returns a clone.
@since1.0.4@returnsConfigured logical size, or `Vector2i.zero()` before initialization.
displaySize
);
this.Demo.cameraPos: Vector2icameraPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
= const clamped: Vector2iclamped.Vector2i.x: number
Horizontal component (defaults to 0).
x
;
this.Demo.cameraPos: Vector2icameraPos.Vector2i.y: number
Vertical component (defaults to 0).
y
= const clamped: Vector2iclamped.Vector2i.y: number
Vertical component (defaults to 0).
y
;
this.Demo.cameraXFloat: numbercameraXFloat = this.Demo.cameraPos: Vector2icameraPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
;
} /** * +1 score every SCORE_INTERVAL_TICKS. * * @param {number} tick - Current tick. */ Demo.updateScore(tick: number): void
+1 score every SCORE_INTERVAL_TICKS.
@paramtick - Current tick.
updateScore
(tick: number
- Current tick.
@paramtick - Current tick.
tick
) {
if (this.Demo.scoreTimer: TimerscoreTimer.Timer.fireIfElapsed(currentTick?: number): boolean
Returns true once per interval and advances the internal last-fired tick.
@paramcurrentTick - Tick to evaluate against; defaults to engine tick counter.@returnsTrue when at least `intervalTicks` have elapsed since the last fire/reset.
fireIfElapsed
(tick: number
- Current tick.
@paramtick - Current tick.
tick
)) {
this.Demo.score: numberscore += 1; } } /** * Spawns a handful of sparkles near the rock on a fixed schedule. * * @param {number} tick - Current tick. */ Demo.updateParticlesSpawn(tick: number): void
Spawns a handful of sparkles near the rock on a fixed schedule.
@paramtick - Current tick.
updateParticlesSpawn
(tick: number
- Current tick.
@paramtick - Current tick.
tick
) {
if (this.Demo.particleSpawnTimer: TimerparticleSpawnTimer.Timer.fireIfElapsed(currentTick?: number): boolean
Returns true once per interval and advances the internal last-fired tick.
@paramcurrentTick - Tick to evaluate against; defaults to engine tick counter.@returnsTrue when at least `intervalTicks` have elapsed since the last fire/reset.
fireIfElapsed
(tick: number
- Current tick.
@paramtick - Current tick.
tick
)) {
for (let let i: numberi = 0; let i: numberi < 3; let i: numberi++) { const const slot: numberslot = const PARTICLE_SLOT_START: 50PARTICLE_SLOT_START + (this.Demo.nextParticleSlot: numbernextParticleSlot % const MAX_PARTICLES: 20MAX_PARTICLES); this.Demo.nextParticleSlot: numbernextParticleSlot++; // Scatter each sparkle around the rock. // BT.random is the engine's shared random number generator, and int() returns a whole number starting // at the first value and stopping just before the second - so int(-10, 10) gives anything from -10 to // 9, and 10 itself never comes up. // Left and right come up about equally often. The y range is lopsided on purpose: most of those // offsets are negative, which on screen means upward, so the sparkles gather above the rock. const const ox: numberox =
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
(-10, 10);
const const oy: numberoy =
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
(-12, 4);
this.Demo.particles: {}particles.push({ // Spawn each sparkle near the center of the rock (half its real // width and height in from the top-left corner), plus the random // offset picked above. pos: Vector2ipos: 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
(
this.Demo.heroPos: Vector2iheroPos.Vector2i.x: number
Horizontal component (defaults to 0).
x
+ this.Demo.heroSize: Vector2i
@type{Vector2i}
heroSize
.Vector2i.x: number
Horizontal component (defaults to 0).
x
/ 2 + const ox: numberox,
this.Demo.heroPos: Vector2iheroPos.Vector2i.y: number
Vertical component (defaults to 0).
y
+ this.Demo.heroSize: Vector2i
@type{Vector2i}
heroSize
.Vector2i.y: number
Vertical component (defaults to 0).
y
/ 2 + const oy: numberoy,
), spawnTick: numberspawnTick: tick: number
- Current tick.
@paramtick - Current tick.
tick
,
paletteSlot: numberpaletteSlot: const slot: numberslot, }); } } } /** * Removes particles older than 72 ticks (~1.2 seconds). * * @param {number} tick - Current tick. */ Demo.cleanupParticles(tick: number): void
Removes particles older than 72 ticks (~1.2 seconds).
@paramtick - Current tick.
cleanupParticles
(tick: number
- Current tick.
@paramtick - Current tick.
tick
) {
this.Demo.particles: {}particles = this.Demo.particles: {}particles.filter((p: anyp) => tick: number
- Current tick.
@paramtick - Current tick.
tick
- p: anyp.spawnTick < 72);
} /** * Updates each particle's palette slot with its current color. * Hue comes from spawnTick; alpha fades out as the particle ages. * The ambient tint is also applied so sparkles dim at night. * * @param {number} tick - Current tick. */ Demo.updateParticleColors(tick: number): void
Updates each particle's palette slot with its current color. Hue comes from spawnTick; alpha fades out as the particle ages. The ambient tint is also applied so sparkles dim at night.
@paramtick - Current tick.
updateParticleColors
(tick: number
- Current tick.
@paramtick - Current tick.
tick
) {
const const ambient: Color32ambient = this.Demo.getAmbientTint(): Color32
Computes the current ambient tint based on the day/night cycle. Returns a Color32 that represents the current "color of the light". Bright white = midday; dark blue = midnight. This is called in update() to drive the palette, not in render().
@returnsThe ambient light color.
getAmbientTint
();
for (const const p: anyp of this.Demo.particles: {}particles) { const const age: numberage = tick: number
- Current tick.
@paramtick - Current tick.
tick
- const p: anyp.spawnTick;
const const fade: numberfade = 1 - const age: numberage / 72; const const alpha: anyalpha = Math.floor(200 * const fade: numberfade); const const hue: numberhue = (const p: anyp.spawnTick * 7 + const age: numberage * 4) % 360; const const base: Color32base = 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, 90, 65);
const const lit: Color32lit = const base: Color32base.Color32.multiply(other: Color32): Color32
Multiplies this color by another color component-wise. Useful for tinting and color modulation.
@paramother - Color to multiply with.@returnsNew color with each channel multiplied and normalized.
multiply
(const ambient: Color32ambient);
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 p: anyp.paletteSlot, 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 lit: Color32lit.Color32.r: number
Red channel (0-255).
r
, const lit: Color32lit.Color32.g: number
Green channel (0-255).
g
, const lit: Color32lit.Color32.b: number
Blue channel (0-255).
b
, const alpha: anyalpha));
} } /** * Draws active particles as 3x3 colored squares. * Colors were already computed in update() - render() just reads the slot. */ Demo.renderParticles(): void
Draws active particles as 3x3 colored squares. Colors were already computed in update() - render() just reads the slot.
renderParticles
() {
for (const const p: anyp of this.Demo.particles: {}particles) { this.Demo.tempRect: Rect2itempRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2i
Sets all components of this rectangle. Modifies this rectangle directly for maximum performance. WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.
@paramx - New x position.@paramy - New y position.@paramwidth - New width.@paramheight - New height.@returnsThis rectangle for chaining.
set
(const p: anyp.pos.x - 1, const p: anyp.pos.y - 1, 3, 3);
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.drawRectFill: (rect: Rect2i, paletteIndex: number) => void
Draws a filled rectangle.
@since0.1.0@paramrect - Rectangle bounds in display coordinates.@parampaletteIndex - Palette color index.
drawRectFill
(this.Demo.tempRect: Rect2itempRect, const p: anyp.paletteSlot);
} } /** * Short legend for first-time viewers (screen space, top-left), built with the shared * UI kit. The Save button also listens for the Space key, so keyboard, mouse, and * touch all trigger the same PNG capture (Image Output demo). */ Demo.renderLegend(): void
Short legend for first-time viewers (screen space, top-left), built with the shared UI kit. The Save button also listens for the Space key, so keyboard, mouse, and touch all trigger the same PNG capture (Image Output demo).
renderLegend
() {
// Anchor the group to the top-left corner; the kit sizes the panel to fit its rows. import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT); // panel() gives the group a background, border, and an amber title line. import uiui.panel('Capstone: scroll, tiles, sprite, day/night'); // A dim (secondary) line pointing back at the demos these visuals come from. import uiui.label('Tiles + checker = tilemap + patterns', { color: stringcolor: 'dim' }); // The Save button returns true only on the frame it is clicked, tapped, or its // bound key (Space) is pressed. The `capturing` flag stops a second capture from // starting while the browser is still writing the first PNG. if (import uiui.button('Save PNG (Space)', { key: stringkey: 'Space' }) && !this.Demo.capturing: booleancapturing) { this.Demo.startCapture(): void
Starts one PNG download (Image Output demo). BT.downloadFrame() is asynchronous - it hands the browser a file and resolves later - so we flip `capturing` on now and set the result message (shown for ~3 seconds via messageTimer) when the promise settles.
startCapture
();
} // Progress / result line for the capture, in the kit's green accent color. if (this.Demo.capturing: booleancapturing) { import uiui.label('Capturing...', { color: stringcolor: 'accent' }); } else if (this.Demo.messageTimer: numbermessageTimer > 0) { import uiui.label(this.Demo.lastCaptureMessage: stringlastCaptureMessage, { color: stringcolor: 'accent' }); } // Browsers keep all sound muted until the player clicks or presses a key. // This kit row shows the standard warm "enable sound" hint only while the // audio is still locked, then disappears on its own. import uiui.audioUnlockHint(); import uiui.end(); } /** * Starts one PNG download (Image Output demo). BT.downloadFrame() is asynchronous - it hands * the browser a file and resolves later - so we flip `capturing` on now and set the * result message (shown for ~3 seconds via messageTimer) when the promise settles. */ Demo.startCapture(): void
Starts one PNG download (Image Output demo). BT.downloadFrame() is asynchronous - it hands the browser a file and resolves later - so we flip `capturing` on now and set the result message (shown for ~3 seconds via messageTimer) when the promise settles.
startCapture
() {
this.Demo.capturing: booleancapturing = true;
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
.downloadFrame: (filename?: string) => Promise<void>
Captures the next rendered frame and downloads it from the browser. Convenience wrapper around {@link BT.captureFrame } that creates a temporary object URL and clicks a synthetic anchor element.
@since1.0.3@paramfilename - Target download filename.@exampleawait BT.downloadFrame(); await BT.downloadFrame('screenshot-001.png');
downloadFrame
('blit386-scene.png')
.then(() => { this.Demo.lastCaptureMessage: stringlastCaptureMessage = 'Saved: blit386-scene.png'; this.Demo.messageTimer: numbermessageTimer = 180; this.Demo.capturing: booleancapturing = false;
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.soundPlay: (clip: AudioClip, options?: SoundPlayOptions) => SoundRef
Plays a loaded audio clip through the SFX voice pool. Returns an inert {@link SoundRef } without allocating a voice when the clip hasn't finished loading yet (or was already unloaded), when the pool has no free or stealable voice at this priority, or before the engine has unlocked audio playback.
@since1.3.0@paramclip - Loaded audio clip to play.@paramoptions - Playback options.@returnsA handle identifying the new voice; pass it to {@link BT.soundStop} and the other per-sound controls. Safe to use even when playback was silently dropped - every accessor on an inert handle is a no-op.
soundPlay
(this.Demo.captureBlipClip: AudioClip | null
@type{AudioClip | null} Confirmation blip played after a successful PNG capture.
captureBlipClip
);
return null; }) .catch((err: anyerr) => { this.Demo.lastCaptureMessage: stringlastCaptureMessage = `Error: ${err: anyerr.message}`; this.Demo.messageTimer: numbermessageTimer = 180; this.Demo.capturing: booleancapturing = false; console.error('[GameSceneDemo] Capture failed:', err: anyerr); }); } /** * Human-readable label for where we are in the day/night cycle. * * @param {number} [tick] - Tick to evaluate (defaults to {@link BT.ticks}). * @returns {string} */ Demo.getDayPhaseLabel(tick?: number): string
Human-readable label for where we are in the day/night cycle.
@paramtick - Tick to evaluate (defaults to {@link BT.ticks}).@returns
getDayPhaseLabel
(tick: number | undefined
- Tick to evaluate (defaults to {@link BT.ticks } ).
@paramtick - Tick to evaluate (defaults to {@link BT.ticks}).
tick
=
const BT: {
    FLIP_H: number;
    FLIP_V: number;
    ROT_90_CW: number;
    ROT_180_CW: number;
    ROT_270_CW: number;
    BTN_UP: number;
    BTN_DOWN: number;
    BTN_LEFT: number;
    BTN_RIGHT: number;
    BTN_A: number;
    BTN_B: number;
    BTN_X: number;
    BTN_Y: number;
    BTN_L: number;
    BTN_R: number;
    BTN_START: number;
    BTN_SELECT: number;
    BTN_POINTER_A: number;
    BTN_POINTER_B: number;
    BTN_POINTER_C: number;
    BTN_POINTER_D: number;
    PLAYER_ONE: number;
    PLAYER_TWO: number;
    PLAYER_THREE: number;
    PLAYER_FOUR: number;
    AXIS_LEFT_X: number;
    AXIS_LEFT_Y: number;
    AXIS_RIGHT_X: number;
    AXIS_RIGHT_Y: number;
    AXIS_TRIGGER_L: number;
    ... 106 more ...;
    spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.
BT
.ticks: number
Current fixed-update tick counter. Increments once per engine update. Reset via {@link BT.ticksReset } .
@since1.0.4@returnsCurrent tick count since initialization or last reset.
ticks
) {
const const phaseTick: numberphaseTick = tick: number
- Tick to evaluate (defaults to {@link BT.ticks } ).
@paramtick - Tick to evaluate (defaults to {@link BT.ticks}).
tick
% const DAY_NIGHT_CYCLE_TICKS: 1200DAY_NIGHT_CYCLE_TICKS;
if (const phaseTick: numberphaseTick < const DAY_NIGHT_CYCLE_TICKS: 1200DAY_NIGHT_CYCLE_TICKS * 0.25) { return 'Day'; } if (const phaseTick: numberphaseTick < const DAY_NIGHT_CYCLE_TICKS: 1200DAY_NIGHT_CYCLE_TICKS * 0.5) { return 'Toward dusk'; } if (const phaseTick: numberphaseTick < const DAY_NIGHT_CYCLE_TICKS: 1200DAY_NIGHT_CYCLE_TICKS * 0.75) { return 'Night'; } return 'Toward dawn'; } } 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
One self-running mini scene: walking rock, following camera, HUD, day/night, sparkles. All color computation happens in update(); render() uses only palette indices.
@implementsIBTDemo
Demo
);