Nebula

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:

DeliverySemanticsUsed for
SequencedUnreliable, newest wins, cannot fragment (packets are cut at 500 bytes)Transform streams: WorldState, GhostState, ClientInput, OwnerState, and sync chunks that opt in
ReliableOrderedReliable, orderedEverything 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

MessageDirectionPurpose
Hello, Welcomeany peer to any peerIdentify the role, id and index; a client's Hello carries its name and bot flag
Ping, Pongclient to gatewayRTT measurement for the input lead
SpawnPlayer, DespawnPlayergateway to workerAsk the owner of a container to create or remove a player's entity
ContainerOwnershipworker to gateway to clientThe container-to-worker table with lease epochs, for routing and the overlay

Worker to gateway to client

MessageDeliveryPurpose
WorldStateSequencedPose and velocity of every authoritative entity, batched
OwnerStateSequencedThe predicted behaviour's reconciliation state for its owning client, with the last processed input tick and the worst input lead seen
EntitySpawn, EntityDespawnReliablePrefab, container, pose, owner, flags, the NetworkVariable blob and a sync keyframe
EntityVarsReliableThe full NetworkVariable blob when any variable changed
EntityStateeitherThe sync channel: one chunk per dirty behaviour, delta or keyframe
EntityRpcReliableA ClientRpc or OwnerRpc (targeted at one client or broadcast)

Client to gateway to worker

MessageDeliveryPurpose
ClientInputSequencedOne serialized input struct per tick, stamped with its tick
ServerRpcReliableA ServerRpc from the owning client

Worker to worker (the lateral link)

MessageDeliveryPurpose
GhostSpawn, GhostDespawnReliablePre-warm and retire a ghost
GhostStateSequencedPose and velocity of ghosted entities at 60 Hz
GhostVars, GhostSyncStateReliable or SequencedNetworkVariable changes and sync chunks for ghosts
AuthorityTransferReliableThe flip: final state, new epoch, vars, pending inputs, sync keyframe, handover state, flags
AuthorityRpcReliableAn AuthorityRpc sent by a worker that only holds a ghost of the target
ForwardInputReliableInputs 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.

On this page