HELIX Docs
Web SDK

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

  1. 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 multiplayer writes it for you.
  2. 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 when an event, if a condition, then effects 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:

public/helix.json
{
  "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:

CallPurpose
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 respawn and teleport effects), 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" }).

Reference

On this page