Rendering
Primitives, sprites, text, post-process effects, and frame capture.
All draw calls require a palette to be active (BT.paletteSet(palette) before the first BT.drawSprite,
BT.drawRectFill, etc.). All coordinates are integer pixels - use Vector2i and Rect2i, never floats.
Palette addressing: primitives and BT.systemPrint use an absolute paletteIndex. Sprites and BT.printFont use an
optional paletteOffset added to stored texel indices. See Palette addressing.
Primitives
BT.clear Since 0.1.0
BT.clearRect Since 0.1.0
BT.drawPixel Since 0.1.0
BT.drawLine Since 0.1.0
BT.drawRect Since 0.1.0
BT.drawRectFill Since 0.1.0
.(); // fill entire display
.(, ); // fill rectangular region
.(, ); // Vector2i overload
.(, , ); // numeric overload
.(, , ); // pixel-perfect, no antialiasing
.(, ); // outline only
.(, ); // filledAll primitives write palette indices, not RGBA - the active palette resolves colors at frame end.
Two more primitive-drawing demos: pixel art from number grids, and animated mathematical patterns.
Sprites
Drawing
BT.drawSprite Since 0.1.0
.(, , );
.(, , , );sheet- an indexedSpriteSheet(must have been prepared vialoadIndexedorindexize).srcRect- source region within the sheet in pixels.destPos- top-left destination in display coordinates.paletteOffset- shift added to every stored pixel index before palette lookup (default0).
Palette offset semantics
Stored sprite indices start at 1; index 0 is always transparent and discarded. The offset shifts every stored index
before the palette lookup, so a pixel stored at index i renders as palette[i + paletteOffset]:
| Stored index | paletteOffset = 0 | paletteOffset = N |
|---|---|---|
1 | palette[1] | palette[1 + N] |
2 | palette[2] | palette[2 + N] |
This drives palette-swap effects such as team colors or damage flashes - point the same sprite at a different band of the palette without re-uploading texels.
- Validation:
paletteOffsetmust be a non-negative integer below the active palette size, or the draw throws. - Clamping: the WebGPU sprite shader computes
combined = storedIndex + paletteOffsetand clamps withindex = min(combined, 255u), so any sum past255maps to palette slot255.
.(, , new (10, 10)); // normal
.(, , new (10, 10), 16); // blue-team shiftDraws are auto-batched by texture. Group draws from the same sheet to minimize GPU state changes.
Drawing tiles
BT.drawTile Since 1.8.0
Most retro sprite sheets are grids of equal cells. Give the sheet a tile size once, then draw cells by index or by
column and row - no Rect2i math:
const { } = await .('/sprites/hero.png', , 1, { : 16 });
.(, , new (40, 60)); // row-major index: 0 is top-left
.(, 2, 1, new (40, 60)); // column 2, row 1
.(, 2, 1, new (40, 60), 16); // with a palette offsetA sheet with no grid can still be drawn by passing the tile size before the position:
.(, 2, 1, 8, new (40, 60)); // 8×8 tiles
.(, 2, 1, new (16, 24), new (40, 60)); // 16×24 tiles- Grid:
tileSizeis a number for square tiles or aVector2ifor width × height. Set it with thetileSizeoption ofSpriteSheet.load/loadIndexed, orsheet.tileSize = 16at any time. See Assets. - Whole tiles only: a 40-pixel-wide sheet of 16-pixel tiles has 2 columns; the 8-pixel strip at the edge is not a tile.
- Errors: a tile outside the grid, a negative or fractional index, column, or row, a tile size that is not a positive whole number, or a grid form on a sheet with no tile size all show an error instead of drawing.
- Watch the argument order:
BT.drawTile(sheet, col, row, new Vector2i(16, 16))is the grid form with a destination, not the explicit-size form with a missing destination. - No allocation:
BT.drawTilereuses one internal source rectangle, so it is safe in a hot loop. sheet.tileRect(index)/sheet.tileRect(col, row)return the same rectangle as a newRect2i- use them ininit()to build frame lists.Rect2i.fromTile(col, row, tileSize)does the math with no sheet at all.- To flip or rotate a tile, use
BT.drawSprite(sheet, sheet.tileRect(i), dest, params)- see Flipping, rotating, and scaling sprites.drawTilehas no params object and notint.
Flipping, rotating, and scaling sprites
BT.FLIP_H Since 0.1.0
BT.FLIP_V Since 0.1.0
BT.ROT_90_CW Since 0.1.0
BT.ROT_180_CW Since 0.1.0
BT.ROT_270_CW Since 0.1.0
SpriteDrawParams Since 1.8.0
Pass a SpriteDrawParams object as the 4th argument instead of a number to flip a sprite or turn it in quarter turns.
One sprite sheet frame then covers every facing direction:
// Allocate once, outside the draw loop, with every field present.
const : SpriteDrawParams = { : 0, : , : 1, : 0 };
. = ? . : 0;
.(, , , );
. = . | .; // combine bits with |
.(, , , );
. = 0;
. = 2; // twice as big, nearest-neighbor
.(, , , );
. = new (3, 1); // three times as wide
.(, , , );
. = 1;
.(, , new (10, 40, 50, 12), ); // stretch into a boxTo place a sprite by a point other than its top-left - its feet, its center - set pivot:
const : SpriteDrawParams = { : 0, : new (8, 16), : 2, : 0 };
// Bottom center of the 16×16 source lands on `feet`, at any scale.
.(, , , );
. = ; // back to top-left placementThe sprites demo walks through every SpriteDrawParams field: recolor, flip, quarter turns, scale, pivot, and stretch.
.; // horizontal flip
.; // vertical flip
.; // rotate 90° clockwise
.; // rotate 180°
.; // rotate 270° clockwise// 1. Fast path. Unchanged since 0.1.0.
declare function (: , : , : , ?: number): void;
// 2. Params object (1.8.0). `params` is required here: without it, a call is overload 1.
declare function (: , : , : | , : SpriteDrawParams): void;
interface SpriteDrawParams {
?: number; // any combination of BT.FLIP_* / BT.ROT_* bits (default 0)
?: | undefined; // source pixels, before flags (default undefined: top-left placement)
?: number | ; // positive integers, screen axes, after flags (default 1)
?: number; // same meaning as the fast-path 4th argument (default 0)
}- The params object is read, never mutated or retained. The engine copies the numbers it needs before returning, so a
hot loop can allocate
paramsonce and write fields between draws. Create it with every field present so its shape never changes. In TypeScript, annotate it asSpriteDrawParamssoscalecan later hold aVector2i. - With a
Vector2idest and nopivot,destis the top-left of the post-flags, post-scale footprint. A 16×32 sprite withROT_90_CWcovers 32×16, growing down and right fromdest; withscale: 2it covers 64×32. pivotis a point in source pixels relative to the source rect's top-left, in[0, sw] x [0, sh](corner coordinates, not texel indices). It goes through the flags (the "Point map" column in Transform order and flags), is multiplied byscale, and that point lands ondest. Values outside the sprite are fine. The result is an integer move of the footprint, so pixels are identical on both backends.- An explicit
pivotof(0, 0)is not the same as no pivot. It is the source's top-left corner, which moves when the sprite is flipped or turned. For a 16×16 sprite withFLIP_H, no pivot draws the footprint atdest, whilepivot: new Vector2i(0, 0)draws it 16 pixels to the left, because the source's left edge is now on the right. To return a reused params object to top-left placement, assignparams.pivot = undefined. - With a
Rect2idest, the rectangle is that footprint already: flags only remap texels into it, they do not resize it. Anyscaleother thanundefined,1, or(1, 1)shows an error, because the rect already fixes the box. A definedpivotshows an error too, whatever thescale. The neutralscale: 1keeps one reused params object valid for both dest kinds. An empty or negative source or destination rect draws nothing. scaleis a positive integer (numberorVector2i).scale: 2andscale: new Vector2i(2, 2)match, andscale.xis on-screen width even after a 90-degree turn.0, negatives, and non-integers show an error that points at aRect2idest for uneven sizes.paletteOffsetworks exactly as on the fast path, with the same validation. The only recolor control ispaletteOffset: on the fast path or inside params, never both.- The flipped draw is the same on both backends, pixel for pixel. The fast path (a number or nothing as the 4th
argument) is untouched: two
typeofchecks and no allocation.
Dispatch
Runtime dispatch reads argument 2, then argument 4:
- A
Rect2isource: if argument 4 is an object, it isSpriteDrawParams;nulland arrays show an error. Otherwise (numberorundefined, defaulting to0) it ispaletteOffsetand the fast path. - A string source (a named frame) or raw source numbers show an error for now - see Planned: sprite draw params.
With params, dest instanceof Rect2i is the stretch box and dest instanceof Vector2i is a point; anything else shows
an error. A Rect2i dest needs the params form: drawSprite(sheet, src, destRect) and
drawSprite(sheet, src, destRect, 0) are type errors - write drawSprite(sheet, src, destRect, {}). In development
builds the fast path also shows an error for a Rect2i from untyped code, instead of drawing it unscaled at the
rectangle's top-left; release builds skip the check.
Transform order and flags
params.flagsuses theBT.FLIP_*/BT.ROT_*bits (values unchanged since 0.1.0). Booleans are not accepted.- Every combination inside
0x1fis valid and composes in one fixed order:FLIP_H, thenFLIP_V, then clockwise quarter turns, countingROT_90_CWas 1,ROT_180_CWas 2, andROT_270_CWas 3, summed modulo 4. SoROT_90_CW | ROT_180_CWisROT_270_CW, andROT_180_CW | FLIP_H | FLIP_Vis the identity. Only bits outside0x1f(and non-integers) show an error, naming the constants. - The 32 possible masks reduce to the 8 orientations below. The engine resolves a mask through one 32-entry lookup; the software texel remap and the GPU UV corner order both read the same per-orientation table.
- Arbitrary-angle rotation is not supported. There is no
rotationfield, so a leftoverrotationon an object literal is a type error rather than a silent no-op.
The 8 orientations for a 3×2 source ABC / DEF (sw = 3, sh = 2). "Point map" sends a point (x, y) in
[0, sw] x [0, sh] to the post-flags footprint:
| Orientation | Shortest flags | Also spelled | Result | Footprint | Point map |
|---|---|---|---|---|---|
| 0 | 0 | ROT_180_CW | FLIP_H | FLIP_V | ABC / DEF | sw x sh | (x, y) |
| 1 | FLIP_H | ROT_180_CW | FLIP_V | CBA / FED | sw x sh | (sw - x, y) |
| 2 | FLIP_V | ROT_180_CW | FLIP_H | DEF / ABC | sw x sh | (x, sh - y) |
| 3 | ROT_180_CW | FLIP_H | FLIP_V | FED / CBA | sw x sh | (sw - x, sh - y) |
| 4 | ROT_90_CW | ROT_270_CW | FLIP_H | FLIP_V | DA / EB / FC | sh x sw | (sh - y, x) |
| 5 | ROT_90_CW | FLIP_H | ROT_270_CW | FLIP_V | FC / EB / DA | sh x sw | (sh - y, sw - x) |
| 6 | ROT_90_CW | FLIP_V | ROT_270_CW | FLIP_H | AD / BE / CF | sh x sw | (y, x) |
| 7 | ROT_270_CW | ROT_90_CW | FLIP_H | FLIP_V | CF / BE / AD | sh x sw | (y, sw - x) |
Texel remap uses the same point map on the texel center: source texel (i, j) lands on footprint texel
map(i + 0.5, j + 0.5) - (0.5, 0.5), which is the point map with sw - 1 and sh - 1 in place of sw and sh. For
example, FLIP_H sends column 0 to column sw - 1.
Stretch sampling
Nearest-neighbor for a post-flags footprint (fw, fh) stretched into (dw, dh) samples each destination pixel at its
center, with exact ties rounding down. All operands are non-negative integers, so the division truncates:
const = .(((2 * + 1) * - 1) / (2 * ));
const = .(((2 * + 1) * - 1) / (2 * ));The footprint texel then maps back to a source texel through the orientation's texel remap.
- For
dw = fwand for every integer scale (dw = fw * scale) this givesfloor(dx * fw / dw), so 1:1 and integer-scale output equal the plain block copy. Only unevenRect2istretches depend on the center rule. - A GPU samples at pixel centers too, but in float32 an exact tie can land on either texel. The WebGPU path removes that
risk without shader changes: shift both footprint-space UV edges by
-1 / (4 * dw)texels on x (and-1 / (4 * dh)on y) before sending them through the orientation's point map. Every non-tie sample sits at least1 / (2 * dw)texels from a texel edge, so the shift moves ties down and leaves other samples on their texel. - A float32 simulation of that shifted interpolation, using normalized UVs on a 1024-wide sheet, matched the formula on 964,800 of 964,800 cases (source widths 1-48, destination widths 1-200). Center sampling without the shift missed 7,466 of them. The simulation is not a GPU run. The visual-regression suite renders uneven stretches on both backends and compares them pixel for pixel.
- The 1:1 fast path keeps today's UV and blit math. Scaled and stretched quads use a separate emission path so 1:1 vertices stay identical.
Nine-slice panels
BT.drawNineSlice Since 1.8.0
NineSlice Since 1.8.0
NineSliceOptions Since 1.8.0
NineSliceMode Since 1.8.0
A nine-slice splits one sprite region into 4 corners, 4 edges, and a center, so a single piece of panel art fills boxes of any size. Corners always draw at 1:1. Edges and center either stretch or tile.
// init(): build once. Both rects are in sheet coordinates; inner marks the center.
const = .(, new (0, 0, 16, 16), new (4, 4, 8, 8), {
: 'tile', // default 'stretch'
: 'stretch', // default 'stretch'
});
// render(): any box, every frame; drawNineSlice itself allocates nothing.
.(, new (20, 20, 120, 64));
.(, new (20, 100, 120, 64), 16); // palette offset, as in drawSpriteNineSlice.fromSheet(sheet, outer, inner, options?)copies and freezes both rects.outermust lie inside the sheet,innerinsideouterand at least 1×1. Invalid arguments throw.innermay touchouter: a zero-size top and bottom gives a horizontal three-slice bar.'stretch'fills the box with the stretch sampling rule, so it matchesdrawSpritewith aRect2idest pixel for pixel on both backends. Edges stretch only along their length.'tile'repeats the strip at 1:1 from the box's top-left corner. The last tile on the right and bottom is cropped, so nothing draws outsidedestRect.- When
destRectis smaller than the two corners on an axis, the corners share it in proportion to their sizes (left and top take the floor) and are cropped from their outer side; the edges between them disappear. A panel can animate open from size 0 and keep its outline. - An empty or negative
destRectdraws nothing. Recolor ispaletteOffsetonly - there is no tint. - Each tile is one quad. Tiling a 1-pixel strip across a large box costs one quad per pixel and can fill the sprite buffer; stretch flat fills and tile only patterned art.
Planned: sprite draw params
Planned - not shipped
This section locks the rest of the unified BT.drawSprite v2 shape decided in BT-236 (revised 2026-10-04). Overload 2
with flags, pivot, paletteOffset, scale, and a Vector2i or Rect2i dest shipped in 1.8.0 - see
Flipping, rotating, and scaling sprites. Only overloads 3 and 4 and the
SpritePack / SpriteFrame types have not shipped yet.
All transform features hang off one reusable params object. Separate methods stay separate:
BT.drawTile and BT.drawNineSlice are not further drawSprite overloads.
There is no drawSpriteScaled, no tint, and no arbitrary-angle rotation field in v1.
// 1. Shipped. Unchanged.
declare function (: , : , : , ?: number): void;
// 2. Shipped (1.8.0).
declare function (: , : , : | , : SpriteDrawParams): void;
// 3. Planned named frame (BT-271). Always takes the params path, so `params` may be omitted.
declare function (
: ,
: string,
: | ,
?: SpriteDrawParams,
): void;
// 4. Reserved for BT-262 (milestone 2.0.0). Not added to the 1.8.0 types.
declare function (
: ,
: number,
: number,
: number,
: number,
: number,
: number,
?: number | SpriteDrawParams,
): void;
interface SpriteDrawParams {
?: number; // shipped
?: | undefined; // shipped: source pixels, before flags; assign undefined to reset
?: number | ; // shipped
?: number; // shipped
}
// Planned (BT-271). Returned by SpritePack.frame(name).
interface SpriteFrame {
readonly : ; // the page this frame lives on
readonly : ; // frozen
readonly ?: ; // frozen; filled from Aseprite slice data by BT-364, never applied implicitly
}| undefined on pivot is deliberate: a reused params object returns to the default pivot with
params.pivot = undefined, and that assignment must type-check under exactOptionalPropertyTypes.
Planned dispatch
The shipped dispatch extends as follows:
typeof src === 'string': overload 3.typeof src === 'number': overload 4. Both throw until their tickets ship.- Create a reused params object with every field present, so its shape never changes, annotated as
SpriteDrawParams:const params: SpriteDrawParams = { flags: 0, pivot: undefined, scale: 1, paletteOffset: 0 }. - Overload 3 is
SpritePack.frame(name)followed by overload 2 withframe.sheetandframe.rect. A missing name throws.frame(name)returns the same frozenSpriteFrameon every call, so a hot loop resolves it once and callsBT.drawSprite(frame.sheet, frame.rect, dest)(overload 1) or addsparams(overload 2) - no per-draw string lookup. Carrying the sheet on the frame keeps multi-page packs correct and makes a rect from the wrong sheet impossible.frame.pivotis data only: passparams.pivot = frame.pivotto use it. There is no raw-number form for names. - Overload 4, when it exists, cannot express a destination rectangle, so stretched draws (a
Rect2idest) use overload 2. Raw numbers occupy arguments 2-7; argument 8 ispaletteOffsetorSpriteDrawParams, same split as the fast path, soflags, integerscale, andpivotwork as on overload 2 with a point dest. - Planned transform order: flags, then integer scale, then placement.
scalemultiplies the post-flags footprint in screen axes, soscale.xis on-screen width even after a 90-degree turn. If a later ticket adds an arbitrary angle, the unit is degrees, and the software renderer throws until it can match pixel-for-pixel.
Color
The only recolor control is paletteOffset, on the fast path or inside params, never both and never stacked. There is
no tint field. Older sketches that used Color32 / ColorArg as a 4th argument are stale.
Software-renderer parity
No accepted field degrades. The software blit is the pixel spec; WebGPU must match it. The 1:1 path stays today's
blitIndexedRect / UV quad emission.
| Field | Parity |
|---|---|
paletteOffset | Already matched. Same validation as today. |
flags | Pixel-exact source-index remap via the orientation table, then the same transparency and palette rules. Not a shader-only effect. |
pivot | Pixel-exact integer move of the footprint origin. Components copied into the queued command. |
scale / dest: Rect2i | Pixel-exact nearest-neighbor using the stretch sampling formula above, on both backends. |
rotation | Not accepted. A future software path throws rather than skipping the rotation. |
| Frame name | Resolved to a SpriteFrame, then the same path. No separate software behavior. |
| Nine-slice | Not a sprite-params field. Shipped as BT.drawNineSlice: stretched parts inherit the stretch sampling formula, tiled parts are 1:1 blits. |
GPU note for implementers: the vertex shader already passes UVs through. Flags and scale are CPU-side corner and UV writes into the existing 5-value vertex, with the orientation table choosing which source corner goes to which quad corner. Do not add a flags uniform on the 1:1 path.
Refreshing after a palette-layout swap
BT.spritesRefresh Since 1.0.3
.();
.(); // re-maps all tracked sheets to the new slot positionsLayout swap only
Call spritesRefresh() only after a palette-layout swap - when the same colors have moved to different slot indices. Do
NOT call it after a palette-value swap (when you changed what color a slot holds). In the value-swap case the fragment
shader picks up the new color automatically; calling spritesRefresh() is wasteful and will fail reindexing if original
RGBA values are gone.
Text
System font
BT.systemPrint Since 1.0.3
BT.systemPrintMeasure Since 1.0.3
BT.systemFont Since 1.7.0
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.
BT.systemFont returns the same BitmapFont instance BT.systemPrint draws with, as a live reference. Read
codePoints on it to see every Unicode code point it covers, or pass it to BT.printFont directly.
.(, , ); // draw text at pos
.(); // → Vector2i (pixel width × height)
...; // how many characters the system font covers
..('é'); // test one character before drawing itBitmap fonts
BT.printFont Since 0.1.0
Variable-width fonts from .btfont files. The font's sprite sheet must be indexized before use.
const = await .('fonts/MyFont.btfont');
.(, , );
.(, , , ); // palette-swap variantSee Bitmap Fonts Guide for the .btfont format spec and BMFont conversion.
Post-process effects
Two-tier fullscreen effect pipeline - a pixel tier and a display tier - running between scene render and swap-chain present, with a palette-resolve-and-upscale step bridging the two:
- Pixel tier - operates on the logical
r8uintframebuffer (one palette index per pixel). Effects here stay palette-native (chunky glitch, mosaic). - Palette resolve + upscale -
PaletteResolveUpscalePassconverts indices to RGBA through the active palette LUT and upscales todrawingBufferSize. - Display tier - operates on the RGBA output image. Hosts CRT scanlines, barrel distortion, bloom, etc. Requires
drawingBufferSizein hardware settings.
Both chains add zero cost when empty.
WebGPU only
Post-process is unsupported by the Canvas 2D software backend - calling effectAdd in software mode throws a clear
error. Gate effect registration on BT.activeBackend === 'webgpu'.
BT.effectAdd Since 1.0.3
BT.effectRemove Since 1.0.3
BT.effectClear Since 1.0.3
BT.preset Since 1.0.3
crtPipBoy Since 1.0.3
amber Since 1.0.3
green Since 1.0.3
// Add effect - routed to pixel or display chain by Effect.tier automatically
.(new ());
.(new ());
.(new ());
.(new ()); // pixel-tier effect
// Remove or clear
.(); // remove one; no-op if not found
.(); // remove all from both chains
// One-line presets (return arrays of Effect)
for (const of ..()) .();
for (const of ..()) .();
for (const of ..()) .();
// Equivalent standalone imports also work:
import { , , } from 'blit386';
for (const of ()) .();Built-in effects:
| Tier | Classes |
|---|---|
| Pixel | PixelGlitch, PixelMosaic |
| Display | BarrelDistortion, Scanlines, RGBMask, Vignette, ChromaticAberration, Flicker, RollLine, Interference, Noise, Bloom |
All effect classes are exported from 'blit386'. Each instance owns its own GPU resources and may be mutated each frame
from demo code.
See Post-Process Effects Guide for parameter reference, the Effect interface,
EffectTier, the FullscreenEffect base class, and how to write a custom effect.
Frame capture
BT.captureFrame Since 1.0.3
BT.downloadFrame Since 1.0.3
FrameCaptureOptions Since 1.7.1
FrameCaptureSize Since 1.7.1
Capture the current rendered frame as a PNG.
// Resolves after the next render pass completes
const = await .();
const = .();
// Convenience: capture and trigger a browser download
await .();
await .('screenshot-001.png'); // custom filename
// Exact game pixels: one PNG pixel per logical pixel, at BT.displaySize
const = await .({ : 'display' });
await .('exact.png', { : 'display' });Which size?
FrameCaptureOptions.size picks the resolution, and with it what the PNG contains:
size | PNG dimensions | Contains | Use for |
|---|---|---|---|
'output' (default) | BT.outputSize (drawingBufferSize ?? displaySize) | Upscaled frame plus display-tier post-process effects (scanlines, vignette, bloom, ...) | What the player sees: screenshot buttons, sharing |
'display' | BT.displaySize | Logical frame resolved straight from the palette-indexed scene buffer: no upscale, no display-tier effects (pixel-tier effects such as PixelGlitch are included) | Tests, agents, image diffs, pixel art export |
A game with no configure() inherits the default 640×480 drawing buffer for a 320×240 logical display, so
BT.captureFrame() returns a 640×480 PNG where every game pixel is a 2×2 block. { size: 'display' } returns the
320×240 original. Neither size is affected by how the browser scales the canvas on screen.
While BT.renderAt holds the loop stopped, BT.captureFrame() re-renders the seeked frame to capture it instead of
waiting for a loop frame that never comes, and runs after every renderAt called before it - see
Seeking a frame with renderAt.
The display-size capture has its own pending slot on the renderer, separate from the F9 / Shift+F9 dev shortcuts, so an
agent's capture and a keypress in the same frame both resolve. Two { size: 'display' } requests in the same frame
still reject the earlier one, as two BT.captureFrame() calls always have.
Internal implementation note
BT.captureFrame() uses the internal FrameCapture class (src/utils/FrameCapture.ts), which is not exported from
'blit386'. Demos should use BT.captureFrame() and BT.downloadFrame() only.
The F9 / Shift+F9 dev-mode capture shortcuts use the same logical-size path as { size: 'display' }, from their own
pending slot - see the resolution model in Core for details.
API history
| Symbol | Since | Last changed | Status |
|---|---|---|---|
amber | 1.0.3 | – | stable |
BT.captureFrame | 1.0.3 | 1.8.0 | stable |
BT.clear | 0.1.0 | – | stable |
BT.clearRect | 0.1.0 | – | stable |
BT.downloadFrame | 1.0.3 | 1.7.1 | stable |
BT.drawLine | 0.1.0 | – | stable |
BT.drawNineSlice | 1.8.0 | – | stable |
BT.drawPixel | 0.1.0 | – | stable |
BT.drawRect | 0.1.0 | – | stable |
BT.drawRectFill | 0.1.0 | – | stable |
BT.drawSprite | 0.1.0 | 1.8.0 | stable |
BT.drawTile | 1.8.0 | – | stable |
BT.effectAdd | 1.0.3 | – | stable |
BT.effectClear | 1.0.3 | – | stable |
BT.effectRemove | 1.0.3 | – | stable |
BT.FLIP_H | 0.1.0 | 1.8.0 | stable |
BT.FLIP_V | 0.1.0 | 1.8.0 | stable |
BT.preset | 1.0.3 | – | stable |
BT.printFont | 0.1.0 | – | stable |
BT.ROT_180_CW | 0.1.0 | 1.8.0 | stable |
BT.ROT_270_CW | 0.1.0 | 1.8.0 | stable |
BT.ROT_90_CW | 0.1.0 | 1.8.0 | stable |
BT.spritesRefresh | 1.0.3 | – | stable |
BT.systemFont | 1.7.0 | – | stable |
BT.systemPrint | 1.0.3 | – | stable |
BT.systemPrintMeasure | 1.0.3 | – | stable |
crtPipBoy | 1.0.3 | – | stable |
FrameCaptureOptions | 1.7.1 | – | stable |
FrameCaptureSize | 1.7.1 | – | stable |
green | 1.0.3 | – | stable |
NineSlice | 1.8.0 | – | stable |
NineSliceMode | 1.8.0 | – | stable |
NineSliceOptions | 1.8.0 | – | stable |
SpriteDrawParams | 1.8.0 | – | stable |
1.8.0
- Changed
BT.FLIP_HAccepted by `BT.drawSprite` through `SpriteDrawParams.flags`. - Changed
BT.FLIP_VAccepted by `BT.drawSprite` through `SpriteDrawParams.flags`. - Changed
BT.ROT_180_CWAccepted by `BT.drawSprite` through `SpriteDrawParams.flags`. - Changed
BT.ROT_270_CWAccepted by `BT.drawSprite` through `SpriteDrawParams.flags`. - Changed
BT.ROT_90_CWAccepted by `BT.drawSprite` through `SpriteDrawParams.flags`. - Changed
BT.captureFrameWhile `BT.renderAt` holds the loop stopped, re-renders the seeked frame to capture it instead of waiting for the next loop frame. Runs after every `BT.renderAt` called before it, so an un-awaited seek's frame is the one captured. - Added
BT.drawNineSlice - Changed
BT.drawSpriteAdded the `SpriteDrawParams` 4th-argument form for flips, quarter turns, integer scale, pivot placement, and Rect2i stretch destinations. - Added
BT.drawTile - Added
NineSlice - Added
NineSliceMode - Added
NineSliceOptions - Added
SpriteDrawParams
1.7.1 - Sun Sep 27 2026
- Changed
BT.captureFrameAccepts `{ size: 'display' }` to capture at logical `BT.displaySize`. - Changed
BT.downloadFrameAccepts `{ size: 'display' }` to download at logical `BT.displaySize`. - Added
FrameCaptureOptions - Added
FrameCaptureSize
1.7.0 - Fri Sep 11 2026
- Added
BT.systemFont
1.0.3 - Sat May 16 2026
- Added
BT.captureFrame - Added
BT.downloadFrame - Added
BT.effectAdd - Added
BT.effectClear - Added
BT.effectRemove - Added
BT.preset - Added
BT.spritesRefresh - Added
BT.systemPrint - Added
BT.systemPrintMeasure - Added
amber - Added
crtPipBoy - Added
green
0.1.0 - Sat Dec 13 2025
- Added
BT.FLIP_H - Added
BT.FLIP_V - Added
BT.ROT_180_CW - Added
BT.ROT_270_CW - Added
BT.ROT_90_CW - Added
BT.clear - Added
BT.clearRect - Added
BT.drawLine - Added
BT.drawPixel - Added
BT.drawRect - Added
BT.drawRectFill - Added
BT.drawSprite - Added
BT.printFont
See also
API: Core
API: Palette
Palette Guide
API: Assets
Post-Process Effects
Bitmap Fonts
Testing
Performance Best Practices
Last updated on October 18, 2026