PredictedBehaviourBase
Non-generic surface Nebula's worker and client drive; game code derives from PredictedBehaviour<TInput>.
Nebula · Packages/com.1by3.nebula/Runtime/Core/PredictedBehaviour.cs[RequireComponent(typeof(NetworkTransform))]
public abstract class PredictedBehaviourBase : NetworkBehaviourNon-generic surface Nebula's worker and client drive; game code derives from PredictedBehaviour<TInput>.
Fields
MaxServerEventsPerTick
public const int MaxServerEventsPerTick = 4;The most server events that can apply on one tick of one entity. More are refused and counted in ServerEventsDropped.
ServerEventRepeatTicks
public const int ServerEventRepeatTicks = 30;For how many ticks after the tick it applies on a server event keeps being sent in the owner's reports, so a lost packet does not lose it. The owner ignores the repeats.
Properties
LastProcessedInputTick
public uint LastProcessedInputTick { get; protected set; }Server: tick of the newest input actually simulated. Reported to the owner for reconciliation.
ProcessedInputThisTick
public bool ProcessedInputThisTick { get; protected set; }Server: true when the most recent tick consumed a real (not repeated) input, i.e. the reported state is comparable to the owner's prediction.
PendingInputCount
public int PendingInputCount { get; protected set; }InputsMissed
public int InputsMissed { get; protected set; }Corrections
public int Corrections { get; protected set; }LastCorrectionMagnitude
public float LastCorrectionMagnitude { get; protected set; }MaxCorrectionMagnitude
public float MaxCorrectionMagnitude { get; protected set; }The largest correction since ResetCorrectionStats (or spawn): one big snap hides among many small ones otherwise.
CorrectionSmoothingSeconds
protected virtual float CorrectionSmoothingSeconds { get; }Owning client: how long a correction takes to fade from the screen, in seconds. When the worker's state disagrees with the prediction, the simulated state moves to the corrected one at once (the replay is exact), but the entity is drawn at an offset from it that starts at where it was last drawn and decays exponentially: the offset falls to 1% of its size after this long. Offsets from corrections that arrive faster than they decay add up. Default 0.1 (100 ms); 0 or less turns smoothing off, so the drawn pose snaps with the simulated one.
CorrectionSnapDistance
protected virtual float CorrectionSnapDistance { get; }Owning client: a correction that would leave the drawn pose further than this from the simulated one, in meters, is not smoothed; the entity teleports (a respawn, a long server-side move). Default 3.
CorrectionSpeed
protected virtual float CorrectionSpeed { get; }Owning client: the speed, in meters per second, that scales the correction threshold, the snap distance and the smoothing time below. Read on the worker's reported state of the tick being reconciled. Default: the magnitude of NetworkMotionState.Velocity, which the default WriteState carries; a game whose state keeps its velocity elsewhere overrides this. Return 0 to make all three speed-independent.
CorrectionThresholdTickFraction
protected virtual float CorrectionThresholdTickFraction { get; }Owning client: the correction threshold grows to this fraction of the distance the entity travels in one tick at CorrectionSpeed, when that is larger than CorrectionThreshold. At 400 m/s a tick is 6.7 m, so a fixed 5 cm threshold replays on errors no player can see. Default 0.25 (no change below about 12 m/s at 60 Hz); 0 keeps the threshold fixed.
CorrectionSnapSeconds
protected virtual float CorrectionSnapSeconds { get; }Owning client: the snap distance grows to the distance the entity travels in this many seconds at CorrectionSpeed, when that is larger than CorrectionSnapDistance: a 3 m correction is 7 ms of flight at 400 m/s and should be eased, not snapped. Default 0.25; 0 keeps the snap distance fixed.
CorrectionSmoothingDoublingSpeed
protected virtual float CorrectionSmoothingDoublingSpeed { get; }Owning client: the speed, in meters per second, at which the smoothing time has doubled. The smoothing time is CorrectionSmoothingSeconds × (1 + speed / this), at most three times CorrectionSmoothingSeconds: a correction at speed is eased over a longer time so the drawn pose does not visibly accelerate. Default 1500 (104 ms at 60 m/s, 127 ms at 400 m/s, 300 ms from 3 km/s, with the 100 ms default); 0 keeps it fixed.
LastCorrectionSpeed
public float LastCorrectionSpeed { get; }Owning client: CorrectionSpeed at the most recent reconcile, which sizes the snap distance and smoothing time of the offset it left.
CorrectionOffset
public Vector3 CorrectionOffset { get; }Owning client: the offset from the simulated position to the drawn one, in the current container's local space (zero when nothing is being eased).
CorrectionRotationOffset
public Quaternion CorrectionRotationOffset { get; }Owning client: the rotation that takes the simulated rotation to the drawn one, in the current container's local space.
CorrectionOffsetMeters
public float CorrectionOffsetMeters { get; }Owning client: the size of CorrectionOffset in meters, for diagnostics.
CorrectionOffsetDegrees
public float CorrectionOffsetDegrees { get; }Owning client: the angle of CorrectionRotationOffset in degrees, for diagnostics.
DrawnPosition
public Vector3 DrawnPosition { get; }Owning client: the world position the entity is drawn at, which is the simulated position plus the correction offset still being eased (and, with RenderBetweenTicks, moved back between the last two predicted ticks). Cameras and anything else that follows the entity on screen should read this, or transform.position during a frame, where Nebula has already moved the transform to the drawn pose (it moves it back before the next Simulate). Equal to transform.position when nothing is being eased.
DrawnRotation
public Quaternion DrawnRotation { get; }Owning client: the world rotation the entity is drawn with; see DrawnPosition.
RenderBetweenTicks
protected virtual bool RenderBetweenTicks { get; }Owning client: draw the entity between the poses of its last two predicted ticks instead of at the newest one. Prediction runs in fixed steps (60 Hz by default), so a fast predicted entity drawn at its newest tick moves in 16.7 ms steps whatever the frame rate: at 144 Hz three frames show one pose and the fourth jumps, and a frame with two fixed steps jumps twice as far. With this on, every frame draws the entity at Lerp(previous tick, newest tick, fraction), where the fraction is how far the frame is into the current fixed step (Unity's own RigidbodyInterpolation.Interpolate, done here because a predicted body is stepped by Nebula and its interpolation must stay off). Rotation is slerped the same way.
The blend is part of the drawn pose (DrawnPosition): Nebula moves the transform there at the end of its Update, so renderers and a camera that follows the transform in LateUpdate see the blended pose, and moves it back before the next Simulate, which never sees it. The cost is up to one tick (8 ms on average at 60 Hz) of extra visual latency on the entity. Nothing is blended across a container change, a teleport (a move outside Simulate), or a step longer than both CorrectionSnapDistance and two ticks at the entity's Motion.Velocity. Default false: a game that already interpolates its own view (an eye position of its own) keeps doing so.
RenderTickFraction
public float RenderTickFraction { get; }Owning client: how far the last drawn frame was into the current fixed step, 0 to 1 (1: at the newest tick). Diagnostics.
InputLeadMin
public int InputLeadMin { get; } = int.MaxValue;Server: the smallest input lead (input tick minus the worker's current tick at arrival) seen since the last TakeInputLead. An input only gets simulated when it arrives with a lead of at least 1; the owner uses this to stretch or relax how far ahead it sends. int.MaxValue while nothing arrived.
DriverInputTimeoutTicks
protected virtual int DriverInputTimeoutTicks { get; }Ticks without any input from the driver after which the worker stops repeating the last one and runs PredictedBehaviour<TInput>.GatherServerInput instead, until the driver's input comes back: a driver whose link drops does not leave the vehicle at full throttle. Default 30 (half a second). The driver keeps the seat; take it with NetworkIdentity.ClearDriver when the player leaves.
StepSkippedServerTicks
protected virtual bool StepSkippedServerTicks { get; }Whether the worker, after it skipped tick numbers because it fell too far behind the wall clock, steps this entity through each skipped tick that has an input waiting before it simulates the next one (NEB-412). The owner predicted every one of those ticks with its input, so an entity that is left out ends the next tick short of where its owner has it and is corrected. Return true when Simulate advances the entity itself and is a pure function of state and input, as the owner's replay already requires: each skipped tick then runs exactly as the replay runs it and the entity ends where its owner has it. Leave it false when Simulate relies on the physics engine stepping between ticks (it applies forces or sets a Rigidbody's velocity): the skipped ticks would apply their forces back to back before one physics step and the entity would lurch, so it is corrected after a skip instead. Default false.
SkippedTicksStepped
public int SkippedTicksStepped { get; protected set; }Authority: ticks run as part of a catch-up after the worker skipped their numbers (NEB-412).
ServerEventsPosted
public int ServerEventsPosted { get; }Authority: server events accepted by PostServerEvent<TEvent> since spawn.
ServerEventsDropped
public int ServerEventsDropped { get; }Authority: server events PostServerEvent<TEvent> refused because the tick they would apply on already held MaxServerEventsPerTick.
ServerEventReplays
public int ServerEventReplays { get; }Owning client: times a report made the prediction replay because it carried an event for a tick the client had already simulated without it.
Methods
ResetCorrectionStats
public void ResetCorrectionStats();ClearCorrectionOffset
public void ClearCorrectionOffset();Owning client: drop the correction offset, so the entity is drawn at its simulated pose from now on (a teleport the game wants shown at once).
OnDriverChanged
protected virtual void OnDriverChanged(ulong previous, ulong current);The driver of this entity changed (NetworkIdentity.SetDriver): current is the new driver's client id, 0 when nobody drives it now. Called on the worker that has authority when the change is made, and on every client that holds the entity when it hears of it, the new and the previous driver's included. Use it for what the seat does (a camera, a HUD, handing the controls back to the worker's own logic); Nebula has already switched the input source and the prediction.
PostServerEvent
public bool PostServerEvent<TEvent>(in TEvent e)
where TEvent : struct, INetworkSerializable;Authority only: attach an event to the next tick this entity simulates, so its Simulate sees it there with TryGetServerEvent<TEvent>. The owner's client is sent the event with its state reports and keeps it, so its prediction replays the event on the same tick, and it travels with the entity when another worker takes it over, without applying twice. Use it for what the worker decides and the owner cannot know: a shove, a stun, a forced move.
At most MaxServerEventsPerTick events apply on one tick; the next is refused, counted in ServerEventsDropped, and this returns false. It also returns false, with a warning once, on a copy without authority. TEvent is identified on the wire by a hash of its full name, so use a distinct struct for each kind of event.
TryGetServerEvent
protected bool TryGetServerEvent<TEvent>(out TEvent e)
where TEvent : struct, INetworkSerializable;
protected bool TryGetServerEvent<TEvent>(int index, out TEvent e)
where TEvent : struct, INetworkSerializable;Inside Simulate: the server event of type TEvent posted for the tick being simulated, if any. It is there on the authoritative worker, in the owner's replay of that tick and in the owner's own simulation of it once its client has heard of the event. Apply it to simulated state (velocity, a stun timer: whatever WriteState carries) so that the owner's replay ends where the worker did. Outside Simulate it finds nothing.