/*
Coordinate Patterns: an endless world that remembers nothing at all.
@description An endless world computed from hash1i, hash2i, and hash3i that stores no tiles, yet never changes.
Part of the BLIT386 demo series.
Prerequisites:
- Basics: https://demos.blit386.dev/basics
- Primitives: https://demos.blit386.dev/primitives
- Keyboard Input: https://demos.blit386.dev/keyboard-input
- Snake Game: https://demos.blit386.dev/snake-game
- Seeded Worlds: https://demos.blit386.dev/seeded-worlds
Live version: https://demos.blit386.dev/coordinate-patterns
Guides:
- Random: https://blit386.dev/docs/guides/random#procedural-patterns-from-coordinates
- Input: https://blit386.dev/docs/guides/input#keyboard
Reference:
- Random: https://blit386.dev/docs/api/random#coordinate-hashing
---
WHAT YOU WILL SEE
-----------------
A 640x480 screen with no engine overlay. Along the top runs a framed strip of thin blue
bars, one per column. Below it sits a framed window of 32-pixel tiles in four kinds of
ground - water, rock, forest, and grass - with small pale dots (the "rocks") scattered
over about one tile in five. Three panels of the same height sit along the bottom:
"Travel" with the two jump buttons, "This world" with where you are, and "Third number"
with the Layer slider.
You can scroll around forever with the arrow keys, the on-screen D-pad, or a swipe. Press
"J Jump far" to fling yourself 4000 tiles away, then "H Home" to come straight back - and
find every tile exactly where you left it.
Nothing was saved. The demo stores zero tiles. Each square works out what it is from
nothing but its own position.
WHAT YOU WILL LEARN
-------------------
- hash1i(x, seed) turns one whole number into a scrambled-but-repeatable number.
- hash2i(x, y, seed) does the same for a pair, which is what a tile map needs.
- hash3i(x, y, z, seed) adds a third number, so you get a whole new set of answers.
- All three hand back an unsigned 32-bit number, from 0 up to about 4.3 billion; the %
remainder squashes it into a range you can use.
- "Stateless" means the answer is worked out fresh every time, never looked up.
- BT.isKeyDown(code) reads a held arrow key, and isCapturingKeyboardScroll in configure()
keeps the browser page from scrolling along with it.
- ui.dpad.isDown(direction), ui.dpadWidget(), and ui.swipe() from the shared UI kit give
touch screens the same controls.
HOW THIS DIFFERS FROM SEEDED WORLDS
-----------------------------------
The Seeded Worlds demo rolls its hills and trees once and keeps the list in memory:
https://demos.blit386.dev/seeded-worlds
This demo keeps nothing. Ask about tile (4000, -812) and it answers instantly, without
ever having visited it. That is how games fit worlds far too big for memory.
WHY DOES IT LOOK LIKE STATIC?
-----------------------------
Every tile is scrambled on its own, so a tile knows nothing about its neighbors. That is
why the world looks speckled rather than like real countryside, where a lake is one big
lake instead of scattered puddles. Smoothing that speckle into rolling hills is what the
Noise demo is for: https://demos.blit386.dev/noise
TRY THIS
--------
- Hold an arrow key or a D-pad direction to scroll 6 pixels per update tick, or swipe to
jump 320 pixels - half the screen - in one go.
- Press J ("J Jump far") to travel 128000 pixels right and down - 4000 tiles each way -
then H ("H Home") to return to tile (0, 0). Nothing has moved.
- Drag the "Layer" slider in the "Third number" panel from 0 to 8 and watch closely. The
ground comes from hash2i, which only knows x and y, so the landscape stays put. The dots
come from hash3i, which also knows the layer, so they change completely. Same place,
different detail: that is the third number at work.
- The "stored" readout says "0 tiles" however far you go.
*/
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, function hash1i(x: number, seed?: number): numberDeterministic uint32 hash of a 1D integer coordinate.hash1i, function hash2i(x: number, y: number, seed?: number): numberDeterministic uint32 hash of a 2D integer coordinate.hash2i, function hash3i(x: number, y: number, z: number, seed?: number): numberDeterministic uint32 hash of a 3D integer coordinate.hash3i, class Rect2iInteger rectangle for pixel-perfect bounds and regions.
Used throughout the engine for sprite regions, display-space bounds, and
zero-allocation geometry helpers. Both convenience getters and allocation-free
`*To()` helpers are provided so callers can choose between readability and
hot-path efficiency.Rect2i, class Vector2iInteger 2D vector for pixel-perfect positioning.
Used for points, sizes, directions, and camera offsets throughout the engine.
The API includes both allocation-free `*To()` / `*InPlace()` variants and
convenience methods that return new vectors.Vector2i } from 'blit386';
import { import applyThemeapplyTheme, import uiui } from './shared/ui.js';
/** @typedef {import('blit386').Palette} Palette */
// This demo runs at a doubled screen size of 640x480 "game pixels" (the engine default is
// 320x240), so every layout constant below is twice the size it would be at the default.
const const DISPLAY_W: 640DISPLAY_W = 640;
const const DISPLAY_H: 480DISPLAY_H = 480;
// The seed for this whole world. Change this one number and every tile everywhere becomes
// something else.
const const WORLD_SEED: 20260730WORLD_SEED = 20260730;
// Everything sits MARGIN pixels in from the screen edges, and the boxes are GAP apart.
const const MARGIN: 8MARGIN = 8;
const const GAP: 8GAP = 8;
// Three panels along the bottom, side by side and the same height: travel controls, the
// readout, and the layer slider. The row does not split into three whole-pixel thirds, so
// the middle panel takes the two leftover pixels.
const const PANEL_H: 84PANEL_H = 84;
const const PANEL_Y: numberPANEL_Y = const DISPLAY_H: 480DISPLAY_H - const MARGIN: 8MARGIN - const PANEL_H: 84PANEL_H;
const const PANEL_W: 202PANEL_W = 202;
const const MID_PANEL_W: numberMID_PANEL_W = const DISPLAY_W: 640DISPLAY_W - 2 * const MARGIN: 8MARGIN - 2 * const GAP: 8GAP - 2 * const PANEL_W: 202PANEL_W;
const const MID_PANEL_X: numberMID_PANEL_X = const MARGIN: 8MARGIN + const PANEL_W: 202PANEL_W + const GAP: 8GAP;
const const RIGHT_PANEL_X: numberRIGHT_PANEL_X = const MID_PANEL_X: numberMID_PANEL_X + const MID_PANEL_W: numberMID_PANEL_W + const GAP: 8GAP;
// Widgets inside a panel start 9 pixels in from its left edge and are 20 pixels narrower
// than it, and the first row starts 22 pixels down, under the title.
const const PANEL_CONTENT_X: 9PANEL_CONTENT_X = 9;
const const PANEL_CONTENT_SHRINK: 20PANEL_CONTENT_SHRINK = 20;
const const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP = 22;
const const PANEL_CONTENT_W: numberPANEL_CONTENT_W = const PANEL_W: 202PANEL_W - const PANEL_CONTENT_SHRINK: 20PANEL_CONTENT_SHRINK;
// The Travel panel's two buttons sit side by side. The kit stacks a group's rows, so the
// panel reserves one button row and each button is placed with its own tiny group on
// top. A group pinned with pad 0 starts its first row 5 pixels above the y it is given.
const const BUTTON_ROW_H: 27BUTTON_ROW_H = 27;
const const TRAVEL_BUTTON_W: numberTRAVEL_BUTTON_W = (const PANEL_CONTENT_W: numberPANEL_CONTENT_W - const GAP: 8GAP) / 2;
const const PINNED_ROW_SHIFT: 5PINNED_ROW_SHIFT = 5;
// Each panel's rows fall short of PANEL_H by a different amount; this much empty space
// before each panel's closing note makes all three the same height, with the note at the
// bottom.
const const TRAVEL_NOTE_GAP: 14TRAVEL_NOTE_GAP = 14;
const const WORLD_NOTE_GAP: 11WORLD_NOTE_GAP = 11;
const const LAYER_NOTE_GAP: 5LAYER_NOTE_GAP = 5;
// The readout panel's value column starts after this many characters.
const const WORLD_KV_COLS: 8WORLD_KV_COLS = 8;
// The 1D strip along the top, inside a frame: one bar per column. Its caption sits just
// above the frame.
const const STRIP_RECT: Rect2iSTRIP_RECT = new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2iCreates an integer rectangle, truncating all inputs toward zero.Rect2i(const MARGIN: 8MARGIN, 22, const DISPLAY_W: 640DISPLAY_W - 2 * const MARGIN: 8MARGIN, 46);
// The tile grid below it, also framed, filling the space down to the panels.
const const GRID_TOP: 90GRID_TOP = 90;
const const GRID_RECT: Rect2iGRID_RECT = new new Rect2i(x?: number, y?: number, width?: number, height?: number): Rect2iCreates an integer rectangle, truncating all inputs toward zero.Rect2i(const MARGIN: 8MARGIN, const GRID_TOP: 90GRID_TOP, const DISPLAY_W: 640DISPLAY_W - 2 * const MARGIN: 8MARGIN, const PANEL_Y: numberPANEL_Y - const GAP: 8GAP - const GRID_TOP: 90GRID_TOP);
const const TILE: 32TILE = 32;
// Captions sit this far above the frame they describe.
const const CAPTION_ABOVE: 10CAPTION_ABOVE = 10;
// How fast holding a direction scrolls, in pixels per update tick.
const const SCROLL_SPEED: 6SCROLL_SPEED = 6;
// A swipe throws you this many pixels, and "Jump far" this many.
const const SWIPE_DISTANCE: 320SWIPE_DISTANCE = 320;
const const JUMP_DISTANCE: 128000JUMP_DISTANCE = 128000;
// How many layers the slider can reach.
const const LAYER_MAX: 8LAYER_MAX = 8;
// Color slots. The shared UI kit owns slots 240-251, so scene colors stay well below
// that.
const const C_BG: 1C_BG = 1; // background behind everything
const const C_STRIP: 2C_STRIP = 2; // the 1D bars along the top
const const C_INK: 3C_INK = 3; // frames and bright marks
const const C_DIM: 4C_DIM = 4; // faded lines
const const C_ROCK_DOT: 5C_ROCK_DOT = 5; // the little decorations that hash3i places
// The four kinds of ground, in slots 10-13. The order matters: the number a tile hashes
// to is compared against the thresholds below in this same order.
const const C_TERRAIN_BASE: 10C_TERRAIN_BASE = 10;
const const TERRAIN_WATER: 0TERRAIN_WATER = 0;
const const TERRAIN_ROCK: 1TERRAIN_ROCK = 1;
const const TERRAIN_TREE: 2TERRAIN_TREE = 2;
const const TERRAIN_GRASS: 3TERRAIN_GRASS = 3;
// Works out what kind of ground sits at a tile, using nothing but the tile's position.
// `tx` and `ty` are the tile column and row; either can be any whole number, including
// huge and negative ones. Returns one of the TERRAIN_* values.
//
// There is no list of tiles anywhere in this demo. This function is the world: hand it a
// position and it hands back the ground, the same answer every time, forever.
function function terrainAt(tx: any, ty: any): 2 | 1 | 3 | 0terrainAt(tx: anytx, ty: anyty) {
// hash2i scrambles the two coordinates together into a big number. "Scrambled" is the
// point: neighboring tiles get wildly different answers, so the world looks random
// even though nothing random ever happened.
//
// The number can be up to about 4.3 billion, which is far too big to be useful.
// Taking the remainder after dividing by 100 (that is what % does) squashes it down
// to 0-99, which is easy to split into slices.
const const value: numbervalue = function hash2i(x: number, y: number, seed?: number): numberDeterministic uint32 hash of a 2D integer coordinate.hash2i(tx: anytx, ty: anyty, const WORLD_SEED: 20260730WORLD_SEED) % 100;
if (const value: numbervalue < 12) {
return const TERRAIN_WATER: 0TERRAIN_WATER; // 12 tiles in every 100
}
if (const value: numbervalue < 26) {
return const TERRAIN_ROCK: 1TERRAIN_ROCK; // 14 in every 100
}
if (const value: numbervalue < 42) {
return const TERRAIN_TREE: 2TERRAIN_TREE; // 16 in every 100
}
return const TERRAIN_GRASS: 3TERRAIN_GRASS; // the remaining 58
}
// An endless scrollable world computed from coordinates, storing nothing.
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;
// Where we are looking, measured in world pixels, left to right. Together with camY
// this is the only thing the demo remembers about the world - and it is a position,
// not a map.
Demo.camX: numbercamX = 0;
// Where we are looking, top to bottom.
Demo.camY: numbercamY = 0;
// Which layer the third hash coordinate is reading.
Demo.layer: numberlayer = 0;
// Runs the demo at 640x480. Arrow keys normally scroll the browser page, but this
// demo uses them to scroll the world instead, so it asks the engine to keep the page
// still while you play.
Demo.configure(): {
displaySize: Vector2i;
isCapturingKeyboardScroll: boolean;
isOverlayEnabled: boolean;
}
configure() {
return {
displaySize: Vector2idisplaySize: new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(const DISPLAY_W: 640DISPLAY_W, const DISPLAY_H: 480DISPLAY_H),
isCapturingKeyboardScroll: booleanisCapturingKeyboardScroll: true,
isOverlayEnabled: booleanisOverlayEnabled: false,
};
}
// Builds the palette. There is no world to build - that is the whole idea.
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_BG: 1C_BG, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(12, 14, 22)); // background
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_STRIP: 2C_STRIP, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(126, 195, 255)); // 1D bars
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_INK: 3C_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 marks
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_DIM: 4C_DIM, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(64, 72, 92)); // faded lines
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_ROCK_DOT: 5C_ROCK_DOT, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(232, 214, 160)); // hash3i decorations
// The four kinds of ground: water, rock, forest, and grass.
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_TERRAIN_BASE: 10C_TERRAIN_BASE + const TERRAIN_WATER: 0TERRAIN_WATER, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(44, 82, 140));
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_TERRAIN_BASE: 10C_TERRAIN_BASE + const TERRAIN_ROCK: 1TERRAIN_ROCK, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(104, 104, 112));
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_TERRAIN_BASE: 10C_TERRAIN_BASE + const TERRAIN_TREE: 2TERRAIN_TREE, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(52, 118, 68));
this.Demo.palette: Palettepalette.Palette.set(index: number, color: Color32): voidWrites a color into a palette slot.set(const C_TERRAIN_BASE: 10C_TERRAIN_BASE + const TERRAIN_GRASS: 3TERRAIN_GRASS, new new Color32(r?: number, g?: number, b?: number, a?: number): Color32Creates a clamped 8-bit RGBA color.Color32(86, 150, 84));
// 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);
return true;
}
// Moves the view. Nothing else changes, because there is nothing else.
Demo.update(): voidupdate() {
// Always first: this latches key shortcuts, the D-pad, and the swipe recognizer.
import uiui.tick();
// Held keys and the on-screen D-pad both scroll. isKeyDown is "held right now",
// which is safe to read from either update() or render() - unlike a key press,
// which is a one-off event and must be read here.
if (const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isKeyDown: (key: string) => booleanChecks whether a keyboard key is currently held.
Uses `KeyboardEvent.code` (for example `"KeyW"`, `"Space"`, `"ArrowUp"`).isKeyDown('ArrowLeft') || import uiui.dpad.isDown('left')) {
this.Demo.camX: numbercamX -= const SCROLL_SPEED: 6SCROLL_SPEED;
}
if (const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isKeyDown: (key: string) => booleanChecks whether a keyboard key is currently held.
Uses `KeyboardEvent.code` (for example `"KeyW"`, `"Space"`, `"ArrowUp"`).isKeyDown('ArrowRight') || import uiui.dpad.isDown('right')) {
this.Demo.camX: numbercamX += const SCROLL_SPEED: 6SCROLL_SPEED;
}
if (const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isKeyDown: (key: string) => booleanChecks whether a keyboard key is currently held.
Uses `KeyboardEvent.code` (for example `"KeyW"`, `"Space"`, `"ArrowUp"`).isKeyDown('ArrowUp') || import uiui.dpad.isDown('up')) {
this.Demo.camY: numbercamY -= const SCROLL_SPEED: 6SCROLL_SPEED;
}
if (const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 124 more ...;
spritesRefresh: () => void;
}
Main BLIT386 API namespace used by runtime demos.BT.isKeyDown: (key: string) => booleanChecks whether a keyboard key is currently held.
Uses `KeyboardEvent.code` (for example `"KeyW"`, `"Space"`, `"ArrowUp"`).isKeyDown('ArrowDown') || import uiui.dpad.isDown('down')) {
this.Demo.camY: numbercamY += const SCROLL_SPEED: 6SCROLL_SPEED;
}
// A swipe throws the view half a screen's width (320 pixels) in one go, so a
// phone can cover ground without holding anything down. Swiping left pulls the
// world left, so the view moves right - the same way a photo moves under your
// finger.
const const swipe: anyswipe = import uiui.swipe();
if (const swipe: anyswipe === 'left') {
this.Demo.camX: numbercamX += const SWIPE_DISTANCE: 320SWIPE_DISTANCE;
} else if (const swipe: anyswipe === 'right') {
this.Demo.camX: numbercamX -= const SWIPE_DISTANCE: 320SWIPE_DISTANCE;
} else if (const swipe: anyswipe === 'up') {
this.Demo.camY: numbercamY += const SWIPE_DISTANCE: 320SWIPE_DISTANCE;
} else if (const swipe: anyswipe === 'down') {
this.Demo.camY: numbercamY -= const SWIPE_DISTANCE: 320SWIPE_DISTANCE;
}
}
// Draws the panels, the 1D strip, and the tile grid.
//
// The panels come first: Jump far, Home, and the Layer slider change what the strip
// and grid show, and those drawn after them should already show it. 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_BG: 1C_BG);
this.Demo.renderTravelPanel(): voidrenderTravelPanel();
this.Demo.renderWorldPanel(): voidrenderWorldPanel();
this.Demo.renderLayerPanel(): voidrenderLayerPanel();
import uiui.caption(const STRIP_RECT: Rect2iSTRIP_RECT.Rect2i.x: numberLeft edge X coordinate (defaults to 0).x, const STRIP_RECT: Rect2iSTRIP_RECT.Rect2i.y: numberTop edge Y coordinate (defaults to 0).y - const CAPTION_ABOVE: 10CAPTION_ABOVE, 'hash1i: one number in');
import uiui.caption(const GRID_RECT: Rect2iGRID_RECT.Rect2i.x: numberLeft edge X coordinate (defaults to 0).x, const GRID_RECT: Rect2iGRID_RECT.Rect2i.y: numberTop edge Y coordinate (defaults to 0).y - const CAPTION_ABOVE: 10CAPTION_ABOVE, 'hash2i and hash3i: a whole world');
this.Demo.renderStrip(): voidrenderStrip();
this.Demo.renderGrid(): voidrenderGrid();
// The D-pad draws itself and appears once the demo has seen a touch. The margin
// lifts it off the panels, into the bottom-right corner of the grid.
import uiui.dpadWidget({ margin: numbermargin: const DISPLAY_H: 480DISPLAY_H - (const GRID_RECT: Rect2iGRID_RECT.Rect2i.y: numberTop edge Y coordinate (defaults to 0).y + const GRID_RECT: Rect2iGRID_RECT.Rect2i.height: numberHeight in pixels (defaults to 0).height) + const GAP: 8GAP });
}
// Draws one bar per column inside the strip's frame, using the 1D hash.
Demo.renderStrip(): voidrenderStrip() {
// Inside the 1-pixel frame: the bars stand on the bottom row and can reach the
// top.
const const left: numberleft = const STRIP_RECT: Rect2iSTRIP_RECT.Rect2i.x: numberLeft edge X coordinate (defaults to 0).x + 1;
const const right: numberright = const STRIP_RECT: Rect2iSTRIP_RECT.Rect2i.x: numberLeft edge X coordinate (defaults to 0).x + const STRIP_RECT: Rect2iSTRIP_RECT.Rect2i.width: numberWidth in pixels (defaults to 0).width - 2;
const const floorY: numberfloorY = const STRIP_RECT: Rect2iSTRIP_RECT.Rect2i.y: numberTop edge Y coordinate (defaults to 0).y + const STRIP_RECT: Rect2iSTRIP_RECT.Rect2i.height: numberHeight in pixels (defaults to 0).height - 2;
const const maxH: numbermaxH = const STRIP_RECT: Rect2iSTRIP_RECT.Rect2i.height: numberHeight in pixels (defaults to 0).height - 2;
for (let let sx: numbersx = const left: numberleft; let sx: numbersx <= const right: numberright; let sx: numbersx++) {
// The bar belongs to a world column, not a screen column, so it scrolls with
// the view instead of sitting still. Math.floor keeps it a whole number even
// when the camera is at a negative position.
const const worldColumn: numberworldColumn = Math.floor(this.Demo.camX: numbercamX) + let sx: numbersx - const left: numberleft;
// Same trick as the tiles: scramble the coordinate, then squash the huge
// result down to a height that fits the strip.
const const height: numberheight = function hash1i(x: number, seed?: number): numberDeterministic uint32 hash of a 1D integer coordinate.hash1i(const worldColumn: numberworldColumn, const WORLD_SEED: 20260730WORLD_SEED) % const maxH: numbermaxH;
if (const height: numberheight > 0) {
const BT: {
FLIP_H: number;
FLIP_V: number;
ROT_90_CW: number;
ROT_180_CW: number;
ROT_270_CW: number;
BTN_UP: number;
BTN_DOWN: number;
BTN_LEFT: number;
BTN_RIGHT: number;
BTN_A: number;
BTN_B: number;
BTN_X: number;
BTN_Y: number;
BTN_L: number;
BTN_R: number;
BTN_START: number;
BTN_SELECT: number;
BTN_POINTER_A: number;
BTN_POINTER_B: number;
BTN_POINTER_C: number;
BTN_POINTER_D: number;
PLAYER_ONE: number;
PLAYER_TWO: number;
PLAYER_THREE: number;
PLAYER_FOUR: number;
AXIS_LEFT_X: number;
AXIS_LEFT_Y: number;
AXIS_RIGHT_X: number;
AXIS_RIGHT_Y: number;
AXIS_TRIGGER_L: number;
... 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(let sx: numbersx, const floorY: numberfloorY), new new Vector2i(x?: number, y?: number): Vector2iCreates an integer 2D vector, truncating inputs toward zero.Vector2i(let sx: numbersx, const floorY: numberfloorY - const height: numberheight + 1), const C_STRIP: 2C_STRIP);
}
}
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(const STRIP_RECT: Rect2iSTRIP_RECT, const C_DIM: 4C_DIM);
}
// Draws every tile currently inside the grid's frame, working each one out from its
// position.
Demo.renderGrid(): voidrenderGrid() {
// The window the tiles fill: inside the 1-pixel frame.
const const left: numberleft = const GRID_RECT: Rect2iGRID_RECT.Rect2i.x: numberLeft edge X coordinate (defaults to 0).x + 1;
const const top: numbertop = const GRID_RECT: Rect2iGRID_RECT.Rect2i.y: numberTop edge Y coordinate (defaults to 0).y + 1;
const const right: numberright = const GRID_RECT: Rect2iGRID_RECT.Rect2i.x: numberLeft edge X coordinate (defaults to 0).x + const GRID_RECT: Rect2iGRID_RECT.Rect2i.width: numberWidth in pixels (defaults to 0).width - 1;
const const bottom: numberbottom = const GRID_RECT: Rect2iGRID_RECT.Rect2i.y: numberTop edge Y coordinate (defaults to 0).y + const GRID_RECT: Rect2iGRID_RECT.Rect2i.height: numberHeight in pixels (defaults to 0).height - 1;
// Which tile is under the top-left corner of the window, and how far into that
// tile we are. The leftover is what makes scrolling look smooth instead of
// jumping a whole tile at a time.
const const firstTileX: anyfirstTileX = Math.floor(this.Demo.camX: numbercamX / const TILE: 32TILE);
const const firstTileY: anyfirstTileY = Math.floor(this.Demo.camY: numbercamY / const TILE: 32TILE);
const const offsetX: numberoffsetX = this.Demo.camX: numbercamX - const firstTileX: anyfirstTileX * const TILE: 32TILE;
const const offsetY: numberoffsetY = this.Demo.camY: numbercamY - const firstTileY: anyfirstTileY * const TILE: 32TILE;
// One extra column and row so the partly-visible tiles at the edges still get
// drawn.
const const cols: anycols = Math.ceil((const right: numberright - const left: numberleft) / const TILE: 32TILE) + 1;
const const rows: anyrows = Math.ceil((const bottom: numberbottom - const top: numbertop) / const TILE: 32TILE) + 1;
for (let let row: numberrow = 0; let row: numberrow < const rows: anyrows; let row: numberrow++) {
const const tileTop: numbertileTop = const top: numbertop + let row: numberrow * const TILE: 32TILE - const offsetY: numberoffsetY;
// The grid is a window, not the whole screen, so every tile is trimmed to the
// part that lands inside it. Without this, tiles would spill over the frame
// and cover the caption above and the panels below.
const const visibleTop: anyvisibleTop = Math.max(const tileTop: numbertileTop, const top: numbertop);
const const visibleH: numbervisibleH = Math.min(const tileTop: numbertileTop + const TILE: 32TILE, const bottom: numberbottom) - const visibleTop: anyvisibleTop;
// A row that ended up entirely outside the window has nothing to draw.
if (const visibleH: numbervisibleH <= 0) {
continue;
}
for (let let col: numbercol = 0; let col: numbercol < const cols: anycols; let col: numbercol++) {
const const tileLeft: numbertileLeft = const left: numberleft + let col: numbercol * const TILE: 32TILE - const offsetX: numberoffsetX;
const const visibleLeft: anyvisibleLeft = Math.max(const tileLeft: numbertileLeft, const left: numberleft);
const const visibleW: numbervisibleW = Math.min(const tileLeft: numbertileLeft + const TILE: 32TILE, const right: numberright) - const visibleLeft: anyvisibleLeft;
if (const visibleW: numbervisibleW <= 0) {
continue;
}
const const tx: anytx = const firstTileX: anyfirstTileX + let col: numbercol;
const const ty: anyty = const firstTileY: anyfirstTileY + let row: numberrow;
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(const visibleLeft: anyvisibleLeft, const visibleTop: anyvisibleTop, const visibleW: numbervisibleW, const visibleH: numbervisibleH),
const C_TERRAIN_BASE: 10C_TERRAIN_BASE + function terrainAt(tx: any, ty: any): 2 | 1 | 3 | 0terrainAt(const tx: anytx, const ty: anyty),
);
// The decoration is the only thing that knows about the layer. hash3i
// takes a third coordinate, so changing the layer gives a completely
// different answer for the very same tile - while the ground underneath,
// which came from hash2i, does not budge. One tile in five (a remainder
// under 20 out of 100) gets a 6x6 dot.
//
// A dot is too small to be worth trimming, so one that would poke outside
// the window is simply left out.
const const dotX: numberdotX = const tileLeft: numbertileLeft + 12;
const const dotY: numberdotY = const tileTop: numbertileTop + 12;
const const isDotInside: booleanisDotInside = const dotX: numberdotX >= const left: numberleft && const dotX: numberdotX + 6 <= const right: numberright && const dotY: numberdotY >= const top: numbertop && const dotY: numberdotY + 6 <= const bottom: numberbottom;
if (const isDotInside: booleanisDotInside && function hash3i(x: number, y: number, z: number, seed?: number): numberDeterministic uint32 hash of a 3D integer coordinate.hash3i(const tx: anytx, const ty: anyty, this.Demo.layer: numberlayer, const WORLD_SEED: 20260730WORLD_SEED) % 100 < 20) {
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(const dotX: numberdotX, const dotY: numberdotY, 6, 6), const C_ROCK_DOT: 5C_ROCK_DOT);
}
}
}
// The frame goes on last so it sits on top of the tiles.
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(const GRID_RECT: Rect2iGRID_RECT, const C_DIM: 4C_DIM);
}
// The travel panel: Jump far and Home side by side, placed on top of the panel (see
// PINNED_ROW_SHIFT), and a reminder of the scroll controls.
Demo.renderTravelPanel(): voidrenderTravelPanel() {
const const x: 8x = const MARGIN: 8MARGIN;
import uiui.begin('topLeft', { x: numberx, y: numbery: const PANEL_Y: numberPANEL_Y, width: numberwidth: const PANEL_W: 202PANEL_W });
import uiui.panel('Travel');
import uiui.spacer(const BUTTON_ROW_H: 27BUTTON_ROW_H + const TRAVEL_NOTE_GAP: 14TRAVEL_NOTE_GAP);
import uiui.label('Arrows, D-pad, or swipe', { color: stringcolor: 'dim' });
import uiui.end();
const const buttonY: numberbuttonY = const PANEL_Y: numberPANEL_Y + const PANEL_CONTENT_TOP: 22PANEL_CONTENT_TOP + const PINNED_ROW_SHIFT: 5PINNED_ROW_SHIFT;
import uiui.begin('topLeft', { x: numberx: const x: 8x + const PANEL_CONTENT_X: 9PANEL_CONTENT_X, y: numbery: const buttonY: numberbuttonY, pad: numberpad: 0 });
if (import uiui.button('J Jump far', { key: stringkey: 'KeyJ', width: numberwidth: const TRAVEL_BUTTON_W: numberTRAVEL_BUTTON_W })) {
// Thousands of tiles in one step. A world held in memory could not do this -
// there would be nothing out there yet. Here there is nothing to prepare.
this.Demo.camX: numbercamX += const JUMP_DISTANCE: 128000JUMP_DISTANCE;
this.Demo.camY: numbercamY += const JUMP_DISTANCE: 128000JUMP_DISTANCE;
}
import uiui.end();
import uiui.begin('topLeft', { x: numberx: const x: 8x + const PANEL_CONTENT_X: 9PANEL_CONTENT_X + const TRAVEL_BUTTON_W: numberTRAVEL_BUTTON_W + const GAP: 8GAP, y: numbery: const buttonY: numberbuttonY, pad: numberpad: 0 });
if (import uiui.button('H Home', { key: stringkey: 'KeyH', width: numberwidth: const TRAVEL_BUTTON_W: numberTRAVEL_BUTTON_W })) {
// Coming home proves the point: the tiles are exactly as they were, because
// they were never stored and never had a chance to drift.
this.Demo.camX: numbercamX = 0;
this.Demo.camY: numbercamY = 0;
}
import uiui.end();
}
// Where we are, and the number that matters most.
Demo.renderWorldPanel(): voidrenderWorldPanel() {
import uiui.begin('topLeft', { x: numberx: const MID_PANEL_X: numberMID_PANEL_X, y: numbery: const PANEL_Y: numberPANEL_Y, width: numberwidth: const MID_PANEL_W: numberMID_PANEL_W, kvCols: numberkvCols: const WORLD_KV_COLS: 8WORLD_KV_COLS });
import uiui.panel('This world');
import uiui.kv('tile x', Math.floor(this.Demo.camX: numbercamX / const TILE: 32TILE));
import uiui.kv('tile y', Math.floor(this.Demo.camY: numbercamY / const TILE: 32TILE));
// The headline. However far you travel, this never moves off zero.
import uiui.kv('stored', '0 tiles');
import uiui.spacer(const WORLD_NOTE_GAP: 11WORLD_NOTE_GAP);
import uiui.label('Every tile is worked out fresh.', { color: stringcolor: 'dim' });
import uiui.end();
}
// The layer slider and what it does.
Demo.renderLayerPanel(): voidrenderLayerPanel() {
import uiui.begin('topLeft', { x: numberx: const RIGHT_PANEL_X: numberRIGHT_PANEL_X, y: numbery: const PANEL_Y: numberPANEL_Y, width: numberwidth: const PANEL_W: 202PANEL_W });
import uiui.panel('Third number');
// The slider answers a number anywhere in its range; rounding snaps it to whole
// layers, so the knob steps instead of sliding between two different worlds.
this.Demo.layer: numberlayer = Math.round(
import uiui.slider('Layer', this.Demo.layer: numberlayer, { min: numbermin: 0, max: numbermax: const LAYER_MAX: 8LAYER_MAX, width: numberwidth: const PANEL_CONTENT_W: numberPANEL_CONTENT_W, decimals: numberdecimals: 0 }),
);
import uiui.spacer(const LAYER_NOTE_GAP: 5LAYER_NOTE_GAP);
import uiui.label('Ground ignores the layer.', { color: stringcolor: 'dim' });
import uiui.label('Rocks do not.', { color: stringcolor: 'dim' });
import uiui.end();
}
}
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);