Multiplayer
Zero is multiplayer by default — in both edit mode and play mode. A world is a live, shared session: other people and agents may be connected to the same world at the same time, editing alongside you…
What syncs, and when
The two modes share differently (edit vs play is the engine guide):
- In edit mode, the world itself syncs. Source content and the entities you place propagate to everyone connected — editing is collaborative, like a shared document. This is why there's no "save and send": writing to
/zero/sourceis sharing it. - In play mode, gameplay runs per peer, and only fields you mark for replication sync. Each component field is declared with a replication mode, and only
Syncfields cross the wire during play.
public = {
health = Field.number(100, Sync), -- replicated to all peers during play
_vfxSeed = Field.number(0, NoSync), -- local-only; stays on this peer
}
Sync / NoSync is the second argument to every Field.<kind>(default, mode) (the components guide covers fields). Choosing per field is the whole replication design: shared game state is Sync; cosmetic or per-peer scratch is NoSync. A component's private fields take the same argument and replicate the same way when Sync; the only difference is that a private field routes to the private table on every peer instead of the inspector-visible one (the components guide covers the private surface).
Joining: the scene materializes first
A peer that joins a play session receives the current scene as a snapshot before its gameplay starts. While that snapshot is still arriving, gameplay simulation is held — component update(dt) / fixedUpdate(dt), physics, and hooks don't run — so a freshly-joined character can't fall through ground that hasn't streamed in yet. Once the snapshot lands, simulation resumes and the character settles onto the now-present scene.
engine.gameplayReady reports this: it is true when gameplay is simulating (and false while a joiner's scene is still materializing, or in edit mode). If your update() isn't firing in play mode, read it — gameplayReady == false with engine.paused == false means the scene is still materializing. The same per-frame state is on every execute response as state.gameplayReady.
Entities: who owns what
Entities carry an owner, and you decide which ones replicate at all:
local e = entity(id)
e.setSynced(true) -- this entity participates in multiplayer
e.synced() -- boolean
e.isLocal() -- does this peer own it?
e.owner() -- owning peer id (0 = unowned / server)
The owner is the peer that simulates an entity; other peers receive its synced state. When ownership matters at runtime (picking up an object, taking control of a vehicle), the multiplayer namespace handles it:
multiplayer.isOwner(id)
multiplayer.claimOwnership(id)
multiplayer.releaseOwnership(id)
Author so that the owning peer drives logic and everyone else reflects it — a common shape is "only act on entities where isLocal() is true."
Players and peers
Players are first-class and engine-spawned; the scene's root surfaces them (the scenes guide has the players registry — localPlayer, onJoin, onLeave). A player is the per-peer identity; its avatar is a separate physical entity — an ordinary synced entity that every peer sees, owned by the peer it belongs to and removed when that peer leaves. View concerns stay per-peer: each peer drives its OWN avatar and runs its own camera/input, gating controller logic on entity:isLocal() so a peer never moves another peer's avatar. For the raw session you also have:
world.participants() -- who's connected
world.on("player_join", function(p) end)
world.on("player_leave", function(p) end)
multiplayer.getPeers() -- peer list
multiplayer.getPeerId() -- this peer's id
Beyond this
The multiplayer namespace also exposes collaborative undo/redo, room/mode state, and diagnostics — discover it with lsp.methods("multiplayer") (the discovering guide). The concept to hold onto: the world is shared at all times; Sync/NoSync and entity ownership are how you decide what everyone sees.
Replication is for live state — what's happening right now. For state that must survive restarts and be there next session (progression, saves, per-player records), reach for runtime_data (the runtime-data guide) instead: it's the world's durable, shared memory, where replication is its short-term memory.