byo.games

Lua API

Everything a game can call. The sandbox contains this API, standard Lua tables/strings/math, require (game-local), and nothing else — no io, no os, no filesystem.

System

frame() -> n            frames since boot
log(...)                print to the sim/verify log (bots read this)
byo.complete{goal="…"}  declare a goal from game.toml completed
_save() / _load(t)      optional: expose state for snapshots (rarely
                        needed — globals are auto-snapshotted)

Graphics

cls(c)
px(x, y, c)             pget(x, y) -> c
line(x0,y0,x1,y1,c)
rect(x,y,w,h,c,fill?)   circ(x,y,r,c,fill?)   oval(x,y,w,h,c,fill?)
tri(x0,y0,x1,y1,x2,y2,c,fill?)
print(text, x, y, c)    built-in 6×8 font
spr(name, x, y, opts?)  opts: flip_x, flip_y, frame (1-based),
                        scale (integer), rot (radians, nearest-neighbor)
camera(x, y)  camera()  draw offset / reset
clip(x,y,w,h) clip()    scissor / reset

Colors are P.<name> — the palette table (see game format). Index 255 is transparent in sprite data; there is no alpha. Sprites are referenced by name and come from gen/sprites.lua programs or baked assets/sprites/*.spr files.

Map layers

Tile scene rendering: push cells when they change, draw the scene in one call. Cells are sprite names; 16 px per cell.

map.define(layer, w, h)
map.fill(layer, x, y, csv)     rows ';', cells ',', '' = empty
map.set(layer, x, y, name_or_nil)
map.mask(layer, x, y, w, digits) per-cell: '0' hidden '1' dim '2' full
map.draw(layers, px, py, opts?) layers bottom-up;
                                opts { mask = layer, dim = sprite }

The mask + dim-sprite pair is the fog-of-war primitive.

Input

Three devices. A game reads whichever it wants; replays record all of them.

btn(name, player?)  -> held this frame     (player 1 or 2)
btnp(name, player?) -> pressed this frame
mouse() -> x, y, buttons                   buttons: 1 left, 2 right, 4 middle
mousep(b?) -> pressed this frame           b defaults to 1 (left)
text() -> string                           characters typed this frame
                                           ("\b" backspace, "\n" enter)

Button names: up down left right a b x y l r start select. Browser keys — P1: WASD + Z/X/C/V (space/shift alias a/b); P2: arrows + comma/period/slash. Mouse is canvas-relative in machine pixels. The text stream carries all printable typing — build a typing game if you want.

Random & math

rng(stream) -> Rng      Rng:int(a,b)  :float()  :pick(t)
                        :shuffle(t)  :chance(p)
math.random(...)        aliased to the "math.random" stream
math.sin/cos/atan       deterministic console implementations

Modules

require "gen.foo"       loads gen/foo.lua, game-local, cached

Not (yet) in the machine

Audio (sfx/music) and grid pathfinding intrinsics appear in older design notes but are not implemented. If you need pathfinding, write it in Lua in your game — several games do.

The machine's laws

  • Compute budget: _update and _draw each get 4,000k VM instructions per frame (_init gets 25x). Over budget is a game error — an infinite loop dies at the frame it happens, everywhere, deterministically.
  • Snapshot-clean: every verified game must survive the storm test — save, simulate garbage, restore, and the future must be bit-identical. By default the console snapshots every global your game defines (auto mode); keep state in globals and you get this free. State hidden in file-local variables fails the storm — move it or implement _save() -> table / _load(table) yourself. This law is what makes every game rollback-ready for netplay, scrubbing, and save states.