// Patterns: animated mathematical art using only primitive drawing.
// @description Animated mathematical art from primitives alone: spirals, Lissajous curves, waves, and a tunnel.
//
// Prerequisites: We learned about drawing and the game loop in Basics demo
// (https://demos.blit386.dev/basics), shapes in Primitives demo
// (https://demos.blit386.dev/primitives), and color in Colors demo
// (https://demos.blit386.dev/colors).
//
// We also use the palette system introduced in Colors demo
// (https://demos.blit386.dev/colors). You will see much more of
// palettes later, in palette-presets demo and beyond.
//
// Live version: https://demos.blit386.dev/patterns
// Guide: https://blit386.dev/docs/api/rendering#primitives
//
// All six patterns here are drawn using just pixels, lines, and rectangles
// no images needed. Each pattern is based on simple math (angles, waves, circles)
// that creates surprisingly complex-looking results.
//
// The six patterns arranged in a 2x3 grid are:
// Spiral - dots expanding outward in a spinning coil
// Radial - lines radiating from a center point like sun rays
// Wave - overlapping wave curves that interfere with each other
// Circle - a circle drawn from many tiny line segments
// Lissajous - a smooth looping curve used in physics and electronics
// Tunnel - concentric rectangles that spin to look like a tunnel
//
// HOW COLORS WORK IN THIS DEMO:
//
// Every color must be registered in a "palette" before drawing. Think of it
// like choosing all your paint colors before starting a painting - you pick
// them out first, then use them by number ("color 5", "color 12", etc.).
//
// Some colors never change (white, background, wave colors) - those are set
// once during setup. Other colors animate (spiral, Lissajous, tunnel) - those
// are recalculated every tick in update() and stored back in the palette.
// The render() function only ever uses color numbers (indices), never Color32 objects.
//
// The six pattern captions ("Spiral", "Radial", ...) are drawn with ui.caption() from the
// shared UI kit (src/shared/ui.js), in the same amber header color every other demo in the
// series uses. The patterns themselves are the lesson and are still drawn by hand below.
import { function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>One-liner bootstrap function for BLIT386 demos.
Handles canvas retrieval and engine initialization. Backend selection
(WebGPU or software fallback) is managed internally by BTAPI.
This function provides a streamlined way to start a demo with sensible defaults
while allowing customization through options.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 Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32, class Rect2iInteger 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.Rect2i, class Vector2iInteger 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.Vector2i } from 'blit386';
import { import applyThemeapplyTheme, import uiui } from './shared/ui.js';
/** @typedef {import('blit386').IBTDemo} IBTDemo */
/** @typedef {import('blit386').HardwareSettings} HardwareSettings */
/** @typedef {import('blit386').Palette} Palette */
//
// These numbers are the "addresses" in the palette table.
// Index 0 is always reserved for transparent - we never use it.
// We give each color a readable name so the code is easier to follow.
// Static colors (set once in init, never change).
const const C_WHITE: 1C_WHITE = 1; // Pure white - overlay bar style color (see configure()).
const const C_BG: 2C_BG = 2; // Very dark blue-black background.
const const C_TAG: 3C_TAG = 3; // Dim white - only feeds the overlay timing-chart tag color (see configure()).
const const C_WAVE_1: 5C_WAVE_1 = 5; // Blue - primary sine wave.
const const C_WAVE_2: 6C_WAVE_2 = 6; // Orange - secondary cosine wave.
const const C_WAVE_3: 7C_WAVE_3 = 7; // Green - interference (both waves combined).
// Circle segments: 32 entries at indices 8..39.
// Each segment gets a different hue. Hue never changes so this is static.
const const C_CIRCLE_BASE: 8C_CIRCLE_BASE = 8;
const const CIRCLE_SEGMENTS: 32CIRCLE_SEGMENTS = 32;
// Radial rays: 12 entries at indices 40..51.
// Each ray has a fixed hue. Static.
const const C_RADIAL_BASE: 40C_RADIAL_BASE = 40;
const const RADIAL_LINES: 12RADIAL_LINES = 12;
// Dynamic colors (updated every tick in update()).
// Spiral dots: 100 entries at indices 60..159.
// The hue of each dot shifts forward as animTime grows, making colors scroll.
const const C_SPIRAL_BASE: 60C_SPIRAL_BASE = 60;
const const SPIRAL_POINTS: 100SPIRAL_POINTS = 100;
// Lissajous curve: 32 color bands at indices 160..191.
// The 200-point curve is divided into 32 color groups that rotate over time.
const const C_LISSAJOUS_BASE: 160C_LISSAJOUS_BASE = 160;
const const LISSAJOUS_BANDS: 32LISSAJOUS_BANDS = 32;
// Tunnel rectangles: 20 entries at indices 192..211.
// Outer rectangles are brighter and more saturated; inner ones are darker.
const const C_TUNNEL_BASE: 192C_TUNNEL_BASE = 192;
const const TUNNEL_RECTS: 20TUNNEL_RECTS = 20;
// Engine overlay: measured FPS, target FPS, and demo name (enabled by default).
/**
* Demonstrates animated mathematical patterns using primitive drawing.
* Each section shows a different algorithmic visual effect arranged in a 2x3 grid.
*
* @implements {IBTDemo}
*/
class class DemoDemonstrates animated mathematical patterns using primitive drawing.
Each section shows a different algorithmic visual effect arranged in a 2x3 grid.Demo {
// animTime counts up in seconds. We use it to make patterns move.
Demo.animTime: numberanimTime = 0;
// The palette holds all the colors we are allowed to use.
// Think of it as a box of 256 numbered paint colors.
/** @type {Palette | null} */
Demo.palette: Palette | nullpalette = null;
// Where the shared UI theme colors landed in the palette, filled by applyTheme() in
// init(). The UI kit draws the six pattern captions with these slots.
Demo.theme: nulltheme = null;
// These Vector2i and Rect2i objects are created once and reused every frame.
// Creating new objects inside a loop every frame can slow things down because
// the browser has to clean up old objects. Reusing them avoids that.
Demo.tempVec1: Vector2itempVec1 = new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(0, 0);
Demo.tempVec2: Vector2itempVec2 = new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(0, 0);
Demo.tempRect: Rect2itempRect = new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2iCreates an integer rectangle, truncating all inputs toward zero.Rect2i(0, 0, 0, 0);
/**
* Optional engine settings. We keep the default 320x240 screen and show the full
* 256-slot palette in the overlay grid (default column count). Rich diagnostics and
* the renderer diagnostics bar are enabled so GPU pipeline pressure is visible.
*
* @returns {Partial<HardwareSettings>}
*/
Demo.configure(): Partial<HardwareSettings>Optional engine settings. We keep the default 320x240 screen and show the full
256-slot palette in the overlay grid (default column count). Rich diagnostics and
the renderer diagnostics bar are enabled so GPU pipeline pressure is visible.configure() {
return {
isOverlayPaletteEnabled: booleanisOverlayPaletteEnabled: true,
isOverlayVisibleAtStart: booleanisOverlayVisibleAtStart: true,
overlayStyle: {
barPaletteIndex: number;
textPaletteIndex: number;
gapPaletteIndex: number;
}
overlayStyle: {
barPaletteIndex: numberbarPaletteIndex: const C_WHITE: 1C_WHITE,
textPaletteIndex: numbertextPaletteIndex: const C_BG: 2C_BG,
gapPaletteIndex: numbergapPaletteIndex: const C_BG: 2C_BG,
},
isOverlayTimingChartEnabled: booleanisOverlayTimingChartEnabled: true,
overlayTimingChartDiagnostics: stringoverlayTimingChartDiagnostics: 'rich',
isOverlayRendererDiagnosticsBarEnabled: booleanisOverlayRendererDiagnosticsBarEnabled: true,
overlayTimingChartStyle: {
updateBarPaletteIndex: number;
renderBarPaletteIndex: number;
warningPaletteIndex: number;
errorPaletteIndex: number;
tagPaletteIndex: number;
}
overlayTimingChartStyle: {
updateBarPaletteIndex: numberupdateBarPaletteIndex: const C_WAVE_3: 7C_WAVE_3,
renderBarPaletteIndex: numberrenderBarPaletteIndex: const C_WAVE_1: 5C_WAVE_1,
warningPaletteIndex: numberwarningPaletteIndex: const C_WAVE_2: 6C_WAVE_2,
errorPaletteIndex: numbererrorPaletteIndex: const C_WAVE_2: 6C_WAVE_2,
tagPaletteIndex: numbertagPaletteIndex: const C_TAG: 3C_TAG,
},
};
}
/**
* Runs once when the demo starts. Sets up the palette.
*
* IMPORTANT ORDER:
* 1. Create palette - make the 256-slot color table.
* 2. Fill in static colors - the ones that never change.
* 3. BT.paletteSet() - tell the engine to use this palette.
*
* @returns {Promise<boolean>} Returns true when ready to run.
*/
async Demo.init(): Promise<boolean>Runs once when the demo starts. Sets up the palette.
IMPORTANT ORDER:
1. Create palette - make the 256-slot color table.
2. Fill in static colors - the ones that never change.
3. BT.paletteSet() - tell the engine to use this palette.init() {
// Step 1: Create the palette
// BT.paletteCreate(256) makes a color table with 256 numbered slots.
// Slot 0 is always transparent and cannot be changed.
this.Demo.palette: Palette | nullpalette = 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) => PaletteCreates a standalone palette instance.paletteCreate(256);
// Step 2: Register static colors
// Basic UI colors.
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_WHITE: 1C_WHITE, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(255, 255, 255)); // White for title text.
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_BG: 2C_BG, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(15, 15, 25)); // Very dark blue-black background.
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_TAG: 3C_TAG, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(200, 200, 200)); // Dim white for the timing-chart tag.
// Wave pattern: three fixed colors.
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_WAVE_1: 5C_WAVE_1, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(100, 200, 255)); // Blue wave.
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_WAVE_2: 6C_WAVE_2, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(255, 150, 100)); // Orange wave.
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_WAVE_3: 7C_WAVE_3, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(150, 255, 150)); // Green interference wave.
// Circle segments: 32 colors spread across the rainbow.
// Because these hues do not depend on animTime, we set them here once.
for (let let i: numberi = 0; let i: numberi < const CIRCLE_SEGMENTS: 32CIRCLE_SEGMENTS; let i: numberi++) {
// Spread 32 evenly-spaced hues across the 0-360 degree color wheel.
const const hue: numberhue = (let i: numberi / const CIRCLE_SEGMENTS: 32CIRCLE_SEGMENTS) * 360;
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_CIRCLE_BASE: 8C_CIRCLE_BASE + let i: numberi, class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.fromHSL(h: number, s: number, l: number, a?: number): Color32Creates a color from HSL values.fromHSL(const hue: numberhue, 100, 50));
}
// Radial rays: 12 colors, one per ray.
for (let let i: numberi = 0; let i: numberi < const RADIAL_LINES: 12RADIAL_LINES; let i: numberi++) {
// Slightly desaturated and brightened compared to the circle colors.
const const hue: numberhue = (let i: numberi / const RADIAL_LINES: 12RADIAL_LINES) * 360;
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_RADIAL_BASE: 40C_RADIAL_BASE + let i: numberi, class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.fromHSL(h: number, s: number, l: number, a?: number): Color32Creates a color from HSL values.fromHSL(const hue: numberhue, 80, 60));
}
// Spiral, Lissajous, and tunnel slots are left empty here.
// They will be filled in by update() before the first frame renders.
// Install the shared UI theme the kit draws the pattern captions with.
// It writes 12 colors into slots 240-251, above every range this demo touches
// (the highest animated range is the tunnel at 192-211), so the per-tick palette
// animation never collides with the UI colors. Must run before BT.paletteSet().
this.Demo.theme: nulltheme = import applyThemeapplyTheme(this.Demo.palette: Palettepalette);
// Step 3: Activate the palette
// This tells the engine "use this palette for all drawing from now on".
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) => voidStores 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.paletteSet(this.Demo.palette: Palettepalette);
return true;
}
/**
* Runs at a fixed rate (60 times per second) to:
* 1. Advance the animation timer.
* 2. Recalculate dynamic palette entries for spiral, Lissajous, and tunnel.
*
* All color computation happens here. render() only uses palette index numbers.
*/
Demo.update(): voidRuns at a fixed rate (60 times per second) to:
1. Advance the animation timer.
2. Recalculate dynamic palette entries for spiral, Lissajous, and tunnel.
All color computation happens here. render() only uses palette index numbers.update() {
// deltaSeconds is one fixed update step in seconds (usually 1/60).
this.Demo.animTime: numberanimTime += const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.deltaSeconds: numberFixed-step seconds per update tick.
Equivalent to `1 / BT.targetFPS` when `BT.targetFPS` is finite and positive.
Falls back to `1 / 60` when target FPS is non-finite or non-positive.deltaSeconds;
// Update spiral colors (100 animated dots)
// Each dot gets a hue that depends on its position AND the current time.
// As animTime grows, the hue offset grows too, making colors scroll outward.
for (let let i: numberi = 0; let i: numberi < const SPIRAL_POINTS: 100SPIRAL_POINTS; let i: numberi++) {
// Spread the base hue from 0 to 360 across the 100 dots.
// animTime * 50 makes the colors scroll: 50 degrees per second.
const const hue: numberhue = (let i: numberi / const SPIRAL_POINTS: 100SPIRAL_POINTS) * 360 + this.Demo.animTime: numberanimTime * 50;
// fromHSL takes hue (0-360), saturation (0-100), lightness (0-100).
// Fully saturated (100) and mid-lightness (50) gives vivid rainbow colors.
this.Demo.palette: Palette | nullpalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_SPIRAL_BASE: 60C_SPIRAL_BASE + let i: numberi, class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.fromHSL(h: number, s: number, l: number, a?: number): Color32Creates a color from HSL values.fromHSL(const hue: numberhue % 360, 100, 50));
}
// Update Lissajous color bands (32 bands for the 200-point curve)
// We divide the 200 curve points into 32 color groups to save palette slots.
for (let let i: numberi = 0; let i: numberi < const LISSAJOUS_BANDS: 32LISSAJOUS_BANDS; let i: numberi++) {
// animTime * 30 rotates the color cycle: 30 degrees per second.
const const hue: numberhue = (let i: numberi / const LISSAJOUS_BANDS: 32LISSAJOUS_BANDS) * 360 + this.Demo.animTime: numberanimTime * 30;
this.Demo.palette: Palette | nullpalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_LISSAJOUS_BASE: 160C_LISSAJOUS_BASE + let i: numberi, class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.fromHSL(h: number, s: number, l: number, a?: number): Color32Creates a color from HSL values.fromHSL(const hue: numberhue % 360, 100, 50));
}
// Update tunnel rectangle colors (20 rectangles)
// Outer rectangles (high i, near viewer) get brighter hues.
// Inner rectangles (low i, far away) get darker hues to suggest depth.
for (let let i: numberi = 0; let i: numberi < const TUNNEL_RECTS: 20TUNNEL_RECTS; let i: numberi++) {
// t goes from 0 (innermost/farthest) to 1 (outermost/nearest).
const const t: numbert = let i: numberi / const TUNNEL_RECTS: 20TUNNEL_RECTS;
// The hue rotates at 50 degrees per second. Each rect offsets by t*360
// so the colors spread across the rainbow from inner to outer.
const const hue: numberhue = (const t: numbert * 360 + this.Demo.animTime: numberanimTime * 50) % 360;
// Lightness goes from 30 (dark, far) to 70 (bright, near).
const const lightness: numberlightness = 30 + const t: numbert * 40;
this.Demo.palette: Palette | nullpalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_TUNNEL_BASE: 192C_TUNNEL_BASE + let i: numberi, class Color32Mutable 32-bit RGBA color value with 8-bit channels.Color32.Color32.fromHSL(h: number, s: number, l: number, a?: number): Color32Creates a color from HSL values.fromHSL(const hue: numberhue, 100, const lightness: numberlightness));
}
}
/**
* Runs once per screen refresh to draw all six pattern demonstrations.
* Patterns are arranged in a 2x3 grid across the screen.
*
* Notice: there are NO Color32 objects here. Every draw call uses a palette index.
*/
Demo.render(): voidRuns once per screen refresh to draw all six pattern demonstrations.
Patterns are arranged in a 2x3 grid across the screen.
Notice: there are NO Color32 objects here. Every draw call uses a palette index.render() {
// Fill the whole screen with the background color (very dark blue-black).
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) => voidSets 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.clear(const C_BG: 2C_BG);
// Top row: three patterns centered at y=80.
this.Demo.drawSpiral(center: Vector2i): voidDraws an Archimedean spiral: a coil of colored dots that expands outward
while rotating. The inner dots are near the center, the outer ones are far.
Colors are animated - they scroll along the spiral over time. The actual
color values are computed in update() and stored in palette slots C_SPIRAL_BASE+i.drawSpiral(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(60, 80));
this.Demo.drawRadialLines(center: Vector2i): voidDraws lines radiating from a center point like sun rays.
Each ray has a different color (set once in init) and its length pulses.drawRadialLines(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(160, 80));
this.Demo.drawWavePattern(center: Vector2i): voidDraws three wave curves overlapping each other.
Two separate waves plus a combined "interference" pattern that shows
what happens when the two waves add together.
All three wave colors are static - they never change.drawWavePattern(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(260, 80));
// Bottom row: three more patterns centered at y=150.
this.Demo.drawCircleApproximation(center: Vector2i): voidDraws a circle by connecting many short line segments around its edge.
This shows how circles can be approximated with only line-drawing primitives.
The radius pulses and the whole circle slowly rotates.
Segment colors are static (set once in init based on hue position).drawCircleApproximation(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(60, 150));
this.Demo.drawLissajous(center: Vector2i): voidDraws a Lissajous curve - a parametric figure where the x and y axes
oscillate at different frequencies. The ratio 3:4 here creates an interlocking
looping curve (not a simple figure-eight; a classic figure-eight shape often
comes from a 1:2 frequency ratio instead).
Lissajous figures are used in physics and electronics to visualize frequency ratios.
Colors are animated - 200 curve points are mapped to 32 color bands,
and those bands rotate through the rainbow over time.drawLissajous(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(160, 150));
this.Demo.drawTunnel(center: Vector2i): voidDraws a tunnel effect by stacking concentric rectangles of decreasing size.
The rectangles slowly rotate and wobble, creating an illusion of depth.
Colors are animated - each rectangle's hue and brightness are updated in update().drawTunnel(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(260, 150));
// Label each grid cell so viewers know which pattern they are looking at.
// ui.caption() is the shared UI kit's pinned one-line caption - the same widget
// every demo in the series uses, so all captions look identical everywhere.
// Text draws left-aligned, so we nudge x until short names sit under each center.
import uiui.caption(42, 100, 'Spiral');
import uiui.caption(120, 100, 'Radial');
import uiui.caption(250, 100, 'Wave');
import uiui.caption(42, 180, 'Circle');
import uiui.caption(133, 180, 'Lissajous');
import uiui.caption(243, 180, 'Tunnel');
}
/**
* Draws an Archimedean spiral: a coil of colored dots that expands outward
* while rotating. The inner dots are near the center, the outer ones are far.
*
* Colors are animated - they scroll along the spiral over time. The actual
* color values are computed in update() and stored in palette slots C_SPIRAL_BASE+i.
*
* @param {Vector2i} center - The center point to spiral around.
*/
Demo.drawSpiral(center: Vector2i): voidDraws an Archimedean spiral: a coil of colored dots that expands outward
while rotating. The inner dots are near the center, the outer ones are far.
Colors are animated - they scroll along the spiral over time. The actual
color values are computed in update() and stored in palette slots C_SPIRAL_BASE+i.drawSpiral(center: Vector2i- The center point to spiral around.center) {
const const maxRadius: 35maxRadius = 35;
for (let let i: numberi = 0; let i: numberi < const SPIRAL_POINTS: 100SPIRAL_POINTS; let i: numberi++) {
// t goes from animTime (inner) to animTime + 2*PI (outer).
// 2*PI is one full rotation, so the spiral wraps around once.
const const t: numbert = (let i: numberi / const SPIRAL_POINTS: 100SPIRAL_POINTS) * Math.PI * 2 + this.Demo.animTime: numberanimTime;
// How far from the center this dot is.
// Point 0 is at the center, point 99 is at maxRadius pixels away.
const const radius: numberradius = (let i: numberi / const SPIRAL_POINTS: 100SPIRAL_POINTS) * const maxRadius: 35maxRadius;
// Convert polar coordinates (angle t, distance radius) to x,y screen positions.
//
// Math.cos and Math.sin turn an angle into how far to move on each axis.
// Think of a clock hand: cos is how far right/left the tip is, sin is how far up/down.
// Math.PI is half a full turn (180 degrees); 2*PI is one full turn (360 degrees).
// Multiplying radius scales that direction vector to the dot's distance from center.
const const x: numberx = center: Vector2i- The center point to spiral around.center.Vector2i.x: numberHorizontal component (defaults to 0).x + Math.cos(const t: numbert) * const radius: numberradius;
const const y: numbery = center: Vector2i- The center point to spiral around.center.Vector2i.y: numberVertical component (defaults to 0).y + Math.sin(const t: numbert) * const radius: numberradius;
// Use the animated color for dot i - already updated in update().
this.Demo.tempVec1: Vector2itempVec1.Vector2i.set(x: number, y: number): Vector2iSets both components of this vector.
Modifies this vector directly for maximum performance.
WARNING: Mutates this vector. Don't use on frozen/cached singletons.set(Math.floor(const x: numberx), Math.floor(const y: numbery));
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.drawPixel: (posOrX: Vector2i | number, yOrColor: number, maybeColor?: number) => voidDraws a single pixel.
Accepts either:
- `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index.
- `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.drawPixel(this.Demo.tempVec1: Vector2itempVec1, const C_SPIRAL_BASE: 60C_SPIRAL_BASE + let i: numberi);
}
}
/**
* Draws lines radiating from a center point like sun rays.
* Each ray has a different color (set once in init) and its length pulses.
*
* @param {Vector2i} center - The center point to draw rays from.
*/
Demo.drawRadialLines(center: Vector2i): voidDraws lines radiating from a center point like sun rays.
Each ray has a different color (set once in init) and its length pulses.drawRadialLines(center: Vector2i- The center point to draw rays from.center) {
const const radius: 35radius = 35; // Maximum length of each ray.
for (let let i: numberi = 0; let i: numberi < const RADIAL_LINES: 12RADIAL_LINES; let i: numberi++) {
// Space the rays evenly around the circle. The full circle is 2*PI radians.
// Adding animTime rotates the whole pattern over time.
const const angle: numberangle = (let i: numberi / const RADIAL_LINES: 12RADIAL_LINES) * Math.PI * 2 + this.Demo.animTime: numberanimTime;
// Each ray has a different phase offset (i) so they pulse at different times.
// The length oscillates between radius*0 and radius*1.
const const length: numberlength = const radius: 35radius * (0.5 + 0.5 * Math.sin(this.Demo.animTime: numberanimTime * 2 + let i: numberi));
// Calculate where the tip of this ray is.
const const x: numberx = center: Vector2i- The center point to draw rays from.center.Vector2i.x: numberHorizontal component (defaults to 0).x + Math.cos(const angle: numberangle) * const length: numberlength;
const const y: numbery = center: Vector2i- The center point to draw rays from.center.Vector2i.y: numberVertical component (defaults to 0).y + Math.sin(const angle: numberangle) * const length: numberlength;
// Use the static color for ray i (set in init, never changes).
this.Demo.tempVec1: Vector2itempVec1.Vector2i.set(x: number, y: number): Vector2iSets both components of this vector.
Modifies this vector directly for maximum performance.
WARNING: Mutates this vector. Don't use on frozen/cached singletons.set(Math.floor(const x: numberx), Math.floor(const y: numbery));
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.drawLine: (p0: Vector2i, p1: Vector2i, paletteIndex: number) => voidDraws a pixel-perfect line between two points.
Uses rasterized line drawing without antialiasing.drawLine(center: Vector2i- The center point to draw rays from.center, this.Demo.tempVec1: Vector2itempVec1, const C_RADIAL_BASE: 40C_RADIAL_BASE + let i: numberi);
}
}
/**
* Draws three wave curves overlapping each other.
* Two separate waves plus a combined "interference" pattern that shows
* what happens when the two waves add together.
*
* All three wave colors are static - they never change.
*
* @param {Vector2i} center - The center point to draw the waves around.
*/
Demo.drawWavePattern(center: Vector2i): voidDraws three wave curves overlapping each other.
Two separate waves plus a combined "interference" pattern that shows
what happens when the two waves add together.
All three wave colors are static - they never change.drawWavePattern(center: Vector2i- The center point to draw the waves around.center) {
const const width: 60width = 60; // The wave spans 60 pixels wide.
for (let let x: numberx = 0; let x: numberx < const width: 60width; let x: numberx++) {
// Map x from 0..width to a screen position centered around center.x.
const const baseX: numberbaseX = center: Vector2i- The center point to draw the waves around.center.Vector2i.x: numberHorizontal component (defaults to 0).x - const width: 60width / 2 + let x: numberx;
// Primary wave: a sine wave. Adding animTime*20 makes it scroll to the left.
// Multiplying by 0.2 controls the frequency (how many waves fit in the space).
// Multiplying by 15 controls the amplitude (how tall the wave is in pixels).
const const y1: numbery1 = Math.sin((let x: numberx + this.Demo.animTime: numberanimTime * 20) * 0.2) * 15;
this.Demo.tempVec1: Vector2itempVec1.Vector2i.set(x: number, y: number): Vector2iSets both components of this vector.
Modifies this vector directly for maximum performance.
WARNING: Mutates this vector. Don't use on frozen/cached singletons.set(const baseX: numberbaseX, center: Vector2i- The center point to draw the waves around.center.Vector2i.y: numberVertical component (defaults to 0).y + Math.floor(const y1: numbery1));
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.drawPixel: (posOrX: Vector2i | number, yOrColor: number, maybeColor?: number) => voidDraws a single pixel.
Accepts either:
- `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index.
- `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.drawPixel(this.Demo.tempVec1: Vector2itempVec1, const C_WAVE_1: 5C_WAVE_1);
// Secondary wave: a cosine wave with different speed and size.
const const y2: numbery2 = Math.cos((let x: numberx + this.Demo.animTime: numberanimTime * 15) * 0.15) * 10;
this.Demo.tempVec1: Vector2itempVec1.Vector2i.set(x: number, y: number): Vector2iSets both components of this vector.
Modifies this vector directly for maximum performance.
WARNING: Mutates this vector. Don't use on frozen/cached singletons.set(const baseX: numberbaseX, center: Vector2i- The center point to draw the waves around.center.Vector2i.y: numberVertical component (defaults to 0).y + Math.floor(const y2: numbery2));
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.drawPixel: (posOrX: Vector2i | number, yOrColor: number, maybeColor?: number) => voidDraws a single pixel.
Accepts either:
- `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index.
- `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.drawPixel(this.Demo.tempVec1: Vector2itempVec1, const C_WAVE_2: 6C_WAVE_2);
// Interference: add both waves together. Divide by 2 to keep it in range.
// When waves meet in-phase they reinforce; out-of-phase they cancel.
const const y3: numbery3 = Math.sin((let x: numberx + this.Demo.animTime: numberanimTime * 20) * 0.2) * 15 + Math.cos((let x: numberx + this.Demo.animTime: numberanimTime * 15) * 0.15) * 10;
this.Demo.tempVec1: Vector2itempVec1.Vector2i.set(x: number, y: number): Vector2iSets both components of this vector.
Modifies this vector directly for maximum performance.
WARNING: Mutates this vector. Don't use on frozen/cached singletons.set(const baseX: numberbaseX, center: Vector2i- The center point to draw the waves around.center.Vector2i.y: numberVertical component (defaults to 0).y + Math.floor(const y3: numbery3 / 2));
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.drawPixel: (posOrX: Vector2i | number, yOrColor: number, maybeColor?: number) => voidDraws a single pixel.
Accepts either:
- `(posOrX: Vector2i, yOrColor: number)` where `yOrColor` is the palette index.
- `(posOrX: number, yOrColor: number, maybeColor: number)` for `(x, y, paletteIndex)`.drawPixel(this.Demo.tempVec1: Vector2itempVec1, const C_WAVE_3: 7C_WAVE_3);
}
}
/**
* Draws a circle by connecting many short line segments around its edge.
* This shows how circles can be approximated with only line-drawing primitives.
* The radius pulses and the whole circle slowly rotates.
*
* Segment colors are static (set once in init based on hue position).
*
* @param {Vector2i} center - The center of the circle.
*/
Demo.drawCircleApproximation(center: Vector2i): voidDraws a circle by connecting many short line segments around its edge.
This shows how circles can be approximated with only line-drawing primitives.
The radius pulses and the whole circle slowly rotates.
Segment colors are static (set once in init based on hue position).drawCircleApproximation(center: Vector2i- The center of the circle.center) {
// The radius pulses between 2 and 30 pixels (16 ± 14) using Math.sin.
const const radius: numberradius = 16 + Math.sin(this.Demo.animTime: numberanimTime) * 14;
for (let let i: numberi = 0; let i: numberi < const CIRCLE_SEGMENTS: 32CIRCLE_SEGMENTS; let i: numberi++) {
// Calculate the start and end angle of this segment.
// Together, all 32 segments cover the full circle (2*PI radians).
const const angle1: numberangle1 = (let i: numberi / const CIRCLE_SEGMENTS: 32CIRCLE_SEGMENTS) * Math.PI * 2;
const const angle2: numberangle2 = ((let i: numberi + 1) / const CIRCLE_SEGMENTS: 32CIRCLE_SEGMENTS) * Math.PI * 2;
// Adding animTime to the angles rotates the whole circle over time.
const const x1: numberx1 = center: Vector2i- The center of the circle.center.Vector2i.x: numberHorizontal component (defaults to 0).x + Math.cos(const angle1: numberangle1 + this.Demo.animTime: numberanimTime) * const radius: numberradius;
const const y1: numbery1 = center: Vector2i- The center of the circle.center.Vector2i.y: numberVertical component (defaults to 0).y + Math.sin(const angle1: numberangle1 + this.Demo.animTime: numberanimTime) * const radius: numberradius;
const const x2: numberx2 = center: Vector2i- The center of the circle.center.Vector2i.x: numberHorizontal component (defaults to 0).x + Math.cos(const angle2: numberangle2 + this.Demo.animTime: numberanimTime) * const radius: numberradius;
const const y2: numbery2 = center: Vector2i- The center of the circle.center.Vector2i.y: numberVertical component (defaults to 0).y + Math.sin(const angle2: numberangle2 + this.Demo.animTime: numberanimTime) * const radius: numberradius;
// Each segment uses its own static color from the palette.
this.Demo.tempVec1: Vector2itempVec1.Vector2i.set(x: number, y: number): Vector2iSets both components of this vector.
Modifies this vector directly for maximum performance.
WARNING: Mutates this vector. Don't use on frozen/cached singletons.set(Math.floor(const x1: numberx1), Math.floor(const y1: numbery1));
this.Demo.tempVec2: Vector2itempVec2.Vector2i.set(x: number, y: number): Vector2iSets both components of this vector.
Modifies this vector directly for maximum performance.
WARNING: Mutates this vector. Don't use on frozen/cached singletons.set(Math.floor(const x2: numberx2), Math.floor(const y2: numbery2));
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.drawLine: (p0: Vector2i, p1: Vector2i, paletteIndex: number) => voidDraws a pixel-perfect line between two points.
Uses rasterized line drawing without antialiasing.drawLine(this.Demo.tempVec1: Vector2itempVec1, this.Demo.tempVec2: Vector2itempVec2, const C_CIRCLE_BASE: 8C_CIRCLE_BASE + let i: numberi);
}
}
/**
* Draws a Lissajous curve - a parametric figure where the x and y axes
* oscillate at different frequencies. The ratio 3:4 here creates an interlocking
* looping curve (not a simple figure-eight; a classic figure-eight shape often
* comes from a 1:2 frequency ratio instead).
*
* Lissajous figures are used in physics and electronics to visualize frequency ratios.
*
* Colors are animated - 200 curve points are mapped to 32 color bands,
* and those bands rotate through the rainbow over time.
*
* @param {Vector2i} center - The center of the curve.
*/
Demo.drawLissajous(center: Vector2i): voidDraws a Lissajous curve - a parametric figure where the x and y axes
oscillate at different frequencies. The ratio 3:4 here creates an interlocking
looping curve (not a simple figure-eight; a classic figure-eight shape often
comes from a 1:2 frequency ratio instead).
Lissajous figures are used in physics and electronics to visualize frequency ratios.
Colors are animated - 200 curve points are mapped to 32 color bands,
and those bands rotate through the rainbow over time.drawLissajous(center: Vector2i- The center of the curve.center) {
const const points: 200points = 200; // More points = smoother curve.
const const a: 3a = 3; // Frequency of the horizontal oscillation.
const const b: 4b = 4; // Frequency of the vertical oscillation.
const const radius: 30radius = 30;
// Track the previous point so we can draw a line from previous to current.
let let prevX: numberprevX = 0;
let let prevY: numberprevY = 0;
for (let let i: numberi = 0; let i: numberi <= const points: 200points; let i: numberi++) {
// t goes from 0 to 2*PI, tracing out the full curve.
const const t: numbert = (let i: numberi / const points: 200points) * Math.PI * 2;
// x oscillates at frequency a, y oscillates at frequency b.
// Adding animTime to the x calculation makes the figure rotate over time.
const const x: numberx = center: Vector2i- The center of the curve.center.Vector2i.x: numberHorizontal component (defaults to 0).x + Math.sin(const a: 3a * const t: numbert + this.Demo.animTime: numberanimTime) * const radius: 30radius;
const const y: numbery = center: Vector2i- The center of the curve.center.Vector2i.y: numberVertical component (defaults to 0).y + Math.sin(const b: 4b * const t: numbert) * const radius: 30radius;
if (let i: numberi > 0) {
// Map this point to one of the 32 color bands.
// Math.floor((i / points) * LISSAJOUS_BANDS) gives 0..31.
// We cap at LISSAJOUS_BANDS-1 to avoid going out of range.
const const band: anyband = Math.min(Math.floor((let i: numberi / const points: 200points) * const LISSAJOUS_BANDS: 32LISSAJOUS_BANDS), const LISSAJOUS_BANDS: 32LISSAJOUS_BANDS - 1);
this.Demo.tempVec1: Vector2itempVec1.Vector2i.set(x: number, y: number): Vector2iSets both components of this vector.
Modifies this vector directly for maximum performance.
WARNING: Mutates this vector. Don't use on frozen/cached singletons.set(Math.floor(let prevX: numberprevX), Math.floor(let prevY: numberprevY));
this.Demo.tempVec2: Vector2itempVec2.Vector2i.set(x: number, y: number): Vector2iSets both components of this vector.
Modifies this vector directly for maximum performance.
WARNING: Mutates this vector. Don't use on frozen/cached singletons.set(Math.floor(const x: numberx), Math.floor(const y: numbery));
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.drawLine: (p0: Vector2i, p1: Vector2i, paletteIndex: number) => voidDraws a pixel-perfect line between two points.
Uses rasterized line drawing without antialiasing.drawLine(this.Demo.tempVec1: Vector2itempVec1, this.Demo.tempVec2: Vector2itempVec2, const C_LISSAJOUS_BASE: 160C_LISSAJOUS_BASE + const band: anyband);
}
// Save this point to use as "previous" in the next iteration.
let prevX: numberprevX = const x: numberx;
let prevY: numberprevY = const y: numbery;
}
}
/**
* Draws a tunnel effect by stacking concentric rectangles of decreasing size.
* The rectangles slowly rotate and wobble, creating an illusion of depth.
*
* Colors are animated - each rectangle's hue and brightness are updated in update().
*
* @param {Vector2i} center - The vanishing point (center) of the tunnel.
*/
Demo.drawTunnel(center: Vector2i): voidDraws a tunnel effect by stacking concentric rectangles of decreasing size.
The rectangles slowly rotate and wobble, creating an illusion of depth.
Colors are animated - each rectangle's hue and brightness are updated in update().drawTunnel(center: Vector2i- The vanishing point (center) of the tunnel.center) {
for (let let i: numberi = 0; let i: numberi < const TUNNEL_RECTS: 20TUNNEL_RECTS; let i: numberi++) {
// t = i / TUNNEL_RECTS: i = 0 is the outer/near largest rectangle;
// the final i is the inner/far smallest (size uses 1 - t below).
const const t: numbert = let i: numberi / const TUNNEL_RECTS: 20TUNNEL_RECTS;
// Outer rectangles are large, inner ones are small.
// The sine term adds a gentle pulsing wobble to the size.
const const size: numbersize = (1 - const t: numbert) * 60 + Math.sin(this.Demo.animTime: numberanimTime * 2 + let i: numberi * 0.3) * 5;
// Each rectangle orbits slightly around the center at different speeds.
const const angle: numberangle = this.Demo.animTime: numberanimTime + let i: numberi * 0.2;
const const offsetX: numberoffsetX = Math.cos(const angle: numberangle) * let i: numberi;
const const offsetY: numberoffsetY = Math.sin(const angle: numberangle) * let i: numberi;
// Position the rectangle centered on the offset point.
const const x: numberx = center: Vector2i- The vanishing point (center) of the tunnel.center.Vector2i.x: numberHorizontal component (defaults to 0).x - const size: numbersize / 2 + const offsetX: numberoffsetX;
const const y: numbery = center: Vector2i- The vanishing point (center) of the tunnel.center.Vector2i.y: numberVertical component (defaults to 0).y - const size: numbersize / 2 + const offsetY: numberoffsetY;
// Use the animated color for this rectangle (updated in update()).
this.Demo.tempRect: Rect2itempRect.Rect2i.set(x: number, y: number, width: number, height: number): Rect2iSets all components of this rectangle.
Modifies this rectangle directly for maximum performance.
WARNING: Mutates this rectangle. Don't use on frozen/cached singletons.set(Math.floor(const x: numberx), Math.floor(const y: numbery), Math.floor(const size: numbersize), Math.floor(const size: numbersize));
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) => voidDraws an unfilled rectangle outline.drawRect(this.Demo.tempRect: Rect2itempRect, const C_TUNNEL_BASE: 192C_TUNNEL_BASE + let i: numberi);
}
}
}
// Hand the Demo class to BLIT386 to start the demo loop.
function bootstrap(DemoClass: DemoConstructor, options?: BootstrapOptions): Promise<boolean>One-liner bootstrap function for BLIT386 demos.
Handles canvas retrieval and engine initialization. Backend selection
(WebGPU or software fallback) is managed internally by BTAPI.
This function provides a streamlined way to start a demo with sensible defaults
while allowing customization through options.bootstrap(class DemoDemonstrates animated mathematical patterns using primitive drawing.
Each section shows a different algorithmic visual effect arranged in a 2x3 grid.Demo);