Publishing
Publishing puts your game at a link players open. Sign in once with spawnite login, create the game once with spawnite games create, and run spawnite publish from the game’s folder: it builds the game, uploads it as the game’s next version, makes that version live, and prints the link. A first publish is unlisted, so only people with the link can play. In a game with a room, they all play in one room until it is full; a game played alone runs on each player’s own page, so each plays a copy of their own. Putting the game on the store is a separate step: spawnite visibility public, spawnite publish --public, or the MCP’s set_visibility. Each game runs on an origin of its own, <id>.spawnite.net, as the trust boundary says.
The states of a game
Section titled “The states of a game”Every game is in one of the following states. The cli, the MCP, the creator portal and the store all use these names:
| State | What it means | Who plays it |
|---|---|---|
| Not published | Versions may be uploaded, but none is live. | Nobody |
| Unlisted | A version is live at the game’s link, and the game is not on the store. | Anyone with the link, which has a key: in a game with a room, in one room until it is full |
| Public | A version is live, and the game is on the store. | Everyone |
| Taken down | The platform took the game down. Its maker sees the reason the platform gave, in every reply. | Nobody |
To play with just your friends, keep the game unlisted and share the link: only people with the link can join, and in a game with a room they all play in one room. In a public game with a room, a player can also open a private room from the game’s page for the people they invite, as Multiplayer says.
The store setting, public or unlisted, belongs to the game, not to a version. Publishing a new version of a public game keeps the game on the store; publishing never takes a game off it, and only spawnite visibility unlisted or a takedown does.
Nobody reviews a game before it goes live, on its link or on the store. Moderation acts afterward, on players’ reports: the platform takes a game down when a report shows it breaks the rules. That keeps a game’s link working the moment its creator publishes, and reports are how players reach the platform about a game.
What does run is automatic, and each check names what it refuses:
- An upload checks the bundle: its size, its files and its manifest, as What the check refuses lists.
- A publish checks the version’s save against the saves the game’s earlier versions made, and the engine release it is pinned to, as Publish a game says.
- A word filter reads the game’s name, its slug and every line of its store page each time they are written, and refuses a name that holds Spawnite.
- Putting a game on the store needs a live version and a store page.
The commands
Section titled “The commands”The following commands move a game between its states. Each prints the game’s link, its live version and its state, and each has an MCP tool with the same reply:
| Command | What it does | MCP tool |
|---|---|---|
spawnite build |
Builds the bundle on disk. Sends nothing. | none |
spawnite upload [folder] |
Builds the bundle, or takes a folder, and uploads it as the next version: checked, and not live. | upload_version |
spawnite publish |
Builds, uploads and makes that version live at the game’s link, in one command. | publish_version |
spawnite publish <version> |
Makes an uploaded version live as it is, newer or older than the live one. Builds nothing. Refuses a version whose save or engine release is older than one players hold. | publish_version |
spawnite visibility public / unlisted |
Puts the game on the store, which needs a live version and a store page, or takes it off. | set_visibility |
spawnite store upload |
Sends the game’s store page. | upload_store_page |
spawnite publish --public publishes and puts the game on the store in one command. It refuses, before it builds anything, a game with no store page. spawnite versions list lists the game’s versions, and spawnite games list lists your games with the state of each. How players take each version, from its first player, is in your game’s numbers, which your agent reads too.
Publish a game
Section titled “Publish a game”A game has an id, which never changes, a slug, the readable text in its address, and a name. Other games may share the slug and the name.
Sign in once with spawnite login, then take the following steps from the game’s folder:
-
Create the game, once, from its name:
Terminal window spawnite games create "Tower Defense"It prints the game’s id and its slug, and writes the id into the folder’s
package.jsonas"spawnite": { "game": "48201973" }. The commands below read the game from there;--game <id>names another. -
Publish it:
Terminal window spawnite publishThe command builds the game into
dist/bundle, uploads the bundle as the game’s next version, and makes that version live. Its report takes the following form, here for version 3 of a game with the id 48201973:Built the bundle at dist/bundle: engine 0.1.0, game code 146 KB gzipped.Uploaded version 3: 41 files, checked.Published version 3 of Tower Defense, game 48201973. Players load it within a minute.Link: https://spawnite.com/games/tower-defense/48201973?k=pT3xR9sYw2KqLmN8vB4cZaLive version: 3State: unlisted, not on the store. Only people with the link can play.To put it on the store, send a store page with spawnite store upload, then run spawnite visibility publicA page opened after that loads the new version. A page already open keeps the version it loaded until it reloads, and a room keeps its version until it ends. Share the link with the people you want to play. When the version’s manifest runs a room,
always, Play seats each player in one of the game’s rooms; a version withnoneplays alone and never gets a room. -
Write the game’s store page and send it, as Write the store page describes:
Terminal window spawnite store upload -
Put the game on the store:
Terminal window spawnite visibility publicFrom then on the store lists the game, and its link needs no key. Every later
spawnite publishkeeps it there.spawnite visibility unlistedtakes it off the store again, and its link needs the key again. -
Check that its rooms start:
Terminal window spawnite versions listIt prints each version, newest first, with its engine release, whether it pins that release, and which one is live. When three rooms in a row cannot load a version’s server half, the version starts no more rooms, and the list says so under the version, with what the last room reported. Players who press Play then read that the game failed to load. Publish a fixed version to start rooms again. The platform also retries the version when it serves a new engine release that the version runs.
To roll back, publish an earlier version by its number, such as spawnite publish 2. A rollback meets the same two checks as any publish. It is refused where the earlier version’s save is older than one players already hold, and where its engine release is older than one a published version of the game runs on, as the next paragraph says. To upload a version without making it live, run spawnite upload, then spawnite publish <version> when you want it live.
A publish checks the version’s save against every version the game ever made live, because a player may hold a save from any of them. The game keeps a mark, the highest version of each part of its save any live version had, which each publish checks against and raises, as Saving progress says. It refuses, naming the part of the save, a version below the highest that part ever had, and the same version with a changed schema: raise version in the save’s declaration and add the migration step.
A rollback is checked the same way, so an earlier version whose save is older than one players already hold is refused: publish that code again with a higher version and a step. No flag publishes past the check, and the platform runs it again against the mark as it makes a version live, so a client that skips it is refused too. Where it cannot compare a schema, it warns and publishes; a persisted store has no schema, so its warning is expected. spawnite publish also refuses a version pinned to an engine release older than one a published version of the game runs on, since an engine release can raise the version of its own saves: build against the newer release, or publish without the pin.
Only the game’s developer may upload, publish, set the visibility of or list it. A game the platform publishes itself, which no developer owns, takes an admin. An account uploads at most 20 versions a day, and renames its games or changes their visibility at most 30 times a day.
An account keeps at most 10 GB of versions, store pictures and shared assets, across all its games. Every version you upload counts its whole bundle, and a store page counts each picture once per game, so a page that changes only its text adds nothing. The Storage box on your game’s page on the creator site shows how much the account holds. An upload that would pass the limit is refused before any file moves, with a message that says how much the account holds. To raise the limit, write to dev@spawnite.com.
The game’s address
Section titled “The game’s address”A game’s page is spawnite.com/games/<slug>/<id>, its play route is that address with /play, and its origin is <id>.spawnite.net.
The id is the game’s identity: the platform draws it when the game is created, it never changes, and no two games share one. The slug is the readable text before it, and any number of games share one: every game named mmorpg has the slug mmorpg, each at its own id.
spawnite games create makes the slug from the game’s name: Tower Defense gives tower-defense. A name that starts with a digit, or has no ASCII letter, gives a slug that starts with game. A rename moves the slug with the name.
To set other text, at any time, run spawnite publish --slug tower-td, which sets it before the publish builds anything. The MCP’s publish_version takes slug the same way.
A slug is 3 to 40 lowercase ASCII letters, digits and single hyphens, and starts with a letter. The text filter applies to it as it does to a name. A slug you set stays through a rename.
A link never breaks when the slug changes: the id names the game, so spawnite.com/games/<id> and an address with an old slug both move to the game’s page.
The platform can grant a game a vanity name, a short host under the play domain such as holdfast.spawnite.net, which moves to the game’s page. A vanity name belongs to one game and is never granted again. A creator cannot set one.
Who can play an unlisted game
Section titled “Who can play an unlisted game”An unlisted game is live at its link, and the link carries the game’s key: spawnite.com/games/<slug>/<id>?k=<key>. The address alone is no secret, since eight digits can be counted through, so the key is what keeps an unlisted game to the people its maker shares it with. The platform draws the key, 22 characters, at the game’s first upload or its first spawnite visibility, whichever comes first, and keeps it for good; a public game’s link ignores it. So a link you shared cannot be taken back: no command draws a new key or takes a live game back to not published. To reach only new people, create a new game with spawnite games create and publish it, which gives it a link with a key of its own.
An unlisted game plays as follows:
- By the keyed link alone. Its page says “Not on the store yet. Only people with this link can play.” Its address without the key, on the app or on its origin, shows the not-found page, and a join without the key finds no game. The key follows the link into the game’s frame and its room.
- With no account. A 13+ game, or one with no store page yet, plays as a guest, as a public 13+ game does. A game’s age band comes from its store page. There is no band under 13: a player must be 13 or older.
- In one room at a time, in a game with a room, up to the game’s own player cap. A second room starts once the first ends; a public game starts as many rooms as its players fill.
The bundle
Section titled “The bundle”A bundle is one folder with three parts:
manifest.json, the game’s contract with the platform. It names the engine release the game was built against as{ "engine": { "version": "0.1.0" } }.page/, the page half:page/index.htmland the scripts, styles and assets it loads. The engine stays out of it; the page imports the engine and its peers by bare name, through an import map.server/, the server half a room loads. The site never serves it.
Build a bundle
Section titled “Build a bundle”spawnite upload and spawnite publish build the bundle before they send it. To build it alone and send nothing, run the following command in the game’s folder:
spawnite buildThe command builds the game with its own Vite config twice, once for each half, into dist/bundle, or the folder --out names, emptied first. It writes the following layout, which is the layout the store keeps under the game’s id and version:
| Path | What it holds |
|---|---|
page/ |
The game’s page, scripts, stylesheet and assets. Its index.html carries an import map for the engine and its peers, and a spawnite-build meta tag with the game’s build. |
server/ |
One .mjs module per scene under src/scenes, which a room loads with Node, and build.json, the same build the page names. spawnite build --server builds this half alone. |
manifest.json |
The engine release the game was built against, when it plays in a room, the scene a room runs, its player cap, the items it sells, and every other file of the folder by its size and SHA-256. |
The page’s import map points the engine and its peers at the engine release under /_engine/<release>/, which the site replaces with the engine’s address. The manifest lists each file’s size and SHA-256 so a file fetched alone, such as the server half, checks against the bundle’s hash.
The command prints the bundle’s hash, the SHA-256 of the sha256sum listing of its files, each half’s size and the scripts’ gzipped size. --json carries the same report with the whole manifest.
It also prints Game code: the gzipped size of the game’s own code a player downloads before the game starts, the part of that download the game can change. spawnite upload and spawnite publish name it in their Built the bundle line, and so do the MCP’s upload_version and publish_version tools. --json carries it as code.gameBytes, and the gzipped size of the engine release files the page imports, which the game cannot cut, as code.engineBytes.
Past 500 KB of game code gzipped, the build warns, as it warns of a large bundle:
Warning: The game's code is 612 KB gzipped. Past 500 KB a first-time player on a phone waits a second or two longer before the game starts. Nothing is refused.Unpacked, 500 KB gzipped is about 1.7 MB of code, and a mid-range phone parses and compiles roughly a megabyte of code a second. To shorten the wait, load each scene’s code when the scene opens, with a dynamic import, and check the heaviest modules for a large library imported whole. The warning goes in warnings with the upload’s own, and the command exits 0.
Under Game code, spawnite build lists the heaviest of the files a player downloads before the game starts, until they make up 90% of their total, then counts the rest on one line. Each line names the file’s gzipped size, whether it comes from the page or the release, and, for a release file, the packages its code comes from. The example below, from games/example, keeps three of its 26 listed files:
Game code: 32 KB gzippedFiles a player downloads before the game starts, gzipped bytes, heaviest first: 122,856 release @spawnite/engine.js: @pixiv/three-vrm-animation, @spawnite/assets, @spawnite/engine, framer-motion, react-error-boundary 100,768 release chunks/three.core-BoJD7UsQ.js: three ... 20,348 page assets/index-tgKY-7nf.js ... and 68 smaller files, 86,181 bytesA page file is the game’s to cut. A release file loads because some module imports it: the page, or another release module the page imports. A package the page imports by name, such as @react-three/drei, the page can drop or load on demand. A package can also be listed because the engine imports it: a page that imports only @spawnite/engine still loads three, through the engine’s own imports. The page cannot cut such a package; cutting it takes a change to the module that imports it, in the engine or the release. --json lists every file under code.files.
The manifest reads whether the game plays in a room from its package.json: always where it names a spawnite.room, and none, played alone, where it names none, as Played alone or in a room says. The scene a room runs, scene, is the module its spawnite.room’s scene names, such as Holdfast for server/Holdfast.mjs; a game played alone names none.
The room’s players and send rate, maxPlayers and sendRate, are what the room’s scene exports under room, the defineRoom settings. Each the scene leaves out, the manifest leaves out, and a room takes the platform’s default, 20 players and 60 sends a second. voice is false where the scene’s room turns voice off, and left out otherwise.
The build refuses settings outside the room’s rule, a room that src/game.ts exports and the scene does not, and a room in a game played alone. The backend checks again at the upload, whatever built the bundle, and at each room’s start. It refuses a manifest with a player count under 2 or past 50, or a send rate other than 30 or 60, with a finding that names it. It refuses one that is played alone and names a room’s scene or settings the same way, and one that plays in a room and names no scene or no server/<scene>.mjs, and one whose rooms is neither always nor none.
The engine and its peers
Section titled “The engine and its peers”A game shares the following packages with the engine, and neither half of its bundle holds them:
@spawnite/engine, itscoreandthreeentries, its entry for each plugin,abilities,weapons,npcs,inventory,dragAndDrop,tracksandrounds, and each look under@spawnite/engine/looks/@spawnite/schema@spawnite/uiand@spawnite/ui/motion- React,
react/jsx-runtime,react-domandreact-dom/client - React Three Fiber and drei
postprocessingand@react-three/postprocessing, the libraries<PostProcessing>draws withthree.quarks, the library<Particles>draws with@pixiv/three-vrmand@pixiv/three-vrm/nodes- koota and
koota/react - three,
three/tsl,three/webgpu, and every addon underthree/addons/orthree/examples/jsm/ - zustand,
zustand/middleware,zustand/shallowandzustand/vanilla
One copy of each runs on the page: the engine’s release built them together, so a context React Three Fiber creates, or a trait koota registers, is the one the engine reads. Any other package a game imports, such as lucide-react, is bundled into the page as usual.
The release holds no stylesheet. A stylesheet one of these packages imports from its own code, such as the UI kit’s, is built into the page’s stylesheet with your own, as your build resolves it, so the published page wears the same styles as the dev server’s. A package your code imports dynamically has its stylesheets load with the page, not when the import runs.
A page downloads only the parts of drei and of @spawnite/schema it imports. The release serves each of their exports as a module of its own, such as @react-three/drei/exports/-html.js for Html, and the page’s build takes each name it imports from that name’s module. Your code still imports from @react-three/drei and @spawnite/schema, as it does on the dev server. The schema’s z carries every export of zod, so a page that imports it loads all of zod. Two decoders load only when a file needs them: hls.js, which plays an HLS video in drei’s video texture, and zstd’s decoder, which three’s KTX2 loader uses for a zstd-compressed texture.
The build fails where either half imports something the release does not have, and the error names the file, the import and the release:
src/app/hud.tsx imports useNothing from "@spawnite/engine", which engine 0.1.0 does not export.To fix it, import something the release has, or build against a release that has the export. A specifier the release does not serve, such as zustand/traditional, fails the same way, and the error lists what the release serves from that package.
The engine release
Section titled “The engine release”An engine release is a folder of ES modules, one per specifier above, with the chunks they share, the engine’s models and Rapier’s WebAssembly. Beside them, importmap.json holds the import map, each bare name and its file in the folder, with the SHA-384 of every script under integrity, and release.json the version, every module’s exports, and the packages each script’s code comes from. The site serves each release’s folder at one address for every game, engine.spawnite.net/<release>/, so a browser downloads a release once and reuses it in each game. A game’s origin writes the page’s import map from the release’s importmap.json, each file at that address.
The release is minified throughout, the readable plugins too. The engine’s npm package ships the core closed and minified, and ships each readable plugin, today rounds, as its source under readable/, so a creator’s agent can read how the plugin works and copy it. The release builds each readable plugin from that same source as a module of its own, which loads the engine through the import map’s @spawnite/engine and @spawnite/engine/core, so the page runs one copy of the core. A closed plugin’s entry re-exports names the release’s root already carries.
spawnite build builds against the release in the installed engine’s dist-browser folder, which the engine’s npm package carries. --engine <folder> names another release’s folder.
Pin a version to its release
Section titled “Pin a version to its release”By default, a published version follows its engine release’s compatibility line: when the platform serves a newer release on that line, the version’s next page load and next room run it, with no republish. That is how engine fixes reach your game. The line is the following, the same releases npm’s ^ range takes from 0.1.0 on:
- From 1.0.0 on, the major: a version built on 1.2.0 runs 1.9.0 once the platform serves it, and never 2.0.0.
- Below 1.0.0, the major and minor, because a 0.x minor may change the API: a version built on 0.1.3 runs 0.1.4 once the platform serves it, and never 0.2.0.
- A prerelease, such as
0.1.0-beta.13, is a line of its own: a version built on one stays on it.
A release on a line never breaks the public API, a running game’s network messages or a saved shape. A change that does is the next line, so your game takes it only when you publish a version built on that line. Before 1.0 each minor is a line of its own, so a release that renames or removes an API is a new minor, such as 0.2.0 after 0.1.x, and a game built on 0.1.x keeps running 0.1.x.
To keep your versions on the exact release each one is built against, pin the game in its package.json, beside spawnite.game:
"spawnite": { "game": "48201973", "engine": { "pin": "exact" }}Every build reads the pin from there, spawnite build, spawnite upload and spawnite publish alike, so it stays with the game in version control and no later build drops it. You can write it by hand, or let the first pinned build write it:
spawnite publish --pin--pin writes spawnite.engine.pin into package.json and builds a pinned version. The manifest’s engine then carries "pin": "exact" beside its version, and the command prints pinned by package.json beside the release. A pinned version runs that release whatever the platform serves later. pin takes one value, "exact". A build with neither flag refuses any other value, and any other key under spawnite.engine, before it builds; --pin and --no-pin replace such a block.
A pin costs you every engine fix: speed-ups, device support and bug fixes stop reaching the version until you publish a new one. Pin only when a change in how the engine behaves is a bigger risk to your game than a missing fix.
To remove the pin, publish with --no-pin, which removes spawnite.engine.pin from package.json and builds, uploads and makes live a version that follows the platform’s release. A version never changes after upload, so the versions you uploaded pinned stay pinned. spawnite versions list prints pinned beside each pinned version’s release.
Check a version locally
Section titled “Check a version locally”To play a built version before it goes to the store, serve it with --serve:
spawnite build --serve 4510After the build, the command serves the page at http://localhost:4510/ and the engine release at /_engine/<release>/ on the same port, until you stop it. The site serves the release from the engine’s address instead, as What the site serves says. dist/bundle sits in the game’s dist, which git and Tailwind already skip.
spawnite play join joins pages to a room by its address, such as the development room spawnite play start runs, and says what each saw. Each page reports the build it joined with, and a room of another build names both.
Write the store page
Section titled “Write the store page”The store page is what players see before they press Play: the game’s name, a tagline, a description, its age band and its pictures. You write it in the game’s own package.json, under spawnite.store, beside spawnite.game, and keep its pictures as files in the game’s folder:
"spawnite": { "game": "48201973", "store": { "name": "Holdfast", "tagline": "Hold a stone circle with your friends against waves of monsters.", "description": "Up to four wardens hold the stone circle at dusk against the Hollow…", "age": "13+", "capture": { "file": "store/capture.jpg", "alt": "A warden firing across the stone circle" }, "previews": [{ "file": "store/preview-1.jpg", "alt": "The warden by the fire before the first wave" }] }}The fields are the following:
| Field | Required | What it holds |
|---|---|---|
tagline |
Yes | One line, up to 80 characters, that says what the player does |
description |
Yes | A paragraph or two, up to 1,000 characters, on how the game plays |
age |
Yes | The youngest players it is for: 13+, 16+ or 18+ |
capture |
Yes | The frame its card and its page lead with, as { file, alt } |
previews |
No | Up to 8 more frames, each { file, alt } |
name |
No | The game’s name on the store, up to 50 characters; left out, it keeps its name |
Each file is a path from the game’s folder, and each alt says what the picture shows, up to 200 characters, for a screen reader. Each picture is a PNG, JPEG or WebP file of at most 2 MB, 16:9, from 1280 by 720 to 3840 by 2160 pixels. The store names the game’s maker by your account’s nickname, which you change in Settings on spawnite.com or with spawnite account name <name>, so the page has no field for it.
Send the page from the game’s folder:
spawnite store uploadThe command checks the page before it sends anything, and names each field the page lacks or gets wrong with what the field should hold. The backend checks each picture again, its type and size read from its bytes, and names every picture it refuses. It also runs the text filter over the name and every line of text, and refuses a name that holds Spawnite, as the trust boundary says; its refusal names the field and quotes the passage of your text that holds the word, so you can reword it. A page it takes replaces the one before, and the command says where the game stands. An agent sends the same page with the MCP’s upload_store_page tool. An account sends at most 30 store pages a day.
A page puts no game on the store. A public game shows its new page at once; an unlisted game goes on the store with spawnite visibility public, which needs the page. A game the platform takes down stays off the store whatever page, version or visibility its maker sends after.
Roblox’s creators write an experience’s page in Studio or on the Creator Dashboard, and Steam’s, itch.io’s and the app stores’ in a web form, each with pictures at set sizes and, but for itch.io, an age rating before the page goes public. fastlane, a tool for the app stores, keeps the listing as files in the app’s repository and uploads it with one command. This platform takes fastlane’s shape, since the agent that builds a game already works in its folder, and keeps the required age band and the check of each picture.
What the check refuses
Section titled “What the check refuses”The backend refuses a bundle for each of the following, and names every finding:
| Refused | Why |
|---|---|
| More than 1 GB in all | The trust boundary’s bundle limit; the refusal names the three largest files |
| More than 5,000 files | What one upload’s listing holds |
Over 50 MB of HTML, scripts and styles under page/ |
The initial download’s limit |
A path with \, or an empty, . or .. part |
Every file stays inside its version |
No manifest.json, or no page/index.html |
The two parts every version needs |
A manifest that names no engine release as 1.2.3 |
The platform picks the page’s engine from that release |
A maxPlayers that is not a whole number from 2 to 50 |
Join seats players in a room up to that cap |
A bundle may hold WebAssembly, by its .wasm name or under any other, since a game runs on an origin of its own, as the trust boundary says. The site serves a .wasm file as application/wasm, which WebAssembly.instantiateStreaming needs.
No file has a limit of its own: the bucket takes up to 5 GiB in one request, past the whole bundle’s 1 GB. The cli checks the first three rows itself, against the same numbers, which @spawnite/schema exports as bundleLimits. spawnite build prints whether the bundle is within them. spawnite upload and spawnite publish, and the MCP’s upload_version and publish_version tools, refuse a bundle past one before they send anything. Each finding names the bundle’s value, the limit, the largest files or the fullest folders, and what to change.
The bundle check warns rather than refuses at one size: past 100 MB in all. A player downloads only the files the game loads, so a large bundle costs a player nothing on its own. The warning names the largest files: check that the bundle ships none the game never loads. spawnite build prints the warning and exits 0, and spawnite upload, spawnite publish and the MCP’s tools print it and upload the bundle.
The check also warns of each file the page loads from another site, such as a font from Google Fonts or a picture on a creator’s CDN. It warns because a published game’s page loads files from its own origin alone, as the trust boundary says, and the browser refuses the rest. It reads the page’s HTML and stylesheets for an address in a src, an href, a url() or an @import, and its scripts for a quoted address that ends in a file’s extension, such as .png, .woff2 or .mp3. Put each file it names in the game’s folder, under public/ or imported from src/, so the bundle carries it. An address on https://assets.spawnite.com, where a build names each shared file, is the platform’s own, and the warning skips it. The warning refuses nothing, and a scan misses an address a script builds as it runs, so the browser’s console is the last word: it names each load the policy refused.
A bundle names each shared file, from @spawnite/assets or the engine, by its URL on https://assets.spawnite.com rather than carrying it. spawnite upload and spawnite publish, and the MCP’s tools, ask the site for each such URL before they send anything. They refuse a bundle that names a file the site does not serve, naming the bundle’s file that holds the URL, or when the site cannot be reached. spawnite build asks nothing, so it builds offline.
The 50 MB of HTML, scripts and styles is counted uncompressed, as an upload guard. How long a player waits before the game starts depends on the gzipped size of the game’s code, which spawnite build and spawnite publish print on every build, with a warning past 500 KB, as Build a bundle says. spawnite play profile measures the wait itself, as the bytes the page fetched before its loading screen lifted.
What the site serves
Section titled “What the site serves”Each game’s origin, <id>.spawnite.net, answers the following paths. While the game is unlisted, a page, any path ending in / or .html, answers only a request whose k is the game’s key, and a 404 otherwise; the scripts and assets a page loads need no key:
| Path | Serves |
|---|---|
/<file> |
The published version’s page/<file>; / is page/index.html |
/v/<version>/<file> |
That version’s page/<file>, while it is the published one or a room still runs it, on the room’s release |
/v/<version>/e/<release>/<file> |
The same, with its page on the engine <release>, the pair a room runs |
The engine’s address, engine.spawnite.net, answers /<release>/<file> with an engine release’s file, from the bucket’s engine/<release>/, for a year, to a page on any origin. It sends Access-Control-Allow-Origin: * and Cross-Origin-Resource-Policy: cross-origin, and it serves no game’s file. Every game’s page names the same addresses in its import map, so a player’s second game loads the engine from the browser’s cache.
Any path opened in a tab of its own rather than the app’s frame, with no room or profile in its address, answers with a redirect to the app’s page for the game, with the k the address carried, since the page plays only in the app. The trust boundary says how the site tells the two apart.
A room keeps its version for its whole life, so a version stays served at its own path after a newer one is published, until its last room ends. Any other version is a 404, and so is a bundle in the bucket that no version row names. Every response carries the trust boundary’s headers. Files under a version’s own path never change, so a browser keeps them for a year; the page and the files at the root revalidate.
The site writes the page’s import map for its engine release, from the release’s engine/<release>/importmap.json, which maps each bare name to a file in the release’s folder:
{ "imports": { "three": "three.js", "@spawnite/engine": "@spawnite/engine.js", "three/addons/": "three/addons/" }}The map replaces the page’s own, first in its head, under the response’s nonce. It carries the release’s integrity too, each script’s hash by its address, so a browser refuses an engine module whose bytes differ from the ones the release was built with. The hashes cover what the page imports as a module. A script the engine fetches as a file and runs in a worker, as three.js does with a model’s Draco decoder, is not checked. A release built before the hashes has none, and its modules load unchecked. A release with no importmap.json leaves the page as it is.
The engine release a page gets is its version’s resolved release, which the backend keeps on the version’s row: the platform’s served release of the compatibility line of the release the manifest names, never below the manifest’s own release. A new release on that line reaches a published game when the platform serves it, with no republish, and each engine tag’s release reaches the bucket through the platform’s release workflow. A version built pinned, from spawnite.engine.pin in the game’s package.json, stays on its manifest’s release instead, as Pin a version to its release says.