Network Time

Overview

Fusion maintains a synchronized clock across all clients in a room. This clock is derived from the Photon server's authoritative time and adjusted locally to account for network latency and jitter.

Time Model

Each client tracks several internal values to maintain clock synchronization.

Field Type Description
_localClock double Accumulated real time from UpdateFrameBegin(dt) calls; drives internal timers
_serverClock double The network clock — the local estimate of the server's authoritative time
_timeDiff double Last measured offset: server time minus the network clock, sampled when a state packet arrives
_serverClockScale double Rate multiplier applied to the network clock while converging

The synchronized time is the network clock: NetworkTime() returns the server clock estimate directly. Each frame, UpdateFrameBegin(dt) advances it by dt * _serverClockScale, so the scale — not an offset added at read time — is what pulls all clients toward the server's clock.

Client API

NetworkTime()

C++

double Client::NetworkTime() const;

Returns the current synchronized network time in seconds — the local estimate of the server's authoritative clock. This is the primary time value for gameplay synchronization. All clients in the same room converge to approximately the same NetworkTime() value, accounting for their individual latencies.

Use NetworkTime() for:

  • Synchronized game events (countdowns, round starts)
  • Interpolation timestamps
  • Time-based gameplay logic that must agree across clients

NetworkTimeScale()

C++

double Client::NetworkTimeScale() const;

Returns the current time scale factor — the rate multiplier applied to the network clock each frame. The SDK sets this to smoothly converge the network clock toward the server clock:

  • Under normal conditions (offset at or below 0.1 seconds), the scale is exactly 1.0.
  • When the network clock is behind the server, the scale is 1 + 1/60 (speeds up).
  • When the network clock is ahead of the server, the scale is 1 - 1/60 (slows down).

Engine integrations can use this to scale gameplay speed in sync with the network clock, though this is optional.

NetworkTimeDiff()

C++

double Client::NetworkTimeDiff() const;

Returns the raw offset between the server time in the last received state packet and the network clock at that moment (server time minus NetworkTime()). A positive value means the server clock is ahead of the local estimate. Useful for diagnostics and latency estimation, but rarely needed in gameplay code.

GetTime(obj)

C++

double Client::GetTime(const Object* obj);

Returns the synchronized time for a specific object. On the client that can modify the object (CanModify() is true — the owner or authority), this returns the same value as NetworkTime(). On every other client it returns the object's per-object time — the timestamp the authority attached to the most recently received state snapshot — which may lag NetworkTime() depending on send rate and latency. Returns 0 for a null object or an object without a root.

Per-Object Time

C++

class ObjectRoot : public Object {
public:
    double GetTime() const;  // Network-synchronized time for this object (backing field is private)
};

This value is set during UpdateFrameBegin() when state updates arrive for the object. It represents the timestamp at which the authority client generated the state snapshot. Use this for per-object interpolation or extrapolation.

Tick Counters

In addition to continuous time, the SDK maintains discrete tick counters.

GetSendTick()

C++

Tick Client::GetSendTick() const;

Returns the current outgoing tick counter. Tick is a monotonically increasing uint32_t that increments each time the SDK sends a state packet. Used internally for change detection and ack tracking.

GetReceivedCounter()

C++

Tick Client::GetReceivedCounter() const;

Returns the received state packet counter. This tells you how many state updates the client has received total. Useful for detecting stalls or measuring update frequency.

Per-Object Ticks

Each object tracks its own send/ack ticks, exposed through read-only accessors (the backing fields are private):

C++

class Object {
public:
    Tick GetRemoteTickSent() const;   // Last tick sent for this object
    Tick GetRemoteTickAcked() const;  // Last tick the receiver acknowledged
};

Time Encoding

The SDK uses quantized encoding to transmit time values efficiently over the wire:

C++

int64_t ClockQuantizeEncode(double clock);
double  ClockQuantizeDecode(int64_t clock);

These functions convert between double seconds and a compact int64_t representation suitable for network transmission via ReadBuffer/WriteBuffer:

C++

// Writing time in a packet
void WriteBuffer::Time(double time);
void WriteBuffer::TimeBase(double time);

// Reading time from a packet
double ReadBuffer::Time();
double ReadBuffer::TimeBase();

TimeBase is written once per packet as an absolute reference. Subsequent Time values within the same packet are encoded as deltas relative to the base, reducing bandwidth.

How Time Synchronization Works

The server's authoritative time arrives as the time base of every state packet, which the SDK processes during UpdateFrameBegin(). On each received sample the SDK compares it against the network clock and picks one of three behaviors based on the offset.

Offset Behavior
First sample ever The network clock snaps to the server time; scale is set to 1.0.
Above 0.25 s Hard snap: the network clock jumps to the server time; scale resets to 1.0.
0.1 s to 0.25 s Rate correction: scale is set to 1 + 1/60 (behind) or 1 - 1/60 (ahead).
At or below 0.1 s Passthrough: scale is 1.0; the clock runs at normal speed.

Each frame, UpdateFrameBegin(dt) advances the network clock by dt * scale.

The clock hard-snaps only on the first sample or when the offset exceeds 0.25 seconds — typically at startup, after long stalls or during map loads. Within the 0.1 to 0.25 second band it converges gradually at a fixed ±1/60 rate, producing smooth, jitter-free time progression; below 0.1 seconds it is left alone. After StateUpdatesResume() (see Frame Loop), incoming server time is ignored for one second so backlogged packets cannot yank the clock.

The dt Parameter

The dt parameter to UpdateFrameBegin(double dt) is critical for time synchronization:

C++

// Correct: real elapsed time
fusionClient->UpdateFrameBegin(delta_from_engine);

// Incorrect: fixed timestep (unless truly fixed framerate)
fusionClient->UpdateFrameBegin(1.0 / 60.0);  // Causes drift

// Incorrect: zero
fusionClient->UpdateFrameBegin(0.0);  // Time stops advancing

The SDK uses dt to advance the network clock (scaled by NetworkTimeScale()) and its internal timers. Inaccurate values cause the network clock to drift from the server, forcing larger rate corrections — or hard snaps — and potential gameplay issues.

Always pass the real elapsed time from the engine to UpdateFrameBegin(). Using a fixed timestep or zero causes clock drift and synchronization problems.

RTT (Round-Trip Time)

C++

double Client::GetRtt() const;

Returns the round-trip time in seconds. This is the round trip measured on Fusion's own reliable delivery (Notify) layer — a smoothed average taken from acknowledgment timing — not the raw transport ping. It returns 0 before Client::Start() has run (no Notify connection exists yet) and after Stop(). Useful for:

  • Displaying ping to the player
  • Adjusting interpolation buffer size
  • Compensating for network delay in gameplay

Integration Guidelines

Guideline Rationale
Always pass real delta time to UpdateFrameBegin() Prevents clock drift
Do not cache NetworkTime() across frames Value changes every frame
Use NetworkTime() for gameplay, not wall-clock time Wall-clock diverges across clients
Use GetTime(obj) for per-object interpolation More accurate than global time
Use GetSendTick() for frame-level comparisons Integer, no floating-point issues
  • {VersionPath}/manual/architecture -- How dt feeds into time synchronization
  • {VersionPath}/manual/rpcs -- Time encoding in ReadBuffer/WriteBuffer
Back to top