NebulaClient
The game client.
Nebula · Packages/com.1by3.nebula/Runtime/Client/NebulaClient.cspublic sealed partial class NebulaClient : MonoBehaviour, IRpcSinkThe 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);| Parameter | Description |
|---|---|
autoConnect | Connect 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 StateLists the supported state values.
Members
| Name | Value | Description |
|---|---|---|
Disconnected | ||
Connecting | ||
Connected | ||
InGame |