diff --git a/README.md b/README.md index d0b6054..1ade16e 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Current scenarios: - `server-initiated-external-source` (Client enters and leaves `external_source` mid-stream): start the server first, then the client. The client transitions to `external_source` and back; the harness verifies the server emits the right `group/update` and `stream/end` and that the previous group is restored. Per the audit only SendspinKit exposes a client-side API; no client in the matrix declares the `external-source-client-api` capability yet, so every case fails fast - `server-initiated-multi-server-arbitration` (Client arbitrates between two connected servers): two servers connect to one client with different `connection_reason` values; the harness verifies the client sends `client/goodbye` with `'another_server'` on the right socket and ends up speaking to the right server per the spec's decision table. Per the audit only sendspin-cpp and sendspin-cli implement these rules; no client in the matrix declares the `multi-server-arbitration` capability yet, so every case fails fast - `server-initiated-static-delay` (Client applies configured `static_delay_ms`): start the server first, then the client. The client is configured with a non-zero `static_delay_ms`; the server pre-compensates and the harness asserts the client's actual emission time matches `T − static_delay_ms` within the spec's drift tolerance. Per the audit, `sendspin-rs` and `SendspinKit` parse the field but never apply it (highest-severity bug); seven SDKs don't persist across restarts. No client in the matrix declares the `static-delay-applied` capability yet, so every case fails fast +- `server-initiated-volume-curve` (Server calibrates client volume curve): start the server first, then the client. The server sets `volume = 50`, the client plays a known fixture, and the harness measures the peak amplitude and asserts the spec's perceptual curve (`0.5^1.5 ≈ 0.354`, ≈ −9 dB) vs. the non-conformant linear gain (≈ −6 dB). Per the audit, sendspin-go and sendspin-js apply linear `vol/100` gain today. No client in the matrix declares the `volume-perceptual-curve` capability yet, so every case fails fast ## Current coverage diff --git a/src/conformance/implementations.py b/src/conformance/implementations.py index 685e2e4..7fdc968 100644 --- a/src/conformance/implementations.py +++ b/src/conformance/implementations.py @@ -69,6 +69,7 @@ "external-source-client-api", "multi-server-arbitration", "static-delay-applied", + "volume-perceptual-curve", ), ), ), diff --git a/src/conformance/scenarios.py b/src/conformance/scenarios.py index abc6687..e822093 100644 --- a/src/conformance/scenarios.py +++ b/src/conformance/scenarios.py @@ -196,6 +196,26 @@ ) +SERVER_INITIATED_VOLUME_CURVE = ScenarioSpec( + id="server-initiated-volume-curve", + display_name="Server calibrates client volume curve", + description=( + "Start the server first, then the client. The server sets `volume = 50`, " + "the client plays a known fixture, and the harness measures the peak " + "amplitude of the recorded output and asserts it matches the spec's " + "perceptual curve (`0.5^1.5 ≈ 0.354` × original, ≈ −9 dB). A " + "non-conformant linear-gain client outputs ≈ `0.5` × original (≈ −6 dB). " + "Per the audit, sendspin-go and sendspin-js apply linear `vol/100` gain " + "today; sendspin-jvm and sendspin-dotnet need follow-up audits." + ), + initiator_role="server", + preferred_codec="pcm", + required_role_families=("player",), + verification_mode="capability-only", + required_capability="volume-perceptual-curve", +) + + SERVER_INITIATED_STATIC_DELAY = ScenarioSpec( id="server-initiated-static-delay", display_name="Client applies configured `static_delay_ms`", @@ -290,6 +310,7 @@ SERVER_INITIATED_EXTERNAL_SOURCE, SERVER_INITIATED_MULTI_SERVER_ARBITRATION, SERVER_INITIATED_STATIC_DELAY, + SERVER_INITIATED_VOLUME_CURVE, ) SCENARIOS: dict[str, ScenarioSpec] = {scenario.id: scenario for scenario in SCENARIO_LIST}