The Target Matrix
A target is not a thing the system knows about. There is no switch in the runtime for “is this a PS1”, no per-platform branch in the VM, and no list a new machine has to be added to. There is one runtime codebase and a set of levers — how wide a value is, how numbers are represented, which collector runs, whether threads exist, which endowment packages the build draws from, which actors the cart carries. A target is a recipe: a named bundle of positions for those levers, where the name is something the recipe declares and nothing interprets.
That is the whole design, and it is why porting is writing a recipe and filling in an provider package rather than editing the runtime. The file itself — where it lives, what it holds, how a build selects one — is Recipes; the levers it sets are Target Profiles. Those two pages are normative and this one is not.
What follows is a catalog of recipes that have actually been built and run. Read it as worked examples — “a PS1 wants a 32-bit word, no dynamic loading, and a cart carrying no compiler” is advice about which lever positions suit that machine, not a capability the system grants specially to the PS1.
No component reads meaning out of a target name, cake included. The one place a name
appears outside a recipe is a package’s own modules arms, where the package declares how
it varies and the recipe’s name selects an arm. Everything else works in levers.
The catalog below covers the desktop-through-console range. The smaller machines — the tiny handhelds (GBA-class, NDS) and the nano targets (Game Boy, NES) — take a fixed-point or 16-bit-word profile and are covered in Consoles and Constrained Targets.
Recipes that have been built
| Target | Class | Value word | Numeric rep | Threads | Dynamic native loading |
|---|---|---|---|---|---|
darwin | desktop | 64-bit | nan64 | yes | yes |
linux | desktop | 64-bit | nan64 | yes | yes |
windows | desktop | 64-bit | nan64 | yes | yes |
ios / tvos / watchos | sealed mobile | 64-bit | nan64 | yes | no |
emscripten | web | 64-bit | nan64 | single | no |
playdate | handheld | 32-bit | nan32 | single | no |
psp | console | 32-bit | nan32 | single | no |
dc (Dreamcast) | console | 32-bit | nan32 | single | no |
n64 | console | 32-bit | nan32, big-endian | single | no |
ps1 | console | 32-bit | nan32 | single | no |
The desktop targets are where development happens: full threads, dynamic loading, the compiler, and the four debugging endowments. Everything to the right is a sealed target — the shipped object is the whole world the program sees, native code is linked in ahead of time rather than loaded, and the runtime is trimmed to what the machine can hold.
Endowment packages per recipe
A recipe names the provider packages its build draws endowments from, and what those packages hold is what compiles on that platform. The package boundary is the compilation boundary and nothing narrower: there is no per-file subsetting and no catalog saying which calls a target gets. See Endowments.
The names below are package identities in the recipes. The shop resolves them like any
other linked or installed package, and their modules remain available to ordinary use()
resolution.
| Recipe | draws endowments from |
|---|---|
darwin | lang-endowments, std_endowments, posix, bsd, apple, darwin, metal, spritekit |
linux | lang-endowments, std_endowments, posix, linux |
windows | lang-endowments, std_endowments, windows |
ios | lang-endowments, std_endowments, posix, bsd, apple, uikit, metal, spritekit, ios |
tvos | lang-endowments, std_endowments, posix, bsd, apple, uikit, metal, spritekit, tvos |
watchos | lang-endowments, std_endowments, posix, bsd, apple, spritekit, watchos |
emscripten | lang-endowments, std_endowments, web |
playdate | lang-endowments, std_endowments, playdate |
psp · dc · n64 · ps1 | lang-endowments, std_endowments, that console’s package |
Windows does not name posix because posix does not compile there — and a build using an
msys2-flavoured toolchain legitimately could name it. That is the only kind of reason a
package is on or off the list.
To read what a recipe can grant, read those directories. darwin holds darwin_file.c,
appkit_window.c, coreaudio.c, metal.c, fsevents.c and the rest; web holds
memfs.c, canvas.c, webaudio.c, browser_fetch.c. Listing the directory is reading the
list, and there is no second place to check.
What absence proves, and what it does not
Two different things get called “the target does not have it”, and only one of them is a build-time fact.
A package that is not on the list is genuinely absent. A Game Boy recipe names no package holding a socket, so a program claiming one fails to build for it — the honest, checkable form of “a runtime does exactly what it was built with”.
A call inside a package that is on the list answers for itself. $posix_spawn is
present on iOS, because it compiles there; it returns -1 exactly as the operating system
does. The provider passes the OS’s answer through rather than pretending the surface is
missing, and that honesty is the runtime’s job rather than the build’s.
So the checkable question is which packages a recipe names, and the answerable-at-run-time question is what a given call does on a given OS. Reading the second as though it were the first is what a per-file capability catalog would have encouraged, which is why there is none.
Portable library modules sit above all of it. use('file') gives the same interface
everywhere; underneath, the arm binds internal/file_darwin on macOS and
internal/file_windows on Windows, and those claim the raw endowments their platform
package supplies. Your code names the capability; the recipe decides the provider. See
Packages for how that selection works.
What a build ships
Which payload form a cart or bundle carries is chosen when that cart or bundle is composed — it is the composer’s choice, not the target’s. The recipe describes the runtime; the cart’s contents are whoever-is-building’s call, made fresh each time with the same shop tools.
The common compositions: development seeds carry mcode, because mcode survives compiler edits and keeps a seed portable; shipped bundles carry the compact per-VM mach payload, ready to run. And a build description can ship source — a cart holding a minimal compiler plus the program’s source, compiled on the device at first boot — which suits a machine too small to carry a whole mach fleet, or a build that wants to allow mods. Any of these compositions works on any target; the same desktop that usually gets mach can get source, and a Playdate can get whichever its builder prefers.
Whether a shipped artifact can load native code is a separate lever entirely — a profile power compiled into the binary, plus whether the cart carries cake at all. See Compilation and Shipping.
Adding one
Adding a target is writing a recipe — choosing lever positions, naming the endowment packages — and filling those packages with the C that compiles on that machine. No page in this track changes, no enum grows in the runtime, nothing in the VM learns a new name, and nothing in cake learns one either. The only things that gain a row are the recipe itself and this catalog. The file’s shape is in Recipes.
How each of these machines is actually met — the floating-point line, the memory budgets, the cartridge access modes, and the small end below the consoles — is Consoles and Constrained Targets.