What the platform requires, and what the engine already does
What the platform requires and already does, and how the engine is hosted.
The platform's rules are in https://onceworlds.com/agents.md ("Rules of the platform", "Multiplayer rooms", "Matches"). With the engine most of them are done for you. This is the list to check against, and the rules that are still yours.
The engine does (keep the defaults):
- One server, chosen by the platform.
Flowandgame.join()callrooms.joinonce as the game starts. Don't build Solo, Host, Join, Quick match, server codes or lists, invite links, friend lists, chat or volume controls: the platform has them. - Lobbies follow the rule: everyone readies up; a host starts private ones; joining never starts a game.
Flow.roundsshows the live lobby (players move about while the others get ready), the Ready/Start strip and the 3-2-1 countdown. Public lobbies start themselves. - Host changes and reloads. The match phase lives in the room (
room.match) plus one record the host keeps; a page that loads into a round resumes it, a new host continues from the newest state, and a paused match resumes under the new host. This holds only if the rules run behindauthority: 'host'(ornet.acting()) and the state that matters is replicated or in the room. - Ghost players. A reload or a dropped connection holds the seat (15 and 30 s); the engine follows
connectedfor you. - Touch controls. The action map becomes the platform's stick and buttons while play is on, and goes away on every other screen.
The mouse-look rules (lock only from a click in the world, panels free it, Esc opens your menu, a refused lock never pauses,
touch never locks) are in
Input.pointer({ lock: 'while-playing' })andinput.lock. - Safe zones. The UI stays out of the top left 130 x 56 (the platform's menu and chat), the lobby's Ready strip, the thumbs' corners and the notch. Don't put your own HUD there.
- Reduce motion, Graphics quality, volume and mute come from the platform's menu through
render.quality,render.reducedMotionand the audio buses. - Saves and records.
defineSave/Savewrite throughonceworlds.saveafter a pause and when the page hides, never every frame, with versions and migrations. A save the game can't read (amigratestep that throws) is never written over: it is reported, so test a new step with an old save. Badges, leaderboards and the stats a match reports are declared once inFlow(progress).
Still yours:
controlsinonceworlds.jsonlists every key and button so "How to play" in the platform menu is right.onceworlds doctor --fixwrites it from the game's own action map: it importsgame.config.jsand usescontrolsFromActions(actions), the engine's exact function (runnpm installfirst; without it the CLI can only read the source text, and says so). Keep the action map and the list in step.- Text from other players is untrusted. Names, chat, room messages and shared state come from other browsers. Draw them as text
(
Text,UI.label,ui.draw.text,ui.overlay.el('span', { text }): there is noinnerHTMLto misuse), and validate every message you send withnet.send:rpcandclaimcheck their arguments against a schema for you, which is why they are the default. - Never ask for a password or personal details, never draw login or "session expired" screens. Names that imitate Onceworlds, its staff or brands are refused. Players and games can be reported.
- Network. The game loads only its own files, the hosted engine, and the CDNs cdn.jsdelivr.net, cdnjs.cloudflare.com,
unpkg.com, esm.sh, ga.jspm.io (and Google Fonts). Other APIs are blocked: use
onceworlds.fetchwith a secret for AI and media APIs, never a key in game code (see the platform guide, "Secrets"). - The game frame is sandboxed.
alert,confirm,prompt, popups, navigation, clipboard writes and service workers are blocked;localStorageand cookies live in memory and vanish on reload (useSave); module workers don't work (classic ones do); noSharedArrayBuffer;history.pushStatedoes nothing. The engine already avoids all of them. - Phones. Guard browser APIs that phones lack (
navigator.vibrate,Notification,getGamepads): feature-detect ortry/catch. Keep text readable at 360 px, handleresize(the engine's renderers do), and play the game once in Safari on an iPhone (Xcode's Simulator is enough): a game that only ever ran in Chrome often draws nothing there. Import maps need Safari 16.4 or newer, so an iPhone older than that can't run an engine game. - Limits. 100 MB per game, 25 MB per file, 2,000 files, paths up to 200 characters, HTML up to 10 MB. Rooms seat at most 30
players, and the more players, the less each may send (
room.budget): the replication budget (Net) keeps a full room smooth. - Game page art before the first deploy (below), and a real
genre,descriptionandcontrols.
Hosting, the engine version and the files
- Keep the game's
createGameconfig in a module with no side effects (game.config.jsat the top or insrc/) and export it asconfig(an object, or a function(page) => configwhen each player's page differs).main.jsstarts it; tests andonceworlds checkimport the same file, so what is tested is what players run. Export the action map asactionstoo andcheckcompares "How to play" with it exactly. - Export a
quickconfig too when the game's rounds or turns are long: the same game with one short round, short screens and a short turn timer (the kits make it with the same function that makesconfig, given short timings).checkplays it instead ofconfig, because its simulated players only ready up and never steer: with the ordinary timers a match can take longer than the run and the end of the match, the scoreboard andprogressnever get exercised.checksays so when no match reached its results. "engine": "1"inonceworlds.jsonmakes the platform add an import map before your scripts, soimport ... from '@onceworlds/engine'and'@onceworlds/engine/modules'work in a plain page and a zip upload."1"is the newest 1.x (fixes and features arrive without a redeploy);"1.2"the newest 1.2.x;"1.2.3"exactly that version (if it is ever withdrawn for a security fix, the release that replaces it). A deploy that names a version the platform doesn't have (or a withdrawn one) fails and says what to write. A game without the field gets no map (a game that bundles its own copy of the engine doesn't need one), so a deploy whose code imports@onceworlds/enginewithout it is refused.- The engine's files come from
onceworlds.com/engine/<version>/, one address for every game, cached for a year per version, and the physics WebAssembly (about 1 MB, only when a game uses physics) is a separate file that never changes. You never copy, host or import it yourself. Do not write your own import map for@onceworlds/engine: the platform's comes first. - Games never import three.js or Rapier. Everything is reached through the engine; the few members that expose a backend
(
render3d.native,physics.raw) are marked unstable: use them only when nothing else can do it. - Plain JavaScript is the default: only
.js,.mjs, HTML, CSS, JSON, images, audio, fonts, glTF, WebAssembly and shaders are deployed (a.tsfile isn't). Write TypeScript if you like and build it to JavaScript with a build script: the project'sdirinonceworlds.jsonis then the build's output (see the platform guide). package.jsonlists@onceworlds/engine(for the tests, the types andonceworlds api) and vitest; it is not part of the game.KIT.mdis the kit's own notes (made bynew; delete it when it has served).