How we built the p5.js cartridge
A stranger’s JavaScript, running on your origin, is a security incident. This is how it became a feature instead: an opaque-origin container, a wrapper that makes a hundred lines of p5 multiplayer, and the two bugs only a real second browser could find.
Checked against the sources on 2026-08-30
What it is, and the rule it refused to break
A sketch XP is the third kind of document. A level describes a world the engine draws; a framed cartridge names a game the platform already ships; a sketch carries its game as source - p5.js files inside the JSON - and the platform runs code it has every reason to distrust.
The backend’s folder rules refuse .js files outright, and that refusal was made deliberately: a script served from the product’s own origin is stored XSS with the reader’s session. The sketch does not reopen that argument. The sources are strings inside a document, never files on disk; they only become executable inside an iframe with an opaque origin - sandbox without allow-same-origin - and a content policy of default-src none, opened just wide enough for the platform’s own art. A sketch cannot read a cookie, cannot touch the page around it, and cannot phone home.
What the author gets for accepting all that is everything the engine’s script sandbox deliberately lacks: a real canvas, requestAnimationFrame, WebGL, the whole of p5.
The build, in the order it happened
One fork, not one route
The runtime asks one question before any of its machinery spins up: is this document a sketch? If so it mounts the container instead of a 3D scene. Because that is the whole integration, every existing surface - the store, the battle wizard, the rooms, the editor preview - opens sketches without having changed. The framed cartridge made this argument first; the sketch inherited it for free.
Watch out: If a new kind of thing needs its own route, it will also need its own listing, its own picker and its own share link. A fork at the end of the old route needs none of them.
The wrapper is the game half
A sketch wakes up with window.xp already there: a roster with join and leave, an avatar that broadcasts itself ten times a second and arrives smoothed on every other screen, one input axis fed by arrows, WASD or a thumbstick, named buttons that fire as triggers for every player, shared objects with a claim - the ball rule - and the document’s flow driven by the platform with a round strip drawn over the canvas.
The point of the surface is what an author never writes: netcode. Setting xp.avatar.x in draw() is the whole of multiplayer movement. A live scoreboard is one line, because the avatar carries a free data object that syncs with it.
Validate on the trusted side of the membrane
The container talks to the platform through postMessage and nothing else. Every message crossing outward is read through one narrow protocol - shapes checked, strings cut to length, payloads size-capped, sends rate-limited - and all of it on the platform’s side, because the other side is the code being limited. A hacked sketch can lie about its own game; it cannot spend the room’s bandwidth or another player’s trust.
Watch out: Rate limits enforced inside the sandbox are advice. The same limits outside it are rules.
Let a second browser call you a liar
The unit tests all passed and the first real two-browser run failed four checks of seven. The wire’s envelope carries the sender inside the payload - that is also how a client drops its own echo - and the stage sent bare payloads, so ball claims travelled while avatars and button presses arrived from nobody. The fix was one field; finding it required two actual signed-in browsers in one actual room.
The same probe caught the editor’s code pane editing text three screens below the caret, because its two stacked layers wrapped long lines differently. The cure was to stop wrapping entirely, like every code editor.
Watch out: A protocol test proves the shapes agree. Only a second client proves the meanings do.
Give the sketch the platform’s own art
The container’s content policy allows exactly three asset directories, so the wrapper turns them into loaders: pack pictures as stable handles, shipped sounds as take-cycling players, and pack models through a small glTF reader - mesh, transform, base-colour texture - because p5 reads OBJ and STL and the packs ship glTF. A player’s own peep can stand in a sketch, textured, from one call.
One measured surprise is worth writing down: p5 2.x returns Promises from its loaders where 1.x returned images. Everything the wrapper hands out is therefore a handle - check ready, draw image - so no sketch ever has to know which p5 is underneath.
The words in the code
- sketch block
- The document’s carried project: files, entry, stick, timeline. Parsed and capped like everything else in the format.
- Opaque origin
- What a sandboxed iframe without allow-same-origin runs as: an origin that matches nothing, holds nothing, and can reach nothing of yours.
- window.xp
- The wrapper a sketch wakes up inside - roster, avatar, input, objects, flow, packs, translation.
- The ball rule
- One writer per shared object, everybody watching; claim() takes it. The engine’s elected tier, spoken in p5.
- The stage
- The platform side of the membrane: socket relay, validation, rate limits, the flow driver, the touch controls.
What we would tell ourselves at the start
- Containment is a place, not a habit: put every layer of it in one file, because the layer a refactor loses is the one you stopped seeing.
- The wrapper’s worth is measured in netcode nobody writes. If an author must think about packets, the wrapper is not done.
- Enforce limits where the limited code cannot reach them.
- Run two real clients before believing any of it. Twice.
- Wrap nothing in a two-layer code pane. Wrapping is where the layers learn to disagree.
Read the real thing
- The p5 reference in the creator docsThe whole window.xp surface, with the recipes.
- src/app/xp/_sketch in the repositoryThe container, the protocol, the stage and the wrapper - the headers carry these arguments in full.
- How we built boxingThe other way to be a game here: a trusted package importing five interfaces, instead of untrusted source in a container.
This is a map, not legal or tax advice - and an honest one about how it was drawn: the Germany guide was written by a person who walked the route; most other countries were drafted with AI against the official sources and have not yet been walked by someone who did it. Laws change. Every guide carries the date it was last checked and the sources to check it yourself - and if you have been through one of these routes, your corrections are exactly what this handbook wants.


