StemStudio's multiplayer is built on Colyseus. In OSS mode, the Colyseus server runs as a local sidecar on ws://localhost:2567 — the exact same code that runs in production deployments, just bound to localhost with in-memory room state.
When you run bun run dev, the sidecar starts automatically alongside Vite and the AI server via concurrently (look at the dev script in package.json). No additional configuration required.
To run only the sidecar:
bun run dev:mpTo run the editor without multiplayer (e.g., when working on offline-only features):
bun run dev:editorBehaviors that depend on multiplayer detect that the sidecar isn't reachable, log a warning once, and no-op for the rest of the session.
Open two browser tabs on http://localhost:5173. Load the same project in both. Add any behavior that uses the colyseusRoom Erth helper. Both tabs join the same room and see each other's state updates.
This works because:
- Colyseus is running on
ws://localhost:2567and both tabs connect to it. - Rooms are keyed by project ID, so the same project = the same room.
- State is held in the sidecar's memory; both tabs receive schema-synced updates.
Environment variables read by the multiplayer sidecar:
| Var | Default | Purpose |
|---|---|---|
MULTIPLAYER_PORT |
2567 |
Port to bind |
MULTIPLAYER_HOST |
0.0.0.0 |
Interface to bind |
MULTIPLAYER_LOG_LEVEL |
info |
debug, info, warn, error |
Environment variables read by the editor:
| Var | Default | Purpose |
|---|---|---|
REACT_APP_MULTIPLAYER_SERVER_URL |
ws://localhost:2567 |
Where to connect |
If you want the editor to connect to a remote sidecar:
REACT_APP_MULTIPLAYER_SERVER_URL=wss://mp.example.com bun run dev:editorIn OSS mode, the sidecar holds no persistent state. Rooms exist only while they have at least one connected client. When the last client disconnects, the room is destroyed and its state is gone.
If you need cross-session persistence — leaderboards, character progression, world state that survives a server restart — that lives in a separate game-services backend which is not part of OSS. You can either:
- Run a local persistence layer yourself by wiring a behavior to write to
localStorageor IndexedDB on each client. - Self-host a game-services backend; see the self-hosting section below.
To deploy the multiplayer sidecar on a real server (so players on different machines can connect):
- Clone the multiplayer submodule into its own deployment:
git clone <multiplayer-submodule-url> stemstudio-multiplayer cd stemstudio-multiplayer npm install npm run build
- Run it with a public hostname:
MULTIPLAYER_HOST=0.0.0.0 MULTIPLAYER_PORT=2567 npm start
- Front it with a TLS-terminating reverse proxy (Caddy, nginx, Cloudflare).
- Point editor clients at the public URL via
REACT_APP_MULTIPLAYER_SERVER_URL=wss://your-host.
For production use you'll want to add:
- A MongoDB connection (the submodule supports persistent rooms via
MONGO_URL; the OSS sidecar runs in-memory by default). - Rate limiting at the reverse proxy.
- Process supervision (systemd, Docker, Kubernetes).
- Monitoring (the sidecar exposes basic
/healthand/metricsendpoints).
See the multiplayer submodule's own README.md for full deployment details.
Behaviors that use multiplayer should:
- Check sidecar availability before subscribing to room events. The
colyseusRoomErth helper returnsnullif no sidecar is reachable. - Handle disconnects gracefully — players will drop and rejoin. Don't assume continuous presence.
- Pin schema versions — if your behavior reads a schema field, version it so old clients don't crash on a new server.
- Test the offline path by killing the sidecar with
kill $(lsof -ti:2567)and verifying your behavior still loads.
The multiplayer client code lives under client/packages/editor-oss/src/multiplayer/ — read MultiplayerProxy.ts and the worker code in worker/ to see the wire protocol and how rooms are joined.