Skip to content

Define the time filter algorithm in the spec - #303

Merged
maximmaxim345 merged 8 commits into
mainfrom
docs/embed-time-filter
Oct 21, 2026
Merged

maximmaxim345 merged 8 commits into
mainfrom
docs/embed-time-filter

Conversation

@maximmaxim345

@maximmaxim345 maximmaxim345 commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

Moves the time filter algorithm from the time-filter repository into Clock Synchronization, so the spec defines it directly instead of only linking to it.

The algorithm itself stays a MUST, like before. The parameter values are now a SHOULD, where before they were only the library's defaults. The burst strategy from the library README is now a MUST instead of a known-good baseline, with at least 8 messages per burst and at most 10 s between burst starts. Servers must not close the connection only because bursts are late, since a network stall can delay them.

The filter now ignores samples with a max_error of 0 or less, the same as the reference implementation:

I dropped the aiosendspin link since it doesn't follow the burst pattern.

Open question: Should the parameter values be a MUST?

The player sync accuracy floor is measured against the client's own filter, not the server clock, so a badly tuned filter can be compliant but out of sync. This PR makes the values a SHOULD, but I'm not sure that's the right level.

MUST:

  • Closes that gap, and every client syncs the same way.
  • Adaptive forgetting only starts after about 17 minutes at one burst per 10 s.
  • The time-filter README calls the values preliminary, and sendspin-js and sendspin-rs use other values today.

SHOULD (this PR):

  • Gives implementations one known-good default, while still allowing tuned values.
  • Leaves the gap open.

No keyword, like before:

  • Describes the reference without asking implementations to follow it.
  • Leaves the gap open.

Implementations

Only sendspin-cpp matches the reference everywhere:

Implementation Algorithm (MUST) Parameters (SHOULD) Bursts (MUST) Integer inputs (MUST) Rounding (MUST)
sendspin-cpp yes yes yes yes yes
sendspin-dotnet no, waits 5 measurements before using drift and adds minimum variances yes yes, every 500 ms until converged, then every 10 s no, floating point no, halves to even
SendspinDroid no, adds outlier rejection and a ±500 ppm drift limit yes no, 3 to 15 messages 50 ms apart without waiting for replies, every 0.2 to 3 s yes, plus a 1 µs minimum for max_error no, halves toward positive infinity
aiosendspin no, adds a minimum for S yes no, single messages every 0.2 to 3 s, every sample fed no, rounds halves to even no, halves to even
sendspin-js yes no no, feeds the median of the 3 lowest-delay samples no, floating point no, halves toward positive infinity
sendspin-rs no, no drift from samples under 100 ms apart, >= in the drift test, conversions refused above 0.2 drift no no, single messages every 1 s, every sample fed no, measurement rounds halves away from zero yes

sendspin-js uses process_std_dev 0.01, forget_factor 1.1, drift_process_std_dev 0, adaptive_cutoff 2.0 and no max_error_scale. sendspin-rs uses process_std_dev 0.01, forget_factor 1.001, drift_process_std_dev 0, adaptive_cutoff 0.75 and no max_error_scale.

Closes #293

Comment thread README.md Outdated

@kahrendt kahrendt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, looks good to me!

@maximmaxim345 maximmaxim345 mentioned this pull request Oct 21, 2026
7 of 9 tasks
@maximmaxim345
maximmaxim345 merged commit f3cc992 into main Oct 21, 2026
1 check passed
@maximmaxim345
maximmaxim345 deleted the docs/embed-time-filter branch October 21, 2026 17:08
maximmaxim345 added a commit to Sendspin/sendspin-js that referenced this pull request Oct 22, 2026
# What does this implement/fix?

<!-- Quick description and explanation of changes. -->

The client didn't follow the spec's rules for `client/time` bursts. It
fed the time filter the median of the three fastest measurements instead
of the one with the lowest `max_error`, and bursts always started
slightly more than 10 seconds apart.

Now each burst only feeds its lowest `max_error` measurement to the
filter, `measurement` and `max_error` are truncated to whole
microseconds, and bursts are scheduled every 9 seconds so a late timer
can't push them past the 10 second limit.

Bursts can still end up more than 10 seconds apart when the previous
burst is still running at the next tick (round trips averaging over
about 1.1 s), or when Chrome throttles timers in a tab that has been
hidden and silent for a while. Servers must not close the connection
over this.

**Related issue or spec PR (if applicable):**

- Sendspin/spec#303

## Types of changes

<!--
Tick exactly one box. CI (.github/workflows/pr-labels.yaml) applies the
matching label, and the release notes use it to sort this change.
-->

- [x] Bugfix (non-breaking change which fixes an issue): `bugfix`
- [ ] New feature (non-breaking change which adds functionality):
`new-feature`
- [ ] Enhancement to an existing feature: `enhancement`
- [ ] Breaking change (changes the public API or the protocol in a way
that is not backwards compatible): `breaking-change`
- [ ] Refactor (no behavior change): `refactor`
- [ ] Documentation only: `documentation`
- [ ] Maintenance / chore: `maintenance`
- [ ] CI / workflow change: `ci`
- [ ] Dependencies bump: `dependencies`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Include time filter into the specification

2 participants