Nebula
Client

NebulaClient

The game client.

generated from the sources
public sealed partial class NebulaClient : MonoBehaviour, IRpcSink

The game client. Talks only to the gateway, sends inputs, receives snapshots tagged (tick, epoch, owner) and predicts its own player. It is never a party to the handover protocol: which worker produced a snapshot is metadata used for epoch filtering and the debug overlay, nothing else.

Fields

PendingCorrectionTimeoutSeconds

public float PendingCorrectionTimeoutSeconds = 0.25f;

How long a queued correction waits for a fixed step before it is replayed in Update anyway (seconds): a client that stops predicting (a stall, no pawn) still applies what the worker said.

DefaultLeaveTimeoutSeconds

public const float DefaultLeaveTimeoutSeconds = 0.5f;

How long Leave waits, by default, for the gateway to confirm the goodbye.

Properties

Config

public NebulaConfig Config { get; }

ConnectionState

public State ConnectionState { get; }

ClientId

public ulong ClientId { get; }

PlayerName

public string PlayerName { get; } = "";

Identity

public string Identity { get; } = "";

The local player's identity across sessions (PlayerIdentity), from the gateway's Welcome: derived from the ID token in AuthToken, or from the anonymous token the gateway issued (kept in PlayerPrefs and presented again next time). Empty until welcomed.

AuthToken

public string AuthToken { get; set; } = "";

An OpenID Connect ID token to present at the next connection: what your sign-in flow got from a provider the mesh trusts (NebulaConfig.AuthIssuers). Set it before ConnectTo. Empty (the default) presents the saved anonymous token, or asks the gateway for a new anonymous identity. -nebula-auth-token (or ?nebula-auth-token= in a web build) seeds it.

SessionToken

public string SessionToken { get; set; } = "";

The session token from the last Welcome (WelcomeMsg.SessionToken), presented on every reconnection so the gateway that answers, whichever one of the mesh it is, gives this client its session and pawn back. Kept for the life of the process; Disconnect keeps it too, so a deliberate reconnect continues the session. Clear it to start a new session on the next connection.

ScopeKey

public string ScopeKey { get; set; } = CommandLine.Get("nebula-scope", "");

The simulation scope this client asks to be placed in, as the opaque key the game chose (EntityLocation.ScopeKey). Empty, the default, is the public world. Set it before connecting — a travel menu or a matchmaking reply hands the key over — and the gateway spawns the player into that scope's containers instead of the public ones. The scope must already have been activated (IControlPlane.ActivateScope); the gateway holds the join until it is ready. Seeded from -nebula-scope. Changing it takes effect on the next connection.

SessionReclaimed

public bool SessionReclaimed { get; }

True when the last Welcome reclaimed the session (the pawn is the one from before the reconnection).

LocalPlayer

public NetworkIdentity LocalPlayer { get; }

DrivenEntities

public IReadOnlyList<NetworkIdentity> DrivenEntities { get; }

The entities this client drives (NetworkIdentity.IsLocallyDriven): usually none, or the one vehicle the player sits at the wheel of. Each is predicted every tick with the player's input, before the pawn, and reconciled to its worker's state, the way the pawn is (docs/driven-vehicles.md).

FarEntities

public IReadOnlyDictionary<ulong, FarEntity> FarEntities { get; }

The far relevance tier (NEB-388): entities beyond their normal relevance radius but within their NetworkIdentity.FarRelevanceRadius of this client's focus, keyed by net id. Not replicas: a pose at the entity's far rate and its spawn facts, enough to draw a marker or an impostor and to target it. An entity is never both here and a replica; coming within its normal radius moves it to the replicas (FarEntityRemoved with FarEntityRemoval.Replicated, then EntitySpawned). Needs a gateway of protocol 26 or later; empty otherwise.

Join

public JoinState Join { get; }

How far the join has got, as the gateway sees it. A mesh with NebulaConfig.MinWorkers at 0 has nowhere to spawn the first player after an idle period, so the gateway holds the join in JoinState.Starting while a worker boots and completes it with no reconnect; show a "world starting" screen while this is JoinState.Starting.

JoinEstimatedSeconds

public int JoinEstimatedSeconds { get; }

Roughly how many seconds the gateway expects the JoinState.Starting hold to last; 0 when unknown.

JoinHoldReason

public JoinHoldReason JoinHoldReason { get; }

Why the gateway is holding the join, while Join is JoinState.Starting: the world is booting, or the scope this client named is not ready, is restoring or is retiring. Read it in a JoinStateChanged handler to say something more useful than "please wait".

JoinRejectReason

public JoinRejectReason JoinRejectReason { get; }

Why the last join was refused, in typed form (JoinRejectReason). The reason string is for the player; this is what the game's code branches on - a full station wants a queue or another instance, a bad token wants a sign-in screen. See docs/capacity-admission.md.

JoinRejectSaturation

public float JoinRejectSaturation { get; }

How full the target was when it refused the join, for Nebula.JoinRejectReason.AtCapacity: 1 = the whole of the dominant cost component's budget. 0 when the gateway did not say.

ServerProtocolWindow

public (ushort Min, ushort Max) ServerProtocolWindow { get; }

The inclusive protocol range reported by the gateway on the last refused join. Use it with Nebula.JoinRejectReason.ProtocolUnsupported to identify a version mismatch. A (0, 0) value means no range has been reported.

ServerContentVersion

public uint ServerContentVersion { get; }

The gateway's game content version from the last refused join. Zero means content version checks are disabled or no version has been reported.

NegotiatedProtocolVersion

public ushort NegotiatedProtocolVersion { get; }

The protocol version accepted in the last welcome, or HelloMsg.ProtocolVersion when the welcome omits it. Zero before the first welcome.

RttMs

public int RttMs { get; } = -1;

NetworkSimulation

public NetworkSimulation NetworkSimulation { get; } = new NetworkSimulation();

The network conditions this client simulates on its link to the gateway, for testing prediction and lag compensation on one machine. Read-only here; change them with SetNetworkSimulation. Its Nebula.NetworkSimulation.Active is false when nothing is simulated, always in a release build and on a web client.

EstimatedServerTick

public double EstimatedServerTick { get; }

PredictedTick

public uint PredictedTick { get; }

InputLeadTicks

public int InputLeadTicks { get; }

InputLeadAdjustTicks

public int InputLeadAdjustTicks { get; }

Adaptive part of InputLeadTicks. Half the RTT plus a fixed margin is a guess that ignores the gateway's and worker's frame loops; the worker reports how early inputs really land and this closes the gap.

LastReportedInputLead

public int LastReportedInputLead { get; }

Worst input lead the worker reported most recently (ticks). Diagnostics.

InputLeadIncreases

public int InputLeadIncreases { get; }

EntityCount

public int EntityCount { get; }

ReplicaCount

public int ReplicaCount { get; }

Replicas this client holds: with interest management this follows what is near it, not the world.

SpawnsReceived

public int SpawnsReceived { get; }

Spawns and despawns received since the last telemetry report; also in the stats line.

DespawnsReceived

public int DespawnsReceived { get; }

FocusHint

public Vector3? FocusHint { get; set; }

Where the player is looking, in this client's own Unity world space, when that is not simply where the pawn is: a free camera, an RTS view, a spectator. Set it and the client sends the gateway a ClientFocusHintMsg at up to NebulaConfig.InterestHintMaxHz, converting the point to absolute world coordinates on the way out (AbsoluteFocusHint), so a floating-origin shift between two hints does not move the place the gateway thinks you are watching.

The gateway treats it as an input and never as authority: it is refused unless the server allowed this client a focus (NebulaGateway.SetClientFocusMode), and by default it is clamped to NebulaConfig.InterestHintMaxDistance of the pawn. A game cannot widen its own interest by lying about where it is looking.

ContentAnchor

public Transform ContentAnchor { get; }

What this client's content follows: which cells or chunks are kept loaded, and where the floating origin sits. Null (the default) means the local pawn.

A strategy game does not want that. Its camera flies over a front the commander has no pawn near — or has no pawn at all — and content anchored to the pawn would leave the camera looking at unloaded terrain however much the gateway is willing to send. Handing the camera's transform to SetContentAnchor moves the window with the camera instead.

ActiveContentAnchor

public Transform ActiveContentAnchor { get; }

The transform content is actually anchored to right now: ContentAnchor when one is set and still alive, the local pawn otherwise, and null before either exists. Read by NebulaWorldStreaming (baked cell scenes) and NebulaChunkedWorld (runtime chunks), so one setting moves both.

Entities

public IEnumerable<NetworkIdentity> Entities { get; }

AuthorityChangesSeen

public int AuthorityChangesSeen { get; }

StreamLagTicks

public int StreamLagTicks { get; }

How far behind the newest tick heard the world-state stream of the slowest worker currently runs (ticks), over the last two seconds.

RenderDelayTicks

public int RenderDelayTicks { get; }

The render delay in use: NebulaConfig.InterpolationDelayTicks plus the measured stream lateness (or lag; see NebulaConfig.AdaptiveInterpolationDelay).

StreamLatenessTicks

public float StreamLatenessTicks { get; }

The world-state streams' lateness in ticks at NebulaConfig.InterpolationJitterPercentile, the worst worker's, over the last two seconds (StreamLateness). With NebulaConfig.AdaptiveInterpolationDelay the render delay grows by it, rounded down.

AdaptiveDelayTicks

public int AdaptiveDelayTicks { get; }

The ticks the adaptive render delay currently adds (NebulaConfig.AdaptiveInterpolationDelay).

LastError

public string LastError { get; } = "";

Last connection failure or disconnect reason, for connection UI. Empty while healthy.

SecondsSinceServerState

public float SecondsSinceServerState { get; }

Seconds since state last arrived from the server: entity state, owner state, variables, maps, RPCs, spawns or despawns, anything a worker produced. 0 before the first state since the client joined, and while it is not in the world. A healthy server sends the local player's own pawn state every tick, so anything above a few tenths of a second is worth noticing; see ServerStalled.

IsServerStalled

public bool IsServerStalled { get; }

True between ServerStalled and ServerResumed.

LastDisconnect

public DisconnectInfo LastDisconnect { get; } = new DisconnectInfo(DisconnectReason.Unknown, "", false);

How the last connection, or the last attempt to connect, ended: the same value Disconnected carried, updated by every failed retry too. DisconnectReason.Unknown with an empty message before the first disconnect.

IsReconnecting

public bool IsReconnecting { get; }

True while the client is reconnecting after losing a connection it had been welcomed on, or after its gateway asked it to move: from the drop until the next welcome (Reconnected), until it gives up (ReconnectGaveUp), or until the connection ends for good (a refusal, a replaced session, Disconnect). ConnectionState keeps moving between Disconnected, Connecting and Connected meanwhile. False while a first connection is still being attempted.

ReconnectAttempt

public int ReconnectAttempt { get; }

How many reconnection attempts the client has started in the current outage (1 during the first). 0 when not reconnecting.

ReconnectElapsedSeconds

public float ReconnectElapsedSeconds { get; }

Seconds since the connection dropped, while IsReconnecting; 0 otherwise.

WantsConnection

public bool WantsConnection { get; }

Whether the client is trying to be connected (set by Connect/ConnectTo, cleared by Disconnect).

ConnectsAutomatically

public bool ConnectsAutomatically { get; }

Whether the client connected on its own at startup because the command line supplied the connection (-nebula-gateway, -nebula-bot, or -nebula-connect). Connection UI such as NebulaTitleScreen stays hidden when this is true.

IsLeaving

public bool IsLeaving { get; }

True from Leave until the gateway has confirmed the goodbye or the timeout has passed; see LeaveFinished.

PortalQueries

public PortalQueries PortalQueries { get; }

Raycasts, sweeps and overlaps that continue through an open portal, for prediction and aim (docs/portals.md D32). A client queries the scenes it holds, in the frame each scope shares (ScopeFrames.Resolve), against the replicas as they stand; it keeps no state history, so a tick in the options has no effect here.

LinksWarming

public int LinksWarming { get; }

How many links this client is a member of and has not yet told their worker it holds the far window of.

Events

FarEntityAdded

public event Action<FarEntity> FarEntityAdded;

An entity entered the far tier (FarEntities).

FarEntityUpdated

public event Action<FarEntity> FarEntityUpdated;

A far entity's pose was updated (once per its far update interval).

FarEntityRemoved

public event Action<FarEntity, FarEntityRemoval> FarEntityRemoved;

An entity left the far tier, and why.

EntitySpawned

public event Action<NetworkIdentity> EntitySpawned;

EntityDespawned

public event Action<NetworkIdentity> EntityDespawned;

LocalPlayerSpawned

public event Action<NetworkIdentity> LocalPlayerSpawned;

EntityAuthorityChanged

public event Action<NetworkIdentity, ushort, ushort> EntityAuthorityChanged;

ContainerOwnershipChanged

public event Action ContainerOwnershipChanged;

ConnectionStateChanged

public event Action<State> ConnectionStateChanged;

JoinRejected

public event Action<string> JoinRejected;

The gateway refused the join (the reason is fit to show the player); see LastError. The client stops reconnecting unless the refused token was a saved anonymous one, which it forgets and retries without.

JoinRefused

public event Action<JoinRejectedMsg> JoinRefused;

The same refusal, typed: the reason code and how saturated the target was (JoinRejectedMsg). Raised after JoinRejected, on the main thread. A game that wants to put the player in a docking queue when a station is full listens here.

JoinStateChanged

public event Action<JoinState, int> JoinStateChanged;

The join's state changed: (state, estimated seconds). Raised on the main thread.

GatewayDraining

public event Action<int> GatewayDraining;

The gateway is draining and asked the client to reconnect (it does so by itself, with its session token); the argument is the seconds it was given. Raised after Disconnected and Reconnecting, with the client already State.Disconnected.

SessionReplaced

public event Action<string> SessionReplaced;

This player connected again somewhere else and that newer connection took the session and the pawn (NebulaConfig.SingleSessionPerPlayer). The client has left the world and does not reconnect by itself: show the reason (the argument, also in LastError) and let the player choose to take the session back with Connect. Raised on the main thread.

Disconnected

public event Action<DisconnectInfo> Disconnected;

The connection to the gateway ended, or the first attempt to connect failed. The argument says why (DisconnectInfo.Reason), with a readable message, and whether the client will try again by itself (DisconnectInfo.WillRetry). Raised once per outage: attempts that fail again while the client retries update LastDisconnect without raising it. It is raised again, with DisconnectInfo.WillRetry false, when the client stops trying: a refusal, a replaced session, or Disconnect. Raised on the main thread, after ConnectionStateChanged.

Reconnecting

public event Action Reconnecting;

The client lost a connection it had been welcomed on, or its gateway is draining, and it has started reconnecting: IsReconnecting is now true. Raised once per outage, after Disconnected. Show a "reconnecting" message rather than the main menu.

Reconnected

public event Action<bool> Reconnected;

A reconnection succeeded: the gateway welcomed the client again. The argument is SessionReclaimed: true when the same session and pawn came back, false when the worker had already let the pawn go and the player starts afresh. Raised on the main thread, after ConnectionStateChanged.

ReconnectGaveUp

public event Action ReconnectGaveUp;

The client stopped reconnecting after NebulaConfig.ReconnectGiveUpSeconds: WantsConnection is false, ConnectionState is State.Disconnected, and Disconnected has been raised with DisconnectInfo.WillRetry false and the reason of the last failure. Call Connect to try again.

LocalPlayerLost

public event Action<NetworkIdentity, LocalPlayerLossCause> LocalPlayerLost;

The local player's pawn was despawned while the connection stayed up. The cause says whether the worker that simulated it failed (LocalPlayerLossCause.WorkerLost: the gateway is placing the player again and LocalPlayerSpawned follows) or the server removed it (LocalPlayerLossCause.Despawned). Raised after EntityDespawned, while the pawn's object still exists; LocalPlayer is already null. Not raised when the connection ends: the whole world is cleared then, and Disconnected says why.

ServerStalled

public event Action ServerStalled;

No state has arrived from the server for NebulaConfig.ClientStallSeconds while the client is in the world and its link is up: the worker simulating the player has probably stopped. Entities stand still until state arrives again (ServerResumed) or the mesh recovers the player. Show a "server not responding" notice rather than disconnecting. Raised on the main thread.

ServerResumed

public event Action ServerResumed;

A stall reported by ServerStalled is over. Raised exactly once after each ServerStalled: when state arrives again, or when the client stops watching because the join went back to JoinState.Starting (a recovery) or the connection ended. Check Join and ConnectionState to tell which.

LeaveFinished

public event Action<bool> LeaveFinished;

A Leave is over. True when the gateway confirmed the goodbye (it closed the link) and the session has ended; false when there was no connection to say goodbye on, or the gateway did not answer in time, in which case the server treats the player as disconnected and ends the session after the reclaim grace. Raised on the main thread.

Methods

SetNetworkSimulation

public bool SetNetworkSimulation(float latencyMs, float jitterMs, float lossPercent);

Simulates a slower, less reliable link between this client and the gateway, for example from a game's console. latencyMs is the added round trip: half delays what this client sends and half what it receives, so RttMs rises by that much. jitterMs varies each packet's delay by up to half that much either way. lossPercent (0 to 100) is the share of packets lost in each direction; a lost reliable packet arrives a round trip late instead. Pass zeros to turn it off. The setting is logged when it changes and also shown in NebulaDebugOverlay. Returns false and changes nothing in a release build and on a web client, where it is not available.

ClearFocusHint

public void ClearFocusHint();

Withdraw the focus hint: interest returns to the pawn. Going quiet is not enough — the gateway keeps the last hint it accepted — so the clear is said out loud, reliably, under a new generation that makes any hint still in flight stale.

AbsoluteFocusHint

public void AbsoluteFocusHint(out double x, out double y, out double z);

FocusHint in absolute world coordinates: the frame position with the floating origin of the local pawn's scope added back, in double, exactly as a worker resolves its own entities (WorkerInterest.ToAbsolute). A scoped chunk grid moves its own origin, not the public one, so the hint is converted through the scope's frame (docs/scope-frames.md D4b, NEB-338). Without a world definition or a scope frame the origin never moves and the two spaces are the same. All zero when there is no hint.

SetContentAnchor

public void SetContentAnchor(Transform anchor);

Anchor content to this transform — the active strategy camera, a selected unit, a cinematic rig — instead of to the local pawn. Pass null to go back to the pawn. It is deliberately a transform and not the focus hint: the hint is an optional, rate-limited, server-validated request about what to be sent, while the anchor is a local decision about what to keep in memory, sampled every frame so load/unload hysteresis and origin shifts have something continuous to measure against.

Both are usually the same place, and a game that drives a free camera normally sets FocusHint to the camera's aim point and the anchor to the camera. Doing so is safe across an origin shift: the hint is converted to absolute coordinates as it is sent (AbsoluteFocusHint), so moving the origin under the camera does not move the point the gateway thinks anyone is watching. The pawn's prediction is unaffected too — it reconciles in the same frame everything else is in, and an origin shift moves that frame for all of them at once.

Initialize

public void Initialize(NebulaConfig config, bool autoConnect = true);
ParameterDescription
autoConnectConnect to NebulaConfig.GatewayAddress at once (bots, scripted clients); false leaves the client idle until game code, or NebulaTitleScreen, calls ConnectTo.

ConnectTo

public void ConnectTo(string address, ushort port, string playerName);

Connect to a gateway chosen at runtime, for example from your own connection UI. Replaces the configured address for reconnects. A blank playerName keeps the current PlayerName.

Connect

public void Connect();

ForgetStoredToken

public void ForgetStoredToken();

Drop the saved anonymous identity: the next connection without an AuthToken gets a new one, and the old player's entities and records stay behind. A "reset progress" or "play as guest" button.

Disconnect

public void Disconnect();

Leave the gateway and stop reconnecting. The client stays idle until the next Connect or ConnectTo. To the server this looks like a lost link: the worker keeps the pawn for NebulaConfig.SessionReclaimSeconds so the player can come back to it. Use Leave when the player is leaving for good.

Leave

public void Leave(float timeoutSeconds = DefaultLeaveTimeoutSeconds);

Leave the world for good: the player chose to quit, or to go back to the menu. Unlike Disconnect, the session ends at once instead of waiting out NebulaConfig.SessionReclaimSeconds: the client sends a goodbye, the worker calls NebulaGameMode.OnPlayerDespawn and removes the pawn (a persistent pawn keeps its record), and other players stop seeing it. The session token is cleared, so the next Connect starts a new session.

The client is State.Disconnected when this returns and raises Disconnected with DisconnectReason.ClientRequested, but it keeps the link open until the gateway closes it, which is how it knows the goodbye arrived, or until timeoutSeconds has passed. IsLeaving is true until then, and LeaveFinished says which it was. Before quitting, wait for it: yield return client.LeaveAndWait(); then Application.Quit(). A goodbye that does not arrive costs nothing but the grace: the server treats the player as disconnected.

With no welcomed connection there is nobody to say goodbye to: it acts like Disconnect and raises LeaveFinished with false at once.

LeaveAndWait

public System.Collections.IEnumerator LeaveAndWait(float timeoutSeconds = DefaultLeaveTimeoutSeconds);

Leave, then wait until the gateway has confirmed the goodbye or timeoutSeconds has passed. Run it as a coroutine before quitting: yield return client.LeaveAndWait(); Application.Quit();. The client keeps polling its transport in Update meanwhile, so the frame loop must keep running.

Find

public NetworkIdentity Find(ulong netId);

Nested types

State

public enum State

Lists the supported state values.

Members

NameValueDescription
Disconnected
Connecting
Connected
InGame

On this page