Cartridges
A cartridge is a sealed store
A cartridge is three things in one read-only object: a sealed store, the boot rows that say what to start, and a placement plan that puts everything at a final address. Read it as a small boot followed by a wad of content-addressed objects already sitting where they will run.
Because everything is at a final offset, the runtime maps the file and reads its tables in
place — mapping the cart is the whole of loading it. Because it is one object with a known
layout, a cartridge is what a runtime boots from: booting needs a working memory image
before there is a shop to ask, and a cartridge is exactly that. And because it is a store,
it answers the same get_manifest / get_content interface as any other
backend, served from its own verified ranges.
A bundle is the same store snapshot without the placement. It carries the same
content-addressed objects and the same manifest, signed and portable, and it is sliced —
player, development, debug — so a recipient takes the part it needs. What a cart adds
is physical: addresses, banking, and the residency decisions a fixed-budget machine
requires. That is the only difference, and it is why the cart survives being fused into a
single object while the bundle stays movable between machines. See
Packages and Distribution.
What is inside
A cartridge carries a small fixed header — magic, the profile/ABI stamp, and where the metadata sits — then its metadata as one nota value, then the pool sections as raw bytes at aligned offsets the metadata addresses. The fixed header is checked by comparison before anything is parsed; the nota decodes into ordinary pit records; the pools are mapped where they lie. Two formats cover the whole object: nota for everything that is metadata, and the pool format for the one thing that is ABI-specific by nature. The metadata groups into three ideas:
What can start. A catalog of names and the target realizations they select. A
realization names the execution form, the exact pool hashes it runs, and the start plan —
so starting an actor is following its realization’s rows, not re-deciding anything. Names
live here, at the catalog, and nowhere deeper: the catalog is what lets boot answer a
start request with an exact lookup and no resolver, and what those names look like is the
shop’s business rather than the cart format’s.
What runs. The Mach pools themselves, each its own section at an aligned offset so a section can be mapped exactly where it lies, plus the section directory that addresses them by offset, length and content hash, and the placement rows that map each pool into memory. A placement row records a pool’s content hash, its group, and where it sits; and because pools are named by content, the same pool can be placed in more than one group with one identity verifying every copy. That is what ROM banking is made of. On a target with a small tier of fast on-chip memory, a placement row may also carry a profile-derived hint that a hot function be copied into that fast RAM at mount — GBA IWRAM, the PS1 scratchpad — while the rest runs in place from the cart. The hint comes from profiling; the default is to execute in place, where the pool sits.
What it needs and carries. The extension spec the runtime binary must satisfy, the boot roots and services, an asset index over cold asset data, and the integrity hashes and signature that seal the whole thing.
The byte-level layout is in Artifact Formats; this page is the shape.
A code window holds a pool
Every code window in a cartridge is a finalized Mach pool (pmp1) — target-final, stamped
with the profile and ABI it was pressed for. The window’s bytes are the bytes the VM runs:
mapping the pool is the whole of loading it, and it executes in place, on the development
machine exactly as on a console.
mcode is the portable artifact, and a pool is what it becomes for one exact profile. That last step — cementing — happens when the cart is pressed, on a machine with a working pit system, never on the machine that boots the cart. Portability ends at mcode: a cart is a finished object for one profile, and a different profile gets its own cart, pressed from the same mcode.
A cart is therefore always mounted by a runtime whose stamp matches it, and the stamp comparison at mount is the whole check. When the profile or Mach ABI moves, forge runs the predecessor’s Pit lowering for the next generation before rebuilding C — a new ABI is one more profile to press, using the same emitter that presses a Playdate cart. Every commit carries a source tree and a cart that match, so a binary built from any commit mounts that commit’s cart. See Build and Artifacts.
Two executables whose closures overlap share one pool and one placement identity, so a cartridge holding a dozen programs is far smaller than a dozen programs. It is a multi-call image: many entry points over shared pools, with the engine as a shared root.
Meeting the C floor
A cartridge is complete for pit code. Every pool it runs is inside it, addressed by content and placed by the placement plan.
Its native side is declared rather than contained. The cart build emits, from its own closure, the exact set of C modules and endowment providers its actors can reach — package, file, full symbol, and the facts each C file declares about itself. That set is the extension spec, and the runtime binary is built to satisfy exactly it. At start, the realization binds those names against the binary; a program that reaches for something outside the declared set refuses to start rather than starting and failing later.
So a cartridge and a runtime binary are two halves that meet at the extension spec: the cartridge says what it needs, the binary is built to provide it, and the match is checked when the cart is mounted — the profile stamps on both sides are compared first, and a mismatch is refused by naming both. A dev runtime links from the shop’s whole lists and needs no spec at all; a ship runtime links the spec and nothing else. See Building C.
Sealed ROMs
A sealed ROM collapses that meeting into build time. The runtime binary, the cartridge, and the native code are linked into one object, with every C binding resolved before shipping, so starting the program is jumping to its entry point.
This is the form for a target where the shipped object is the whole world the program will ever see — a console cartridge, running on hardware that loads exactly one image. It is also the smallest and fastest form, since the layout is fixed and calls into C bind directly.
Boot cartridges
A boot cartridge is what starts a runtime, and how much it carries is a choice — made by the invocation that composes it.
A minimal one reads its own store, or its store plus a mounted bundle: exactly enough to run a game. A larger one can fetch and compile, which is what a program needs when modifying itself is part of what it does. A cart may equally carry a small compiler and the program’s source and compile it on the device at first boot; that is another entry in the same description, not a different kind of cart — see the shop you ship.
The difference is in what the boot cartridge includes, not in how it starts. Both mount the same way and both run the same images; one simply carries more of the system with it.