open-standards/joule-code/joule-code-rs/crates/joule-moq-web/web/room/cowatch.test.mjs
Transaction Science 4e8e4fb098
All checks were successful
joule-moq-remote host agent / joule-moq-remote · does this change touch it (pull_request) Successful in 20s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (pull_request) Successful in 26s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 24s
WAI operation catalogue / does this change touch it (pull_request) Successful in 13s
wai cross-architecture byte-equality / does this change touch it (pull_request) Successful in 21s
joule-code conformance gate / joule-moq-relay, its own energy on a counter (macos) (pull_request) Successful in 46s
wai-app and Create / wai-app · does this change touch it (pull_request) Successful in 29s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 18s
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 34s
wai-audit collector and player integration / does this change touch it (pull_request) Successful in 29s
WAI conformance / does this change touch it (pull_request) Successful in 16s
WAI examples / WAI examples · does this change touch them (pull_request) Successful in 8s
WAI HTTP delivery / WAI HTTP · does this change touch it (pull_request) Successful in 12s
wai-key-proxy / does this change touch it (pull_request) Successful in 13s
WAI neural sink gates / does this change touch it (pull_request) Successful in 12s
wai-ledger service / does this change touch it (pull_request) Successful in 14s
WAI neural decoders end-to-end / does this change touch it (pull_request) Successful in 14s
wai-ops integer operators / does this change touch it (pull_request) Successful in 6s
WAI JPEG-AI whole decode (pinned inputs, required) / does this change touch it (pull_request) Successful in 17s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 6s
wai-quantum command line / does this change touch it (pull_request) Successful in 5s
wai-quantum contract / does this change touch it (pull_request) Successful in 5s
wai-fleet service / does this change touch it (pull_request) Successful in 21s
WAI energy receipts / does this change touch it (pull_request) Successful in 24s
wai-ops-gpu integer operators on GPUs / does this change touch it (pull_request) Successful in 20s
wai-quantum docs, course and locales / does this change touch it (pull_request) Successful in 5s
wai-quantum CUDA member / does this change touch it (pull_request) Successful in 7s
wai-quantum dataflow member / does this change touch it (pull_request) Successful in 8s
wai-quantum signal processor / does this change touch it (pull_request) Successful in 8s
wai-quantum editors / does this change touch it (pull_request) Successful in 6s
wai-quantum resource estimation / does this change touch it (pull_request) Successful in 7s
wai-quantum FPGA member / does this change touch it (pull_request) Successful in 6s
wai-quantum OpenQASM reader / does this change touch it (pull_request) Successful in 7s
wai-quantum Metal member / does this change touch it (pull_request) Successful in 7s
wai-quantum npu-cores member / does this change touch it (pull_request) Successful in 9s
wai-quantum OpenCL member / does this change touch it (pull_request) Successful in 7s
wai-quantum neural-processor member / does this change touch it (pull_request) Successful in 10s
wai cross-architecture byte-equality / macos-arm64 (reference) (pull_request) Successful in 36s
wai-quantum HIP member / does this change touch it (pull_request) Successful in 17s
wai-quantum for JavaScript / does this change touch it (pull_request) Successful in 20s
wai-quantum other formats / does this change touch it (pull_request) Successful in 22s
wai-quantum pulse and dynamics / does this change touch it (pull_request) Successful in 11s
wai-quantum for Python / does this change touch it (pull_request) Successful in 10s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (pull_request) Successful in 1m20s
wai-quantum error correction and mitigation / does this change touch it (pull_request) Successful in 10s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 2m14s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 21s
wai-quantum-qpu / does this change touch it (pull_request) Successful in 10s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (pull_request) Successful in 1m48s
wai-quantum extension points / does this change touch it (pull_request) Successful in 11s
wai-quantum service / does this change touch it (pull_request) Successful in 11s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (pull_request) Successful in 1m58s
wai cross-architecture byte-equality / linux-aarch64 (pull_request) Successful in 34s
wai-quantum matrix-extension member / does this change touch it (pull_request) Successful in 10s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 10s
wai-quantum CUDA member / member · CUDA (jetson-hub) (pull_request) Successful in 1m58s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 58s
wai-quantum tensor-engine member / does this change touch it (pull_request) Successful in 9s
wai-quantum portable tensor program / does this change touch it (pull_request) Successful in 9s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 34s
wai-quantum CUDA member / member · CUDA (thor) (pull_request) Successful in 2m20s
wai-quantum variational and algorithm library / does this change touch it (pull_request) Successful in 9s
wai-quantum on every processor / emulated · armv7-unknown-linux-gnueabihf (pull_request) Successful in 2m25s
WAI conformance / fast integer kernels, every x86_64 kernel set (AVX-512 silicon) (pull_request) Successful in 2m57s
joule-code conformance gate / joule-moq-relay, every target (pull_request) Successful in 3m49s
wai-quantum CPU vector units / does this change touch it (pull_request) Successful in 10s
wai-quantum OpenCL member / member · OpenCL GPU and CPU (jetson-hub) (pull_request) Successful in 38s
wai-quantum verdicts and repairs / does this change touch it (pull_request) Successful in 5s
wai-quantum WebGPU member / does this change touch it (pull_request) Successful in 5s
wai-quantum views in agent hosts / does this change touch it (pull_request) Successful in 10s
wai-sink-android native sink library / does this change touch it (pull_request) Successful in 5s
WAI mbt2018_mean integer decode (regenerated vectors) / does this change touch it (pull_request) Successful in 5s
wai-sink-swift native sink package / does this change touch it (pull_request) Successful in 10s
WAI Studio / does this change touch it (pull_request) Successful in 5s
wai-vector vector graphics / does this change touch it (pull_request) Successful in 9s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 5s
wai-web wasm32 / does this change touch it (pull_request) Successful in 5s
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Successful in 4m34s
wai-quantum on every processor / emulated · loongarch64-unknown-linux-gnu (pull_request) Successful in 3m12s
wai-quantum OpenCL member / member · OpenCL GPU and CPU (thor) (pull_request) Successful in 1m2s
wai-quantum npu-cores member / member · npu-cores, neural processor (framework-128gb silicon) (pull_request) Successful in 1m17s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 4m30s
wai-quantum service / service energy · linux packages (pull_request) Successful in 1m36s
wai-audit collector and player integration / collector · linux (pull_request) Successful in 1m1s
wai-quantum OpenCL member / member · OpenCL GPU (framework-128gb) (pull_request) Successful in 25s
wai cross-architecture byte-equality / linux-x86_64 (pull_request) Successful in 28s
wai-quantum portable tensor program / tensor program · GPUs (jetson-hub) (pull_request) Successful in 34s
wai-ledger service / service · macos (pull_request) Successful in 49s
wai-quantum on every processor / emulated · s390x-unknown-linux-gnu (pull_request) Successful in 5m3s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (pull_request) Successful in 2m2s
wai-quantum HIP member / member · HIP (framework-128gb) (pull_request) Successful in 1m7s
wai-quantum on every processor / emulated · riscv64gc-unknown-linux-gnu (pull_request) Successful in 5m24s
wai-quantum portable tensor program / tensor program · integrated GPU (framework-128gb) (pull_request) Successful in 13s
wai-ops integer operators / macos-arm64 (pull_request) Successful in 48s
joule-code conformance gate / joule-moq-ingest, every target (pull_request) Successful in 7m0s
wai-quantum CPU vector units / member · vector units, avx2 and avx512 (framework-128gb silicon) (pull_request) Successful in 19s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 20s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 36s
wai-quantum on every processor / emulated · powerpc64-unknown-linux-gnu (pull_request) Successful in 6m9s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (pull_request) Successful in 3m10s
WAI HTTP delivery / HTTP corpus · every patch and mutation in exactly one shard (pull_request) Successful in 5s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (pull_request) Successful in 32s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 47s
WAI conformance / faces from keypoints under wasm32-wasip2 (wasmtime) (pull_request) Successful in 1m50s
wai-key-proxy / proxy · linux (pull_request) Successful in 1m13s
wai-pack / wai-pack · macos (pull_request) Successful in 2m42s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 3m35s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 2m11s
WAI neural sink gates / the exact JPEG-AI decoder reports its path (pinned inputs, required) (pull_request) Successful in 2m12s
wai-ledger service / service · linux (pull_request) Successful in 1m47s
wai-quantum on every processor / emulated · powerpc64le-unknown-linux-gnu (pull_request) Successful in 9m29s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 2m46s
wai-quantum command line / command line · macos (pull_request) Successful in 1m17s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (pull_request) Successful in 8m26s
wai-ops integer operators / wasm32 under Node (pull_request) Successful in 30s
wai-ops integer operators / wasm32-wasip2 (wasmtime) (pull_request) Successful in 36s
WAI neural decoders end-to-end / synthetic fixtures → Rust decode → reference (±4) (pull_request) Successful in 1m32s
wai-ops integer operators / linux-x86_64 (pull_request) Successful in 1m2s
wai-ops integer operators / independent checker (Python) (pull_request) Successful in 14s
wai-quantum on every processor / emulated · sparc64-unknown-linux-gnu (pull_request) Successful in 10m23s
wai-quantum contract / the policy is linked, and the deprecations page reads (pull_request) Successful in 7s
wai-fleet service / service · macos (pull_request) Successful in 54s
wai-quantum on every processor / emulated · aarch64-unknown-linux-gnu (pull_request) Successful in 12m16s
wai-pack / wai-pack · linux (pull_request) Successful in 2m6s
wai-quantum WebGPU member / member · DX12 and Vulkan (cbio-windows) (pull_request) Successful in 13m3s
WAI operation catalogue / the catalogue and every door · linux (pull_request) Successful in 10m18s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (pull_request) Successful in 11m27s
wai-quantum command line / command line · linux (pull_request) Successful in 3m18s
wai-quantum dataflow member / member · dataflow device absent (macos) (pull_request) Successful in 21s
wai-quantum WebGPU member / member · integrated GPU, Vulkan (framework-128gb) (pull_request) Successful in 8m28s
wai-fleet service / service · linux (pull_request) Successful in 2m21s
WAI examples / WAI examples · portable · linux (pull_request) Successful in 9m18s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 6m39s
wai-quantum docs, course and locales / the Studio's words in every locale · linux (pull_request) Successful in 8s
wai-quantum contract / API compatibility · linux (pull_request) Successful in 5m21s
wai-quantum editors / editors · macos (pull_request) Successful in 2m8s
wai-ops-gpu integer operators on GPUs / builds · no adapter is not a pass (linux) (pull_request) Successful in 3m53s
wai-quantum docs, course and locales / the course and the command line's languages · linux (pull_request) Successful in 2m18s
WAI conformance / each feature of the independent verifiers' steps compiles alone (pull_request) Successful in 14m36s
wai-quantum contract / schemas and the committed tool lists · linux (pull_request) Successful in 6m46s
wai-quantum FPGA member / member · FPGA datapath, simulated (macos) (pull_request) Successful in 1m25s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 18m0s
wai-quantum dataflow member / member · dataflow device, simulated (pci 1e52:401e) (pull_request) Successful in 1m45s
wai-quantum CUDA member / member · CUDA absent (linux) (pull_request) Successful in 4m6s
wai-quantum FPGA member / member · FPGA simulation absent (linux) (pull_request) Successful in 23s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 8m20s
wai-quantum Metal member / member · Metal absent (linux) (pull_request) Successful in 33s
wai-quantum OpenCL member / member · OpenCL GPU (macos) (pull_request) Successful in 50s
wai-quantum on every processor / emulated · i686-unknown-linux-gnu (pull_request) Successful in 18m7s
wai-quantum OpenCL member / member · OpenCL absent (linux) (pull_request) Successful in 26s
wai-quantum editors / editors · linux (pull_request) Successful in 2m28s
wai-quantum Metal member / member · Metal (macos) (pull_request) Successful in 1m19s
wai-quantum resource estimation / estimation and calibration · linux, release (pull_request) Successful in 1m54s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 18m56s
wai-quantum neural-processor member / member · neural processor absent (linux) (pull_request) Successful in 50s
wai-quantum OpenQASM reader / reader and corpus · linux (pull_request) Successful in 1m47s
wai-quantum pulse and dynamics / the same bits · macos (pull_request) Successful in 1m7s
wai-quantum OpenQASM reader / corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 1m52s
wai-quantum dataflow member / member · dataflow device, simulated (pci 1e52:b140) (pull_request) Successful in 4m13s
wai-quantum-qpu / credentials in the keychain (macos) (pull_request) Successful in 52s
wai-quantum pulse and dynamics / reader, simulator and pulse jobs under wasm32-wasip2 (wasmtime) (pull_request) Successful in 52s
wai-quantum other formats / the set under wasm32-wasip2 (wasmtime) (pull_request) Successful in 1m38s
wai-quantum matrix-extension member / member · matrix extension absent (macos) (pull_request) Successful in 34s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 10m58s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 16m0s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 8m6s
wai-quantum-qpu / clients against their contracts (linux) (pull_request) Successful in 46s
wai-quantum extension points / the registry under wasm32-wasip2 (wasmtime) (pull_request) Successful in 56s
wai-quantum for Python / python · macos (pull_request) Successful in 2m59s
wai-quantum matrix-extension member / member · matrix extension, emulated (aarch64 Linux) (pull_request) Successful in 20s
wai-quantum portable tensor program / tensor program · Metal and the arm64 CPU (macos) (pull_request) Successful in 26s
wai-quantum other formats / readers, writers and the set · linux (pull_request) Successful in 3m42s
wai-quantum for JavaScript / javascript · linux (pull_request) Successful in 3m28s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 21m22s
wai-quantum error correction and mitigation / codes, decoders, texts and mitigation · linux, release (pull_request) Successful in 2m41s
wai-quantum neural-processor member / member · neural processor (macos) (pull_request) Successful in 4m40s
wai-quantum CPU vector units / member · vector unit absent (macos) (pull_request) Successful in 35s
wai-quantum extension points / the registry and the example · linux (pull_request) Successful in 2m36s
wai-quantum studio and desktop / desktop app · windows (pull_request) Successful in 10m41s
wai-quantum portable tensor program / tensor program · CPU, mutants, GPU builds (pull_request) Successful in 40s
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Successful in 12m33s
wai-quantum service / service · macos (pull_request) Successful in 4m1s
wai-quantum CPU vector units / member · vector unit, rvv 1024-bit (riscv64, emulated) (pull_request) Successful in 23s
wai-quantum pulse and dynamics / reader, simulator and pulse jobs · linux, release (pull_request) Successful in 4m41s
wai-quantum CPU vector units / member · vector unit, sve 128-bit (aarch64, emulated) (pull_request) Successful in 37s
wai-quantum CPU vector units / member · vector unit, sve 2048-bit (aarch64, emulated) (pull_request) Successful in 11s
WAI examples / WAI examples · android-app · linux-arm64 (pull_request) Successful in 7m28s
wai-quantum CPU vector units / member · vector unit, avx2 (x86_64 silicon) (pull_request) Successful in 26s
wai-quantum WebGPU member / member · browser WebGPU (macos) (pull_request) Successful in 2m7s
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux, release (pull_request) Successful in 1m59s
wai-quantum CPU vector units / member · vector unit, sve 512-bit (aarch64, emulated) (pull_request) Successful in 10s
wai-quantum CPU vector units / member · vector unit, rvv 128-bit (riscv64, emulated) (pull_request) Successful in 1m18s
wai-quantum WebGPU member / member · Metal (macos) (pull_request) Successful in 2m30s
wai-quantum service / service · linux (pull_request) Successful in 4m43s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 5m17s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 3m51s
wai-quantum WebGPU member / member · GPU, Vulkan (jetson-hub) (pull_request) Successful in 21m17s
wai-quantum verdicts and repairs / the intent set and the verdict under wasm32-wasip2 (wasmtime) (pull_request) Successful in 1m51s
wai-vector vector graphics / macos-arm64 (pull_request) Successful in 2m7s
wai-quantum verdicts and repairs / verdicts, repairs and the intent set · linux (pull_request) Successful in 2m30s
wai-quantum WebGPU member / member · software Vulkan (linux) (pull_request) Successful in 1m48s
wai-quantum WebGPU member / member · GPU, Vulkan (thor) (pull_request) Successful in 24m55s
wai-quantum tensor-engine member / member · tensor engine, the kernel toolchain's simulator (linux) (pull_request) Successful in 5m59s
wai-quantum for Python / python · linux (pull_request) Successful in 9m4s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (pull_request) Successful in 1m0s
wai-quantum views in agent hosts / views · the page and its engine (pull_request) Successful in 3m13s
wai-vector vector graphics / wasm32-wasip2 (wasmtime) (pull_request) Successful in 50s
wai-vector vector graphics / wasm32 under Node (pull_request) Successful in 25s
wai-vector vector graphics / fuzz, peak allocation, comparison count (pull_request) Successful in 16s
wai-quantum studio and desktop / desktop app · linux (pull_request) Successful in 7m38s
WAI HTTP delivery / HTTP corpus · falsify patches 0/3 (pull_request) Successful in 5m54s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (pull_request) Successful in 7m4s
wai-quantum views in agent hosts / views · a scripted host (pull_request) Successful in 4m8s
wai-quantum signal processor / processor and member · signal processor, emulated (pull_request) Successful in 15m47s
wai-vector vector graphics / wasm32 in three browser engines (pull_request) Successful in 2m35s
wai-vector vector graphics / independent checker (Python) (pull_request) Successful in 3m0s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 3m54s
wai-vector vector graphics / linux-x86_64 (pull_request) Successful in 5m37s
wai-quantum WebGPU member / shader checks · browser build · wasip2 inertness · MSRV (pull_request) Successful in 9m14s
wai-quantum docs, course and locales / every command and snippet in the docs · linux (pull_request) Successful in 21m22s
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Successful in 7m27s
WAI Studio / Studio in Chromium and Firefox · macos (pull_request) Successful in 10m15s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (pull_request) Successful in 11m59s
WAI HTTP delivery / HTTP corpus · falsify patches 1/3 (pull_request) Successful in 6m48s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 6m44s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 9m5s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 33m23s
WAI HTTP delivery / HTTP corpus · falsify patches 2/3 (pull_request) Successful in 7m11s
WAI Studio / Studio in WebKit · linux (pull_request) Successful in 18m7s
WAI HTTP delivery / HTTP corpus · verified both ways, and the differential (pull_request) Successful in 2m19s
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Successful in 1m20s
wai-ops integer operators / linux-aarch64 (pull_request) Successful in 1m2s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 1m45s
WAI HTTP delivery / HTTP corpus · falsify mutations 2/3 (pull_request) Successful in 32m36s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 32s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 16s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 7m5s
wai-quantum npu-cores member / member · npu-cores absent (linux-arm64) (pull_request) Successful in 27s
wai-quantum HIP member / member · HIP absent (linux-arm64) (pull_request) Successful in 58s
WAI HTTP delivery / HTTP corpus · falsify mutations 1/3 (pull_request) Successful in 39m55s
wai-quantum pulse and dynamics / the same bits · linux-arm64 (pull_request) Successful in 1m24s
WAI HTTP delivery / HTTP corpus · falsify mutations 0/3 (pull_request) Successful in 40m34s
wai-quantum tensor-engine member / member · tensor engine absent (linux-arm64) (pull_request) Successful in 20s
wai-quantum variational and algorithm library / parameters, gradients, optimisers, VQE, QAOA and builders · linux-arm64, release (pull_request) Successful in 2m40s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (pull_request) Successful in 2m39s
wai-quantum extension points / the registry and the example · linux-arm64 (pull_request) Successful in 4m1s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 11m3s
wai-vector vector graphics / linux-aarch64 (pull_request) Successful in 4m17s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 1h10m6s
watch-together: the page makes its audio context before its join; a slot is set apart only when the machine ran nothing of the page
The watch-together example failed twice on the macOS runner for #616, on a loaded host (load
average about 50, then about 30). Each time the first page in the browser, Ada's, signed its
first presentation statement after its join, at 3.83 s and then 3.44 s on the room clock, and
then none in the slots from 4 s and 5 s (and 6 s the second time), while it was still entering
(begin()): it followed the log from 7.49 s, then 8.49 s, 3.7 s and 5.0 s after its first
statement, where Ben's page took 1.2 s and 1.0 s. Ada's page was silent at least 2.17 s, then
at least 3.56 s, with nothing refused, and was never taken for gone. What held it on the runner
was not measured.

What was measured:
- On the CI host, with CI jobs running (load average 30 to 39), in a browser launched as the
  example's test launches it (headless, the same flags, at nice 10), on a blank page with
  nothing else in it: the first audio context in a fresh browser held its page's thread 172 to
  192 ms (3 runs), for the constructor call alone; a second page's took 3.8 to 4.0 ms; neither
  page's thread had a gap over 60 ms in the 1.5 s after. That is a tenth or less of Ada's
  silences. Moving the context before the join takes about 0.2 s, as measured there, out of the
  stretch in which the page must state; what held Ada's page for the rest is unexplained. The
  rest of begin() (its audio worklet, its relay session, its WebAssembly, its first draws, the
  camera and microphone) was not measured there, nor an audio context made beside that work.
- On a laptop, in 25 runs of the example: the first page's audio context 94 to 128 ms, the
  second's 0.8 to 1.4 ms.
- On a laptop, on main, two injected faults each leave empty the slots just after the first
  page's first statement, as in CI: the audio-context call held 2.5 s (a busy wait in it; the
  slots from 1 s and 2 s, 1 run), and the first page's renderer process stopped 2.5 s from its
  join (SIGSTOP; the slot from 2 s, 2 of 2 runs). Neither was measured on the CI host.

This change is not shown to make the example pass on the CI host. If what held Ada's page there
was the page's own work on its thread, or the host running the page's other threads and not
that one, the check below still fails (its heartbeat ticking), and what it prints does not tell
those two apart.

1. The page makes the composition's audio context before it enters the co-watch log (app.js
   enter(), the new Compositor.openAudio()), since from that join it states in every slot
   (co-watch §7.3), so that hold is not in the stretch in which it must state. The rest of the
   entry (begin()) still runs after the join. begin() takes the compositor the entry made; in
   any other room it makes one, as before. The snapshot carries when the context was made, on
   the room clock, and how long making it held the thread (cowatch.audio_open).

2. A compositor made for an entry that failed is closed when the next entry makes its own
   (Compositor.close(): its mix timer, its worklet node, its drawing, its frames and its audio
   context). Before, it stayed, its audio context open and its mix timer running.

3. The statement loop records its stalls (cowatch.js run(), snapshot().stalls): each gap over
   half a slot between two of its passes, `{ from, to }` on the page's estimate of the room
   clock, with how long its wait and its signing took in it, on that clock (`timer_us`,
   `signing_us`); the rest of a stall is the loop's own work. It states every half slot while
   it runs, so it leaves a slot empty only inside such a stall. Its wait is a method (`wait`),
   and roomTime(ms) gives the room clock at an instant of the page's clock. The snapshot
   carries the loop's wait (`pass_ms`) and the page's clock beside the room clock (`wall_ms`).
   The co-watch rule and §7.3 are unchanged.

4. The example's slot check (co-watch §7.3: a statement in every slot while present) sets an
   empty slot apart only if the machine ran nothing of the page in the slot's second half: a
   stall of the loop spans that half; a heartbeat worker that the test starts in each page,
   ticking every 20 ms, did not tick in it either (to within a pass at each end); and the stall
   is the loop's wait and its signing but for at most one pass of its own work. Otherwise the
   check fails and says why: no stall of the loop (a statement lost); the heartbeat ticking (the
   page's thread did not run its loop while the worker ran: the page's own work holding the
   thread, or the host running the worker and not the page's thread, which the check does not
   tell apart); or more than a pass of the loop's own work (its own code, an await outside its
   measures, or the thread taken off the processor inside it). On a host that runs the page's
   threads unevenly, the check can fail in these ways with no defect in the page. Pass or fail,
   it prints each page's audio-context time, its loop's stalls and its heartbeat's silences over
   100 ms. They show whether the page's thread or the whole page was held, not what held it.

Unit tests (cowatch.test.mjs) run the loop on a room clock the test moves: no stall and every
slot held while it runs; a thread held while the loop waited, recorded once with all of the
stall its wait, and the only empty slots those it spans; a gap just over half a slot recorded
and one of half a slot not; a stall in a pass's own work recorded with a wait of one pass, the
rest neither wait nor signing.

Verified on a laptop with this change on main (152aab5c7), in a browser launched as the
example's test launches it and niced:
- From an archive build.sh made, with the WebAssembly build-web.sh made from crates this change
  does not touch, run-examples.sh browser passes video-room, watch-together, messaging,
  remote-desktop and package-and-deliver in each of 2 runs, and watch-together alone 2 more.
- With this room client served beside an earlier archive's binaries (the same Rust but for the
  relay's DVR change in the commit before), watch-together passes 3 of 3, and the faults below
  ran on that set. In each passing run the first page's audio context was made before its join
  and Ada's loop had no stall. Ben's deliberate hold, a synchronous request, shows as a stall of
  3.8 to 4.2 s with his heartbeat never silent.
- The room client's unit tests pass under node --test (77).
- Falsified, each fault put into a scratch copy of the room client, 2 runs each unless said:
  the audio context made in begin() again and that call held 2.5 s (a busy wait), the check
  fails: no statement in the slots from 1 s and 2 s (Ada) and 4 s and 5 s (Ben), each in a
  stall of the loop with its heartbeat ticking, the context printed as made after the join in
  2.5 to 2.6 s. A 2.5 s busy wait just after the join, or 4 s after it, fails it the same way:
  the slots it spans, with the heartbeat ticking. Every statement of the third whole slot after
  each join dropped fails it (no stall of its loop). One 1.5 s await in the loop's own work
  fails it (heartbeat ticking, 1.5 s neither its wait nor its signing); the same await inside
  the loop's wait fails it on the heartbeat alone. The page's renderer process stopped 2.5 s
  (SIGSTOP) at Ada's join, or 2.5 s after it, or Ben's 1 s after his (1 run): it passes, the
  slots inside the stop set apart; the heartbeat was silent and the loop stalled over the stop,
  to within 25 ms. The stop at Ada's join and a 2.5 s busy wait 4 s after it (1 run): the
  stop's slot is set apart and the busy wait's fail.
- In the unit tests, the wait measured on another clock than the stall, no stall recorded, a
  stall recorded only past a whole slot, and the wait left out of the record each fail one or
  two of the stall tests.
- A video room: one page publishes; the other presses Watch, its first relay connection is
  refused (a fault the probe injects), and it presses Watch again. Then that page holds one
  audio context open (the first closed) and one mix timer, and mixes 50 blocks of 20 ms a
  second. On main it holds two open and two timers; with close() doing nothing, two open. The
  same in a watch room: one open with this change, two on main.
- Not shown: that the example passes on the CI host.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 18:56:36 -05:00

411 lines
21 KiB
JavaScript

// The browser client's co-watch module (cowatch.js), against the corpus
// wai/live-conformance/cowatch/ (`node --test web/room/cowatch.test.mjs`): every catch-up case
// its correction (co-watch §9); each accepted room's controlled position recomputed from its
// policies and controls (§5.4, §6), as the corpus states it at every change and at every
// statement; its controls written byte for byte (§5.1); its presentation statements
// written and signed byte for byte (§7.1), each at the rate the catch-up rule gives (§9); the
// player taken for gone by the room re-entering with its chain whole (§7.3, §10); and the
// player stating in every slot while its loop runs, recording each stretch it did not (§7.3).
import test from "node:test";
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import * as cw from "./cowatch.js";
const doc = JSON.parse(readFileSync(new URL("../../../../../../wai/live-conformance/cowatch/vectors.json", import.meta.url), "utf8"));
const vector = (name) => doc.vectors.find((v) => v.name === name);
const ACCEPTED = ["watch_party", "next_episode", "drift"];
/** The corpus's key `i`: SHA-256 of its label, a zero byte and `i` (its README). */
const key = async (i) => cw.keyFromSeed(await cw.sha256(new TextEncoder().encode("wai:co-watch/corpus/key\x00"), Uint8Array.of(i)));
const INDEX = { authority: 0, clock: 1, host: 2, guest: 3, late: 4, fourth: 5 };
/** Every record of a vector, by its digest. */
async function records(v) {
const out = new Map();
for (const chain of v.chains) {
for (const h of chain) {
const b = cw.unhex(h);
const ev = cw.readEvent(b);
out.set(cw.hex(await cw.sha256(ev.body)), ev);
}
}
return out;
}
const timelineOf = (v) =>
new cw.Timeline(v.policies.map((h) => cw.readPolicy(cw.unhex(h))), v.controls.map((c) => ({ at: c.at, t: c.t, digest: c.digest, action: cw.ACTION[c.action], arg: c.arg })));
test("every catch-up case gets the corpus's correction", () => {
const { params, cases } = doc.catch_up;
assert.ok(cases.length > 0);
for (const c of cases) assert.deepEqual(cw.catchUp(params, c.playing, c.rate, c.controlled, c.duration, c.presented), c.correction, c.about);
});
test("the controlled position of each accepted room is the corpus's", () => {
for (const name of ACCEPTED) {
const v = vector(name);
const tl = timelineOf(v);
assert.deepEqual(tl.points.map(([x, s]) => ({ at: x, playing: s.playing, rate: s.rate, position: s.pos })), v.timeline, name);
for (const c of v.controls) assert.equal(!tl.overridden.has(c.digest), c.applied, `${name}: ${c.digest.slice(0, 8)}`);
for (const p of v.presentations) assert.equal(tl.positionAt(p.at), p.controlled, `${name}: the statement at ${p.at}`);
}
});
test("each accepted room's controls are written byte for byte", async () => {
for (const name of ACCEPTED) {
const v = vector(name);
const recs = await records(v);
for (const c of v.controls) {
const ev = recs.get(c.digest);
assert.ok(ev, `${name}: a control's record`);
assert.deepEqual(cw.controlPayload({ action: cw.ACTION[c.action], at: c.at, arg: c.arg }), ev.payload, `${name}: ${c.action} at ${c.at}`);
assert.deepEqual(cw.readStatement(ev.payload), { kind: "control", action: cw.ACTION[c.action], at: c.at, arg: c.arg });
}
}
});
test("each accepted room's presentation statements are written and signed byte for byte, at the catch-up rule's rate", async () => {
const keys = {};
for (const [n, i] of Object.entries(INDEX)) keys[doc.principals[n]] = await key(i);
for (const name of ACCEPTED) {
const v = vector(name);
const recs = await records(v);
const policies = v.policies.map((h) => cw.readPolicy(cw.unhex(h)));
const tl = timelineOf(v);
for (const p of v.presentations) {
const ev = recs.get(p.digest);
const pol = policies[cw.policyAt(policies, p.at)];
const st = tl.stateAt(p.at);
const duration = tl.durationAt(p.at);
// The player states its own rate: the controlled one, or the catch-up rule's (§9).
let rate = st.rate;
if (st.playing && p.controlled < duration) {
const c = cw.catchUp(pol, true, st.rate, p.controlled, duration, p.position);
if (c.action === "rate") rate = c.value;
}
const state = !st.playing ? cw.STATE.paused : p.controlled >= duration ? cw.STATE.ended : cw.STATE.playing;
assert.equal(state, cw.STATE[p.state], `${name}: the state at ${p.at}`);
const k = keys[p.player];
assert.equal(cw.hex(k.publicKey), p.player);
const mine = await cw.signPresentation({ room: v.room, player: p.player, source: pol.source, at: p.at, position: p.position, state, rate }, k.privateKey);
assert.deepEqual(mine, ev.payload, `${name}: the statement of ${p.player.slice(0, 8)} at ${p.at}`);
}
}
});
test("skew statements read back with their verdicts", async () => {
const v = vector("drift");
const recs = await records(v);
for (const s of v.skews) {
const st = cw.readStatement(recs.get(s.digest).payload);
assert.equal(st.kind, "skew");
assert.deepEqual(st.viewers.map((x) => [x.principal, x.verdict, x.pq]), s.viewers.map((x) => [x.principal, x.verdict, x.pq]));
}
});
test("a control the room would refuse is not applied", () => {
const v = vector("watch_party");
const policies = v.policies.map((h) => cw.readPolicy(cw.unhex(h)));
const yes = () => true;
assert.equal(cw.controlRefusal(policies, 5_000_000, { action: 2, at: 4_000_000, arg: 0 }, yes), "control_not_ahead");
assert.equal(cw.controlRefusal(policies, 5_000_000, { action: 2, at: 5_100_000, arg: 0 }, yes), "control_not_ahead");
assert.equal(cw.controlRefusal(policies, 5_000_000, { action: 2, at: 16_000_000, arg: 0 }, yes), "control_beyond_horizon");
assert.equal(cw.controlRefusal(policies, 5_000_000, { action: 3, at: 6_000_000, arg: policies[0].duration_us + 1 }, yes), "control_out_of_range");
assert.equal(cw.controlRefusal(policies, 5_000_000, { action: 4, at: 6_000_000, arg: 3_000_000 }, yes), "control_out_of_range");
assert.equal(cw.controlRefusal(policies, 5_000_000, { action: 2, at: 6_000_000, arg: 0 }, () => false), "control_not_permitted");
assert.equal(cw.controlRefusal(policies, 5_000_000, { action: 2, at: 6_000_000, arg: 0 }, yes), null);
});
test("the clock's offset is taken from the least round trip", () => {
assert.equal(cw.clockOffset([{ sent: 1000, got: 1100, server: 5_000_000 }, { sent: 2000, got: 2010, server: 7_000_000 }]), 7_000_000 - 2_005_000);
});
// A record of the tests below: its fields as JSON, in place of a signed WEV1 record, and its
// digest the SHA-256 of those bytes.
const fakeWai = {
eventSign: (seed, room, seq, prev, t, kind, payload) => new TextEncoder().encode(JSON.stringify({ seq: Number(seq), prev: cw.hex(prev), t: Number(t), payload: cw.hex(payload) })),
eventRead: (rec) => JSON.stringify({ ok: true, digest: createHash("sha256").update(rec).digest("hex") }),
};
// A watch room's service as joule-moq-room's watch.rs carries one viewer's events (co-watch
// §10): while the viewer is present, each event the next of its chain, at or after its `join`
// and its last event, and a control at least the lead ahead of its `t`; the first that is not
// is refused with the service's reason, those before it carried. Its clock signs a `join`
// `late` µs after the page's estimate of now. `failJoins`: the next joins its clock cannot
// sign (503). `leaveAfterJoin`: the next joins whose presence a `leave` the room had asked
// before them, signed after them, ends at once (a service that ordered them so).
function watchService(lead_ms, late) {
const s = { now: 0, present: false, joined: 0, lastT: 0, joins: 0, chain: [], controls: [], presentations: [], failJoins: 0, leaveAfterJoin: 0 };
s.api = async (path, body) => {
if (path === "/watch/challenge") return { status: 200, body: { challenge: "00".repeat(32) } };
if (path === "/watch/join") {
if (s.failJoins > 0) {
s.failJoins--;
return { status: 503, body: { error: "the clock has no session" } };
}
s.joined = s.now + late;
s.lastT = Math.max(s.lastT, s.joined);
s.present = true;
s.joins++;
if (s.leaveAfterJoin > 0) {
s.leaveAfterJoin--;
s.present = false;
}
return { status: 200, body: { ok: true, t: s.joined } };
}
assert.equal(path, "/watch/post");
for (const [k, h] of body.events.entries()) {
const ev = JSON.parse(new TextDecoder().decode(cw.unhex(h)));
const st = cw.readStatement(cw.unhex(ev.payload));
const why = !s.present ? "not_present"
: ev.seq !== s.chain.length ? "delivery_gap"
: ev.prev !== (s.chain.length ? s.chain[s.chain.length - 1] : "00".repeat(32)) ? "chain_broken"
: ev.t < s.lastT || ev.t < s.joined ? "time_reversed"
: st && st.kind === "control" && st.at < ev.t + lead_ms * 1000 ? "control_not_ahead"
: null;
if (why) return { status: 409, body: { error: why, carried: k } };
s.chain.push(createHash("sha256").update(cw.unhex(h)).digest("hex"));
s.lastT = ev.t;
if (st && st.kind === "control") s.controls.push({ action: st.action, at: st.at, t: ev.t });
if (st && st.kind === "presentation") s.presentations.push(st.at);
}
return { status: 200, body: { ok: true, carried: body.events.length } };
};
return s;
}
/** Until the page has posted all it holds, or stopped. */
async function settled(c) {
const end = Date.now() + 5000;
while ((c.posting || c.outbox.length) && !c.stopped && Date.now() < end) await new Promise((r) => setTimeout(r, 1));
}
test("a page the room took for gone enters again, and its control signed before then is carried", async () => {
const policy = vector("watch_party").policies[0];
const lead = cw.readPolicy(cw.unhex(policy)).lead_ms;
const svc = watchService(lead, 200_000);
const seed = new Uint8Array(32).fill(7);
const key = await cw.keyFromSeed(seed);
const logs = [];
const c = new cw.CoWatch({
api: svc.api, wai: fakeWai, room: "11".repeat(32), t0: 0, seed, principal: cw.hex(key.publicKey), grant: "33".repeat(100), media: {},
policies: [policy], objects: [], sourceUrl: "", mayControl: () => true, target: "//127.0.0.1/watch/join", log: (l) => logs.push(l),
});
c.now = () => svc.now;
svc.now = 1_000_000;
await c.join();
svc.now = 1_500_000;
const playAt = c.control(cw.ACTION.play);
await settled(c);
assert.deepEqual(svc.controls, [{ action: cw.ACTION.play, at: playAt, t: 1_500_000 }]);
// The page falls silent past the room's limit (its thread held, on a loaded machine): the
// room takes it for gone. Then it states what it presents, and a pause is pressed, both
// signed before the room's next `join` for it, which its clock signs 200 ms later.
svc.present = false;
svc.now = 7_000_000;
const source = cw.readPolicy(cw.unhex(policy)).source;
const body = { room: c.room, player: c.principal, source, at: svc.now, position: 5_500_000, state: cw.STATE.playing, rate: cw.NORMAL };
c.outbox.push(c.event(svc.now, await cw.signPresentation(body, key.privateKey)));
const pauseAt = c.control(cw.ACTION.pause);
await settled(c);
assert.equal(c.stopped, null, logs.join("\n"));
assert.deepEqual(c.stats.post_refused, {});
assert.equal(svc.joins, 2);
assert.deepEqual([c.stats.rejoins, c.stats.resigned, c.stats.dropped], [1, 1, 1]);
// The room's copy of the chain is whole: the play, then the pause signed again after the
// join, at the earliest instant its lead allows from there; the statement of an instant
// before the page was present again is not carried.
assert.equal(svc.chain.length, 2);
const [, pause] = svc.controls;
assert.equal(pause.action, cw.ACTION.pause);
assert.ok(pause.t >= svc.joined, `the pause's t ${pause.t}, the join's ${svc.joined}`);
assert.equal(pause.at, svc.joined + lead * 1000 + 50_000);
assert.ok(pause.at > pauseAt);
assert.deepEqual([c.seq, c.prev], [2, svc.chain[1]]);
// And it goes on: the next event follows the re-signed one.
svc.now = 8_000_000;
c.control(cw.ACTION.play);
await settled(c);
assert.equal(c.stopped, null);
assert.deepEqual([svc.chain.length, svc.controls.length], [3, 3]);
});
/** A page in a watch room kept by `watchService`, entered at room clock 1 s, its play carried
* at 1.5 s. */
async function entered() {
const policy = vector("watch_party").policies[0];
const p = cw.readPolicy(cw.unhex(policy));
const svc = watchService(p.lead_ms, 200_000);
const seed = new Uint8Array(32).fill(7);
const key = await cw.keyFromSeed(seed);
const logs = [];
const c = new cw.CoWatch({
api: svc.api, wai: fakeWai, room: "11".repeat(32), t0: 0, seed, principal: cw.hex(key.publicKey), grant: "33".repeat(100), media: {},
policies: [policy], objects: [], sourceUrl: "", mayControl: () => true, target: "//127.0.0.1/watch/join", log: (l) => logs.push(l),
});
c.now = () => svc.now;
svc.now = 1_000_000;
await c.join();
svc.now = 1_500_000;
c.control(cw.ACTION.play);
await settled(c);
assert.equal(svc.controls.length, 1);
return { svc, c, logs, lead: p.lead_ms };
}
test("a page taken for gone again right after it entered again enters once more, its control carried", async () => {
const { svc, c, logs, lead } = await entered();
// Gone; and the `leave` the room asked for it is signed only after its next join, which it
// ends (a service that signed them in that order).
svc.present = false;
svc.leaveAfterJoin = 1;
svc.now = 7_000_000;
c.control(cw.ACTION.pause);
await settled(c);
assert.equal(c.stopped, null, logs.join("\n"));
assert.deepEqual(c.stats.post_refused, {});
assert.equal(svc.joins, 3);
assert.deepEqual([c.stats.gone, c.stats.rejoins, c.stats.resigned], [2, 2, 2]);
assert.deepEqual(svc.controls.map((x) => x.action), [cw.ACTION.play, cw.ACTION.pause]);
assert.ok(svc.controls[1].t >= svc.joined);
assert.equal(svc.controls[1].at, svc.joined + lead * 1000 + 50_000);
});
test("a join the service cannot make is tried again by the next post, and the page goes on", async () => {
const { svc, c, logs } = await entered();
svc.present = false;
svc.failJoins = 1;
svc.now = 7_000_000;
c.control(cw.ACTION.pause);
await settled(c);
assert.equal(c.stopped, null, logs.join("\n"));
assert.deepEqual(c.stats.post_refused, {});
assert.equal(svc.joins, 2);
assert.deepEqual([c.stats.gone, c.stats.rejoins, c.stats.resigned], [2, 1, 1]);
assert.deepEqual(svc.controls.map((x) => x.action), [cw.ACTION.play, cw.ACTION.pause]);
assert.ok(logs.some((l) => /entering again failed/.test(l)), logs.join("\n"));
});
test("a page the room takes for gone at every entry stops after a few", async () => {
const { svc, c } = await entered();
svc.present = false;
svc.leaveAfterJoin = Infinity;
svc.now = 7_000_000;
c.control(cw.ACTION.pause);
await settled(c);
assert.match(c.stopped || "", /not_present/);
assert.deepEqual(c.stats.post_refused, { not_present: 1 });
assert.deepEqual([c.stats.gone, c.stats.rejoins, svc.joins], [3, 3, 4]);
assert.equal(svc.controls.length, 1);
});
test("a control pressed while the room had taken the page for gone is dropped where signing it again would put it past another viewer's", async () => {
const { svc, c, logs, lead } = await entered();
const other = (at, action) => c.controls.push({ at, t: svc.now, digest: cw.hex(createHash("sha256").update(String(at)).digest()), action, arg: 0, author: "22".repeat(32) });
// Gone; another viewer's pause is carried for 100 ms after this page's seek as pressed.
// Signed again after the join, 200 ms later, the seek would apply after that pause: dropped.
svc.present = false;
svc.now = 7_000_000;
other(svc.now + lead * 1000 + 50_000 + 100_000, cw.ACTION.pause);
c.control(cw.ACTION.seek, 1_000_000);
await settled(c);
assert.equal(c.stopped, null, logs.join("\n"));
assert.deepEqual([c.stats.rejoins, c.stats.resigned, c.stats.dropped], [1, 0, 1]);
assert.equal(svc.controls.length, 1, "the seek is not carried");
assert.ok(logs.some((l) => /is dropped: moved to/.test(l)), logs.join("\n"));
// Gone again; other viewers' controls before its instant as pressed, and past where it
// moves to: signed again, it keeps its place among them.
svc.present = false;
svc.now = 9_000_000;
other(svc.now + 100_000, cw.ACTION.pause);
other(svc.now + 5_000_000, cw.ACTION.play);
const seekAt = c.control(cw.ACTION.seek, 2_000_000);
await settled(c);
assert.equal(c.stopped, null, logs.join("\n"));
assert.deepEqual([c.stats.rejoins, c.stats.resigned], [2, 1]);
assert.deepEqual(svc.controls.map((x) => x.action), [cw.ACTION.play, cw.ACTION.seek]);
assert.ok(svc.controls[1].at > seekAt && svc.controls[1].at === svc.joined + lead * 1000 + 50_000);
});
/** The player's loop run for `passes` passes on a room clock the test moves, so the test, not
* the host, sets each pass's instant: by `step(i)` µs in the loop's `i`th wait between two
* passes, and by `work(i)` µs in the work of its `i`th pass (in `onChange`, which the loop
* calls in each pass, as work of its own or an await it does not measure would be); then
* until the page has posted all it holds. */
async function runFor(c, svc, passes, step, work = () => 0) {
let i = 0;
c.media = { currentTime: 0, paused: true, ended: false, readyState: 4, playbackRate: 1, play: () => Promise.resolve(), pause: () => {} };
c.now = () => svc.now;
c.onChange = () => { svc.now += work(i); };
c.wait = async () => { svc.now += step(i++); await new Promise((r) => setTimeout(r, 0)); };
await c.run(() => i < passes);
await settled(c);
}
/** The slots from the page's first statement to its last: each held by a statement, else
* empty, and of those whose second half a stall of the page spans. */
function slotsOf(ats, stalls, P) {
const out = { held: 0, empty: [], stalled: [] };
for (let s = Math.ceil(ats[0] / P) * P; s + P <= ats[ats.length - 1]; s += P) {
if (ats.some((x) => x >= s && x < s + P)) out.held++;
else (stalls.some((x) => x.from < s + P / 2 && x.to >= s + P) ? out.stalled : out.empty).push(s);
}
return out;
}
test("while its loop runs, the player states in every slot and records no stall", async () => {
const { svc, c, logs } = await entered();
const P = c.policy.period_ms * 1000;
await runFor(c, svc, 40, () => 50_000);
assert.equal(c.stopped, null, logs.join("\n"));
assert.deepEqual(c.stats.stalls, []);
const s = slotsOf(svc.presentations, c.stats.stalls, P);
assert.ok(s.held >= 1, JSON.stringify(s));
assert.deepEqual([s.empty, s.stalled], [[], []]);
for (let k = 1; k < svc.presentations.length; k++) assert.ok(svc.presentations[k] - svc.presentations[k - 1] <= P / 2 + 50_000);
});
test("the player records the stretch its thread was held, and only then leaves a slot empty", async () => {
const { svc, c, logs } = await entered();
const P = c.policy.period_ms * 1000;
// Its passes every 50 ms, but one: the page's thread held for three and a half slots while
// the loop waited, so its wait took that long.
const HELD = 15;
await runFor(c, svc, 50, (i) => (i === HELD ? 3.5 * P : 50_000));
assert.equal(c.stopped, null, logs.join("\n"));
assert.equal(c.stats.stalls.length, 1, JSON.stringify(c.stats.stalls));
const [st] = c.stats.stalls;
assert.equal(st.to - st.from, 3.5 * P);
// The whole stall is the wait: the loop's own work and its signing took no time on the clock.
assert.deepEqual([st.timer_us, st.signing_us], [3.5 * P, 0]);
// The slots the held thread left empty are those the stall spans; every other holds a
// statement, and none is empty outside it.
const s = slotsOf(svc.presentations, c.stats.stalls, P);
assert.ok(s.stalled.length >= 2, JSON.stringify(s));
assert.deepEqual(s.empty, []);
for (const x of s.stalled) assert.ok(st.from < x + P / 2 && st.to >= x + P);
// A pass more than half a slot after the one before is a stall: the loop states every half
// slot, so only such a stretch can leave a slot empty.
const { svc: svc2, c: c2 } = await entered();
await runFor(c2, svc2, 30, (i) => (i === HELD ? P / 2 + 1 : i === HELD + 5 ? P / 2 : 50_000));
assert.deepEqual(c2.stats.stalls.map((x) => x.to - x.from), [P / 2 + 1]);
});
test("a stall in the loop's own work is recorded with a wait that does not cover it", async () => {
const { svc, c, logs } = await entered();
const P = c.policy.period_ms * 1000;
// One pass's own work takes two slots (or an await the loop does not measure would): its
// wait after it took 50 ms, and the stall is the rest.
const HELD = 15;
await runFor(c, svc, 50, () => 50_000, (i) => (i === HELD ? 2 * P : 0));
assert.equal(c.stopped, null, logs.join("\n"));
assert.equal(c.stats.stalls.length, 1, JSON.stringify(c.stats.stalls));
const [st] = c.stats.stalls;
assert.equal(st.to - st.from, 2 * P + 50_000);
assert.deepEqual([st.timer_us, st.signing_us], [50_000, 0]);
assert.equal(st.to - st.from - st.timer_us - st.signing_us, 2 * P);
});