Roles and network messages
Understand each Nebula process, its connections, and the messages it exchanges.
Build the Unity player and standalone .NET services with nebula build. Workers and clients use NebulaBootstrap, which reads -nebula-role, loads the configured game scene, and starts the matching components. In the Unity Editor, it uses EditorRole instead. The orchestrator and gateway read the game data exported during the build and run without Unity.
A process cannot run both the worker and client roles. You can run the gateway and orchestrator together with -nebula-role services, although the CLI starts them as separate processes.
Worker
To start a worker directly, pass -batchmode -nographics -nebula-role worker. A worker loads the game scene in a headless Unity process, so your server code can use colliders, physics, and NavMesh.
A worker also holds the mesh's persistence service. It checkpoints the entities it controls that carry a PersistentEntity and restores them when it gains a container lease. See Persistence.
The worker listens on the port from -nebula-port, the PORT environment variable, or WorkerBasePort + index, in that order, and registers that port with the control plane. Gateways and other workers connect to the registered port, so it does not have to be agreed in advance. Each tick, it simulates authoritative entities, updates ghosts, performs handovers, and sends entity state to the gateway. Its heartbeat includes tick time and entity counts. See Containers and handover for the tick sequence and Spawning for the gameplay API.
Gateway
The standalone nebula-gateway executable listens for clients on UDP port 7000 by default. It also accepts clients from a Unity Web build: it answers WebRTC offers and serves the web build over HTTP on TCP port 7000, and receives their WebRTC traffic on UDP port 7001. See Build for the web. nebula start launches it for you. When starting it directly, keep nebula-services.json beside the executable or select an export with -nebula-service-manifest <path>.
The gateway connects to workers that hold regions or entities its clients need. It routes each client's input to the worker that controls that client's entity and sends worker replication streams to clients. It rejects entity messages with an old authority epoch or an invalid sender.
When a client connects, the gateway chooses a ready container and asks its worker to spawn the player's entity. It caches one keyframe for each sync behavior so a new client can create entities in the correct state.
The gateway does not hold authoritative state. If a worker stops, the gateway removes that worker's entities and asks another worker to spawn affected players. It filters entities by interest: instance visibility first, then which entities are near enough to a client's foci to be in its set, then a further update-rate reduction for entities farther from the client's pawn. A gateway connects to a worker only while it needs something from it — one of its clients' foci covers a region the worker publishes, or the worker holds an entity the gateway must follow — instead of connecting to every worker in the mesh. Configure the interest radii and the update-rate distance bands in NebulaConfig.
One mesh can have several gateways. Each has a persistent ID (-nebula-gateway-id) and a random incarnation per start, and assigns every client a session ID that is unique across the mesh: (incarnation << 32) | sequence. That session ID is the client ID game code sees. The gateway's welcome carries a signed session token; a client that reconnects through any gateway of the mesh presents it and keeps its session ID and pawn. Workers key player state by session ID and accept input for a session only from the gateway that currently holds it. A gateway asked to drain refuses new clients and tells the ones it has to reconnect elsewhere, and every gateway reports its load on each control-plane heartbeat. See Run more than one gateway.
Orchestrator
The standalone nebula-orchestrator executable starts the gateway and keeps DesiredWorkers workers running. nebula start supplies the exported service data and Unity worker executable path. A worker host decides whether workers run as child processes or on separate cloud virtual machines.
The orchestrator creates one assignment for each container and distributes them across active workers every 500 milliseconds. It serves the dashboard and HTTP API on TCP port 7080 by default. It also reads initial shared settings from -nebula-settings. See Orchestrator and Nebula Dashboard.
Control plane
The control plane is one document the orchestrator hosts over HTTP on its dashboard port (/api/control-plane). It lists workers, container assignments, gateways, and shared game settings. Its writes register processes, record heartbeats, change assignments, and update settings.
Workers, the gateway, and the orchestrator use IControlPlane to read these records. They do not send per-tick entity state through the control plane. If the connection fails, workers continue with the last topology they received. See Control plane.
The control plane holds no entity state and starts empty on every run. Saved entities live in the orchestrator's database (SQLite or PostgreSQL), in a table of their own. Workers reach it through IPersistenceStore, over the same HTTP server (/api/store), not through IControlPlane. Nothing about it is on the per-tick path, and it adds no UDP messages.
Client
The client is the default role and connects only to the gateway. For a predicted player, it sends one input per tick and runs ahead of the worker by an adaptive input lead. It corrects its prediction from authoritative snapshots. It displays other entities InterpolationDelayTicks behind the newest snapshot so it can interpolate between states.
Snapshots include the simulation tick, authority epoch, and worker index. Nebula uses the epoch to reject old state. It uses the worker index for diagnostics.
Bots and clients started with connection arguments connect immediately. Any other client waits until game code calls NebulaClient.ConnectTo, or until the player connects from the optional NebulaTitleScreen. See Connecting clients and Prediction.
Time
NetworkTime.TickRate is 60. Each worker calculates its current tick from the wall clock and a fixed origin. This keeps workers on the same tick number without a coordinating process.
On a client, NetworkTime.Tick is the tick being predicted for the local player. It runs ahead of the worker by the current input lead. NetworkTime.RenderTick is the fractional tick used to display remote entities.
Use tick numbers, not wall-clock timestamps, in gameplay simulation.
Transport
Workers, the gateway, and native clients connect with LiteNetLib over User Datagram Protocol (UDP). A web client connects to the gateway over two WebRTC data channels, one for each delivery class, carrying the same messages. Every link goes through the ITransport interface. A delivery class determines whether Nebula retries a lost message and whether messages must arrive in order.
Delivery | Semantics | Used for |
|---|---|---|
Sequenced | Does not retry lost messages. Keeps only the newest message. Transform batches are split at about 500 bytes. | ClientInput, ForwardInput, OwnerState, and transform or sync-component updates that select sequenced delivery. |
ReliableOrdered | Reliable, ordered | Lifecycle, variables, RPCs, authority transfer, and transform or sync-component updates marked reliable. |
Nebula processes transport events only when it calls Poll. This keeps network callbacks on the simulation thread.
Messages
Every entity message contains a network ID (netId) and authority epoch. The spawning worker creates the ID from its 16-bit worker index and a 48-bit local sequence number.
Nebula sends positions and rotations in container-local space. Each message includes a ContainerRef, which identifies a static container by index or a dynamic container by its carrier's network ID. Find the message structs in Packages/com.1by3.nebula/Runtime/Protocol/Messages.cs or the Protocol reference.
For exact byte layouts, numeric message IDs, delivery requirements, and compatibility rules, see the wire protocol specification.
Session
| Message | Direction | Purpose |
|---|---|---|
Hello | client to gateway; both sides of service links | Identify the role, id, index, protocol version, and flags. A client adds its identity token and, when reconnecting, its session token; a gateway or worker adds its incarnation and, when the mesh has a token, a credential derived from it. |
Welcome | gateway to client | Assign the session ID (the client ID), report the tick rate, server tick, and identity, and hand out the session token. |
JoinStatus, JoinRejected | gateway to client | Report that the join is waiting for a worker or was completed, or refuse it with a reason (and whether retrying makes sense). |
GatewayDraining | gateway to client | Ask the client to reconnect elsewhere within a number of seconds because the gateway is leaving service. A gateway that is stopping also sends it, marked as a server shutdown when no other gateway is serving. |
Goodbye | client to gateway | The player is leaving for good (NebulaClient.Leave): end the session now, without the reclaim grace. |
Kicked | gateway to client | The server removed the player, with the game's code and a reason. The client does not reconnect. |
Ping, Pong | client to gateway and back | Round-trip time (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. Both carry the session's connection generation, so a worker ignores a message from a gateway that has since lost the session. SpawnPlayer also carries the player's identity and the forwarded claims of their verified token. A despawn after a goodbye or a kick removes the pawn at once instead of after the reclaim grace. |
KickPlayer | worker to gateway | Remove a player (NebulaWorker.Kick): the gateway sends the client Kicked and ends the session. |
ContainerOwnership | gateway to client | The containers this client should know about, with lease epochs, for routing and the overlay. A per-client delta scoped to the client's interest window, not the mesh's whole lease table. |
Gateway to worker (interest subscription)
| Message | Direction | Purpose |
|---|---|---|
InterestSubscribe | gateway to worker | The set of regions and explicit entity IDs the gateway's clients currently need, sent as an idempotent delta or a full snapshot. |
InterestResync | worker to gateway | The worker could not apply a delta (its base sequence or the verifying hash did not match) and needs a full snapshot. |
EntityRedirect | worker to gateway | An entity the gateway follows explicitly (by ownership or subscription) moved to another worker. |
EntityForget | worker to gateway | An entity left every region this gateway subscribes on this worker. |
SyncAudience | worker to gateway | The clients chosen by a behavior's Custom sync audience changed. Never forwarded to a client. |
ClientFocusHint | client to gateway | Where a client's camera or scope is looking, as an unreliable, rate-limited input treated only as far as the server-owned focus mode allows for that client — clamped to the pawn by default. See Interest management. |
See Interest management for how these messages are used, and the wire protocol specification for their exact layout.
Worker to gateway to client
| Message | Delivery | Purpose |
|---|---|---|
WorldState | Sequenced or Reliable | Batched selected fields from opted-in root transforms. Reliable delivery carries container changes, teleports, recovery updates, and transforms configured for reliable delivery. |
OwnerState | Sequenced | The predicted behavior's reconciliation state for its owning client, with the last processed input tick and the worst input lead seen |
EntitySpawn, EntityDespawn | Reliable | Creates or removes an entity. A spawn includes its prefab, container, transform, owner, flags, variables, and a complete sync state. |
EntityVars | Reliable | Sends every NetworkVariable value after one changes. |
EntityMaps | Reliable | Sends the entries of a NetworkMap that changed. The gateway keeps each map's current contents, so a client that arrives later gets them in its spawn. |
EntityState | either | Sends state from sync components that have changed. The gateway gives each client only the components whose sync audience includes it. |
EntityRpc | Reliable, or sequenced when the attribute sets Delivery = Delivery.Sequenced | A ClientRpc or OwnerRpc (targeted at one client or broadcast). A broadcast declared with a Radius carries it, and the gateway forwards it only to clients whose pawn is within that distance of the entity. |
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
| Message | Delivery | Purpose |
|---|---|---|
GhostSpawn, GhostDespawn | Reliable | Creates or removes a ghost copy. |
GhostState | Sequenced or Reliable | Selected root transform updates for ghosts, with the same opt-in and recovery rules as WorldState. |
GhostVars | Reliable | Sends the complete variable block after one variable changes. |
GhostMaps | Reliable | Sends the entries of a NetworkMap that changed. |
GhostSyncState | Reliable or Sequenced | Sends sync-component changes to ghosts. |
AuthorityTransfer | Reliable | Transfers authority with the complete final pose, new epoch, pending inputs, handover state, existing ghost recipients, the owner's session (generation and gateway) so the receiver accepts the owner's input at once, and the gateways that were following the entity by explicit interest subscription rather than by region. |
AuthorityRpc | Reliable | An AuthorityRpc sent by a worker that only holds a ghost of the target |
ForwardInput | Sequenced | Inputs that still arrived at the previous owner shortly after a handover, forwarded to the new one |
Trust model
Nebula does not encrypt the worker-to-worker or UDP client links. When the mesh has a MeshToken, gateways and workers refuse a peer that cannot present a credential derived from it; without one, any peer is accepted. Clients are identified by token (see Authentication), but the token travels in the clear on UDP. Run workers on a trusted private network. Put public client traffic behind a relay or protected ingress that meets your security requirements.
Nebula sends a complete entity variable block when any NetworkVariable changes. It sends most positions as full floating-point values. See Serialization.
Distributed physics
What a physics interaction does on one worker, across a container seam through a ghost, and why a joint between two workers is unsupported.
Nebula and other Unity networking libraries
An honest comparison of Nebula with Netcode for GameObjects, Mirror, FishNet, and Photon Fusion, and the gaps Nebula intends to close.