// Starfield: parallax scrolling stars that feel like 3D depth.
// @description Parallax scrolling stars at several speeds, so a flat two-dimensional field reads as real depth.
//
// Part of the BLIT386 demo series.
//
// Prerequisites:
// Basics https://demos.blit386.dev/basics
// Primitives https://demos.blit386.dev/primitives
// Colors https://demos.blit386.dev/colors
//
// Guide: https://blit386.dev/docs/api/rendering#primitives
//
// WHAT YOU WILL SEE
// Three layers of stars scroll to the left at different speeds. Stars that are
// "far away" move slowly and look dim and tiny. Stars that are "close" move fast
// and look bright and a little bigger. Your brain reads that mix as depth, even
// though the screen is flat. Two everyday comparisons:
// - Car window: nearby trees zip past, but faraway mountains barely move.
// - Train window: the fence right beside the tracks blurs by, houses farther
// back drift slowly, and distant hills almost look still.
//
// WHAT YOU WILL LEARN
// - Arrays of simple objects (each star remembers x, y, speed, and a palette slot)
// - Parallax: fake depth by changing speed, size, and brightness together
// - Wrapping: when a star leaves the left side, jump it to the right (endless sky)
// - A shooting star: a fast diagonal line drawn with BT.drawLine
//
// HOW COLORS WORK IN THIS DEMO
// Every star has a unique brightness (how bright its gray color is). Instead of
// making a new Color32 every frame, we register each star's gray color in the
// palette once at startup and store the palette slot number on the star.
// render() just reads that slot number - no Color32 objects needed per frame.
//
// The three explainer lines in the corner are drawn with the shared UI kit
// (src/shared/ui.js), so their colors and spacing match every other demo.
//
// The engine splits work the usual way: update() moves things; render() only draws.
// See the Basics demo for the full story: https://demos.blit386.dev/basics
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, import UI_ANCHORSUI_ANCHORS } from './shared/ui.js';
/** @typedef {import('blit386').IBTDemo} IBTDemo */
/** @typedef {import('blit386').HardwareSettings} HardwareSettings */
/** @typedef {import('blit386').Palette} Palette */
// Logical screen size in "game pixels".
const const DISPLAY_W: 320DISPLAY_W = 320;
const const DISPLAY_H: 240DISPLAY_H = 240;
// How many stars live in each layer. Far = many tiny dots; near = fewer, brighter blobs.
const const FAR_COUNT: 30FAR_COUNT = 30;
const const MEDIUM_COUNT: 20MEDIUM_COUNT = 20;
const const NEAR_COUNT: 10NEAR_COUNT = 10;
// Near stars are drawn as a small solid square this many pixels wide and tall.
const const NEAR_STAR_SIZE: 2NEAR_STAR_SIZE = 2;
// Where in the palette we start registering individual star colors.
// Each star gets its own slot so brightness variety is preserved exactly.
// We have 30 + 20 + 10 = 60 stars, using slots 10..69.
const const SLOT_START: 10SLOT_START = 10;
// Static color slots.
const const C_BG: 2C_BG = 2; // Deep space background (very dark blue-black).
const const C_CHART_TEXT: 3C_CHART_TEXT = 3; // Light blue-white: overlay text and timing chart update bars.
const const C_CHART_WARN: 4C_CHART_WARN = 4; // Dim blue-gray: overlay timing chart warning color.
const const C_CHART_TAG: 5C_CHART_TAG = 5; // Even dimmer: overlay timing chart tag labels.
const const C_STREAK: 7C_STREAK = 7; // Cool white for the shooting star streak.
/**
* Parallax starfield with three layers plus an occasional shooting star.
*
* @implements {IBTDemo}
*/
class class DemoParallax starfield with three layers plus an occasional shooting star.Demo {
// The palette holds all colors used in this demo.
/** @type {Palette | null} */
Demo.palette: Palette | nullpalette = null;
// Slot map for the shared UI kit theme, filled in init() by applyTheme().
// The kit's label widgets draw with these colors automatically.
Demo.theme: nulltheme = null;
// Three separate arrays. Each entry is a plain object:
// { x, y, speed, paletteIndex }
// paletteIndex is the slot number registered during init().
Demo.farLayer: {}farLayer = [];
Demo.mediumLayer: {}mediumLayer = [];
Demo.nearLayer: {}nearLayer = [];
// Shooting star: not an array - only one at a time, or none.
// When active is false, we ignore the numbers until we spawn again.
// prevHeadX/prevHeadY remember the head position at the START of the most recent
// update() tick, before this tick's movement. render() blends between them and
// headX/headY using BT.renderAlpha so the streak glides smoothly between physics
// ticks instead of hopping in 14px jumps - see "Interpolating render state with
// renderAlpha" in the engine's docs/api-game-loop.md.
Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak = {
active: booleanactive: false,
headX: numberheadX: 0,
headY: numberheadY: 0,
prevHeadX: numberprevHeadX: 0,
prevHeadY: numberprevHeadY: 0,
};
// Counts how many update() ticks passed since the last shooting star (for timing).
Demo.ticksSinceShoot: numberticksSinceShoot = 0;
// After a shooting star finishes, wait this many ticks before planning the next one.
Demo.nextShootDelay: numbernextShootDelay = 200;
/**
* Shows the timing chart while many stars move each frame (useful for spotting render spikes).
*
* @returns {Partial<HardwareSettings>}
*/
Demo.configure(): Partial<HardwareSettings>Shows the timing chart while many stars move each frame (useful for spotting render spikes).configure() {
return {
isOverlayTimingChartEnabled: booleanisOverlayTimingChartEnabled: true,
overlayStyle: {
barPaletteIndex: number;
textPaletteIndex: number;
gapPaletteIndex: number;
}
overlayStyle: {
barPaletteIndex: numberbarPaletteIndex: const C_BG: 2C_BG,
textPaletteIndex: numbertextPaletteIndex: const C_CHART_TEXT: 3C_CHART_TEXT,
gapPaletteIndex: numbergapPaletteIndex: const C_BG: 2C_BG,
},
overlayTimingChartStyle: {
updateBarPaletteIndex: number;
renderBarPaletteIndex: number;
warningPaletteIndex: number;
errorPaletteIndex: number;
tagPaletteIndex: number;
}
overlayTimingChartStyle: {
updateBarPaletteIndex: numberupdateBarPaletteIndex: const C_CHART_TEXT: 3C_CHART_TEXT,
renderBarPaletteIndex: numberrenderBarPaletteIndex: const C_STREAK: 7C_STREAK,
warningPaletteIndex: numberwarningPaletteIndex: const C_CHART_WARN: 4C_CHART_WARN,
errorPaletteIndex: numbererrorPaletteIndex: const C_STREAK: 7C_STREAK,
tagPaletteIndex: numbertagPaletteIndex: const C_CHART_TAG: 5C_CHART_TAG,
},
};
}
/**
* Sets up the palette and creates star layers.
*
* IMPORTANT ORDER:
* 1. Create palette and register static colors.
* 2. Build the star layers (this decides each star's brightness).
* 3. Register each star's gray color in the palette, store the slot on the star.
* 4. BT.paletteSet() - tell the engine to use this palette.
*
* @returns {Promise<boolean>}
*/
async Demo.init(): Promise<boolean>Sets up the palette and creates star layers.
IMPORTANT ORDER:
1. Create palette and register static colors.
2. Build the star layers (this decides each star's brightness).
3. Register each star's gray color in the palette, store the slot on the star.
4. BT.paletteSet() - tell the engine to use this palette.init() {
console.log('[StarfieldDemo] Initializing...');
// Step 1: Create palette and static colors
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);
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(4, 6, 18)); // Deep space: very dark blue-black.
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_CHART_TEXT: 3C_CHART_TEXT, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(200, 210, 230)); // Overlay text and chart update bars.
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_CHART_WARN: 4C_CHART_WARN, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(140, 150, 170)); // Dim blue-gray chart warnings.
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_CHART_TAG: 5C_CHART_TAG, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(110, 120, 140)); // Dimmer chart tag labels.
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_STREAK: 7C_STREAK, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(230, 240, 255)); // Cool white shooting streak.
// Step 2: Build the three star layers
// Each helper picks random positions, speeds, and brightness values in the given ranges.
// Far stars: slow (0.3..0.5 pixels/tick), dim (80..120 brightness).
// Medium stars: medium (0.8..1.2), brighter (150..200).
// Near stars: fast (1.5..2.5), bright (220..255).
this.Demo.farLayer: {}farLayer = this.Demo.createLayerData(count: number, speedMin: number, speedMax: number, brightMin: number, brightMax: number): Array<{
x: number;
y: number;
prevX: number;
prevY: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
Build one layer's raw data: count stars with random positions and brightness.
Returns objects with { x, y, speed, brightness } - paletteIndex is added later
in init() once the palette is ready.
BT.random is the engine's shared random number generator.
float() returns a decimal from the first value up to (but not including) the second.createLayerData(const FAR_COUNT: 30FAR_COUNT, 0.3, 0.5, 80, 120);
this.Demo.mediumLayer: {}mediumLayer = this.Demo.createLayerData(count: number, speedMin: number, speedMax: number, brightMin: number, brightMax: number): Array<{
x: number;
y: number;
prevX: number;
prevY: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
Build one layer's raw data: count stars with random positions and brightness.
Returns objects with { x, y, speed, brightness } - paletteIndex is added later
in init() once the palette is ready.
BT.random is the engine's shared random number generator.
float() returns a decimal from the first value up to (but not including) the second.createLayerData(const MEDIUM_COUNT: 20MEDIUM_COUNT, 0.8, 1.2, 150, 200);
this.Demo.nearLayer: {}nearLayer = this.Demo.createLayerData(count: number, speedMin: number, speedMax: number, brightMin: number, brightMax: number): Array<{
x: number;
y: number;
prevX: number;
prevY: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
Build one layer's raw data: count stars with random positions and brightness.
Returns objects with { x, y, speed, brightness } - paletteIndex is added later
in init() once the palette is ready.
BT.random is the engine's shared random number generator.
float() returns a decimal from the first value up to (but not including) the second.createLayerData(const NEAR_COUNT: 10NEAR_COUNT, 1.5, 2.5, 220, 255);
// Step 3: Register each star's color in the palette
// We walk all three layers in one pass, giving each star its own slot number.
// new Color32(b, b, b) makes a neutral gray: equal red, green, and blue.
let let slot: numberslot = const SLOT_START: 10SLOT_START;
for (const const star: anystar of this.Demo.farLayer: {}farLayer) {
const const b: anyb = const star: anystar.brightness;
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(let slot: numberslot, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(const b: anyb, const b: anyb, const b: anyb));
const star: anystar.paletteIndex = let slot: numberslot;
let slot: numberslot++;
}
for (const const star: anystar of this.Demo.mediumLayer: {}mediumLayer) {
const const b: anyb = const star: anystar.brightness;
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(let slot: numberslot, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(const b: anyb, const b: anyb, const b: anyb));
const star: anystar.paletteIndex = let slot: numberslot;
let slot: numberslot++;
}
for (const const star: anystar of this.Demo.nearLayer: {}nearLayer) {
const const b: anyb = const star: anystar.brightness;
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(let slot: numberslot, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(const b: anyb, const b: anyb, const b: anyb));
const star: anystar.paletteIndex = let slot: numberslot;
let slot: numberslot++;
}
// Install the shared UI kit theme. applyTheme() writes twelve UI colors into
// high palette slots (240 and up), far above this demo's scene slots (1..69),
// so the star colors and the UI colors never fight over the same slots.
this.Demo.theme: nulltheme = import applyThemeapplyTheme(this.Demo.palette: Palettepalette);
// Activate the palette
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.paletteSet: (palette: Palette) => 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);
// Start the shooting-star timer at a pleasant "about 200 ticks" delay.
this.Demo.ticksSinceShoot: numberticksSinceShoot = 0;
this.Demo.nextShootDelay: numbernextShootDelay = 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: RandomDefault 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.random.Random.int(minOrMaxExclusive: number, maxExclusive?: number): numberReturns a pseudo-random integer in [0, maxExclusive) or [min, maxExclusive).int(180, 220);
console.log('[StarfieldDemo] Ready.');
return true;
}
/**
* Fixed-step game logic: slide every star left, wrap off-screen ones,
* and maybe spawn or advance the shooting star.
*/
Demo.update(): voidFixed-step game logic: slide every star left, wrap off-screen ones,
and maybe spawn or advance the shooting star.update() {
// Move the three layers. wrapW is how wide the star is for "gone off the left?" checks.
this.Demo.moveLayer(layer: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>, wrapW: number): void
Move every star in one layer to the left by its speed, then wrap if it exited left.moveLayer(this.Demo.farLayer: {}farLayer, 1);
this.Demo.moveLayer(layer: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>, wrapW: number): void
Move every star in one layer to the left by its speed, then wrap if it exited left.moveLayer(this.Demo.mediumLayer: {}mediumLayer, 1);
this.Demo.moveLayer(layer: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>, wrapW: number): void
Move every star in one layer to the left by its speed, then wrap if it exited left.moveLayer(this.Demo.nearLayer: {}nearLayer, const NEAR_STAR_SIZE: 2NEAR_STAR_SIZE);
this.Demo.updateStreak(): voidMaybe start a new streak, or move the current one until it leaves the screen.updateStreak();
}
/**
* Draw sky, stars (back to front), shooting streak, then text labels on top.
* Notice: NO Color32 objects appear here. Every draw call uses a palette index number.
*/
Demo.render(): voidDraw sky, stars (back to front), shooting streak, then text labels on top.
Notice: NO Color32 objects appear here. Every draw call uses a palette index number.render() {
// Deep space background.
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);
// Draw back to front so near stars visually cover far ones, like real depth.
// Far and medium stars are single pixels (size 1); near stars are bigger blocks.
this.Demo.drawLayer(layer: Array<{
x: number;
y: number;
prevX: number;
prevY: number;
paletteIndex: number;
}>, size: number): void
Draws one layer of stars. All three layers share the same movement math;
only the drawn size differs, so one method handles them all.
Each star's paletteIndex was set in init() to point at its unique gray shade.drawLayer(this.Demo.farLayer: {}farLayer, 1);
this.Demo.drawLayer(layer: Array<{
x: number;
y: number;
prevX: number;
prevY: number;
paletteIndex: number;
}>, size: number): void
Draws one layer of stars. All three layers share the same movement math;
only the drawn size differs, so one method handles them all.
Each star's paletteIndex was set in init() to point at its unique gray shade.drawLayer(this.Demo.mediumLayer: {}mediumLayer, 1);
this.Demo.drawLayer(layer: Array<{
x: number;
y: number;
prevX: number;
prevY: number;
paletteIndex: number;
}>, size: number): void
Draws one layer of stars. All three layers share the same movement math;
only the drawn size differs, so one method handles them all.
Each star's paletteIndex was set in init() to point at its unique gray shade.drawLayer(this.Demo.nearLayer: {}nearLayer, const NEAR_STAR_SIZE: 2NEAR_STAR_SIZE);
this.Demo.drawStreak(): voidDraw a short bright line: tail behind the head along the motion direction.
Uses the static C_STREAK palette slot registered in init().drawStreak();
this.Demo.drawLabels(): voidExplain the three layers with the shared UI kit (drawn last so text stays readable).
We skip ui.panel() on purpose: without it the group is just floating text, which
keeps the sky visible behind the caption instead of covering it with a box.drawLabels();
}
/**
* Build one layer's raw data: count stars with random positions and brightness.
* Returns objects with { x, y, speed, brightness } - paletteIndex is added later
* in init() once the palette is ready.
*
* BT.random is the engine's shared random number generator.
* float() returns a decimal from the first value up to (but not including) the second.
*
* @param {number} count
* @param {number} speedMin
* @param {number} speedMax
* @param {number} brightMin
* @param {number} brightMax
* @returns {Array<{x: number, y: number, prevX: number, prevY: number, speed: number, brightness: number, paletteIndex: number}>}
*/
Demo.createLayerData(count: number, speedMin: number, speedMax: number, brightMin: number, brightMax: number): Array<{
x: number;
y: number;
prevX: number;
prevY: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
Build one layer's raw data: count stars with random positions and brightness.
Returns objects with { x, y, speed, brightness } - paletteIndex is added later
in init() once the palette is ready.
BT.random is the engine's shared random number generator.
float() returns a decimal from the first value up to (but not including) the second.createLayerData(count: numbercount, speedMin: numberspeedMin, speedMax: numberspeedMax, brightMin: numberbrightMin, brightMax: numberbrightMax) {
const const layer: {}layer = [];
for (let let i: numberi = 0; let i: numberi < count: numbercount; let i: numberi++) {
// Spread stars across the whole sky at the start so the screen looks full.
const const x: numberx = const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.random: RandomDefault 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.random.Random.float(min: number, max: number): numberReturns the next pseudo-random float in [min, max).float(0, const DISPLAY_W: 320DISPLAY_W);
const const y: numbery = const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 106 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.random: RandomDefault 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.random.Random.float(min: number, max: number): numberReturns the next pseudo-random float in [min, max).float(0, const DISPLAY_H: 240DISPLAY_H);
// Speed: how many pixels left per update tick (float keeps motion smooth).
const const speed: numberspeed = 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: RandomDefault 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.random.Random.float(min: number, max: number): numberReturns the next pseudo-random float in [min, max).float(speedMin: numberspeedMin, speedMax: numberspeedMax);
// Brightness: 0 = black, 255 = white. Used to make a gray Color32.
// intInclusive() gives a whole number and, unlike int(), the top value is a possible answer too
// - so the brightest stars really can hit brightMax.
const const brightness: numberbrightness = 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: RandomDefault 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.random.Random.intInclusive(min: number, max: number): numberReturns a pseudo-random integer in [min, max] (inclusive on both ends).intInclusive(brightMin: numberbrightMin, brightMax: numberbrightMax);
// paletteIndex starts at 0; init() will fill it in after palette setup.
// prevX/prevY start equal to x/y - see moveLayer() for how they update.
const layer: {}layer.push({ x: numberx, y: numbery, prevX: numberprevX: const x: numberx, prevY: numberprevY: const y: numbery, speed: numberspeed, brightness: numberbrightness, paletteIndex: numberpaletteIndex: 0 });
}
return const layer: {}layer;
}
/**
* Move every star in one layer to the left by its speed, then wrap if it exited left.
*
* @param {Array<{x: number, y: number, speed: number, brightness: number, paletteIndex: number}>} layer
* @param {number} wrapW how many pixels wide the drawable star occupies (for wrapping)
*/
Demo.moveLayer(layer: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>, wrapW: number): void
Move every star in one layer to the left by its speed, then wrap if it exited left.moveLayer(layer: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
layer, wrapW: numberhow many pixels wide the drawable star occupies (for wrapping)wrapW) {
for (let let i: numberi = 0; let i: numberi < layer: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
layer.length; let i: numberi++) {
const const star: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
star = layer: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
layer[let i: numberi];
// Remember where the star was before this tick moves it, so render() can
// draw a smooth in-between position instead of a pop.
const star: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
star.prevX = const star: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
star.x;
const star: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
star.prevY = const star: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
star.y;
// Left means subtract from x (the origin is at the top-left of the screen).
const star: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
star.x -= const star: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
star.speed;
// If the whole star is past the left edge, teleport it to the right.
// Think of a conveyor belt: exit left, re-enter right with a fresh row position.
if (const star: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
star.x < -wrapW: numberhow many pixels wide the drawable star occupies (for wrapping)wrapW) {
const star: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
star.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.random: RandomDefault 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.random.Random.float(min: number, max: number): numberReturns the next pseudo-random float in [min, max).float(const DISPLAY_W: 320DISPLAY_W, const DISPLAY_W: 320DISPLAY_W + 40);
const star: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
star.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.random: RandomDefault 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.random.Random.float(min: number, max: number): numberReturns the next pseudo-random float in [min, max).float(0, const DISPLAY_H: 240DISPLAY_H);
// Snap prevX/prevY to match the teleported spot too. Without this,
// render() would blend from the old off-screen-left position all the
// way across to the new one, drawing a streak clear across the sky.
const star: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
star.prevX = const star: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
star.x;
const star: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
star.prevY = const star: Array<{
x: number;
y: number;
speed: number;
brightness: number;
paletteIndex: number;
}>
star.y;
}
}
}
/**
* Maybe start a new streak, or move the current one until it leaves the screen.
*/
Demo.updateStreak(): voidMaybe start a new streak, or move the current one until it leaves the screen.updateStreak() {
if (this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.active: booleanactive) {
// Remember the head's position before this tick moves it.
this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.prevHeadX: numberprevHeadX = this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.headX: numberheadX;
this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.prevHeadY: numberprevHeadY = this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.headY: numberheadY;
// Very fast compared to normal stars - several pixels per tick.
this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.headX: numberheadX -= 14;
// A gentle downward drift sells the "falling" look.
this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.headY: numberheadY += 0.7;
// Once the head is well past the left edge, turn it off and reset the timer.
if (this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.headX: numberheadX < -24) {
this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.active: booleanactive = false;
this.Demo.ticksSinceShoot: numberticksSinceShoot = 0;
this.Demo.nextShootDelay: numbernextShootDelay = 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: RandomDefault 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.random.Random.int(minOrMaxExclusive: number, maxExclusive?: number): numberReturns a pseudo-random integer in [0, maxExclusive) or [min, maxExclusive).int(180, 240);
}
return;
}
// No active streak: count ticks until the next launch window.
this.Demo.ticksSinceShoot: numberticksSinceShoot += 1;
if (this.Demo.ticksSinceShoot: numberticksSinceShoot >= this.Demo.nextShootDelay: numbernextShootDelay) {
this.Demo.spawnStreak(): voidPlace a new streak just beyond the right edge so it flies across the sky.spawnStreak();
this.Demo.ticksSinceShoot: numberticksSinceShoot = 0;
}
}
/**
* Place a new streak just beyond the right edge so it flies across the sky.
*/
Demo.spawnStreak(): voidPlace a new streak just beyond the right edge so it flies across the sky.spawnStreak() {
this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.active: booleanactive = true;
// Start slightly off-screen to the right so it enters smoothly.
this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.headX: numberheadX = 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: RandomDefault 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.random.Random.float(min: number, max: number): numberReturns the next pseudo-random float in [min, max).float(const DISPLAY_W: 320DISPLAY_W + 10, const DISPLAY_W: 320DISPLAY_W + 70);
// Keep it in the upper half so it reads as "sky" above the labels.
this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.headY: numberheadY = 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: RandomDefault 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.random.Random.float(min: number, max: number): numberReturns the next pseudo-random float in [min, max).float(16, 16 + const DISPLAY_H: 240DISPLAY_H * 0.45);
// Snap prevHeadX/prevHeadY to the spawn point too, so the very first render
// after spawning does not blend in from wherever the last streak died.
this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.prevHeadX: numberprevHeadX = this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.headX: numberheadX;
this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.prevHeadY: numberprevHeadY = this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.headY: numberheadY;
}
/**
* Draw a short bright line: tail behind the head along the motion direction.
* Uses the static C_STREAK palette slot registered in init().
*/
Demo.drawStreak(): voidDraw a short bright line: tail behind the head along the motion direction.
Uses the static C_STREAK palette slot registered in init().drawStreak() {
if (!this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.active: booleanactive) {
return;
}
// Blend the head's previous and current tick position by BT.renderAlpha - a
// fraction from 0 (a tick just finished) to just under 1 (the next tick is
// about to happen) - so the streak's drawn position matches this exact render
// moment instead of only its last-tick position.
const const alpha: numberalpha = 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: numberFractional progress between the last completed fixed update and the next.
Intended for interpolating render state between fixed-update steps.renderAlpha;
const const hx: anyhx = Math.floor(this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.prevHeadX: numberprevHeadX + (this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.headX: numberheadX - this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.prevHeadX: numberprevHeadX) * const alpha: numberalpha);
const const hy: anyhy = Math.floor(this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.prevHeadY: numberprevHeadY + (this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.headY: numberheadY - this.Demo.streak: {
active: boolean;
headX: number;
headY: number;
prevHeadX: number;
prevHeadY: number;
}
streak.prevHeadY: numberprevHeadY) * const alpha: numberalpha);
// Tail sits to the right and a little up because we move left and down each tick.
const const tailX: anytailX = const hx: anyhx + 14;
const const tailY: numbertailY = const hy: anyhy - 4;
// C_STREAK is the cool white color registered in init().
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(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const tailX: anytailX, const tailY: numbertailY), new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const hx: anyhx, const hy: anyhy), const C_STREAK: 7C_STREAK);
}
/**
* Draws one layer of stars. All three layers share the same movement math;
* only the drawn size differs, so one method handles them all.
* Each star's paletteIndex was set in init() to point at its unique gray shade.
*
* @param {Array<{x: number, y: number, prevX: number, prevY: number, paletteIndex: number}>} layer
* @param {number} size - Star width and height in pixels: 1 draws a single pixel,
* anything bigger draws a filled square (near stars use NEAR_STAR_SIZE).
*/
Demo.drawLayer(layer: Array<{
x: number;
y: number;
prevX: number;
prevY: number;
paletteIndex: number;
}>, size: number): void
Draws one layer of stars. All three layers share the same movement math;
only the drawn size differs, so one method handles them all.
Each star's paletteIndex was set in init() to point at its unique gray shade.drawLayer(layer: Array<{
x: number;
y: number;
prevX: number;
prevY: number;
paletteIndex: number;
}>
layer, size: number- Star width and height in pixels: 1 draws a single pixel,
anything bigger draws a filled square (near stars use NEAR_STAR_SIZE).size) {
// Blend each star's previous and current tick position by BT.renderAlpha - a
// fraction from 0 (a tick just finished) to just under 1 (the next tick is
// about to happen) - so the star's drawn position matches this exact render
// moment instead of only its last-tick position.
const const alpha: numberalpha = 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: numberFractional progress between the last completed fixed update and the next.
Intended for interpolating render state between fixed-update steps.renderAlpha;
for (let let i: numberi = 0; let i: numberi < layer: Array<{
x: number;
y: number;
prevX: number;
prevY: number;
paletteIndex: number;
}>
layer.length; let i: numberi++) {
const const star: Array<{
x: number;
y: number;
prevX: number;
prevY: number;
paletteIndex: number;
}>
star = layer: Array<{
x: number;
y: number;
prevX: number;
prevY: number;
paletteIndex: number;
}>
layer[let i: numberi];
const const px: anypx = Math.floor(const star: Array<{
x: number;
y: number;
prevX: number;
prevY: number;
paletteIndex: number;
}>
star.prevX + (const star: Array<{
x: number;
y: number;
prevX: number;
prevY: number;
paletteIndex: number;
}>
star.x - const star: Array<{
x: number;
y: number;
prevX: number;
prevY: number;
paletteIndex: number;
}>
star.prevX) * const alpha: numberalpha);
const const py: anypy = Math.floor(const star: Array<{
x: number;
y: number;
prevX: number;
prevY: number;
paletteIndex: number;
}>
star.prevY + (const star: Array<{
x: number;
y: number;
prevX: number;
prevY: number;
paletteIndex: number;
}>
star.y - const star: Array<{
x: number;
y: number;
prevX: number;
prevY: number;
paletteIndex: number;
}>
star.prevY) * const alpha: numberalpha);
if (size: number- Star width and height in pixels: 1 draws a single pixel,
anything bigger draws a filled square (near stars use NEAR_STAR_SIZE).size === 1) {
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(new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const px: anypx, const py: anypy), const star: Array<{
x: number;
y: number;
prevX: number;
prevY: number;
paletteIndex: number;
}>
star.paletteIndex);
} else {
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) => voidDraws a filled rectangle.drawRectFill(new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2iCreates an integer rectangle, truncating all inputs toward zero.Rect2i(const px: anypx, const py: anypy, size: number- Star width and height in pixels: 1 draws a single pixel,
anything bigger draws a filled square (near stars use NEAR_STAR_SIZE).size, size: number- Star width and height in pixels: 1 draws a single pixel,
anything bigger draws a filled square (near stars use NEAR_STAR_SIZE).size), const star: Array<{
x: number;
y: number;
prevX: number;
prevY: number;
paletteIndex: number;
}>
star.paletteIndex);
}
}
}
/**
* Explain the three layers with the shared UI kit (drawn last so text stays readable).
* We skip ui.panel() on purpose: without it the group is just floating text, which
* keeps the sky visible behind the caption instead of covering it with a box.
*/
Demo.drawLabels(): voidExplain the three layers with the shared UI kit (drawn last so text stays readable).
We skip ui.panel() on purpose: without it the group is just floating text, which
keeps the sky visible behind the caption instead of covering it with a box.drawLabels() {
import uiui.begin(import UI_ANCHORSUI_ANCHORS.TOP_LEFT);
import uiui.label('FAR: slow, dim, 1 pixel', { color: stringcolor: 'dim' });
import uiui.label('MED: faster, brighter pixel', { color: stringcolor: 'dim' });
import uiui.label('NEAR: fastest, bright 2x2 block', { color: stringcolor: 'dim' });
import uiui.end();
}
}
// bootstrap finds your canvas, constructs Demo, and runs the game loop for you.
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 DemoParallax starfield with three layers plus an occasional shooting star.Demo);