Multiplayer & Networking
How HELIX web worlds share a space and a game. A platform-owned, server-authoritative room runs rules you declare in helix.json. Web-runtime only.
Web-only
This stack is part of the Web runtime. Unreal worlds use the engine's own dedicated-server networking, see Unreal networking. The Platform API (auth, LIX, storage, inventory) is shared; this page is not.
HELIX multiplayer is server-authoritative and declarative. You do not write or deploy a server.
A platform-owned room holds shared state, validates what clients send, and runs the rules you
declare in the multiplayer block of public/helix.json at a fixed 20 Hz. The transport is an
implementation detail; the vocabulary stays World / Build / Instance / Player.
Two layers
- Presence. Players see each other move and animate, wearing their avatars, with nameplates and (by default) proximity voice. This is one call,
CharacterMultiplayer.create, and on its own makes a complete hangout.helix init --kind multiplayerwrites it for you. - Declared game logic. Shared scores and teams, server-driven entities, zones, timers, a state machine, collections, ownership, persistence. You declare them as data; rules of the form
whenan event,ifa condition,theneffects change the state, and the state syncs to every client.
Every world must also work single player: guests and standalone runs never join a room, and
character.room is simply null.
Opt in
The manifest needs maxPlayers above 1, the multiplayer permission, and a multiplayer block:
{
"helixVersion": "0.3",
"maxPlayers": 8,
"permissions": ["auth.profile", "multiplayer", "voice.proximity"],
"multiplayer": { "authoritative": true }
}helix validate enforces the cross-field rules and rejects an unknown verb or variable with a
suggestion. A room locks at maxPlayers and spills extra players into a fresh instance; the platform
also caps concurrent players per room.
Talking to the room
The room handle is HelixRoom (character.room, or Helix.multiplayer.joinRoom() if you manage
joins yourself). Clients never write shared state. They send intents and read synced state:
| Call | Purpose |
|---|---|
room.sendAction(name, args) | Ask the room to run a declared action. Arguments are validated server-side. |
room.vars, room.me, room.player(id) | Guarded live views over room, own and other players' variables (.num, .str, .bool, .vec3, .list) |
room.phase(), room.isMyTurn() | State machine phase and turn order |
room.onMessage(event, cb) | Receive a declared server broadcast |
room.onStateChange(cb) | React to any synced change |
Read state fresh each frame instead of caching it. The exact signatures are in the API reference.
// ask
character.room?.sendAction('claimBonus', {});
// read
const coins = character.room?.me.num('coins') ?? 0;Build your first multiplayer world walks through the full loop with a working example.
The networking model
- Movement is client-authoritative and server-relayed (about 10 Hz, interpolated), with a server plausibility gate. The server rejects implausible position jumps, so respawns and teleports must be server rules (the
respawnandteleporteffects), not client-side moves. - Value (wallet, inventory, rewards, scores) is owned by rules and the economy, never by client events.
Never trust client events for value
A client that moves its own body across the map freezes its avatar for everyone else, and a client that grants itself coins can script infinite coins. Put both behind declared actions and rules. This mirrors the item execution tiers.
Voice
Multiplayer worlds ship proximity voice by default (voice.proximity in the manifest). The join is
four lines after the room connects, and the player controls their own microphone through the shell.
Details and channel tuning are in the voice section of the multiplayer reference that the
MCP server serves as read_doc({ name: "multiplayer-world" }).