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
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-cppis 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-cppis the reference here becauseit is the only SDK that bundles both ends of the cadence:
SendspinTimeBurst(8 messages per burst, best-RTT, 10 s) andSendspinTimeFilter(drift-SNR gate, 3.0 adaptive cutoff).1. Mandate the drift-SNR gate
This codifies what cpp/go/dotnet/js/jvm/SendspinKit already do and
forces
aiosendspinandsendspin-rsto converge. Without the gate,under-converged drift propagates wrong timestamps into the audio
scheduler.
2. Specify burst structure as the recommended cadence
sendspin-cppdoes this exactly;sendspin-jvmandsendspin-jsapproximate it. The other SDKs leave cadence to the embedder, so e.g.
sendspin-clirunsaiosendspinat a fixed 10 s single-messagecadence — 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:
process) and write it into the spec, or
we stop pretending it's a free parameter.
4. Specify a minimum bootstrap
This rules out
sendspin-js's andSendspinKit'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.mdFull audit branch:
claude/stream-sync-correction-sdks-AWoNCPer-SDK digest: sdk-issues digest