Nebula

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.

DeliverySemanticsUsed for
SequencedDoes 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.
ReliableOrderedReliable, orderedLifecycle, 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

MessageDirectionPurpose
Helloclient to gateway; both sides of service linksIdentify 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.
Welcomegateway to clientAssign the session ID (the client ID), report the tick rate, server tick, and identity, and hand out the session token.
JoinStatus, JoinRejectedgateway to clientReport that the join is waiting for a worker or was completed, or refuse it with a reason (and whether retrying makes sense).
GatewayDraininggateway to clientAsk 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.
Goodbyeclient to gatewayThe player is leaving for good (NebulaClient.Leave): end the session now, without the reclaim grace.
Kickedgateway to clientThe server removed the player, with the game's code and a reason. The client does not reconnect.
Ping, Pongclient to gateway and backRound-trip time (RTT) measurement for the input lead.
SpawnPlayer, DespawnPlayergateway to workerAsk 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.
KickPlayerworker to gatewayRemove a player (NebulaWorker.Kick): the gateway sends the client Kicked and ends the session.
ContainerOwnershipgateway to clientThe 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)

MessageDirectionPurpose
InterestSubscribegateway to workerThe set of regions and explicit entity IDs the gateway's clients currently need, sent as an idempotent delta or a full snapshot.
InterestResyncworker to gatewayThe worker could not apply a delta (its base sequence or the verifying hash did not match) and needs a full snapshot.
EntityRedirectworker to gatewayAn entity the gateway follows explicitly (by ownership or subscription) moved to another worker.
EntityForgetworker to gatewayAn entity left every region this gateway subscribes on this worker.
SyncAudienceworker to gatewayThe clients chosen by a behavior's Custom sync audience changed. Never forwarded to a client.
ClientFocusHintclient to gatewayWhere 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

MessageDeliveryPurpose
WorldStateSequenced or ReliableBatched selected fields from opted-in root transforms. Reliable delivery carries container changes, teleports, recovery updates, and transforms configured for reliable delivery.
OwnerStateSequencedThe predicted behavior's reconciliation state for its owning client, with the last processed input tick and the worst input lead seen
EntitySpawn, EntityDespawnReliableCreates or removes an entity. A spawn includes its prefab, container, transform, owner, flags, variables, and a complete sync state.
EntityVarsReliableSends every NetworkVariable value after one changes.
EntityMapsReliableSends 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.
EntityStateeitherSends state from sync components that have changed. The gateway gives each client only the components whose sync audience includes it.
EntityRpcReliable, or sequenced when the attribute sets Delivery = Delivery.SequencedA 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

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

Worker to worker

MessageDeliveryPurpose
GhostSpawn, GhostDespawnReliableCreates or removes a ghost copy.
GhostStateSequenced or ReliableSelected root transform updates for ghosts, with the same opt-in and recovery rules as WorldState.
GhostVarsReliableSends the complete variable block after one variable changes.
GhostMapsReliableSends the entries of a NetworkMap that changed.
GhostSyncStateReliable or SequencedSends sync-component changes to ghosts.
AuthorityTransferReliableTransfers 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.
AuthorityRpcReliableAn AuthorityRpc sent by a worker that only holds a ghost of the target
ForwardInputSequencedInputs 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.

On this page