Roles and wire protocol
The five processes of a mesh, what each one does, and the messages and delivery classes they exchange.
A Nebula deployment is one Unity player build started in different roles. NebulaBootstrap reads -nebula-role (or its EditorRole field when you press Play), loads the game scene, and adds the matching service components to its own GameObject. A process is a worker or a client, never both; the gateway and the orchestrator may share a process (-nebula-role services), though the CLI runs them separately.
Worker
NebulaWorker, started with -batchmode -nographics -nebula-role worker. A headless Unity dedicated-server process that loads the same game scene as everyone else, so colliders, physics and NavMesh are all available to gameplay code. It registers with the control plane, listens on WorkerBasePort + index for the gateway and for peers, and every tick simulates its authoritative entities, moves its ghosts, maintains the ghost band, hands authority over, and streams state to the gateway. Its stats (tick time, entity counts, players, bots, server-driven entities) ride its heartbeat. See Containers and handover for the tick in detail and Spawning for its API.
Gateway
NebulaGateway, -nebula-role gateway, udp/7000 by default. The one address clients connect to. It keeps a link to every worker, routes each client's inputs to the worker that currently owns that client's entity, re-emits the workers' replication streams to clients, and drops anything carrying a stale authority epoch or coming from a worker that is no longer the owner. When a client connects it picks a container whose owner is ready and asks that worker to spawn the player. It caches one keyframe per sync behaviour per entity so a late joiner starts in the right pose. It holds nothing authoritative: a dead worker's entities are dropped and its players are respawned elsewhere. There is no interest management yet; every client receives every entity.
Orchestrator
NebulaOrchestrator, -nebula-role orchestrator. Launches the gateway, keeps DesiredWorkers workers running through a worker host (child processes, or one cloud VM each), writes one lease per container and deals containers across live workers every 500 ms. It serves the dashboard and its HTTP API on tcp/7080 and seeds mesh-wide settings from -nebula-settings. See Orchestrator and dashboard.
Control plane
A SpacetimeDB module (Packages/com.1by3.nebula/SpacetimeDB/Module~) with worker, container_lease, gateway, orchestrator and game_setting tables and the reducers that register and heartbeat nodes, assign and release leases and set settings. Workers, the gateway and the orchestrator subscribe to it through IControlPlane; it is never on the per-tick path, and if it goes away the mesh keeps simulating with its last known topology. See Control plane.
Client
NebulaClient, the default role. Talks only to the gateway. It sends one input per tick for its predicted player, ahead of the server by an adaptive input lead, receives snapshots tagged with tick, epoch and owning worker, predicts its own player and reconciles against the worker's reported state, and interpolates every other entity InterpolationDelayTicks behind the newest snapshot. Which worker produced a snapshot is metadata used for epoch filtering and the debug overlay, nothing else. A human gets a title screen (NebulaTitleScreen); bots and scripted clients connect at once. See Prediction.
Time
NetworkTime.TickRate is 60. Every worker derives the tick it should be simulating from the wall clock and a fixed origin, so all workers agree on tick numbers without a tick master and a restarted worker knows the correct tick immediately. On a client NetworkTime.Tick is the tick the local player is predicting, ahead of the server by the input lead, and NetworkTime.RenderTick is the fractional tick remote entities are being presented at. Gameplay code should only ever see tick numbers, never wall-clock time.
Transport
All links use LiteNetLib over UDP behind the ITransport interface, which exposes exactly two delivery classes so that a QUIC transport could replace it later:
Delivery | Semantics | Used for |
|---|---|---|
Sequenced | Unreliable, newest wins, cannot fragment (packets are cut at 500 bytes) | Transform streams: WorldState, GhostState, ClientInput, OwnerState, and sync chunks that opt in |
ReliableOrdered | Reliable, ordered | Everything else |
The transport is poll-driven: nothing is raised outside Poll, so nothing mutates the world behind the simulation loop's back.
Messages
Every entity message carries (netId, epoch). Entity ids are [workerIndex:16][sequence:48], minted locally by the spawning worker, never by a central allocator. Positions and rotations are sent in container-local space with the container index of the same tick. The message structs live in Packages/com.1by3.nebula/Runtime/Protocol/Messages.cs and are listed in the Protocol reference.
Session
| Message | Direction | Purpose |
|---|---|---|
Hello, Welcome | any peer to any peer | Identify the role, id and index; a client's Hello carries its name and bot flag |
Ping, Pong | client to gateway | RTT measurement for the input lead |
SpawnPlayer, DespawnPlayer | gateway to worker | Ask the owner of a container to create or remove a player's entity |
ContainerOwnership | worker to gateway to client | The container-to-worker table with lease epochs, for routing and the overlay |
Worker to gateway to client
| Message | Delivery | Purpose |
|---|---|---|
WorldState | Sequenced | Pose and velocity of every authoritative entity, batched |
OwnerState | Sequenced | The predicted behaviour's reconciliation state for its owning client, with the last processed input tick and the worst input lead seen |
EntitySpawn, EntityDespawn | Reliable | Prefab, container, pose, owner, flags, the NetworkVariable blob and a sync keyframe |
EntityVars | Reliable | The full NetworkVariable blob when any variable changed |
EntityState | either | The sync channel: one chunk per dirty behaviour, delta or keyframe |
EntityRpc | Reliable | A ClientRpc or OwnerRpc (targeted at one client or broadcast) |
Client to gateway to worker
| Message | Delivery | Purpose |
|---|---|---|
ClientInput | Sequenced | One serialized input struct per tick, stamped with its tick |
ServerRpc | Reliable | A ServerRpc from the owning client |
Worker to worker (the lateral link)
| Message | Delivery | Purpose |
|---|---|---|
GhostSpawn, GhostDespawn | Reliable | Pre-warm and retire a ghost |
GhostState | Sequenced | Pose and velocity of ghosted entities at 60 Hz |
GhostVars, GhostSyncState | Reliable or Sequenced | NetworkVariable changes and sync chunks for ghosts |
AuthorityTransfer | Reliable | The flip: final state, new epoch, vars, pending inputs, sync keyframe, handover state, flags |
AuthorityRpc | Reliable | An AuthorityRpc sent by a worker that only holds a ghost of the target |
ForwardInput | Reliable | Inputs that still arrived at the previous owner shortly after a handover, forwarded to the new one |
Trust model
The lateral link carries no encryption or authentication: it is designed for a private datacenter network where every worker is trusted. The client link is plain UDP as well; put it behind your own edge (a relay or a DDoS-protected ingress) when you need hardening. Messages are written whole (every variable change ships the entity's full variable blob; positions are full floats); see Serialization.
Containers and handover
What a container is in the running code, how the ghost band pre-warms a neighbour, and exactly what happens when an entity crosses.
NetworkBehaviour and NetworkVariable
The component model for meshed entities, the role flags each process sees, the lifecycle hooks, and how replicated fields work.