Repository navigation
feat(sdk): built-in clock synchronization with the authoritative server - #1628
Draft
LautaroPetaccio wants to merge 1 commit into
Draft
LautaroPetaccio wants to merge 1 commit into
LautaroPetaccio wants to merge 1 commit into
Conversation
Multiplayer scenes that schedule anything shared (countdowns, song positions, race starts) or validate client timestamps on the server had no clock they could agree on: the room exposes no server time, no round trip and no per-player latency, so every scene rebuilt the same ping exchange by hand. The sync transport now runs a reserved exchange over the room. Clients ping every 1.5 s with their own stamp; the server answers only that client with the stamp echoed plus its own; the client acknowledges so the server measures its round trip too. Each reply yields a round trip and an offset (server minus local at the trip midpoint); the offset from the fastest of the last ten replies wins, because a fast trip is the least distorted by queueing. Stale replies, negative and oversized trips are dropped, and the server rate-limits pings per player. Scene-facing API in @dcl/sdk/network: - getServerTime(): estimated server clock, undefined until the first reply (the server returns its own clock) - getClockSyncStats(): offset, rtt, best rtt, sample count, frozen flag - freezeServerClock(frozen): hold the estimate so a running performance cannot jump; samples keep flowing for when it is released - getPlayerLatency(userId): server only, the player's last measured round trip The reserved message names start with a double underscore so scene registries cannot collide with them. Players are forgotten when they leave the scene. Tests cover the estimator, the server registry and the wiring against a room double (34 cases); the existing network transport and connectivity specs pass.
Deploying js-sdk-toolchain with
|
| Latest commit: |
ceaffef
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://ec14d7d6.js-sdk-toolchain.pages.dev |
| Branch Preview URL: | https://feat-network-clock-sync.js-sdk-toolchain.pages.dev |
Contributor
Test this pull request
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a built-in ping exchange between clients and the authoritative server and exposes the result from
@dcl/sdk/network:getServerTime(): estimated server clock in ms,undefineduntil the first reply. On the server it returns the server's own clock.getClockSyncStats(): offset, latest round trip, best round trip, sample count and whether the estimate is frozen.freezeServerClock(frozen): hold the estimate so a time-critical sequence cannot jump mid-way; samples keep flowing so the estimate is fresh when released.getPlayerLatency(userId): server only, a player's last measured round trip, for bounding how early a client may claim an input happened.Why
Multiplayer scenes that schedule shared events or validate client timestamps on the server have no clock they can agree on today: the room exposes no server time, round trip or per-player latency. Every such scene ends up rebuilding the same exchange, and getting it wrong only shows under load. This came out of a rhythm game built on the auth server, where the same mechanism was needed for the song clock, the note timestamps and the server's input-plausibility window.
How it works
Clients ping every 1.5 s with their own stamp; the server answers only that client, echoing the stamp and adding its own; the client acknowledges so the server can measure its round trip as well. Each reply yields a round trip and an offset (server minus local at the trip midpoint). The offset from the fastest of the last ten replies wins, since a fast trip is the least distorted by queueing. Stale replies (not matching the latest ping), negative and oversized trips are dropped; the server rate-limits pings to one per 800 ms per player and clamps its own measurement.
The exchange rides on the existing room with three reserved message names (
__sdkClockPing,__sdkClockPong,__sdkClockAck), so it inherits queueing until the room is ready and the server-only-sender rule on clients. Scene registries cannot collide with the names. Players are forgotten ononLeaveScene.Base branch
auth-server: the room, the message registry and the server-side event path exist only there;mainhas noRoom.Testing
test/sdk/network/clock-sync.spec.ts: 34 cases across the estimator (midpoint offset, best-of-window, outlier rejection, freeze/unfreeze), the server registry (rate limit, ack matching, clamping, forget) and the wiring against a room double (ping cadence, room readiness, stale replies, server answers only the sender, no pings on the server). The existing network transport, messages and two-transport connectivity specs pass alongside it.Follow-ups
getServerTime()/getPlayerLatency().