-
Notifications
You must be signed in to change notification settings - Fork 0
Time Synchronization
Synchronizing time between two computers over UDP is a fundamental challenge in multiplayer game networking. Since UDP does not guarantee packet delivery or order, you need a robust algorithm that accounts for network latency and clock drift. We have the extra challenge in that many of the computers we would like to target do not have real-time clocks so we must do something to keep whatever time resource available in-sync as best we can with the server (and thus each other).
One common approach is inspired by the Network Time Protocol (NTP), but simplified for game synchronization. The goal is to estimate the clock difference (offset) and transmission delay.
-
T1 - Client Sends Request The client sends a UDP packet to the server with the local timestamp
$T_1$ . -
T2 - Server Receives Request The server records its receive time
$T_2$ . -
T3 - Server Sends Response The server replies to the client with both
$T_2$ (when it received the request) and$T_3$ (when it is sending the response). -
T4 - Client Receives Response When the client receives the response, it records the local receive timestamp
$T_4$ . -
Calculate Clock Offset & Network Delay The client now has four timestamps:
$T_1$ ,$T_2$ ,$T_3$ ,$T_4$ . Using these, it can estimate:
-
Round-Trip Time (RTT):
$RTT=(T_4-T_1)-(T_3-T_2)$
This approximates the total network delay. -
Clock Offset (
$\Delta$ ):
$\Delta=\frac{(T_2-T_1)+(T_3-T_4)}{2}$
This estimates the difference between the client’s and server’s clocks.
- Adjust Client Time The client can adjust its clock using the estimated offset Δ or use it to interpolate timestamps for synchronization.
- Multiple Requests: Since UDP is unreliable, sending multiple sync requests and averaging results can improve accuracy.
- Smoothing Techniques: Use exponential moving averages to gradually adjust time instead of abrupt changes.
The Atari platforms have a few choices for clocks. One high rate choice is provided by the POKEY chip which, although, can provide fast clock response, doesn't do it for very long without rollover. Given our potential high latency communications the best (better?) alternative might just be the JIFFY clock which is at the rate of the screen refresh.
The JIFFY clock is simply a counter that is updated when each frame is drawn. This means that NTSC and PAL systems will have different rates but possibly still within our timespan of reason. ~60Hz and ~50Hz respectively.
That would all be fine and dandy if they actually were 60Hz and 50Hz. This would mean we could simply convert to actual units of time (milliseconds in this case) by a simple shift operation, forgiving the forever remainder on 60Hz. All said, they do not abide by this nice timing and instead are "close":
NTSC - 59.9227 Hz
PAL - 49.86 Hz
Without taking this into account, our client times (the game systems) will drift from our server time. Time being part of the key of fair play, synchronized collision detection, fired shots, motion, etc.
So, what does this mean for the Atari (at least)?
I think the client needs to give to the server, in some generalized way, a notion of the amount of physical time its time unit represents. With this, the client can then speak its time with the server in its native unit. The server would then convert back and forth, given its greater horsepower and floating point capabilities.
I'm proposing that the game registration packet (soon to be defined) will contain pertinent, generic, platform information. In this case the time unit rate.
What does this look like for the NTSC and PAL Ataris? They would send 59.9227 and 49.86 respectively to the server as part of their registration to play.
How would they represent it? Well, there are many ways. Text is an obvious choice. It's quite easy for the server to just convert the text to a float. It's not a lot of data and it's human readable, so, easy to debug, etc. Another option would be some fixed point representation like 8.16 (24 bits) or 16.16 (32 bits).
16 bits is "enough" to represent the fractional portion for at least the Atari NTSC and PAL:
NTSC:
.9227 * 65536 = 60470.0672 (rounded) => 60470 => 0xEC36
59 => 0x3B
0x003BEC36
0x003B => 59 . 0xEC36 => (60470 / 65536) = 0.9226989 rounded 0.9227
59.9227 (rounded)
PAL:
.86 * 65536 = 56360.9675 (rounded) => 56361 => 0xDC29
49 => 0x31
0x0031DC29
0x0031 => 49 . 0xDC29 -> (56361 / 65536) = 0.86000061 rounded 0.86
49.86
That's a lot of work for something to communicate to the server, which has plenty of ummff to just take the text and make a float of it, AND the client doesn't do anything with it but just send it to the server... once. I think we'll stick with text.
The proposal is this: During registration (which isn't quite defined yet 2025/6/22), the client sends to the server what one unit of time means for itself. During the standard game stream, packets are sent back and forth between the client(s) and the server which resolve current game state (see Client Update Strategy and Server Overview). Client time is sent to the server from the client, in the client unit, as part of its state packet. The thought is that a 32bit integer? would suffice to encapsulate the client time. Here is a little justification:
Let's say that the client "clock" has a granularity of 1 millisecond.
32bits:
Ok, let's say that the client "clock" has a granularity of 1 microsecond.
32bits:
In either case, there is "plenty" of time before we have to deal with a clock rollover.
So, client sends:
- "59.9227" to the server
- Server understands this to be one second of "client time units" is
$\frac{1}{59.9227}$ seconds. - The server converts all client (for this client) time reports by multiplying them by
$\frac{1}{59.9227}$ to convert to seconds. - The server does its magic, estimates the current state (position, etc) of the client and sends that state back with it's time in seconds (with fraction) * 59.9227 and rounded or truncated... whatever. ... and so on.