Consoles and Constrained Targets

The profiles page names the levers a build sets, and the target matrix records settings that have been built. This page is about the hardware behind those settings: what a console or handheld actually offers, and why a given lever position is the right one for it.

Nothing here is a mode the system provides. The same runtime codebase and the same language reach every one of these machines; what differs is which positions the hardware forces you into, and a machine not listed here is reached the same way — by setting the levers its limits imply.

Three properties of the hardware drive nearly every choice: does it have a floating-point unit, how much RAM does it have, and how does it read its cartridge. Read down any platform’s column and those three answers explain the rest.

The floating-point line decides the number

pit’s number is always an IEEE bit pattern — the NaN-banking representations (nan64, nan32) store references and small integers inside the unused NaN space of a real float. That banking needs the bit layout, not the silicon: a CPU with no FPU reproduces the same patterns through a soft-float library, bit-for-bit.

So the FPU is a speed question, not a representation question, and it splits the platforms in two:

  • Has an FPU — desktop, web, Playdate, PSP (VFPU), Dreamcast (SH-4), N64. These run nan32 on the hardware float unit directly. Numbers are fast.
  • No FPU but reproducible floats — PS1. It runs the same nan32 representation over a soft-float library. Every float operation is a library call, so numeric code is slower, but a PS1 program computes bit-identical results to a desktop one, and — critically — can address a cartridge, because a nan32 integer covers the whole offset range a cart needs.

The very smallest machines have neither an FPU nor room for a soft-float library, and they take a different representation entirely — a fixed-point or 16-bit-word profile — covered under the small end below.

Memory is the whole design

On a desktop the memory budget is invisible. On a console it is the design. A profile picks its garbage collector and its cartridge access mode almost entirely to fit a fixed RAM number:

ClassExampleRAM orderCollectorCartridge access
desktop / webdarwin, emscriptengigabytescopyingmapped
large consoleN64~4 MBcompactingstaged
small consolePS1~2 MBcompactingstreamed
handheldPlaydate, PSPtens of MBcopying → compactingmapped
tiny handheld(GBA-class)~256 KBcompactingmapped / XIP
nanoGame Boy, NES~8 KBarena / compactingbanked

The collector choice follows directly. A copying collector is fast but needs headroom — it wants roughly twice the live set free to collect into, which a desktop has and a 2 MB console does not. Below a threshold the profile switches to a compacting collector (Jonkers-style), which slides live objects down in place and needs no second space. A game actor with a 1 MB heap on a 2 MB console cannot afford the 2× a copying collector wants, so the small consoles compact.

Reaching the cartridge

A cartridge is one format, but the bytes are physically reached in whichever way the hardware allows, and the profile names that mode:

  • Mapped — the whole cart is addressable at once: a desktop mmap, web MEMFS, a GBA executing in place from ROM, or a handheld that loads the cart into RAM once. A region simply points into the mapping. Loading is mounting.
  • Staged — the cart is addressable but slow to reach (N64 reads its ROM over PI DMA; the NDS reads its card over DMA). A pager copies code and asset segments into a small RAM cache on demand, and the profile sizes that cache to the budget.
  • Streamed — the cart is a slow sequential medium (a PS1 CD: hundreds of KB/s with long seeks). The same pager runs over sector reads, and the cart’s sections are laid out at build time for locality so a play session seeks as little as possible.
  • Banked — the smallest machines see the cart through a hardware bank window. The build obeys the constraint directly: no code object may span a bank, and cross-bank references go through per-bank tables.

The pager is one mechanism; only its backing — a fast mapping, a DMA copy, a sector read — changes between staged and streamed.

Regions and images

Every profile lays memory out as regions — a small table the runtime installs at boot. A region is a contiguous span with one job: the mutable heap, a static image mapped from a cart, a RAM-resident image. On the 64-bit desktop profile regions are found by address range, the way the collector already works. On the 32-bit profiles a reference is a compact region-plus-offset pair, so the machine addresses far more image than a raw 32-bit pointer into one flat space would reach, and the garbage collector knows without a test that only the dynamic region is ever collected — image regions are immovable by construction.

This is what lets a constrained target hold most of its program as immovable, mapped image and collect only the small mutable remainder. It is the same idea as image layout on a desktop, made load-bearing by scarcity.

Booting: two tiers, one path

Whatever the machine, booting a cart is the same short sequence: read the header, install the region table, mount the images, point the first actor’s frame at its entry, and run. That sequence is small enough to fit on a GBA, and it is the only loader path in the runtime.

What differs is what the cart contains:

  • Loader tier — a console or handheld. The cart is a sealed, target-final image; mounting it verifies the profile stamp and hands control to the entry actor. There is no compiler and nothing to resolve at run time; every decision was made when the cart was built.
  • Workshop tier — a development machine. It mounts a development cart — this repository’s toolchain, itself a set of actors: the compilers, the build program, the signer, the shell. After mounting, the shop opens its mutable overlay and realization cache and listens on the daemon socket. Running a bundle or bare source builds a cart first (content-addressed, so it happens once) and mounts it through the very same path.

The division of labor is constant across tiers: the C runtime is mechanism — parse the header, install regions, start an actor from an index — while the shop is policy and lookup, in pit, reading the mounted image to resolve a locator, run policy over the claims it finds there, and ask the runtime to spawn. A console simply ships the mechanism with the answers already frozen in.

The small end

Below the consoles, two machines stop being able to afford an IEEE float at all — no FPU and no room for a soft-float library. They keep the language and lose only the number’s internal shape:

  • Tiny handhelds (GBA-class, NDS) hold numbers as fixed-point in a 32-bit word. Arithmetic is integer arithmetic on the hardware, with no library calls. The trade is range: a fixed-point word covers a smaller span of whole numbers than a float, which is comfortable for most game math and is an active design constraint where a program addresses large structures directly. The NDS reaches its cart by staged card DMA and collects with a compacting collector; its second processor, the ARM7, is met the same way every other platform surface is — as native firmware endowments over the inter-processor FIFO. That FIFO is transport inside a provider, not a second scheduler and not a second mailbox system: the runtime keeps one scheduler and one mailbox, and the ARM7 is just another endowment behind them.
  • Nano targets (Game Boy, NES) run a 16-bit value word — small immediate integers and a fixed-point immediate lane, references into a banked pool, text in a compact 8-bit encoding, and an arena or compacting collector sized to a few kilobytes. This is not a second runtime: it is the same seams, formats, and test suite with the two largest files — the interpreter and the object core — set to their nano variants. A program written against the language runs here; what shrinks is the machinery underneath it.

The through-line from a workstation to a Game Boy is deliberate: one language, one cartridge format, one boot path, one test suite — and a profile per machine that pays only for what the machine can bear.