Skip to content

Clock synchronization: mandate drift-SNR gate, specify burst cadence and bootstrap #89

@balloob

Description

@balloob

This issue comes out of a cross-SDK conformance audit comparing every Sendspin client/server implementation (aiosendspin, sendspin-cli, sendspin-cpp, sendspin-dotnet, sendspin-go, sendspin-js, sendspin-jvm, sendspin-rs, SendspinKit) against the spec. sendspin-cpp is treated as the reference implementation throughout — it most closely matches the spec's language and is the only SDK that runs on the constrained-embedded target the spec was originally written for.

The spec mandates the time-filter algorithm (2-D Kalman, offset + drift)
but is silent on burst structure, cadence, outlier rejection, how to
gate playback before convergence, and whether to apply drift when the
drift estimate itself is noisy. Those four axes are where
implementations diverge. sendspin-cpp is the reference here because
it is the only SDK that bundles both ends of the cadence:
SendspinTimeBurst (8 messages per burst, best-RTT, 10 s) and
SendspinTimeFilter (drift-SNR gate, 3.0 adaptive cutoff).

1. Mandate the drift-SNR gate

Clients SHOULD only apply the Kalman drift term to server→local
conversions when drift² ≥ k² × drift_covariance for some k ≥ 2.
Below that threshold, clients SHOULD use offset-only conversion.

This codifies what cpp/go/dotnet/js/jvm/SendspinKit already do and
forces aiosendspin and sendspin-rs to converge. Without the gate,
under-converged drift propagates wrong timestamps into the audio
scheduler.

2. Specify burst structure as the recommended cadence

Clients SHOULD send client/time messages in bursts of 8, 10 s apart,
and select the response with the lowest measured RTT as the burst's
single feed into the filter. Median-of-3 selection is an acceptable
alternative.

sendspin-cpp does this exactly; sendspin-jvm and sendspin-js
approximate it. The other SDKs leave cadence to the embedder, so e.g.
sendspin-cli runs aiosendspin at a fixed 10 s single-message
cadence — no burst denoising at all.

3. Standardize the adaptive cutoff

Current values vary 4×: cpp/go use 3.0, js uses 2.0,
aiosendspin/cli/jvm/rs/SendspinKit use 0.75. Either:

  • pick one value (cpp's 3.0 is reasonable for a 2σ-Gaussian-noise
    process) and write it into the spec, or
  • move the constant into a section labelled "implementation notes" so
    we stop pretending it's a free parameter.

4. Specify a minimum bootstrap

Clients SHOULD NOT use compute_client_time() for audio scheduling
until the filter has received at least 2 measurements with finite
offset covariance.

This rules out sendspin-js's and SendspinKit's 1-sample gate,
which combined with js's aggressive 2.0 adaptive cutoff is the
loosest combination in the survey.


Source audit doc: docs/clock-synchronization.md
Full audit branch: claude/stream-sync-correction-sdks-AWoNC
Per-SDK digest: sdk-issues digest

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions