BLIT386BLIT386

Assets

Sprite sheets, bitmap fonts, and asset loading.

Looking for audio assets? AudioClip loading is documented separately in API: Audio.

Asset size limits

Sprite sheets, font atlases, and raw indexed buffers share the same decoded-size policy as render configuration (8192 pixels per side, 16,777,216 total pixels). Limits are enforced before canvas readback, CPU buffer retention, GPU texture creation, and software sprite loops.

LimitDefaultApplies to
Max width / height8192Decoded PNGs, font atlas textures, fromIndexedPixels()
Max total pixels16,777,216 (4096×4096)Same sources
Max .btfont JSON size1,048,576 bytes (1 MiB)BitmapFont.load() before JSON.parse()
Max embedded texture payload524,288 bytes (512 KiB)Base64 data in texture when using data:image/png;base64,...
Max glyph count8192Glyph map entries in a .btfont file
Max software blit area16,777,216 pixelsSoftware renderer source rectangles (clipped to the sheet first)

What happens on overflow

When a limit is exceeded, loading throws an AssetLimitError with a beginner-friendly message. The software renderer skips sprite blits whose source rectangle is empty, non-integer, fully outside the sheet, or still too large after clipping.

  • .btfont files may reference either a relative PNG path or an embedded PNG data URI.
  • Embedded textures must use data:image/png;base64,... and stay within the embedded payload cap above.
  • Other data: schemes (for example JPEG) are rejected before image decode.
  • Decoded atlas dimensions use the same width, height, and pixel-area limits as sprite sheets.
  • Prefer separate PNG files for large atlases so the JSON payload stays under the JSON size limit.

Loading assets

AssetLoader Since 0.1.0

AssetLoader caches images by URL so repeated loads share the same HTMLImageElement. Oversized images are rejected as soon as the browser reports decoded dimensions.

import {  } from 'blit386';

// Load a single image (cached by URL)
const  = await .('sprites.png');

// Load multiple images in parallel
const  = await .(['sprites.png', 'tiles.png']);

// Check cache before loading
if (.('sprites.png')) {
  // already cached
}

// Drop a single URL's cache entry (manual reload or tests; blit386/vite watcher usually skips this)
.('sprites.png');

// Number of image loads currently in flight
.;

AssetLoader.evict() is also the manual escape hatch for forcing a fresh load outside the blit386/vite plugin's automatic asset watcher - see Hot Reload.

BT.loadingAssetsCount Since 1.4.0

For a loading-screen indicator that covers both images and audio clips, use BT.loadingAssetsCount - it sums AssetLoader.loadingCount and AudioClip.loadingCount (see API: Audio) and drops back to 0 once every in-flight load has settled:

if (. > 0) {
  // show a spinner or progress bar
}

Sprite setup - preferred path

SpriteSheet Since 0.1.0 IndexedSpriteLoadResult Since 1.0.3

Use SpriteSheet.loadIndexed() for all standard sprite setup. It combines color registration, image loading, and palette indexization in one call.

import { , , ,  } from 'blit386';

const  = new (256);

const  = await .(
  'sprites/hero.png', // URL
  , // palette to populate
  10, // startSlot - first palette slot to write colors into
  { : 'luminance' }, // optional: color order in palette ('luminance' | 'none')
);

.(); // activate AFTER loadIndexed returns

// Draw using the returned sheet and source rectangle:
.(., ., new (20, 20));

// indexed.colors - list of Color32 values registered into the palette
// indexed.srcRect - Rect2i spanning the full image
  • Colors are sorted by perceived luminance (darkest-first) by default.
  • Pass { sort: 'none' } to preserve row-major scan order.
  • Slot 0 is never touched - transparent pixels in the image map to slot 0 at draw time.

Tile grids

Pass tileSize to loadIndexed (or load) when the image is a grid of equal cells. BT.drawTile and sheet.tileRect then find cells for you:

const {  } = await .('/sprites/walk.png', , 1, { : new (16, 24) });

const  = [0, 1, 2, 3].(() => .()); // built once in init()
. = 8; // or set or change it later; null removes the grid

Setting sheet.tileSize (or the tileSize load option) to anything but a positive whole number of at most 8192 (the largest sheet the engine accepts) on both axes throws a RangeError and leaves the grid unchanged. sheet.tileSize reads back as a frozen Vector2i (or null). The grid survives a hot-reload image swap, and lookups are checked against the sheet's current size. See Drawing tiles.

Sprite setup - manual path

Use this only when you need fine-grained control over the palette layout or want to load several sheets into the same palette sequentially.

import { , ,  } from 'blit386';

const  = new (256);

Register colors into the palette

Each call appends the image's colors starting at the given slot; chain them with running offsets to pack several sheets into one palette.

const  = await .('hero.png', , 10);
const  = await .('tiles.png', , 10 + .);

Load the image into a sprite sheet

const  = await .('hero.png');

Convert RGBA pixels to palette indices

indexize() maps every pixel to its palette slot; activate the palette afterward.

.();
.();

To create a sheet from raw palette-indexed pixel data (advanced / test use):

const  = .(, , );

Palette offset

Pass a paletteOffset (per-draw shift, not an absolute slot) to BT.drawSprite() and BT.printFont() to remap stored texel indices before palette lookup. Useful for team-color variations and damage flashes. Terminology and examples: Palette addressing. Draw semantics: API: Rendering.

.(, , , 16); // render in "blue team" color range

Bitmap fonts

BitmapFont Since 0.1.0

Load .btfont files for proportional, palette-indexed bitmap fonts. After loading, register colors in the palette and indexize the font's internal sprite sheet before drawing (same pattern as manual sprite setup).

const  = new (256);
const  = await .('fonts/MyFont.btfont');
.().();
.();

.(, new (10, 10), 'Hello!');
.(, new (10, 10), 'Hello!', ); // per-draw index shift
  • Font rendering goes through the same sprite pipeline as BT.drawSprite() and is auto-batched.
  • SpriteSheet helpers (after indexize): isIndexed(), getIndexedPixels() (defensive copy), getIndexedPixelsRef() (no copy, for hot-path CPU blitting - do not mutate the returned array), reindexize(palette) when palette layout changes. SpriteSheet.loadIndexed() returns IndexedSpriteLoadResult ({ sheet, srcRect, colors }).
  • See Bitmap Fonts Guide for the .btfont format specification and the BMFont conversion workflow (pnpm run convert-font).

Hot-replacing assets

Under a Vite dev server with the blit386/vite plugin installed, editing a sprite sheet's source image or a .btfont file under a watched asset directory (public/ by default) updates the already-loaded asset in place - no page reload, and demo-held references (a SpriteSheet or BitmapFont instance, or an IndexedSpriteLoadResult.sheet) stay valid. A sprite sheet re-runs indexize() against the active palette when it was already indexized; a bitmap font rebuilds its glyph tables and texture.

Demo-held srcRects and dimension changes

If the replacement image has different dimensions, the sheet's size updates to match, but any Rect2i a demo is holding onto as a srcRect into that sheet does not update itself - reconciling it is the demo's own responsibility. Keep sprite sheet dimensions stable during a hot-reload session, or recompute srcRects from the sheet's current width/height when that matters.

Each SpriteSheet also exposes status ('loading' | 'ready' | 'failed') and progress (0 or 1) tracking a replacement fetch while it's in flight - useful for a per-sheet loading indicator, or combine with BT.loadingAssetsCount for a single engine-wide signal. A normally loaded sheet is always 'ready' with progress: 1.0, since its image has already resolved by the time the sheet is constructed.

.; // 'loading' | 'ready' | 'failed'
.; // 0 while loading, 1.0 once ready

Coarse-grained progress

progress is 0 or 1.0, never a value in between - HTMLImageElement reports no byte-level download progress the way AudioClip's onProgress callback does (see API: Audio). Use status/progress to show or hide a loading indicator, not to drive a percentage bar.

See Hot Reload for the full asset type matrix, including audio and the fallback-to-full-reload behavior for unrecognized file types.

System font

A built-in 6×14 monospace font covering printable ASCII (characters 32-126) plus a set of extra glyphs (dashes, accented Latin punctuation, media icons, arrows, Greek capitals, and more) - see scripts/system-font-extra-chars.mjs for the full list. No load step needed.

.(new (10, 10), , 'Score: 100');
.('Score: 100'); // → Vector2i (pixel width, height)
  • Use BT.systemPrint() for demo-specific HUD panels and labels. Call palette.applyHUD() at init so overlay and demo HUD share the same label/header/dim slot conventions - see API: Palette and Palette Presets - HUD.
  • The engine draws its own overlay (present FPS, target FPS, draw calls, frame/update()/render() timings, backend, resolution, demo title) after each render() when isOverlayEnabled is true; see Overlay Guide.
  • For styled variable-width text, use a bitmap font instead.

API history

SymbolSinceLast changedStatus
AssetLoader0.1.0–stable
BitmapFont0.1.01.7.0stable
BT.loadingAssetsCount1.4.0–stable
IndexedSpriteLoadResult1.0.3–stable
SpriteSheet0.1.0–stable

1.7.0 - Fri Sep 11 2026

  • ChangedBitmapFontAdded the `codePoints` getter, returning every Unicode code point the font defines a glyph for (ascending, derived live from the same glyph map `getGlyph()` and `hasGlyph()` read).

1.5.0 - Tue Aug 11 2026

  • ChangedBitmapFont`getGlyph` / `getGlyphByCode` (and `measureText`, which now shares their lookup) substitute a font-defined fallback glyph for a missing character instead of returning `null`, when the font's glyph map has an entry keyed by `U+FFFD`.

1.4.0 - Thu Jul 23 2026

  • AddedBT.loadingAssetsCount

1.0.3 - Sat May 16 2026

  • AddedIndexedSpriteLoadResult

0.1.0 - Sat Dec 13 2025

  • AddedAssetLoader
  • AddedBitmapFont
  • AddedSpriteSheet

See also

Last updated on October 18, 2026

On this page