/*
Seeded Worlds: the same number always builds the same world.
@description Two worlds side by side, each labeled with its seed. Copy one seed over and the halves match exactly.
Part of the BLIT386 demo series.
Prerequisites:
- Basics: https://demos.blit386.dev/basics
- Primitives: https://demos.blit386.dev/primitives
- Random Basics: https://demos.blit386.dev/random-basics
Live version: https://demos.blit386.dev/seeded-worlds
Guides:
- Random: https://blit386.dev/docs/guides/random#same-seed-same-world
- Random: https://blit386.dev/docs/guides/random#independent-streams
Reference:
- Random: https://blit386.dev/docs/api/random#state-and-streams
---
WHAT YOU WILL SEE
-----------------
Two little night-time worlds side by side, each inside a 152x100 frame: sixteen stars,
rolling hills, four buildings in four shades of blue-gray, and seven trees. Every one of
them was placed by a random number. Above each world is the number it grew from, called
its "seed". The engine overlay is turned off, so the worlds get the whole screen.
Press the buttons in the bottom-left "New seed" panel to give either side a new seed and
watch it rebuild. Then press "E Copy left -> right" and watch the right world turn into an
exact twin of the left one. Same number in, same world out, every single time. The line
under the worlds says "Same seed, same world" whenever the two seeds match.
The bottom-right panel runs a smaller experiment on a separate generator, titled with the
call that made it: three numbers from the original, three from a clone() of it, and three
from a fork() of it.
WHAT YOU WILL LEARN
-------------------
- BT.randomSeed(seed) tells the shared BT.random generator which number to start from.
- The same seed always produces the same sequence of numbers, so it builds the same world.
- BT.random.seedValue reads that number back - even the one the engine picked by itself
from the clock when the page loaded.
- BT.random.int(min, max) and BT.random.insideRect(rect) place everything in a world;
intInclusive(min, max) picks the seeds.
- new Random(seed) makes a generator of your own, separate from the shared one.
- clone() copies a generator so both continue with the same numbers; fork() splits off a
new generator that goes its own way, and its seedValue is undefined on purpose.
- Nothing moves here, so update() only calls ui.tick() to keep the button key shortcuts
working. All the drawing happens in render(), and isOverlayEnabled: false in
configure() turns the engine overlay off.
WHY IS THIS USEFUL?
-------------------
A game can hand you a puzzle from seed 4821 and know every player gets the identical
puzzle. A saved game can store one number instead of a whole map. And when a run goes
really well, you can write the seed down and play it again.
TRY THIS
--------
- Press Q ("Q Left") or W ("W Right") to reroll one side. New seeds are four digits,
from 1000 to 9999.
- Press E ("E Copy left -> right") and the right world becomes a twin of the left one.
- Read the bottom-right panel: the clone() row matches the original row exactly, and the
fork() row does not. The fork's seedValue reads "none", because a fork was never given
a seed of its own.
- The engine also takes a seed from the URL. Open the demo page on its own with
?embed&seed=4821 added and the left world always starts from 4821, because BT.init()
applies ?seed= to BT.random right before init() runs. The shell page around the demo
does not pass the parameter down to its frame.
*/
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;
... 124 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 RandomSeeded PRNG with integer-first generators and stream control (`seed` / `clone` / `fork`).Random, 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 FONT_WFONT_W, import uiui } from './shared/ui.js';
/** @typedef {import('blit386').Palette} Palette */
// This demo runs at the engine's default screen size of 320x240 "game pixels".
// Each world gets its own framed box. Two boxes, side by side, with a gap between them.
// The frame is drawn one pixel outside the box, so with these numbers the frames sit 4
// pixels in from both screen edges and 4 pixels apart.
const const WORLD_W: 152WORLD_W = 152;
const const WORLD_H: 100WORLD_H = 100;
const const WORLD_Y: 19WORLD_Y = 19;
const const WORLD_LEFT_X: 5WORLD_LEFT_X = 5;
const const WORLD_RIGHT_X: 163WORLD_RIGHT_X = 163;
// The caption above each world, and the status line centered under both.
const const SEED_CAPTION_Y: 10SEED_CAPTION_Y = 10;
const const STATUS_Y: 132STATUS_Y = 132;
// The two panels along the bottom, the same height. Widgets inside a panel start 9
// pixels in from its left edge and are 20 pixels narrower than it.
const const PANEL_H: 87PANEL_H = 87;
const const PANEL_Y: numberPANEL_Y = 240 - 4 - const PANEL_H: 87PANEL_H;
const const SEED_PANEL_X: 4SEED_PANEL_X = 4;
const const SEED_PANEL_W: 150SEED_PANEL_W = 150;
const const STREAM_PANEL_X: 160STREAM_PANEL_X = 160;
const const STREAM_PANEL_W: 156STREAM_PANEL_W = 156;
const const PANEL_CONTENT_X: 9PANEL_CONTENT_X = 9;
const const PANEL_CONTENT_SHRINK: 20PANEL_CONTENT_SHRINK = 20;
// The Seeds panel holds two short buttons side by side, then one full-width button.
// The kit stacks a group's rows, so the panel reserves the space with empty rows and
// each button is placed with its own tiny group on top of it. A group pinned with pad 0
// starts its first row 5 pixels above the y it is given, hence PINNED_ROW_SHIFT.
const const SEED_CONTENT_W: numberSEED_CONTENT_W = const SEED_PANEL_W: 150SEED_PANEL_W - const PANEL_CONTENT_SHRINK: 20PANEL_CONTENT_SHRINK;
const const SEED_BUTTON_GAP: 6SEED_BUTTON_GAP = 6;
const const SEED_HALF_W: numberSEED_HALF_W = (const SEED_CONTENT_W: numberSEED_CONTENT_W - const SEED_BUTTON_GAP: 6SEED_BUTTON_GAP) / 2;
const const BUTTON_ROW_H: 27BUTTON_ROW_H = 27;
const const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP = 22;
const const PINNED_ROW_SHIFT: 5PINNED_ROW_SHIFT = 5;
const const SEED_GRID_X: numberSEED_GRID_X = const SEED_PANEL_X: 4SEED_PANEL_X + const PANEL_CONTENT_X: 9PANEL_CONTENT_X;
const const SEED_GRID_Y: numberSEED_GRID_Y = const PANEL_Y: numberPANEL_Y + const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP;
// The stream panel's value column starts after this many characters, so the longest key,
// "original", keeps a space after it.
const const STREAM_KV_COLS: 9STREAM_KV_COLS = 9;
// The system font's right arrow, for the copy button.
const const ARROW_RIGHT: "→"ARROW_RIGHT = '\u2192';
// How many of each thing lives in a world. Fixed counts keep the two sides comparable:
// only the positions and sizes change from seed to seed.
const const STAR_COUNT: 16STAR_COUNT = 16;
const const BUILDING_COUNT: 4BUILDING_COUNT = 4;
const const TREE_COUNT: 7TREE_COUNT = 7;
// The ground is drawn as a run of columns whose height is worked out from four control
// points. Fewer points means smoother, rounder hills.
const const HILL_POINTS: 4HILL_POINTS = 4;
// Seeds are kept small so they are easy to read and easy to type back in.
const const SEED_MIN: 1000SEED_MIN = 1000;
const const SEED_MAX: 9999SEED_MAX = 9999;
// How many numbers the stream comparison shows for each generator.
const const STREAM_SAMPLE: 3STREAM_SAMPLE = 3;
// Color slots. The shared UI kit owns slots 240-251, so scene colors stay well below
// that.
const const C_SKY: 1C_SKY = 1; // night sky inside each world box
const const C_STAR: 2C_STAR = 2; // stars
const const C_GROUND: 3C_GROUND = 3; // solid earth under the hills
const const C_HILL: 4C_HILL = 4; // the hill surface line
const const C_TREE: 5C_TREE = 5; // tree leaves
const const C_TRUNK: 6C_TRUNK = 6; // tree trunks
const const C_INK: 7C_INK = 7; // frames and bright text
const const C_DIM: 8C_DIM = 8; // faded lines
// Four building tints, in slots 10-13.
const const C_BUILDING_BASE: 10C_BUILDING_BASE = 10;
const const BUILDING_TINTS: 4BUILDING_TINTS = 4;
// Builds one complete world from a single seed.
//
// This is the heart of the demo. Every random number it needs comes from BT.random, and
// BT.random was just told where to start, so running this twice with the same seed walks
// through the exact same numbers in the exact same order - and therefore builds the exact
// same world. Returns the seed plus the hills, buildings, trees, and stars it rolled.
function function generateWorld(seed: any): {
seed: any;
hills: {};
buildings: {};
trees: {};
stars: {};
}
generateWorld(seed: anyseed) {
// This is the line that makes everything below repeatable. From here on the engine's
// random numbers are no longer a surprise - they are decided by `seed`.
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.randomSeed: (seed: number) => voidReseeds the default engine PRNG so subsequent draws are reproducible.
Calling this from the demo's `init()` overrides a `?seed=N` URL parameter, which
`BT.init()` applies just before the demo's `init()`.randomSeed(seed: anyseed);
// Stars first, scattered across the top 60 percent of the box.
const const stars: {}stars = [];
for (let let i: numberi = 0; let i: numberi < const STAR_COUNT: 16STAR_COUNT; let i: numberi++) {
// insideRect() hands back a Vector2i somewhere inside the rectangle we describe -
// like closing your eyes and pointing at a spot on a map.
const stars: {}stars.push(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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
Mutating the instance (for example `BT.random.int(10)`) advances the shared stream.random.Random.insideRect(rect: Rect2i): Vector2iReturns a random integer point inside a rectangle (half-open, like
{@link
Rect2i.isContaining
}
).insideRect(new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2iCreates an integer rectangle, truncating all inputs toward zero.Rect2i(0, 0, const WORLD_W: 152WORLD_W, Math.floor(const WORLD_H: 100WORLD_H * 0.6))));
}
// The hills. We pick a few heights spread across the width, then fill in the columns
// between them by sliding smoothly from one height to the next.
const const controls: {}controls = [];
for (let let i: numberi = 0; let i: numberi < const HILL_POINTS: 4HILL_POINTS; let i: numberi++) {
// int(a, b) gives a whole number from a up to (but not including) b, so these
// heights land somewhere in the lower half of the box.
const controls: {}controls.push(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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
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(Math.floor(const WORLD_H: 100WORLD_H * 0.55), Math.floor(const WORLD_H: 100WORLD_H * 0.8)));
}
const const hills: {}hills = [];
for (let let x: numberx = 0; let x: numberx < const WORLD_W: 152WORLD_W; let x: numberx++) {
// Work out which pair of control points this column sits between, and how far
// along it is between them (0 means "right on the left point", 1 means "right on
// the right").
const const spanW: numberspanW = const WORLD_W: 152WORLD_W / (const HILL_POINTS: 4HILL_POINTS - 1);
const const span: anyspan = Math.min(const HILL_POINTS: 4HILL_POINTS - 2, Math.floor(let x: numberx / const spanW: numberspanW));
const const t: numbert = (let x: numberx - const span: anyspan * const spanW: numberspanW) / const spanW: numberspanW;
// Sliding straight from one height to the next gives sharp corners. Feeding the
// position through this curve first eases in and out, so the hills look rounded.
const const eased: numbereased = const t: numbert * const t: numbert * (3 - 2 * const t: numbert);
const hills: {}hills.push(Math.round(const controls: {}controls[const span: anyspan] + (const controls: {}controls[const span: anyspan + 1] - const controls: {}controls[const span: anyspan]) * const eased: numbereased));
}
// Buildings, sitting on whatever ground height is under them.
const const buildings: {}buildings = [];
for (let let i: numberi = 0; let i: numberi < const BUILDING_COUNT: 4BUILDING_COUNT; let i: numberi++) {
const const w: numberw = 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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
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(12, 24);
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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
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(4, const WORLD_W: 152WORLD_W - const w: numberw - 4);
const const h: numberh = 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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
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(14, 34);
const buildings: {}buildings.push({
x: numberx,
w: numberw,
h: numberh,
// The building stands on the ground under its middle, so its top is that
// ground height minus its own height.
groundY: anygroundY: const hills: {}hills[const x: numberx + Math.floor(const w: numberw / 2)],
// int(4) is shorthand for int(0, 4): 0, 1, 2, or 3. That picks one of the
// four building shades.
tint: numbertint: 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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
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(const BUILDING_TINTS: 4BUILDING_TINTS),
});
}
// Trees, scattered along the ground.
const const trees: {}trees = [];
for (let let i: numberi = 0; let i: numberi < const TREE_COUNT: 7TREE_COUNT; let i: numberi++) {
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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
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(3, const WORLD_W: 152WORLD_W - 4);
const trees: {}trees.push({
x: numberx,
groundY: anygroundY: const hills: {}hills[const x: numberx],
h: numberh: 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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
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(6, 13),
});
}
return { seed: anyseed, hills: {}hills, buildings: {}buildings, trees: {}trees, stars: {}stars };
}
// Two worlds side by side, proving that the same seed rebuilds the same world.
class class DemoDemo {
/** @type {Palette | null} */
Demo.palette: Palette | nullpalette = null;
// Slot map for the shared UI kit theme, filled in by applyTheme() during init().
Demo.theme: nulltheme = null;
// The left world. Each world remembers everything it was built from - that is the
// difference between this demo and the Coordinate Patterns one, which remembers
// nothing at all: https://demos.blit386.dev/coordinate-patterns
Demo.left: nullleft = null;
// The right world.
Demo.right: nullright = null;
// Whether the engine picked the very first seeds by itself. Once the user presses a
// button this turns false, because from then on the seeds were chosen on purpose.
Demo.seedsWereAutomatic: booleanseedsWereAutomatic = true;
// A snapshot of what clone() and fork() do, worked out once in init().
Demo.streams: {
seed: number;
base: {};
cloned: {};
forked: {};
forkSeed: undefined;
}
streams = { seed: numberseed: 0, base: {}base: [], cloned: {}cloned: [], forked: {}forked: [], forkSeed: undefinedforkSeed: var undefinedundefined };
// A private generator used only for choosing new seeds.
//
// It cannot be the shared BT.random, because generateWorld() reseeds that one.
// Drawing the next seed from a stream we just reseeded makes the answer a fixed
// consequence of the seed we reseeded it with - so "Copy left seed" followed by "New
// right" would hand back the very same number every time, and the button would look
// broken.
/** @type {Random | null} */
Demo.seedPicker: Random | nullseedPicker = null;
// Turns the engine overlay off: nothing moves here, so there is no timing to watch,
// and the worlds and panels need the whole screen.
Demo.configure(): {
isOverlayEnabled: boolean;
}
configure() {
return {
isOverlayEnabled: booleanisOverlayEnabled: false,
};
}
// Builds the palette, then grows both worlds from seeds nobody chose.
async Demo.init(): unknowninit() {
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;
... 124 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_SKY: 1C_SKY, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(16, 20, 38)); // night sky
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_STAR: 2C_STAR, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(220, 228, 245)); // stars
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_GROUND: 3C_GROUND, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(28, 40, 32)); // solid earth
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_HILL: 4C_HILL, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(74, 116, 80)); // grassy hill surface
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_TREE: 5C_TREE, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(96, 168, 104)); // leaves
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_TRUNK: 6C_TRUNK, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(90, 68, 48)); // trunks
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_INK: 7C_INK, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(226, 232, 244)); // frames and bright text
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_DIM: 8C_DIM, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(72, 80, 102)); // faded lines
// Four window-lit building shades, from dull to bright.
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_BUILDING_BASE: 10C_BUILDING_BASE + 0, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(64, 72, 96));
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_BUILDING_BASE: 10C_BUILDING_BASE + 1, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(84, 92, 118));
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_BUILDING_BASE: 10C_BUILDING_BASE + 2, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(104, 112, 140));
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_BUILDING_BASE: 10C_BUILDING_BASE + 3, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(126, 134, 162));
// Install the shared UI colors, then hand the finished palette to the engine.
this.Demo.theme: nulltheme = import applyThemeapplyTheme(this.Demo.palette: Palettepalette);
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;
... 124 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);
// Set up the seed picker before anything asks it for a number. Left to itself, a
// new Random seeds from the clock, so this stream is unrelated to the shared one.
this.Demo.seedPicker: Random | nullseedPicker = new new Random(seed?: number): RandomCreates a PRNG. Omit `seed` to time-seed from `Date.now()` (lower 32 bits).Random();
// Nobody has chosen a seed yet. The engine seeded itself from the clock when it
// started up (or from ?seed= in the URL, if there was one), and seedValue hands
// that number back - so we can build a world from it and still show which number
// it was.
//
// That is the part worth noticing: even the run you did not plan has a seed you
// can write down and return to.
this.Demo.left: nullleft = function generateWorld(seed: any): {
seed: any;
hills: {};
buildings: {};
trees: {};
stars: {};
}
generateWorld(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;
... 124 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, or open the page with `?seed=N` - applied in `BT.init()`
just before the demo's own `init()`, so a `randomSeed` call there still wins.
Mutating the instance (for example `BT.random.int(10)`) advances the shared stream.random.Random.seedValue: number | undefinedThe last seed passed to the constructor or
{@link
seed
}
, normalized to an unsigned 32-bit value (the
same representation
{@link
getState
}
uses, not necessarily the raw number passed in). `undefined`
after
{@link
setState
}
(the stream position no longer corresponds to a known seed) or on a
{@link
fork
}
ed child (a fork is a new stream and should not claim to have been seeded by its caller).
{@link
clone
}
copies whatever value the parent currently holds.seedValue ?? const SEED_MIN: 1000SEED_MIN);
this.Demo.right: nullright = function generateWorld(seed: any): {
seed: any;
hills: {};
buildings: {};
trees: {};
stars: {};
}
generateWorld(this.Demo.rollSeed(): numberrollSeed());
this.Demo.sampleStreams(): voidsampleStreams();
return true;
}
// Nothing moves in this demo, so update() only keeps the UI kit's key shortcuts
// alive.
Demo.update(): voidupdate() {
// Always first: this is what makes the { key } shortcuts on buttons work.
import uiui.tick();
}
// Draws both worlds, their seeds, the status line, and the control panels.
//
// The panels come first: their buttons can rebuild a world this very frame, and the
// worlds drawn after them should already show the new one. Shapes are drawn under
// every panel anyway, so the order costs nothing on screen.
Demo.render(): voidrender() {
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;
... 124 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_SKY: 1C_SKY);
this.Demo.renderSeedPanel(): voidrenderSeedPanel();
this.Demo.renderStreamPanel(): voidrenderStreamPanel();
this.Demo.renderWorld(world: any, originX: any): voidrenderWorld(this.Demo.left: nullleft, const WORLD_LEFT_X: 5WORLD_LEFT_X);
this.Demo.renderWorld(world: any, originX: any): voidrenderWorld(this.Demo.right: nullright, const WORLD_RIGHT_X: 163WORLD_RIGHT_X);
// Each world's seed sits directly above it, so there is no doubt which is which.
import uiui.caption(const WORLD_LEFT_X: 5WORLD_LEFT_X, const SEED_CAPTION_Y: 10SEED_CAPTION_Y, `seed ${this.Demo.left: nullleft.seed}`);
import uiui.caption(const WORLD_RIGHT_X: 163WORLD_RIGHT_X, const SEED_CAPTION_Y: 10SEED_CAPTION_Y, `seed ${this.Demo.right: nullright.seed}`);
this.Demo.renderStatusLine(): voidrenderStatusLine();
}
// One centered line under the worlds. When both seeds match, say so plainly - this is
// the moment the demo exists for.
Demo.renderStatusLine(): voidrenderStatusLine() {
let let text: stringtext = 'Different seeds, different worlds';
let let color: stringcolor = 'dim';
if (this.Demo.left: nullleft.seed === this.Demo.right: nullright.seed) {
let text: stringtext = 'Same seed, same world';
let color: stringcolor = 'accent';
} else if (this.Demo.seedsWereAutomatic: booleanseedsWereAutomatic) {
// Nobody typed the left seed: the engine made it from the clock when the demo
// started (or took it from ?seed= in the URL). seedValue is how we can still
// read it.
let text: stringtext = 'The left seed came from the clock';
}
// FONT_W is the width of one character; the last one's empty spacing column is
// left out, so the visible letters are what gets centered.
const const x: numberx = Math.floor(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;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.displaySize: Vector2iActive logical render resolution in pixels.
This is the game/simulation coordinate space configured by the demo, not
the canvas element's CSS size. Each read returns a clone.displaySize.Vector2i.x: numberHorizontal component (defaults to 0).x / 2) - Math.floor((let text: stringtext.length * import FONT_WFONT_W - 1) / 2);
import uiui.caption(const x: numberx, const STATUS_Y: 132STATUS_Y, let text: stringtext, { color: stringcolor });
}
// Draws one world inside its frame. `originX` is the left edge of this world's box on
// screen.
Demo.renderWorld(world: any, originX: any): voidrenderWorld(world: anyworld, originX: anyoriginX) {
// Everything inside a world is stored in "world coordinates" starting at 0, so
// each thing is drawn by adding the box's own corner to it. That is what lets the
// exact same world data be drawn on either side of the screen.
for (const const star: anystar of world: anyworld.stars) {
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;
... 124 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(originX: anyoriginX + const star: anystar.x, const WORLD_Y: 19WORLD_Y + const star: anystar.y), const C_STAR: 2C_STAR);
}
// The ground: one vertical line per column, from the hill surface down to the
// bottom, with a single brighter pixel marking the surface itself.
for (let let x: numberx = 0; let x: numberx < const WORLD_W: 152WORLD_W; let x: numberx++) {
const const top: anytop = world: anyworld.hills[let 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;
... 124 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(originX: anyoriginX + let x: numberx, const WORLD_Y: 19WORLD_Y + const top: anytop),
new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(originX: anyoriginX + let x: numberx, const WORLD_Y: 19WORLD_Y + const WORLD_H: 100WORLD_H - 1),
const C_GROUND: 3C_GROUND,
);
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;
... 124 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(originX: anyoriginX + let x: numberx, const WORLD_Y: 19WORLD_Y + const top: anytop), const C_HILL: 4C_HILL);
}
// Buildings: a filled box in one of the four tints, standing on its ground
// height.
for (const const b: anyb of world: anyworld.buildings) {
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;
... 124 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(originX: anyoriginX + const b: anyb.x, const WORLD_Y: 19WORLD_Y + const b: anyb.groundY - const b: anyb.h, const b: anyb.w, const b: anyb.h), const C_BUILDING_BASE: 10C_BUILDING_BASE + const b: anyb.tint);
}
for (const const t: anyt of world: anyworld.trees) {
// A trunk, then a 5x5 blob of leaves on top of it.
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 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(originX: anyoriginX + const t: anyt.x, const WORLD_Y: 19WORLD_Y + const t: anyt.groundY),
new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(originX: anyoriginX + const t: anyt.x, const WORLD_Y: 19WORLD_Y + const t: anyt.groundY - const t: anyt.h),
const C_TRUNK: 6C_TRUNK,
);
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;
... 124 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(originX: anyoriginX + const t: anyt.x - 2, const WORLD_Y: 19WORLD_Y + const t: anyt.groundY - const t: anyt.h - 3, 5, 5), const C_TREE: 5C_TREE);
}
// The frame goes on last so it sits cleanly on top of everything.
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;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.drawRect: (rect: Rect2i, paletteIndex: number) => voidDraws an unfilled rectangle outline.drawRect(new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2iCreates an integer rectangle, truncating all inputs toward zero.Rect2i(originX: anyoriginX - 1, const WORLD_Y: 19WORLD_Y - 1, const WORLD_W: 152WORLD_W + 2, const WORLD_H: 100WORLD_H + 2), const C_DIM: 8C_DIM);
}
// The three buttons that reseed the worlds: the panel itself with empty rows where
// the buttons go, then each button pinned on top of it (see SEED_GRID_X).
Demo.renderSeedPanel(): voidrenderSeedPanel() {
import uiui.begin('topLeft', { x: numberx: const SEED_PANEL_X: 4SEED_PANEL_X, y: numbery: const PANEL_Y: numberPANEL_Y, width: numberwidth: const SEED_PANEL_W: 150SEED_PANEL_W });
import uiui.panel('New seed');
import uiui.spacer(2 * const BUTTON_ROW_H: 27BUTTON_ROW_H);
import uiui.end();
if (this.Demo.pinnedButton(label: any, key: any, x: any, y: any, width: any): anypinnedButton('Q Left', 'KeyQ', const SEED_GRID_X: numberSEED_GRID_X, const SEED_GRID_Y: numberSEED_GRID_Y, const SEED_HALF_W: numberSEED_HALF_W)) {
this.Demo.left: nullleft = function generateWorld(seed: any): {
seed: any;
hills: {};
buildings: {};
trees: {};
stars: {};
}
generateWorld(this.Demo.rollSeed(): numberrollSeed());
this.Demo.seedsWereAutomatic: booleanseedsWereAutomatic = false;
}
const const rightX: numberrightX = const SEED_GRID_X: numberSEED_GRID_X + const SEED_HALF_W: numberSEED_HALF_W + const SEED_BUTTON_GAP: 6SEED_BUTTON_GAP;
if (this.Demo.pinnedButton(label: any, key: any, x: any, y: any, width: any): anypinnedButton('W Right', 'KeyW', const rightX: numberrightX, const SEED_GRID_Y: numberSEED_GRID_Y, const SEED_HALF_W: numberSEED_HALF_W)) {
this.Demo.right: nullright = function generateWorld(seed: any): {
seed: any;
hills: {};
buildings: {};
trees: {};
stars: {};
}
generateWorld(this.Demo.rollSeed(): numberrollSeed());
this.Demo.seedsWereAutomatic: booleanseedsWereAutomatic = false;
}
const const copyLabel: "E Copy left → right"copyLabel = `E Copy left ${const ARROW_RIGHT: "→"ARROW_RIGHT} right`;
if (this.Demo.pinnedButton(label: any, key: any, x: any, y: any, width: any): anypinnedButton(const copyLabel: "E Copy left → right"copyLabel, 'KeyE', const SEED_GRID_X: numberSEED_GRID_X, const SEED_GRID_Y: numberSEED_GRID_Y + const BUTTON_ROW_H: 27BUTTON_ROW_H, const SEED_CONTENT_W: numberSEED_CONTENT_W)) {
// Nothing about the right world is copied here - only the number. The world
// is rebuilt from scratch, and it comes back identical because the number is
// the same.
this.Demo.right: nullright = function generateWorld(seed: any): {
seed: any;
hills: {};
buildings: {};
trees: {};
stars: {};
}
generateWorld(this.Demo.left: nullleft.seed);
this.Demo.seedsWereAutomatic: booleanseedsWereAutomatic = false;
}
}
// One button in its own tiny group, with its row starting at (x, y). Returns true on
// the frame it is clicked, tapped, or its key is pressed, like ui.button().
Demo.pinnedButton(label: any, key: any, x: any, y: any, width: any): anypinnedButton(label: anylabel, key: anykey, x: anyx, y: anyy, width: anywidth) {
import uiui.begin('topLeft', { x: anyx, y: anyy: y: anyy + const PINNED_ROW_SHIFT: 5PINNED_ROW_SHIFT, pad: numberpad: 0 });
const const pressed: anypressed = import uiui.button(label: anylabel, { key: anykey, width: anywidth });
import uiui.end();
return const pressed: anypressed;
}
// The clone-versus-fork comparison, on a generator of its own.
Demo.renderStreamPanel(): voidrenderStreamPanel() {
import uiui.begin('topLeft', { x: numberx: const STREAM_PANEL_X: 160STREAM_PANEL_X, y: numbery: const PANEL_Y: numberPANEL_Y, width: numberwidth: const STREAM_PANEL_W: 156STREAM_PANEL_W, kvCols: numberkvCols: const STREAM_KV_COLS: 9STREAM_KV_COLS });
import uiui.panel(`new Random(${this.Demo.streams: {
seed: number;
base: {};
cloned: {};
forked: {};
forkSeed: undefined;
}
streams.seed: numberseed})`);
import uiui.kv('original', this.Demo.streams: {
seed: number;
base: {};
cloned: {};
forked: {};
forkSeed: undefined;
}
streams.base: {}base.join(' '));
import uiui.kv('clone()', this.Demo.streams: {
seed: number;
base: {};
cloned: {};
forked: {};
forkSeed: undefined;
}
streams.cloned: {}cloned.join(' '));
import uiui.kv('fork()', this.Demo.streams: {
seed: number;
base: {};
cloned: {};
forked: {};
forkSeed: undefined;
}
streams.forked: {}forked.join(' '));
import uiui.spacer(4);
import uiui.label('clone: same numbers', { color: stringcolor: 'dim' });
// A fork's seedValue is undefined on purpose - see sampleStreams().
import uiui.label(`fork: seedValue ${this.Demo.streams: {
seed: number;
base: {};
cloned: {};
forked: {};
forkSeed: undefined;
}
streams.forkSeed: undefinedforkSeed ?? 'none'}`, { color: stringcolor: 'dim' });
import uiui.end();
}
// Picks a fresh, easy-to-read seed.
Demo.rollSeed(): numberrollSeed() {
// intInclusive(a, b) can return b itself, unlike int(a, b) which stops just short
// of it. For a seed range meant to read as "1000 to 9999", inclusive is what we
// want.
//
// This comes from seedPicker rather than BT.random - see the field for why.
return this.Demo.seedPicker: Random | nullseedPicker.Random.intInclusive(min: number, max: number): numberReturns a pseudo-random integer in [min, max] (inclusive on both ends).intInclusive(const SEED_MIN: 1000SEED_MIN, const SEED_MAX: 9999SEED_MAX);
}
// Works out, once, what clone() and fork() do to a generator.
Demo.sampleStreams(): voidsampleStreams() {
// A generator of our own, separate from the shared BT.random, so sampling it
// cannot disturb the worlds we just built. `new Random(seed)` is how you make
// one.
const const seed: numberseed = this.Demo.rollSeed(): numberrollSeed();
const const original: Randomoriginal = new new Random(seed?: number): RandomCreates a PRNG. Omit `seed` to time-seed from `Date.now()` (lower 32 bits).Random(const seed: numberseed);
// Order matters here. fork() has to draw one number from the parent to seed the
// child, which nudges the parent forward. So we fork FIRST, and only then take
// the clone - that way the original and its clone are standing in the same place,
// and any difference you see between them would be a real one.
const const forked: Randomforked = const original: Randomoriginal.Random.fork(): RandomReturns an independent sub-stream. Advances this generator once to seed the child. The child's
{@link
seedValue
}
is always `undefined` - a fork should not claim a caller-chosen seed.fork();
const const cloned: Randomcloned = const original: Randomoriginal.Random.clone(): RandomReturns a new generator with the same state (identical stream from this point). The copy also shares
this generator's
{@link
seedValue
}
, including `undefined`.clone();
for (let let i: numberi = 0; let i: numberi < const STREAM_SAMPLE: 3STREAM_SAMPLE; let i: numberi++) {
// int(100) is shorthand for int(0, 100): a whole number from 0 to 99.
this.Demo.streams: {
seed: number;
base: {};
cloned: {};
forked: {};
forkSeed: undefined;
}
streams.base: {}base.push(const original: Randomoriginal.Random.int(minOrMaxExclusive: number, maxExclusive?: number): numberReturns a pseudo-random integer in [0, maxExclusive) or [min, maxExclusive).int(100));
this.Demo.streams: {
seed: number;
base: {};
cloned: {};
forked: {};
forkSeed: undefined;
}
streams.cloned: {}cloned.push(const cloned: Randomcloned.Random.int(minOrMaxExclusive: number, maxExclusive?: number): numberReturns a pseudo-random integer in [0, maxExclusive) or [min, maxExclusive).int(100));
this.Demo.streams: {
seed: number;
base: {};
cloned: {};
forked: {};
forkSeed: undefined;
}
streams.forked: {}forked.push(const forked: Randomforked.Random.int(minOrMaxExclusive: number, maxExclusive?: number): numberReturns a pseudo-random integer in [0, maxExclusive) or [min, maxExclusive).int(100));
}
this.Demo.streams: {
seed: number;
base: {};
cloned: {};
forked: {};
forkSeed: undefined;
}
streams.seed: numberseed = const seed: numberseed;
// A fork is a brand new stream, so the engine refuses to claim it was seeded by
// us - its seedValue is deliberately left undefined, which the panel shows as
// "unknown".
this.Demo.streams: {
seed: number;
base: {};
cloned: {};
forked: {};
forkSeed: undefined;
}
streams.forkSeed: undefinedforkSeed = const forked: Randomforked.Random.seedValue: number | undefinedThe last seed passed to the constructor or
{@link
seed
}
, normalized to an unsigned 32-bit value (the
same representation
{@link
getState
}
uses, not necessarily the raw number passed in). `undefined`
after
{@link
setState
}
(the stream position no longer corresponds to a known seed) or on a
{@link
fork
}
ed child (a fork is a new stream and should not claim to have been seeded by its caller).
{@link
clone
}
copies whatever value the parent currently holds.seedValue;
}
}
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 DemoDemo);