Nebula

Introduction

What Nebula is, how the pieces fit, and where to go next.

Nebula is middleware for dynamically meshed multiplayer game servers, Unity-first. One world is served by many dedicated server processes. Each one is leased authority over a set of containers, the designer-authored volumes your level is divided into, and entities cross between them without the player noticing. The reference point is Star Citizen's server meshing; the hard part Nebula targets is the dynamic case, where which server owns which part of the world changes at runtime.

A Nebula mesh is a small set of roles, all built from one Unity player: N workers, one gateway, one orchestrator and one control plane. The design is validated on the strictest case, a first-person shooter where players cross container boundaries mid-firefight.

How the pieces fit

                 ┌──────────────────────────────────────────────────────────┐
                 │  SpacetimeDB  (control plane: workers, leases, gateways)  │
                 └──────▲──────────────▲──────────────▲──────────────▲──────┘
                        │ reducers     │ subscribe    │              │
                 ┌──────┴──────┐  ┌────┴────┐   ┌─────┴─────┐  ┌─────┴─────┐
                 │ Orchestrator│  │ Gateway │   │ Worker w1 │◄─►│ Worker w2 │ ... lateral link (UDP)
                 │ spawns procs│  │ udp/7000│◄─►│ udp/7101  │   │ udp/7102  │     ghosts + authority transfers
                 └─────────────┘  └────▲────┘   └───────────┘  └───────────┘
                                       │ one connection per client
                                  ┌────┴────┐
                                  │ Client  │  inputs up, snapshots down, predicted local player
                                  └─────────┘

Every box is the same Unity player build started with a different -nebula-role. The Editor plays the client role by default.

  • Workers are headless Unity processes simulating at 60 Hz. Each holds authority over the containers it leases, ghosts entities that approach a neighbouring container to that container's worker, and hands authority over when they cross.
  • The gateway is the one address clients connect to. It routes each client's inputs to whichever worker currently owns their entity and re-emits the workers' replication streams.
  • The orchestrator keeps N workers running and deals containers to them through the control plane. Its dashboard lets you add, remove and kill workers while the game runs.
  • The control plane is a tiny SpacetimeDB module holding worker registrations, container leases with epochs, and mesh-wide settings. It is never on the per-tick path.
  • Clients predict and reconcile their own player and are never a party to the handover protocol.

The API

Gameplay code is written against a surface Mirror and Netcode for GameObjects users already know: NetworkBehaviour, NetworkVariable<T>, [ClientRpc], [ServerRpc], NetworkTransform, NetworkAnimator, NetworkRigidbody, and a PredictedBehaviour<TInput> for client-predicted controllers. The meshing-specific additions are small: HasAuthority, IsGhost, OnGainedAuthority/OnLostAuthority, a handover-state hook, and [AuthorityRpc], which runs on whichever worker owns an entity no matter who calls it.

public sealed class PlayerController : PredictedBehaviour<ShooterInput>
{
    public NetworkVariable<float> Health = new NetworkVariable<float>(100f);

    protected override ShooterInput GatherInput() { /* owner client: read the Input System */ }
    protected override void Simulate(uint tick, in ShooterInput input, float dt) { /* worker + predicting client */ }

    [AuthorityRpc] void TakeDamage(float amount, ulong attacker, string attackerName) { Health.Value -= amount; }
    [ClientRpc]    void RpcShotFired(Vector3 from, Vector3 to, bool hit) { /* laser bolt */ }
}

Where to go

On this page