diff --git a/.github/workflows/wai-conformance.yml b/.github/workflows/wai-conformance.yml index feafd20cc..53943ac4b 100644 --- a/.github/workflows/wai-conformance.yml +++ b/.github/workflows/wai-conformance.yml @@ -63,6 +63,8 @@ on: - 'wai/extensions/moq-streaming-format.md' - 'wai/extensions/jwp-receipts.md' - 'wai/wai-web/**' + # The audio provenance check below reads the corpora above with this tool. + - 'wai/tools/wai_audio_provenance_check.py' - '.github/workflows/wai-conformance.yml' pull_request: paths: @@ -94,6 +96,8 @@ on: - 'wai/extensions/moq-streaming-format.md' - 'wai/extensions/jwp-receipts.md' - 'wai/wai-web/**' + # The audio provenance check below reads the corpora above with this tool. + - 'wai/tools/wai_audio_provenance_check.py' - '.github/workflows/wai-conformance.yml' # No global `shell:` default. The Windows runner is a host executor whose @@ -268,9 +272,10 @@ jobs: # tests/manifest_builders.rs: the manifest types are #[non_exhaustive], and the # builders write the JSON a struct literal wrote; members the types keep only # as `extra` (and `security`) survive a round trip. - - name: Carriage conformance and the wai CLI (wrap, registry export) + # corpus_provenance: every corpus README states where its content comes from. + - name: Carriage conformance, the wai CLI (wrap, registry export) and corpus provenance working-directory: wai/wai-rs - run: cargo test --no-default-features --test carriage --test manifest_builders --test cli_wrap_intent --test cli_registry + run: cargo test --no-default-features --test carriage --test manifest_builders --test cli_wrap_intent --test cli_registry --test corpus_provenance # tests/cli_http.rs runs `wai http publish`, `codecs`, `origin` and `bench` as a user # does: objects named by their content hash with their head proofs, a sequence's union # and floor, the origin's headers, ranges and If-Range on loopback, and a fetch by @@ -382,6 +387,24 @@ jobs: shell: bash working-directory: wai run: python3 pin-conformance/pin_verify.py pin-conformance + # Every committed audio vector must come from a seeded generator. This check reads + # each one to PCM (each audio base's stage 0 in the staged corpus, each measured + # source over one, each spatial-audio source, every audio file under wai/) and + # fails if any correlates above 0.5, at any lag, with a recording in the reference + # set the host has: its system sound directory, or the directories named in + # WAI_AUDIO_REFERENCE_DIRS. A generated signal stays near 0.2; a lossy coding of a + # recording is near 1. A host with no reference set, or no converter to read it, + # prints why and skips: the linux legs skip, the macOS leg checks. + - name: No committed audio vector is a coding of a reference recording (unix) + if: matrix.os != 'windows' + shell: bash + working-directory: wai + run: | + if command -v python3 >/dev/null 2>&1; then + python3 tools/wai_audio_provenance_check.py + else + echo "audio provenance: SKIPPED — no python3 on this host" + fi # OER's second implementation is portable C, not Rust. Rebuild it from source and # require its output to match the pinned corpus byte for byte: the kernel is # deterministic, and wai_oer_vectors above has already verified the pinned copy, diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index 2335e2aa3..47cb3bd19 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -6,6 +6,35 @@ wire format may change and such changes are recorded here. ## v0.4.0 — current +### The int_codec model and vectors: trained and coded on a generated signal + +- The int_codec model and vectors are now trained and coded on a generated signal; + earlier revisions used a third-party recording. +- `tools/wai_int_audio_export.py` reads no audio file. It writes its training signal + and the clip it codes from a seed (16 kHz mono: tones, chirps, noise bursts and + speech-like formant sweeps), in integer arithmetic, so a seed gives the same samples + on every machine; `--signal-only` writes one with the Python standard library alone. + Training is seeded from the same seed with deterministic algorithms and a fixed + thread count: two runs gave byte-identical files. The seed and the command are in + [`wai-rs/tests/vectors/int_audio/README.md`](wai-rs/tests/vectors/int_audio/README.md). +- Regenerated through their generators: `wai-rs/tests/vectors/int_audio/` (the model, + both bitstream forms, `audio.json`, `sha256.json`); `int-payload-conformance`'s + `vectors.json` and its thirteen `wia1_*` refusal fixtures; the staged-delivery case + `audio_int_codec_1l` and `index.json`; the staged-measurement audio title, + `goldens.json` and `sim/results.json`. The `WIA1` format, the capability and the + model's shape are unchanged. `wai_int_payload_vectors` now reads the place of a + `WIA1`'s first table from the payload's header for the two fixtures that change it, so + they keep their meaning on tables of another length (on the earlier vector the + offsets are the same). +- `tools/wai_audio_provenance_check.py`, run by CI: every committed audio vector is read + to PCM and correlated, at every lag, with the reference recordings the CI host has; + above 0.5 it fails, and a host with none skips it and says so. +- Every corpus states its content's provenance: a `**Provenance.**` line in each + `*-conformance/README.md` (OER/2's settled corpus aside) and a row per directory in + `wai-rs/tests/vectors/README.md`, required by `tests/corpus_provenance.rs`. + CONTRIBUTING states the rule. Two vectors are flagged there as not generated and to + be replaced: `int_video`'s `video_real` and `neural-conformance`'s `photo`. + ### Staged tracks over MoQ: a track per layer, a receipts track per track, delivery timeouts per hop, chains that recover - **Re-read against the drafts.** [moq-streaming-format](extensions/moq-streaming-format.md) diff --git a/wai/CONTRIBUTING.md b/wai/CONTRIBUTING.md index 082bf5dda..f1c2fd26f 100644 --- a/wai/CONTRIBUTING.md +++ b/wai/CONTRIBUTING.md @@ -70,6 +70,21 @@ generator (the `wai_*_vectors gen` binaries and the export tools in [`tools/`](tools/)), never by hand. A test whose comment says its hex must never be edited to make it pass means exactly that. +### Corpus content: where it comes from + +Corpus content is generated in this repository, by a generator from a seed or a +formula, or it is an input whose source and licence are recorded and whose licence +permits redistribution. An image, a recording or footage that was not made here and +has no such licence is not committed, as content or as training data. + +Every corpus states its provenance: each `*-conformance/README.md` in a line that +begins `**Provenance.**`, and `wai-rs/tests/vectors/README.md` in a row for each +vector directory. The line names the generator and its input, and for an input not +generated here, its source and licence. A word such as "synthetic" is not a +provenance line. `wai-rs/tests/corpus_provenance.rs` requires the line in every +corpus, and `tools/wai_audio_provenance_check.py`, which CI runs, fails when a +committed audio vector correlates with a reference recording the host has. + ### Independent implementations Conformance is defined by the committed corpora (`*-conformance/`) and the diff --git a/wai/audio-conformance/README.md b/wai/audio-conformance/README.md index 3c1edd607..c044b755b 100644 --- a/wai/audio-conformance/README.md +++ b/wai/audio-conformance/README.md @@ -6,6 +6,8 @@ layout, a conforming sink MUST produce the identical canonical PCM, hence the identical BLAKE3 — on every machine, no tolerance parameter. The audio counterpart of replay-equivalence. +**Provenance.** Generated here: `wai_audio_vectors gen` writes every scene and its sources, square-wave tones built in code. No recording. + All vectors mix to the mandatory `stereo` floor under the `wai.det.fixed64` numeric profile, `wai.audio.pan.sqrt_constpower` pan, `wai.audio.dist.inverse_clamped` distance. Sources are deterministic diff --git a/wai/avatar-conformance/README.md b/wai/avatar-conformance/README.md index 2ebd87634..24dcfc4bf 100644 --- a/wai/avatar-conformance/README.md +++ b/wai/avatar-conformance/README.md @@ -10,6 +10,8 @@ quaternion + scale, all `wai.det.fixed64`); the forward-kinematics composition into world space and the skinning of a `wai.mesh.glb` are the sink's presentation, never in the hash. +**Provenance.** Generated here: `wai_avatar_vectors gen` writes every vector from rigs and motion authored in code. No capture. + The container is the worlds `WWLD`; the sim is `wai.world.sim.skeletal0` (the identity step — a pose changes only when a signed op sets a joint). Mutations: `CREATE` / `DELETE` / `SET_ROTATION` / `SET_TRANSLATION` / diff --git a/wai/byte-exact-conformance/README.md b/wai/byte-exact-conformance/README.md new file mode 100644 index 000000000..318fb11ff --- /dev/null +++ b/wai/byte-exact-conformance/README.md @@ -0,0 +1,14 @@ +# byte-exact-conformance — cross-architecture byte equality + +A crate that compiles WAI's pure-integer decode paths (the rANS entropy coder and the +integer fixed-point syntheses, `#[path]`-included from `wai-rs`) without the system +codec libraries, so the byte-equality matrices (`.github/workflows/wai-byte-equality.yml`, +and *cross-arch byte-equality* in `wai-conformance.yml`) run them on every OS and +architecture. `cargo test --release` here decodes the committed vectors and the +staged-delivery corpus to the goldens in `src/*_golden.rs`, generated once on a +reference machine, and must match them on every machine. + +**Provenance.** The goldens are digests of decodes of the vectors under +[`../wai-rs/tests/vectors/`](../wai-rs/tests/vectors/) and of the staged-delivery +corpus; that directory's README states each vector's provenance. Among them, the +`int_video` half-pel clip (`video_real`) is not generated content (see there). diff --git a/wai/cache-conformance/README.md b/wai/cache-conformance/README.md index 80fcb83d9..b541f6013 100644 --- a/wai/cache-conformance/README.md +++ b/wai/cache-conformance/README.md @@ -5,6 +5,8 @@ an HTTP cache that fronts an origin serving the objects of [`http-conformance/`](../http-conformance/), and reports, per cache label and version, whether the configuration meets each rule. +**Provenance.** Written here: the tests, the driver and the controls are the code in this directory, and the objects a run fetches are `http-conformance`'s (see its provenance). No media of its own. + | file | what | |---|---| | `tests.json` | the tests: id, rule, level (MUST or informative) and what each checks | diff --git a/wai/claim-conformance/README.md b/wai/claim-conformance/README.md index dbb2b8c0d..50f744ede 100644 --- a/wai/claim-conformance/README.md +++ b/wai/claim-conformance/README.md @@ -6,6 +6,8 @@ Conformance for [typed receipt classes and the soft binding class](../extensions `operating-point`, `stage`, `delivery-gap` and `reconstruct-session`, and recovery through a soft binding under the ancestor rule. +**Provenance.** Generated here by `wai_claim_vectors`, from fixed seeds and fixed values (below). No media. + Each vector declares **one outcome**: `accept` — with the claim's `link_hash`, which pins the canonical form, or for a recovery the ancestor it attests — or `reject` at a named §3.1 step with a named reason. A verifier that rejects for another reason, or at diff --git a/wai/feed-conformance/README.md b/wai/feed-conformance/README.md index 688ec4323..c46926c28 100644 --- a/wai/feed-conformance/README.md +++ b/wai/feed-conformance/README.md @@ -9,6 +9,8 @@ non-physics world whose sim (`wai.world.sim.feed0`) is the identity step; convergence comes entirely from applying typed-field updates in the canonical total order. +**Provenance.** Generated here: `wai_feed_vectors gen` writes every vector from feeds authored in code. No recorded data. + The container is the worlds `WWLD`; the node is a record of self-describing typed cells (`Int` i64, `Fixed` Q32.32, `Text` UTF-8). Mutations: `CREATE` / `DELETE` / `SET_CELL` (LWW) and `ADD_INT` / diff --git a/wai/film-conformance/README.md b/wai/film-conformance/README.md index b855eded7..995c84d07 100644 --- a/wai/film-conformance/README.md +++ b/wai/film-conformance/README.md @@ -7,6 +7,8 @@ tick || the interpolated camera pose)` — hence the identical per-frame hashes and the identical Merkle root over them, on every machine, no tolerance parameter. +**Provenance.** Generated here: `wai_film_vectors gen` writes every vector from shots authored in code. No footage. + A film composes two things already proven elsewhere: the embedded world's **replay-equivalence** (every sink agrees on the state hash at every tick — the `world-conformance/` corpus) and the `wai.det.fixed64` camera track diff --git a/wai/haptic-conformance/README.md b/wai/haptic-conformance/README.md index aac06cb4f..47d788d74 100644 --- a/wai/haptic-conformance/README.md +++ b/wai/haptic-conformance/README.md @@ -7,6 +7,8 @@ the identical canonical `u8` buffer (per-channel interleaved machine, no tolerance parameter. The touch counterpart of replay/mixdown-equivalence, with the tick replaced by the haptic sample. +**Provenance.** Generated here: `wai_haptic_vectors gen` writes every vector from envelopes authored in code. No recording. + All vectors reconstruct in the `wai.det.fixed64` floor (Fx linear interpolation of the keyframed envelope → u8). Signals are self-contained (no external sources), so each vector is just a `.whap` diff --git a/wai/http-conformance/README.md b/wai/http-conformance/README.md index 31a54dda6..8af0498b9 100644 --- a/wai/http-conformance/README.md +++ b/wai/http-conformance/README.md @@ -16,6 +16,8 @@ reference adaptation policies. Two implementations check every case and must agr `../claim-conformance/_blake3.py` (BLAKE3 from its specification) and `../registry/capabilities.json` (the published registry export). +**Provenance.** Generated here by `wai_http_vectors`, from a staged-delivery case and from the frozen sources `src/SOURCES.json` lists, each encoded once by an encoder of its format: pictures of the `int_mlicpp` vector's generated 64×64 pattern, AV1 colour-bar test patterns, and a clip of a square moving over a gradient. The test patterns and the clip are generated content whose generators are not in this tree. None is a recording or a photograph. + ## Files | path | what | diff --git a/wai/int-payload-conformance/README.md b/wai/int-payload-conformance/README.md index 868405d07..70879ef72 100644 --- a/wai/int-payload-conformance/README.md +++ b/wai/int-payload-conformance/README.md @@ -6,6 +6,8 @@ Vectors for the payload formats SPEC §5 pins to the WAI-native integer codecs for `wai.neural.int_hyper`, `WIA1` for `wai.audio.int_codec` and `WIS1` for `wai.splat.int_codec`. +**Provenance.** Generated here: `wai_int_payload_vectors gen` converts the vectors under `../wai-rs/tests/vectors/` and changes them for the refusal fixtures; `tests/vectors/README.md` states each vector's provenance. One payload indexed here is not generated content: `video_real` (`int_video`) codes a third-party image (see there). + **Code-equivalence.** A conforming sink decodes every payload to the output digests its vector states, and refuses every fixture with the code it states, from the extension's closed list (§8). diff --git a/wai/int-payload-conformance/refusals/wia1_afrac_model.bin b/wai/int-payload-conformance/refusals/wia1_afrac_model.bin index 3c5391d10..c040ce6e5 100644 Binary files a/wai/int-payload-conformance/refusals/wia1_afrac_model.bin and b/wai/int-payload-conformance/refusals/wia1_afrac_model.bin differ diff --git a/wai/int-payload-conformance/refusals/wia1_afrac_zero.bin b/wai/int-payload-conformance/refusals/wia1_afrac_zero.bin index d06bc2990..cace903c1 100644 Binary files a/wai/int-payload-conformance/refusals/wia1_afrac_zero.bin and b/wai/int-payload-conformance/refusals/wia1_afrac_zero.bin differ diff --git a/wai/int-payload-conformance/refusals/wia1_cdf_malformed.bin b/wai/int-payload-conformance/refusals/wia1_cdf_malformed.bin index 5a2276262..ffb9a7ad8 100644 Binary files a/wai/int-payload-conformance/refusals/wia1_cdf_malformed.bin and b/wai/int-payload-conformance/refusals/wia1_cdf_malformed.bin differ diff --git a/wai/int-payload-conformance/refusals/wia1_exhausted.bin b/wai/int-payload-conformance/refusals/wia1_exhausted.bin index 758a5fa32..2275c4ce6 100644 Binary files a/wai/int-payload-conformance/refusals/wia1_exhausted.bin and b/wai/int-payload-conformance/refusals/wia1_exhausted.bin differ diff --git a/wai/int-payload-conformance/refusals/wia1_latent_too_long.bin b/wai/int-payload-conformance/refusals/wia1_latent_too_long.bin index a13648788..f6699baf7 100644 Binary files a/wai/int-payload-conformance/refusals/wia1_latent_too_long.bin and b/wai/int-payload-conformance/refusals/wia1_latent_too_long.bin differ diff --git a/wai/int-payload-conformance/refusals/wia1_median_range.bin b/wai/int-payload-conformance/refusals/wia1_median_range.bin index d932c5d59..cd5c4159b 100644 Binary files a/wai/int-payload-conformance/refusals/wia1_median_range.bin and b/wai/int-payload-conformance/refusals/wia1_median_range.bin differ diff --git a/wai/int-payload-conformance/refusals/wia1_order_table_before_length.bin b/wai/int-payload-conformance/refusals/wia1_order_table_before_length.bin index df51fa5f3..08937c1f1 100644 Binary files a/wai/int-payload-conformance/refusals/wia1_order_table_before_length.bin and b/wai/int-payload-conformance/refusals/wia1_order_table_before_length.bin differ diff --git a/wai/int-payload-conformance/refusals/wia1_sr_zero.bin b/wai/int-payload-conformance/refusals/wia1_sr_zero.bin index 84a51220d..ccf2eb4cd 100644 Binary files a/wai/int-payload-conformance/refusals/wia1_sr_zero.bin and b/wai/int-payload-conformance/refusals/wia1_sr_zero.bin differ diff --git a/wai/int-payload-conformance/refusals/wia1_t_out.bin b/wai/int-payload-conformance/refusals/wia1_t_out.bin index 4e8394902..9b1a27c68 100644 Binary files a/wai/int-payload-conformance/refusals/wia1_t_out.bin and b/wai/int-payload-conformance/refusals/wia1_t_out.bin differ diff --git a/wai/int-payload-conformance/refusals/wia1_too_large.bin b/wai/int-payload-conformance/refusals/wia1_too_large.bin index 7556093a4..d62ec8752 100644 Binary files a/wai/int-payload-conformance/refusals/wia1_too_large.bin and b/wai/int-payload-conformance/refusals/wia1_too_large.bin differ diff --git a/wai/int-payload-conformance/refusals/wia1_trailing.bin b/wai/int-payload-conformance/refusals/wia1_trailing.bin index 343085bba..f234c2534 100644 Binary files a/wai/int-payload-conformance/refusals/wia1_trailing.bin and b/wai/int-payload-conformance/refusals/wia1_trailing.bin differ diff --git a/wai/int-payload-conformance/refusals/wia1_under_splat.bin b/wai/int-payload-conformance/refusals/wia1_under_splat.bin index 05d5af267..f50153003 100644 Binary files a/wai/int-payload-conformance/refusals/wia1_under_splat.bin and b/wai/int-payload-conformance/refusals/wia1_under_splat.bin differ diff --git a/wai/int-payload-conformance/refusals/wia1_unread_word.bin b/wai/int-payload-conformance/refusals/wia1_unread_word.bin index 36cfb7639..4437969a4 100644 Binary files a/wai/int-payload-conformance/refusals/wia1_unread_word.bin and b/wai/int-payload-conformance/refusals/wia1_unread_word.bin differ diff --git a/wai/int-payload-conformance/vectors.json b/wai/int-payload-conformance/vectors.json index a06965a83..118bfc9fc 100644 --- a/wai/int-payload-conformance/vectors.json +++ b/wai/int-payload-conformance/vectors.json @@ -59,7 +59,7 @@ "name": "audio", "capability": "wai.audio.int_codec", "payload": "wai-rs/tests/vectors/int_audio/audio_bitstream.wia1", - "payload_sha256": "b45e727525f06ac1ac5a5a7121886c85a7c33613451acbb482c20e37f3c79055", + "payload_sha256": "e4343c9468f6c7b652f911e24e273223ef14feaa7014df014f2d99e9f28e1b3b", "converted_from": { "json": "wai-rs/tests/vectors/int_audio/audio_bitstream.json", "sample_rate": "wai-rs/tests/vectors/int_audio/audio.json" diff --git a/wai/jwp-conformance/README.md b/wai/jwp-conformance/README.md index 18ab55fe3..beccf7365 100644 --- a/wai/jwp-conformance/README.md +++ b/wai/jwp-conformance/README.md @@ -11,6 +11,8 @@ read as signed only where a signed statement restates it; one chain for each key track; and an object whose receipt is checked without its envelope reported as `inclusion-verified`, never as delivered. +**Provenance.** Generated here by `wai_jwp_vectors`, from fixed seeds (below). No media. + ## Files | file | what it is | diff --git a/wai/neural-conformance/README.md b/wai/neural-conformance/README.md index a25bd85eb..30e53fa32 100644 --- a/wai/neural-conformance/README.md +++ b/wai/neural-conformance/README.md @@ -7,6 +7,8 @@ because its synthesis is pure `i64` arithmetic (integer transposed convolutions + arithmetic-shift requantize + integer ReLU), not float ONNX. Two sinks honoring the spec produce the same bytes on every architecture. +**Provenance.** `gradient`, `bloom` and `facade` are generated here by `wai_int_synth_vectors` from a fixed SplitMix64 sequence. `photo` is not: it codes a third-party picture that was not made for this repository and has no recorded licence for redistribution, and its decoder was trained on images that are not recorded. It is to be replaced by generated content. + The float decoders (`bmshj2018`, `mbt2018_mean`, `elic`, `mlicpp`, …) are deliberately **not** here: their ONNX synthesis is float, so two conforming sinks on different hardware may differ in the low bits — a byte-equality @@ -44,9 +46,9 @@ is reproducible without a float RNG: - `facade` — 16×16 latent, 3 ch → ×2 → ×2 → refine = 72×72 RGB, three layers, two ReLUs (a deeper integer stack). -And one **real trained model on a real photograph**: -- `photo` — a 128×128 macOS *Sonoma* landscape, encoded to a 6,156-byte int8 - latent (96×8×8) + a four-layer integer decoder **trained on real images** +And one **real trained model**, on a third-party picture (see *Provenance*, above): +- `photo` — a 128×128 third-party picture, encoded to a 6,156-byte int8 + latent (96×8×8) + a four-layer integer decoder trained on images the corpus does not record (`tools/wai_int_synth_real.py`: float-train + post-training quantization with per-layer activation-scale calibration). Reconstruction ≈ 21.6 dB at ~3 bpp. This is the honest scope of int_synth: a pure integer transposed-conv decoder @@ -54,8 +56,8 @@ And one **real trained model on a real photograph**: that the decode is bit-identical on every machine**. At `gen` time the Rust decoder is cross-checked against the tool's python integer-forward (`ref_rgb.bin`) to max|Δ|=0, and the frozen `rgb_hash` then enforces the same - bytes on every platform in CI — a real trained model, a real photo, decoded - identically everywhere. + bytes on every platform in CI — a real trained model, decoded identically + everywhere. ## Running diff --git a/wai/pack-conformance/README.md b/wai/pack-conformance/README.md index 9a656fe94..97ff92333 100644 --- a/wai/pack-conformance/README.md +++ b/wai/pack-conformance/README.md @@ -4,6 +4,8 @@ Vectors for [`extensions/packaging.md`](../extensions/packaging.md): the policy, digest and what it may ask a packager to emit (§3), how a source is cut into pieces (§4), and the integer video payloads a packager writes (§5.5). +**Provenance.** Generated here: the `wai-pack` fixture generator (`wai-pack/src/fixture.rs`, `wai-pack/src/conformance.rs`) writes every frame from integer functions of the frame and pixel index, and a package's floor is an AV1 coding of those frames. No footage. + - `policy/` — policies a packager accepts (every rung one it may emit, §3.6), and `expected.json`: each policy's effective canonical JSON and digest (`eq-defaults-a.toml` also with `royalty_free_only` forced on, the one way an diff --git a/wai/pin-conformance/README.md b/wai/pin-conformance/README.md index 8eb9a9b09..edd0fd855 100644 --- a/wai/pin-conformance/README.md +++ b/wai/pin-conformance/README.md @@ -2,6 +2,8 @@ Vectors for the parameter-set pin (SPEC.md §3.1, §4, §7, §7.1). +**Provenance.** Generated here by `wai_pin_vectors`; the parameter-set files under `sets/` are stand-in bytes written for the corpus. No media. + **Reason-equivalence.** A conforming verifier reports exactly the declared outcome for every case: diff --git a/wai/quantum-conformance/README.md b/wai/quantum-conformance/README.md index 93b732246..715cd7308 100644 --- a/wai/quantum-conformance/README.md +++ b/wai/quantum-conformance/README.md @@ -4,6 +4,8 @@ Statevector-equivalence for the `wai.quantum.circuit` extension: given the same `WQC` circuit, a conforming sink MUST reconstruct the identical fixed-point statevector — **hash equality, no tolerance**. +**Provenance.** Generated here by `wai_quantum_vectors` from circuits authored in code. No media. + `vectors/*.wqc` plus `expected_hashes.json` are the pinned corpus. They are the one reference all three surfaces are checked against, rather than being checked against each other. diff --git a/wai/quantum-ops-conformance/README.md b/wai/quantum-ops-conformance/README.md new file mode 100644 index 000000000..9662fdfa2 --- /dev/null +++ b/wai/quantum-ops-conformance/README.md @@ -0,0 +1,15 @@ +# quantum-ops-conformance — calibration and job receipts + +The corpus of the quantum-ops extension: `wai.quantum.calibration` and `wai.quantum.job` +receipts, portable JSON signed with Ed25519 over a BLAKE3 Merkle root. Each vector is a +`vectors/.json` receipt and the `artifacts/` bytes it content-addresses; +`expected_hashes.json` pins every `receipt_hash`. A third party parses the JSON, +recomputes the Merkle root, enforces the grant's joule ceiling, verifies the signature, +checks each content hash against the artifact bytes and, for a job, the calibration +cross-link. + +**Provenance.** Generated here by `wai_quantum_ops_vectors gen`, from pinned demonstration +signing keys and fixed content. No media. + + cd ../wai-rs + cargo run --no-default-features --features quantum_ops --bin wai_quantum_ops_vectors -- verify ../quantum-ops-conformance diff --git a/wai/research/decode-throughput.md b/wai/research/decode-throughput.md index a7d206adc..2b4552bb8 100644 --- a/wai/research/decode-throughput.md +++ b/wai/research/decode-throughput.md @@ -345,6 +345,15 @@ What was checked (the claim rests on these, not on the argument alone): *width*, not on trained values, with one exception that is visible below: activation magnitude selects the IGDN arithmetic path. + > **Correction (2026-10-07).** The int_audio vector timed here was trained on, + > and coded, a third-party recording, not content made for the repository. It + > has been replaced: the int_audio model and vector are now trained and coded on + > a signal `tools/wai_int_audio_export.py` generates from a seed. The model has + > the same shape (16 channels, 512 latent samples, 8192 output samples); the + > row's figures are for the earlier vector, and the current one's time can + > differ through its activations (the exception above) and the length of its + > rANS stream. + ## 4. Results ### Whole decoders diff --git a/wai/score-conformance/README.md b/wai/score-conformance/README.md index 1963b5764..b880f66c0 100644 --- a/wai/score-conformance/README.md +++ b/wai/score-conformance/README.md @@ -8,6 +8,8 @@ parameter. The music counterpart of spatial audio's mixdown-equivalence, with the recorded sound objects replaced by note events. +**Provenance.** Generated here: `wai_score_vectors gen` writes every score from notes authored in code. No recording. + The reference synth is trig-free: a phase accumulator drives a piecewise-linear oscillator (saw / square / triangle / pulse) shaped by a linear ADSR envelope, all in the `wai.det.fixed64` floor. Pitch is diff --git a/wai/signer-conformance/README.md b/wai/signer-conformance/README.md index 4628dfa67..f1a2c9acc 100644 --- a/wai/signer-conformance/README.md +++ b/wai/signer-conformance/README.md @@ -5,6 +5,8 @@ Conformance for [key discovery, signing authority and module authorisation](../e verifier reports beside a receipt's or claim's verdict, and the checks a sink runs before it runs a decoder or reconstruct module. +**Provenance.** Generated here by `wai_signer_vectors`, from published one-byte seeds (below). No media. + Each vector declares **one verdict**, set from the text in the generator and never copied from a verifier. A verifier that reaches another verdict — another level, step, reason, head or anchor path — does not conform. diff --git a/wai/staged-conformance/README.md b/wai/staged-conformance/README.md index d49a0ea55..89c4b5386 100644 --- a/wai/staged-conformance/README.md +++ b/wai/staged-conformance/README.md @@ -96,10 +96,10 @@ receipt for it, so that step 5 requires `delivered-verified` of its layers. the f64 form's rounding near a half. - `index.json` — the cases and their final clip hashes. -**The content is synthetic**: pictures, frames and PCM computed by integer functions in -the generator. The bases of the integer codecs are the committed vectors under -[`../wai-rs/tests/vectors/`](../wai-rs/tests/vectors/), decoded against their -parameter sets with their pins; the `wai.video.int_motion` bases are coded by the +**Provenance.** Generated here: pictures, frames and PCM computed by integer functions +in the generator. The bases of the integer codecs are the committed vectors under +[`../wai-rs/tests/vectors/`](../wai-rs/tests/vectors/), each generated too (that +directory's README states how), decoded against their parameter sets with their pins; the `wai.video.int_motion` bases are coded by the library's keyframe-0 reference encoder (`int_payload::encode_wiv1_zero_motion`, which `wai stage base` also runs: zero motion, one block per frame), and the PNG and FLAC bases are written by small writers in the generator (stored deflate with diff --git a/wai/staged-conformance/cases/audio_int_codec_1l/L1.wai b/wai/staged-conformance/cases/audio_int_codec_1l/L1.wai index 7f0efde87..0588b83c5 100644 Binary files a/wai/staged-conformance/cases/audio_int_codec_1l/L1.wai and b/wai/staged-conformance/cases/audio_int_codec_1l/L1.wai differ diff --git a/wai/staged-conformance/cases/audio_int_codec_1l/base.wai b/wai/staged-conformance/cases/audio_int_codec_1l/base.wai index 205d92a2a..5bc84b93c 100644 Binary files a/wai/staged-conformance/cases/audio_int_codec_1l/base.wai and b/wai/staged-conformance/cases/audio_int_codec_1l/base.wai differ diff --git a/wai/staged-conformance/cases/audio_int_codec_1l/case.json b/wai/staged-conformance/cases/audio_int_codec_1l/case.json index ce9fd47cf..d10181b28 100644 --- a/wai/staged-conformance/cases/audio_int_codec_1l/case.json +++ b/wai/staged-conformance/cases/audio_int_codec_1l/case.json @@ -5,7 +5,7 @@ "bases": [ { "file": "base.wai", - "object": "d45db2bc98dd38873553beb15fdb026a4e6b7fd7c8c7dbe4f4a0b6fcc0709a54", + "object": "751a7ca553e0dbf039f5bd98be79af34baf436e37afc1a1765fd9eedbfe20660", "decoded_via": "wai.audio.int_codec", "via_fallback": false, "attested": false, @@ -23,7 +23,7 @@ } ], "stage0": "stage0.bin", - "stage0_sha256": "b8ff83a442c112661ec7a41d6e51cb32559510a699a0e9bb37fa301f8952978a", + "stage0_sha256": "4b0344c87c67fc30b7d977ae2077fc310822199598e5d2f9f8fc06314b3a2875", "decode": { "kind": "int", "set": { @@ -35,8 +35,8 @@ "files": [ { "file": "L1.wai", - "object": "e462824f3bf3827ec4d8861e1c93f64fabf2bec20303013347eee7a271f968a2", - "bytes": 19375 + "object": "5541cb6893420d23696e929a00e3a7aa7b755d2d7ffcd6254944e8401b85a9fc", + "bytes": 28759 } ], "config": { @@ -67,7 +67,7 @@ [], [ { - "object": "e462824f3bf3827ec4d8861e1c93f64fabf2bec20303013347eee7a271f968a2", + "object": "5541cb6893420d23696e929a00e3a7aa7b755d2d7ffcd6254944e8401b85a9fc", "envelope": null, "detail": null, "held_for_base": false, @@ -84,7 +84,7 @@ "units": [ { "stage": 1, - "producer": "e462824f3bf3827ec4d8861e1c93f64fabf2bec20303013347eee7a271f968a2", + "producer": "5541cb6893420d23696e929a00e3a7aa7b755d2d7ffcd6254944e8401b85a9fc", "frozen": false, "tier": "decode-equivalence", "shape": [ @@ -92,10 +92,10 @@ 1, 1 ], - "stage_sha256": "b9ca33fc990f6a28c479c4a8193dca0bd9faa1d8dedf9cef9614513da4b1d0bd" + "stage_sha256": "f48dd33054661c4da9c14449579b54fd2bd0c52408829468a4ddc76a945ecc23" } ], - "clip_sha256": "ec7a4ad312d40a6ebdb33a5b55df02967415c3eb8f065c72e427d1a189b8810d", + "clip_sha256": "3a20087e9668f23d47584467849d2d4c1c5fc99bb099b02227759f8ab42bcf3c", "n_units": 1 } ] diff --git a/wai/staged-conformance/cases/audio_int_codec_1l/stage0.bin b/wai/staged-conformance/cases/audio_int_codec_1l/stage0.bin index f6acedd95..3a7f6ec22 100644 Binary files a/wai/staged-conformance/cases/audio_int_codec_1l/stage0.bin and b/wai/staged-conformance/cases/audio_int_codec_1l/stage0.bin differ diff --git a/wai/staged-conformance/index.json b/wai/staged-conformance/index.json index a210b272e..10829dd2a 100644 --- a/wai/staged-conformance/index.json +++ b/wai/staged-conformance/index.json @@ -64,7 +64,7 @@ "name": "audio_int_codec_1l", "about": "a pinned wai.audio.int_codec base, then an identity layer", "clip_sha256": [ - "ec7a4ad312d40a6ebdb33a5b55df02967415c3eb8f065c72e427d1a189b8810d" + "3a20087e9668f23d47584467849d2d4c1c5fc99bb099b02227759f8ab42bcf3c" ] }, { diff --git a/wai/staged-measure-conformance/README.md b/wai/staged-measure-conformance/README.md index fc4b47df9..fec8525f4 100644 --- a/wai/staged-measure-conformance/README.md +++ b/wai/staged-measure-conformance/README.md @@ -60,9 +60,9 @@ identity (Appendix D). Across the corpus: the record-wide deduplication totals ( with a short timeout (`fifo-timeout-short`), and `fifo` decode on one worker, whose stall passes the bound of the same plan with `base-first` decode (`strict-window`). -**The content is synthetic.** Each base is a committed vector under -[`../wai-rs/tests/vectors/`](../wai-rs/tests/vectors/), as the staged-delivery corpus -wraps it: a `wai.neural.int_hyper` picture, a `wai.video.int_hyper` clip, a +**Provenance.** Generated here. Each base is a committed vector under +[`../wai-rs/tests/vectors/`](../wai-rs/tests/vectors/), generated too (that directory's +README states how), as the staged-delivery corpus wraps it: a `wai.neural.int_hyper` picture, a `wai.video.int_hyper` clip, a `wai.audio.int_codec` clip and a `wai.splat.int_codec` attribute tensor, each pinned to its parameter set. Each source is the base's output with fine detail added by integer functions of position, channel and a seed (for audio, two triangle waves), raised to the diff --git a/wai/staged-measure-conformance/fixtures/audio/L01.wai b/wai/staged-measure-conformance/fixtures/audio/L01.wai index 73ae8da1c..fd95be3c7 100644 Binary files a/wai/staged-measure-conformance/fixtures/audio/L01.wai and b/wai/staged-measure-conformance/fixtures/audio/L01.wai differ diff --git a/wai/staged-measure-conformance/fixtures/audio/L02.wai b/wai/staged-measure-conformance/fixtures/audio/L02.wai index 4d2689fbe..d2420331a 100644 Binary files a/wai/staged-measure-conformance/fixtures/audio/L02.wai and b/wai/staged-measure-conformance/fixtures/audio/L02.wai differ diff --git a/wai/staged-measure-conformance/fixtures/audio/L03.wai b/wai/staged-measure-conformance/fixtures/audio/L03.wai index a12b9c1f2..11580d9ec 100644 Binary files a/wai/staged-measure-conformance/fixtures/audio/L03.wai and b/wai/staged-measure-conformance/fixtures/audio/L03.wai differ diff --git a/wai/staged-measure-conformance/fixtures/audio/R0_00.wai b/wai/staged-measure-conformance/fixtures/audio/R0_00.wai index e615fa00c..bc22f256b 100644 Binary files a/wai/staged-measure-conformance/fixtures/audio/R0_00.wai and b/wai/staged-measure-conformance/fixtures/audio/R0_00.wai differ diff --git a/wai/staged-measure-conformance/fixtures/audio/R1_00.wai b/wai/staged-measure-conformance/fixtures/audio/R1_00.wai index b52afac83..b27501ea0 100644 Binary files a/wai/staged-measure-conformance/fixtures/audio/R1_00.wai and b/wai/staged-measure-conformance/fixtures/audio/R1_00.wai differ diff --git a/wai/staged-measure-conformance/fixtures/audio/expect.json b/wai/staged-measure-conformance/fixtures/audio/expect.json index c27725d72..d65d1e85e 100644 --- a/wai/staged-measure-conformance/fixtures/audio/expect.json +++ b/wai/staged-measure-conformance/fixtures/audio/expect.json @@ -5,35 +5,35 @@ { "unit": 0, "layers": 1, - "out_sha256": "b8ff83a442c112661ec7a41d6e51cb32559510a699a0e9bb37fa301f8952978a" + "out_sha256": "4b0344c87c67fc30b7d977ae2077fc310822199598e5d2f9f8fc06314b3a2875" }, { "unit": 0, "layers": 2, - "out_sha256": "7cf4ee1daabc1f8779252bb3e02f16ba2ab2d8a65b374c8cbb1c3c3354121cdd" + "out_sha256": "f6d16fa64e91ca80f593b2f598680e4e95af1b0b982e88f7cf1187fadf7ede85" }, { "unit": 0, "layers": 3, - "out_sha256": "02c3e3540210f1e5fce2b9f75d38e74d9b877cf17f2233b7825a8a10b823c64c" + "out_sha256": "5f2b20307a79713c3c8975e3cfbc5a2fa9accc3bd651f570078dfd51a808def9" }, { "unit": 0, "layers": 4, - "out_sha256": "8405e88c07e18cd93aecd578493cb3ff060c98be1790ebca0edeb0d1af1c512a" + "out_sha256": "5acb4b0f4839b79f10506e5c2d17637b5895a94bc4b46694a4bcee49c192b095" } ], "references": [ { "label": "one layer, q 1", "out_sha256": [ - "8405e88c07e18cd93aecd578493cb3ff060c98be1790ebca0edeb0d1af1c512a" + "5acb4b0f4839b79f10506e5c2d17637b5895a94bc4b46694a4bcee49c192b095" ] }, { "label": "one layer, q 8", "out_sha256": [ - "9b34a3dcbc301e180a7a675b3304ed57ddee43858863e113a55455a926d614e8" + "676f2597f1e3106b56cfb6798771abb7dc6b06733ee9d1f95e5dcf627b67525b" ] } ] diff --git a/wai/staged-measure-conformance/fixtures/audio/source.bin b/wai/staged-measure-conformance/fixtures/audio/source.bin index e536cf120..a235262ec 100644 Binary files a/wai/staged-measure-conformance/fixtures/audio/source.bin and b/wai/staged-measure-conformance/fixtures/audio/source.bin differ diff --git a/wai/staged-measure-conformance/fixtures/audio/title.json b/wai/staged-measure-conformance/fixtures/audio/title.json index 17f511284..ab72302fb 100644 --- a/wai/staged-measure-conformance/fixtures/audio/title.json +++ b/wai/staged-measure-conformance/fixtures/audio/title.json @@ -18,7 +18,7 @@ ], "source": { "file": "source.bin", - "sha256": "8405e88c07e18cd93aecd578493cb3ff060c98be1790ebca0edeb0d1af1c512a" + "sha256": "5acb4b0f4839b79f10506e5c2d17637b5895a94bc4b46694a4bcee49c192b095" }, "references": [ { diff --git a/wai/staged-measure-conformance/goldens.json b/wai/staged-measure-conformance/goldens.json index 0334219ec..e5d630f33 100644 --- a/wai/staged-measure-conformance/goldens.json +++ b/wai/staged-measure-conformance/goldens.json @@ -4,15 +4,15 @@ "fixtures": [ { "title": "audio", - "content_id": "dd64e04d02388d970bf07a6759ea97f073e2329abcb03acb910d5d0a25ac8462", + "content_id": "b07e34629aab67de9a625cc04b3662cbb730bcaf1312190f449078969da79676", "framing": "registered", "unit_class": "pcm16", "unit_us": 512000, "bases": [ { - "object": "d45db2bc98dd38873553beb15fdb026a4e6b7fd7c8c7dbe4f4a0b6fcc0709a54", + "object": "751a7ca553e0dbf039f5bd98be79af34baf436e37afc1a1765fd9eedbfe20660", "capability": "wai.audio.int_codec", - "pin": "98b382c92112660a7d3909f07da831125fcdb75a548aa0cf64ad17a0901bd9ef", + "pin": "132958122b251869ad6725d24a2bb96e2733daaf3e09ef65ebc7af6f46030855", "tier": "decode-equivalence", "first_unit": 0, "capture_us": null, @@ -23,7 +23,7 @@ "objects": [ { "file": "staged-conformance/cases/audio_int_codec_1l/base.wai", - "object": "d45db2bc98dd38873553beb15fdb026a4e6b7fd7c8c7dbe4f4a0b6fcc0709a54", + "object": "751a7ca553e0dbf039f5bd98be79af34baf436e37afc1a1765fd9eedbfe20660", "capability": "wai.audio.int_codec", "base": 0, "layer": 0, @@ -34,13 +34,13 @@ "counted": { "header": 12, "manifest": 338, - "payload": 1444, - "total": 1794 + "payload": 2648, + "total": 2998 } }, { "file": "staged-measure-conformance/fixtures/audio/L01.wai", - "object": "14c630b04c617542d3df5a40b398c2fa5c960d6020f84a961046ff5f7831dc69", + "object": "a57b31df6e0982f99d2dce2b71d1c840a054ac77044f3eda42e6239abb13b6ee", "capability": "wai.audio.int_refine", "base": 0, "layer": 1, @@ -57,7 +57,7 @@ }, { "file": "staged-measure-conformance/fixtures/audio/L02.wai", - "object": "4f935f7fade9bd09ba65d65fca37083e18b57c6251c4f1ded91d7bab2c1640b0", + "object": "149cf3636040d0aa2df34c5a0a841d7789320e7cad2801609c50a2bd13c4b50a", "capability": "wai.audio.int_refine", "base": 0, "layer": 2, @@ -74,7 +74,7 @@ }, { "file": "staged-measure-conformance/fixtures/audio/L03.wai", - "object": "ed29fe08a0e7aafba5c71016408cbab959ddcda07914ee0ca08cc58f2d7cf549", + "object": "042b901f77c6510cbc27bf8d1370762d26eac51b2cb705949f4ffdfee923bacf", "capability": "wai.audio.int_refine", "base": 0, "layer": 3, @@ -90,16 +90,16 @@ } } ], - "bfpf_min": 1794, + "bfpf_min": 2998, "prefixes": [ { "layers": 1, - "stage_counted": 1794, - "counted": 1794, + "stage_counted": 2998, + "counted": 2998, "per_base": [ { - "stage_counted": 1794, - "counted": 1794 + "stage_counted": 2998, + "counted": 2998 } ], "units": 1, @@ -109,59 +109,59 @@ { "layers": 2, "stage_counted": 10351, - "counted": 12145, + "counted": 13349, "per_base": [ { "stage_counted": 10351, - "counted": 12145 + "counted": 13349 } ], "units": 1, "vs_source": { "kind": "pcm16", "n_samples": 16384, - "sse": "5600413", - "ref_energy": "77076760253" + "sse": "5600292", + "ref_energy": "2886011628499" }, "vs_final": { "kind": "pcm16", "n_samples": 16384, - "sse": "5600413", - "ref_energy": "77076760253" + "sse": "5600292", + "ref_energy": "2886011628499" } }, { "layers": 3, "stage_counted": 6859, - "counted": 19004, + "counted": 20208, "per_base": [ { "stage_counted": 6859, - "counted": 19004 + "counted": 20208 } ], "units": 1, "vs_source": { "kind": "pcm16", "n_samples": 16384, - "sse": "89997", - "ref_energy": "77076760253" + "sse": "89988", + "ref_energy": "2886011628499" }, "vs_final": { "kind": "pcm16", "n_samples": 16384, - "sse": "89997", - "ref_energy": "77076760253" + "sse": "89988", + "ref_energy": "2886011628499" } }, { "layers": 4, "stage_counted": 6831, - "counted": 25835, + "counted": 27039, "per_base": [ { "stage_counted": 6831, - "counted": 25835 + "counted": 27039 } ], "units": 1, @@ -169,36 +169,36 @@ "kind": "pcm16", "n_samples": 16384, "sse": "0", - "ref_energy": "77076760253" + "ref_energy": "2886011628499" }, "vs_final": { "kind": "pcm16", "n_samples": 16384, "sse": "0", - "ref_energy": "77076760253" + "ref_energy": "2886011628499" } } ], "decoders": [ { "capability": "wai.audio.int_codec", - "pin": "98b382c92112660a7d3909f07da831125fcdb75a548aa0cf64ad17a0901bd9ef", - "object": "d45db2bc98dd38873553beb15fdb026a4e6b7fd7c8c7dbe4f4a0b6fcc0709a54" + "pin": "132958122b251869ad6725d24a2bb96e2733daaf3e09ef65ebc7af6f46030855", + "object": "751a7ca553e0dbf039f5bd98be79af34baf436e37afc1a1765fd9eedbfe20660" }, { "capability": "wai.audio.int_refine", "pin": null, - "object": "14c630b04c617542d3df5a40b398c2fa5c960d6020f84a961046ff5f7831dc69" + "object": "a57b31df6e0982f99d2dce2b71d1c840a054ac77044f3eda42e6239abb13b6ee" }, { "capability": "wai.audio.int_refine", "pin": null, - "object": "4f935f7fade9bd09ba65d65fca37083e18b57c6251c4f1ded91d7bab2c1640b0" + "object": "149cf3636040d0aa2df34c5a0a841d7789320e7cad2801609c50a2bd13c4b50a" }, { "capability": "wai.audio.int_refine", "pin": null, - "object": "ed29fe08a0e7aafba5c71016408cbab959ddcda07914ee0ca08cc58f2d7cf549" + "object": "042b901f77c6510cbc27bf8d1370762d26eac51b2cb705949f4ffdfee923bacf" } ], "presentations": [ @@ -211,7 +211,7 @@ 1, 1 ], - "out_sha256": "b8ff83a442c112661ec7a41d6e51cb32559510a699a0e9bb37fa301f8952978a", + "out_sha256": "4b0344c87c67fc30b7d977ae2077fc310822199598e5d2f9f8fc06314b3a2875", "vs_source": null, "vs_final": null }, @@ -224,18 +224,18 @@ 1, 1 ], - "out_sha256": "7cf4ee1daabc1f8779252bb3e02f16ba2ab2d8a65b374c8cbb1c3c3354121cdd", + "out_sha256": "f6d16fa64e91ca80f593b2f598680e4e95af1b0b982e88f7cf1187fadf7ede85", "vs_source": { "kind": "pcm16", "n_samples": 16384, - "sse": "5600413", - "ref_energy": "77076760253" + "sse": "5600292", + "ref_energy": "2886011628499" }, "vs_final": { "kind": "pcm16", "n_samples": 16384, - "sse": "5600413", - "ref_energy": "77076760253" + "sse": "5600292", + "ref_energy": "2886011628499" } }, { @@ -247,18 +247,18 @@ 1, 1 ], - "out_sha256": "02c3e3540210f1e5fce2b9f75d38e74d9b877cf17f2233b7825a8a10b823c64c", + "out_sha256": "5f2b20307a79713c3c8975e3cfbc5a2fa9accc3bd651f570078dfd51a808def9", "vs_source": { "kind": "pcm16", "n_samples": 16384, - "sse": "89997", - "ref_energy": "77076760253" + "sse": "89988", + "ref_energy": "2886011628499" }, "vs_final": { "kind": "pcm16", "n_samples": 16384, - "sse": "89997", - "ref_energy": "77076760253" + "sse": "89988", + "ref_energy": "2886011628499" } }, { @@ -270,69 +270,69 @@ 1, 1 ], - "out_sha256": "8405e88c07e18cd93aecd578493cb3ff060c98be1790ebca0edeb0d1af1c512a", + "out_sha256": "5acb4b0f4839b79f10506e5c2d17637b5895a94bc4b46694a4bcee49c192b095", "vs_source": { "kind": "pcm16", "n_samples": 16384, "sse": "0", - "ref_energy": "77076760253" + "ref_energy": "2886011628499" }, "vs_final": { "kind": "pcm16", "n_samples": 16384, "sse": "0", - "ref_energy": "77076760253" + "ref_energy": "2886011628499" } } ], - "output_root": "b2241df38a1249d823c0896c8570826b1ee9417718cd776fa8fff50ad6709d43", + "output_root": "a58dad8b2bb96f08080e1f6705ebff84d097e1e992d387fa8d76615518487a28", "leaf_count": 4, "overhead": [ { "label": "staged", - "counted": 25835, + "counted": 27039, "vs_source": { "kind": "pcm16", "n_samples": 16384, "sse": "0", - "ref_energy": "77076760253" + "ref_energy": "2886011628499" }, "out_sha256": [ - "8405e88c07e18cd93aecd578493cb3ff060c98be1790ebca0edeb0d1af1c512a" + "5acb4b0f4839b79f10506e5c2d17637b5895a94bc4b46694a4bcee49c192b095" ] }, { "label": "one layer, q 1", - "counted": 32145, + "counted": 33349, "vs_source": { "kind": "pcm16", "n_samples": 16384, "sse": "0", - "ref_energy": "77076760253" + "ref_energy": "2886011628499" }, "out_sha256": [ - "8405e88c07e18cd93aecd578493cb3ff060c98be1790ebca0edeb0d1af1c512a" + "5acb4b0f4839b79f10506e5c2d17637b5895a94bc4b46694a4bcee49c192b095" ] }, { "label": "one layer, q 8", - "counted": 19137, + "counted": 20341, "vs_source": { "kind": "pcm16", "n_samples": 16384, - "sse": "89997", - "ref_energy": "77076760253" + "sse": "89989", + "ref_energy": "2886011628499" }, "out_sha256": [ - "9b34a3dcbc301e180a7a675b3304ed57ddee43858863e113a55455a926d614e8" + "676f2597f1e3106b56cfb6798771abb7dc6b06733ee9d1f95e5dcf627b67525b" ] } ], "dedup": { "objects": 8, "unique": 6, - "referenced_bytes": 77117, - "stored_bytes": 73529 + "referenced_bytes": 80729, + "stored_bytes": 74733 } }, { @@ -1815,8 +1815,8 @@ "dedup": { "objects": 46, "unique": 32, - "referenced_bytes": 129930, - "stored_bytes": 112383 + "referenced_bytes": 133542, + "stored_bytes": 113587 } } } diff --git a/wai/staged-measure-conformance/sim/results.json b/wai/staged-measure-conformance/sim/results.json index 0ea784788..486d84bc8 100644 --- a/wai/staged-measure-conformance/sim/results.json +++ b/wai/staged-measure-conformance/sim/results.json @@ -5606,25 +5606,25 @@ { "id": "base.wai", "sent_first_us": 20000, - "arrived_us": 40718, + "arrived_us": 41200, "state": "delivered" }, { "id": "L01.wai", - "sent_first_us": 20718, - "arrived_us": 44859, + "sent_first_us": 21200, + "arrived_us": 45341, "state": "delivered" }, { "id": "L02.wai", - "sent_first_us": 24859, - "arrived_us": 47603, + "sent_first_us": 25341, + "arrived_us": 48085, "state": "delivered" }, { "id": "L03.wai", - "sent_first_us": 27603, - "arrived_us": 50336, + "sent_first_us": 28085, + "arrived_us": 50818, "state": "delivered" } ], @@ -5632,34 +5632,34 @@ [ 0, 1, - 44718 + 45200 ], [ 0, 2, - 46859 + 47341 ], [ 0, 3, - 49603 + 50085 ], [ 0, 4, - 52336 + 52818 ] ], - "bfpf_sched": 11394, - "tfpf_us": 44718, + "bfpf_sched": 12598, + "tfpf_us": 45200, "tffs_us": [ - 44718, - 46859, - 49603, - 52336 + 45200, + 47341, + 50085, + 52818 ], - "offered": 25835, - "delivered": 25835, + "offered": 27039, + "delivered": 27039, "dropped": 0, "abandoned": 0, "never_sent_capped": 0, @@ -5681,12 +5681,12 @@ { "id": "base.wai", "sent_first_us": 20000, - "arrived_us": 44485, + "arrived_us": 47495, "state": "delivered" }, { "id": "L01.wai", - "sent_first_us": 24485, + "sent_first_us": 27495, "arrived_us": null, "state": "abandoned" }, @@ -5707,21 +5707,21 @@ [ 0, 1, - 48485 + 51495 ] ], - "bfpf_sched": 2994, - "tfpf_us": 48485, + "bfpf_sched": 4198, + "tfpf_us": 51495, "tffs_us": [ - 48485, + 51495, null, null, null ], - "offered": 25835, - "delivered": 1794, - "dropped": 13690, - "abandoned": 10351, + "offered": 27039, + "delivered": 2998, + "dropped": 14441, + "abandoned": 9600, "never_sent_capped": 0, "missed": 0, "stall_us": [ @@ -5741,13 +5741,13 @@ { "id": "base.wai", "sent_first_us": 20000, - "arrived_us": 44485, + "arrived_us": 47495, "state": "delivered" }, { "id": "L01.wai", - "sent_first_us": 24485, - "arrived_us": 70363, + "sent_first_us": 27495, + "arrived_us": 73373, "state": "delivered" }, { @@ -5767,24 +5767,24 @@ [ 0, 1, - 48485 + 51495 ], [ 0, 2, - 72363 + 75373 ] ], - "bfpf_sched": 1794, - "tfpf_us": 48485, + "bfpf_sched": 2998, + "tfpf_us": 51495, "tffs_us": [ - 48485, - 72363, + 51495, + 75373, null, null ], - "offered": 25835, - "delivered": 12145, + "offered": 27039, + "delivered": 13349, "dropped": 13690, "abandoned": 0, "never_sent_capped": 0, @@ -5806,25 +5806,25 @@ { "id": "base.wai", "sent_first_us": 20000, - "arrived_us": 40718, + "arrived_us": 41200, "state": "delivered" }, { "id": "L01.wai", - "sent_first_us": 20718, - "arrived_us": 44859, + "sent_first_us": 21200, + "arrived_us": 45341, "state": "delivered" }, { "id": "L02.wai", - "sent_first_us": 24859, - "arrived_us": 47603, + "sent_first_us": 25341, + "arrived_us": 48085, "state": "delivered" }, { "id": "L03.wai", - "sent_first_us": 27603, - "arrived_us": 50336, + "sent_first_us": 28085, + "arrived_us": 50818, "state": "delivered" } ], @@ -5832,34 +5832,34 @@ [ 0, 1, - 44718 + 45200 ], [ 0, 2, - 46859 + 47341 ], [ 0, 3, - 49603 + 50085 ], [ 0, 4, - 52336 + 52818 ] ], - "bfpf_sched": 11394, - "tfpf_us": 44718, + "bfpf_sched": 12598, + "tfpf_us": 45200, "tffs_us": [ - 44718, - 46859, - 49603, - 52336 + 45200, + 47341, + 50085, + 52818 ], - "offered": 25835, - "delivered": 25835, + "offered": 27039, + "delivered": 27039, "dropped": 0, "abandoned": 0, "never_sent_capped": 0, diff --git a/wai/telemetry-conformance/README.md b/wai/telemetry-conformance/README.md index fcd8c8e23..6c09c2cfa 100644 --- a/wai/telemetry-conformance/README.md +++ b/wai/telemetry-conformance/README.md @@ -45,5 +45,5 @@ their metrics. Every value here is a string or an integer, the same on every mac readers and requires the members; and validates every report, and the Event-mode reports together, requiring no error. -The content is synthetic: session ids, content hashes and timestamps are fixed values -chosen for the corpus. +**Provenance.** Generated here: session ids, content hashes and timestamps are fixed +values chosen for the corpus. No recorded traffic. diff --git a/wai/tools/wai_audio_provenance_check.py b/wai/tools/wai_audio_provenance_check.py new file mode 100644 index 000000000..b220f746d --- /dev/null +++ b/wai/tools/wai_audio_provenance_check.py @@ -0,0 +1,482 @@ +#!/usr/bin/env python3 +"""Fail if a committed audio vector is a coding of a recording in a reference set. + +Every audio vector in this tree must come from a seeded generator. This check finds +the ones that do not, by content: it decodes or reads each committed audio vector to +PCM and computes its normalised cross-correlation, at every lag, against every +recording in a reference set the host has. A vector that correlates above the +threshold (0.5 by default) with any reference fails the check. A generated signal +correlates with an unrelated recording at a few hundredths; a lossy coding of a +recording correlates with it near 1. + + python3 tools/wai_audio_provenance_check.py [--wai DIR] [--threshold T] + [--ref DIR]... [--pcm PATH:RATE[:CH]]... + +Run from `wai/` (or pass `--wai`). Python standard library only. + +**The reference set** is every audio file under each `--ref` directory, under each +directory in `WAI_AUDIO_REFERENCE_DIRS` (separated by the platform's path separator), +and under the host's system sound directory (`/System/Library/Sounds`) when it exists. +Each reference is converted to mono PCM at the analysis rate by the host's audio +converter (`afconvert`, else `ffmpeg`); raw `.s16` references are read as 16 kHz mono. +When the set is empty, or no converter can read any of it, the check prints why and +exits 0: it is skipped, not passed. + +**The committed audio vectors** are found by rule, not listed, so a new one is +covered when it lands: + +- each base of a staged-delivery case (`staged-conformance/cases/*/case.json`) that + decodes through a `wai.audio.*` capability: its stage 0 (`stage0.bin`, the decoded + PCM, i16 interleaved), at the sample rate its payload states (a `WIA1` header's + `sr`, a FLAC stream's STREAMINFO); +- each staged-measurement title over such a base + (`staged-measure-conformance/fixtures/*/title.json`): its source (`source.bin`), at + the base's rate scaled by the source's length over the base's; +- each spatial-audio source (`audio-conformance/vectors/*.sources.bin`), at the rate + its scene (`.wscn`) states; +- every file with an audio extension (`.wav`, `.aif`, `.aiff`, `.flac`, `.s16`, …) + anywhere under `wai/`; +- and every `--pcm PATH:RATE[:CH]` given (raw s16le, CH channels interleaved). + +A signal shorter than a quarter of a second at the analysis rate, or silent, is listed +and not judged: over so few samples a generated tone can match a recorded one by +chance (a 50 ms pair of triangle waves reaches 0.5 against a low recorded tone). + +A learned-codec vector (`wai-rs/tests/vectors/*/audio_model.bin` beside a +`sha256.json`) is covered through its decode: the check requires that the decoded +PCM's SHA-256 its sidecar states is the stage 0 of a staged-delivery case above, so +the PCM it scans is that vector's output. A codec vector with no such case fails. + +Exit status: 0 when every vector is under the threshold or the check is skipped; +1 when a vector correlates above it, or a codec vector's decode is not covered. +""" +import argparse +import cmath +import glob +import hashlib +import json +import math +import os +import re +import shutil +import struct +import subprocess +import sys +import tempfile +import wave + +ANALYSIS_RATE = 8000 # Hz: every signal is compared here, mono +SYSTEM_SOUNDS = '/System/Library/Sounds' +AUDIO_EXT = {'.wav', '.wave', '.aif', '.aiff', '.aifc', '.flac', '.mp3', '.ogg', '.opus', + '.m4a', '.caf', '.s16', '.pcm'} +SKIP_DIRS = {'.git', 'target', 'node_modules', 'jpeg-ai-ref', '__pycache__'} +MIN_SAMPLES = ANALYSIS_RATE // 4 # a quarter second: a shorter signal is listed, not judged + + +# ---- PCM helpers -------------------------------------------------------------------- +def i16le(b): + n = len(b) // 2 + return list(struct.unpack(f'<{n}h', b[:2 * n])) + + +def channels(pcm, ch): + return [pcm[c::ch] for c in range(ch)] + + +def lowpass_taps(cut, n=63): + """A Hann-windowed sinc low-pass, cut-off `cut` in cycles per input sample.""" + m = n // 2 + taps = [] + for k in range(n): + x = k - m + s = 2 * cut if x == 0 else math.sin(2 * math.pi * cut * x) / (math.pi * x) + taps.append(s * (0.5 - 0.5 * math.cos(2 * math.pi * k / (n - 1)))) + g = sum(taps) + return [t / g for t in taps] + + +def resample(x, rate): + """Resample `x` from `rate` to the analysis rate: low-pass, then linear interpolation.""" + if rate == ANALYSIS_RATE or not x: + return [float(v) for v in x] + if rate > ANALYSIS_RATE: + taps = lowpass_taps(0.45 * ANALYSIS_RATE / rate) + m = len(taps) // 2 + n = len(x) + y = [] + for i in range(n): + acc = 0.0 + lo, hi = max(0, i - m), min(n, i + m + 1) + for j in range(lo, hi): + acc += taps[j - i + m] * x[j] + y.append(acc) + else: + y = [float(v) for v in x] + step = rate / ANALYSIS_RATE + out, t = [], 0.0 + while t <= len(y) - 1: + i = int(t) + f = t - i + out.append(y[i] if f == 0 or i + 1 >= len(y) else y[i] * (1 - f) + y[i + 1] * f) + t += step + return out + + +# ---- correlation ---------------------------------------------------------------------- +_TW = {} + + +def fft(a, inverse=False): + """In-place iterative radix-2 FFT of a list of complex numbers (len a power of 2).""" + n = len(a) + j = 0 + for i in range(1, n): + bit = n >> 1 + while j & bit: + j ^= bit + bit >>= 1 + j |= bit + if i < j: + a[i], a[j] = a[j], a[i] + key = (n, inverse) + if key not in _TW: + sgn = 1 if inverse else -1 + _TW[key] = [cmath.exp(sgn * 2j * math.pi * k / n) for k in range(n // 2)] + tw = _TW[key] + size = 2 + while size <= n: + half = size // 2 + stride = n // size + for start in range(0, n, size): + for k in range(half): + u = a[start + k] + v = a[start + k + half] * tw[k * stride] + a[start + k] = u + v + a[start + k + half] = u - v + size *= 2 + if inverse: + for i in range(n): + a[i] /= n + return a + + +def spectrum(x, n): + return fft([complex(v) for v in x] + [0j] * (n - len(x))) + + +def prefix_sq(x): + p = [0.0] + for v in x: + p.append(p[-1] + v * v) + return p + + +def best_ncc(xa, fa, pa, xb, fb, pb, n): + """max over lags of |Σ a[i]·b[i+lag]| / sqrt(Σ a² · Σ b²), both sums over the + overlap, among lags whose overlap is at least half the shorter signal.""" + la, lb = len(xa), len(xb) + prod = [fa[k].conjugate() * fb[k] for k in range(n)] + c = fft(prod, inverse=True) # c[lag mod n] = Σ a[i] b[i+lag] + need = max(1, min(la, lb) // 2) + best, at = 0.0, 0 + for lag in range(-(la - 1), lb): + i0, i1 = max(0, -lag), min(la, lb - lag) # a's overlapping range + if i1 - i0 < need: + continue + ea = pa[i1] - pa[i0] + eb = pb[i1 + lag] - pb[i0 + lag] + if ea <= 0 or eb <= 0: + continue + r = abs(c[lag % n].real) / math.sqrt(ea * eb) + if r > best: + best, at = r, lag + return best, at + + +# ---- the reference set -------------------------------------------------------------- +def converter(): + if shutil.which('afconvert'): + return 'afconvert' + if shutil.which('ffmpeg'): + return 'ffmpeg' + return None + + +def read_wav(path): + """(samples per channel list, rate) for PCM 8/16/24/32-bit or float32 WAV; raises on + anything else.""" + with open(path, 'rb') as f: + b = f.read() + if b[:4] != b'RIFF' or b[8:12] != b'WAVE': + raise ValueError('not RIFF/WAVE') + pos, fmt, data = 12, None, None + while pos + 8 <= len(b): + cid, size = b[pos:pos + 4], struct.unpack('= 16: + fmt = struct.unpack(' 64 or rate < 1: + raise ValueError(f'{ch} channels at {rate} Hz') + if tag == 1 and bits == 16: + x = i16le(data) + elif tag == 1 and bits == 8: + x = [(v - 128) * 256 for v in data] + elif tag == 1 and bits == 24: + x = [int.from_bytes(data[i:i + 3], 'little', signed=True) >> 8 for i in range(0, len(data) - 2, 3)] + elif tag == 1 and bits == 32: + x = [v >> 16 for v in struct.unpack(f'<{len(data) // 4}i', data[:len(data) // 4 * 4])] + elif tag == 3 and bits == 32: + x = [v * 32767 for v in struct.unpack(f'<{len(data) // 4}f', data[:len(data) // 4 * 4])] + else: + raise ValueError(f'format tag {tag}, {bits} bits') + x = x[:len(x) // ch * ch] + return channels(x, ch), rate + + +def convert(conv, src, tmp): + """Mono s16 WAV at the analysis rate, via the host's converter.""" + out = os.path.join(tmp, hashlib.sha256(src.encode()).hexdigest()[:16] + '.wav') + if conv == 'afconvert': + cmd = ['afconvert', '-f', 'WAVE', '-d', f'LEI16@{ANALYSIS_RATE}', '-c', '1', src, out] + else: + cmd = ['ffmpeg', '-v', 'error', '-y', '-i', src, '-ac', '1', '-ar', str(ANALYSIS_RATE), + '-c:a', 'pcm_s16le', out] + subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE) + chans, rate = read_wav(out) + return resample(chans[0], rate) + + +def references(dirs, conv, tmp): + refs, notes = [], [] + for d in dirs: + for root, _, files in os.walk(d): + for f in sorted(files): + p = os.path.join(root, f) + ext = os.path.splitext(f)[1].lower() + if ext not in AUDIO_EXT: + continue + try: + if ext == '.s16': + with open(p, 'rb') as fh: + x = resample(i16le(fh.read()), 16000) + elif conv: + x = convert(conv, p, tmp) + else: + chans, rate = read_wav(p) + x = resample(chans[0], rate) + except Exception as e: # noqa: BLE001 — an unreadable reference is reported + notes.append(f'reference unreadable, skipped: {p} ({e})') + continue + refs.append((p, x)) + return refs, notes + + +# ---- the committed audio vectors ---------------------------------------------------- +def envelope_payload(b): + """The payload of a WAI1 envelope: magic, u32 manifest length, manifest, then the + payload (a u32 length, or to the end).""" + if b[:4] != b'WAI1': + return None + mlen = struct.unpack('= 4: + plen = struct.unpack('= 12: + return struct.unpack('= 0 and len(p) >= i + 8 + 13: + si = p[i + 8:i + 8 + 34] # STREAMINFO body + return (si[10] << 12) | (si[11] << 4) | (si[12] >> 4) + return None + + +def staged_bases(wai): + """(case dir, base index) → (pcm, channels, rate, stage0 sha256) for audio bases.""" + out = {} + for cj in sorted(glob.glob(os.path.join(wai, 'staged-conformance/cases/*/case.json'))): + case = json.load(open(cj)) + d = os.path.dirname(cj) + for bi, base in enumerate(case.get('bases', [])): + if not str(base.get('decoded_via', '')).startswith('wai.audio.'): + continue + units = base.get('units') or [{}] + shape = units[0].get('shape') or [0, 1, 1] + ch = int(shape[2]) if len(shape) > 2 else 1 + with open(os.path.join(d, base['stage0']), 'rb') as f: + s0 = f.read() + with open(os.path.join(d, base['file']), 'rb') as f: + rate = payload_rate(envelope_payload(f.read())) + out[(os.path.relpath(d, wai), bi)] = (i16le(s0), ch, rate, hashlib.sha256(s0).hexdigest()) + return out + + +def vectors(wai, extra): + """[(name, [channel samples], rate)], notes, failures.""" + vs, notes, fails = [], [], [] + bases = staged_bases(wai) + for (case, bi), (pcm, ch, rate, _) in bases.items(): + name = f'{case}/stage0 (base {bi})' + if not rate: + fails.append(f'{name}: no sample rate in its payload') + continue + vs.append((name, channels(pcm, ch), rate)) + + for tj in sorted(glob.glob(os.path.join(wai, 'staged-measure-conformance/fixtures/*/title.json'))): + t = json.load(open(tj)) + for b in t.get('bases', []): + key = (b.get('case'), b.get('base', 0)) + if key not in bases: + continue + pcm, ch, rate, _ = bases[key] + src = os.path.join(os.path.dirname(tj), t['source']['file']) + with open(src, 'rb') as f: + x = i16le(f.read()) + if not rate: + continue + r = round(rate * len(x) / len(pcm)) + vs.append((os.path.relpath(src, wai), channels(x, ch), r)) + + for sb in sorted(glob.glob(os.path.join(wai, 'audio-conformance/vectors/*.sources.bin'))): + with open(sb, 'rb') as f: + b = f.read() + scene = sb[:-len('.sources.bin')] + '.wscn' + m = re.search(rb'"sample_rate":(\d+)', open(scene, 'rb').read()) if os.path.exists(scene) else None + rate = int(m.group(1)) if m else 48000 + n, pos = struct.unpack(' 2 else 1 + with open(p, 'rb') as f: + vs.append((p, channels(i16le(f.read()), ch), rate)) + + # A learned codec vector is covered by its decode, which must be a case's stage 0. + stage0 = {sha for (_, _, _, sha) in bases.values()} + for mb in sorted(glob.glob(os.path.join(wai, 'wai-rs/tests/vectors/*/audio_model.bin'))): + side = os.path.join(os.path.dirname(mb), 'sha256.json') + if not os.path.exists(side): + fails.append(f'{os.path.relpath(mb, wai)}: no sha256.json sidecar to tie its decode to a scanned PCM') + continue + sha = json.load(open(side)).get('audio', {}).get('sha256') + if sha not in stage0: + fails.append(f'{os.path.relpath(os.path.dirname(mb), wai)}: its decoded PCM (sha256 {sha}) is no ' + 'staged-delivery case\'s stage 0, so it is not scanned; add a case that decodes it') + else: + notes.append(f'{os.path.relpath(os.path.dirname(mb), wai)}: decode covered as a staged case\'s stage 0') + return vs, notes, fails + + +def main(): + ap = argparse.ArgumentParser(description=__doc__.split('\n')[0]) + ap.add_argument('--wai', default='.', help='the wai/ directory (default: .)') + ap.add_argument('--threshold', type=float, default=0.5) + ap.add_argument('--ref', action='append', default=[], help='a reference directory (repeatable)') + ap.add_argument('--pcm', action='append', default=[], help='PATH:RATE[:CH], raw s16le (repeatable)') + a = ap.parse_args() + wai = os.path.abspath(a.wai) + + dirs = list(a.ref) + env = os.environ.get('WAI_AUDIO_REFERENCE_DIRS', '') + dirs += [d for d in env.split(os.pathsep) if d] + if os.path.isdir(SYSTEM_SOUNDS): + dirs.append(SYSTEM_SOUNDS) + dirs = [d for d in dirs if os.path.isdir(d)] + if not dirs: + print(f'audio provenance: SKIPPED — no reference set on this host ({SYSTEM_SOUNDS} is absent and ' + 'no --ref or WAI_AUDIO_REFERENCE_DIRS directory was given)') + return 0 + conv = converter() + with tempfile.TemporaryDirectory() as tmp: + refs, rnotes = references(dirs, conv, tmp) + for n in rnotes: + print(f' note: {n}') + if not refs: + print(f'audio provenance: SKIPPED — the reference set ({", ".join(dirs)}) has no file this host can ' + f'read (converter: {conv or "none; install afconvert or ffmpeg"})') + return 0 + + vs, notes, fails = vectors(wai, a.pcm) + print(f'audio provenance: {len(vs)} committed audio signals against {len(refs)} references ' + f'({", ".join(dirs)}), at {ANALYSIS_RATE} Hz, threshold {a.threshold}') + for n in notes: + print(f' note: {n}') + + rs = [(path, x, prefix_sq(x)) for path, x in refs] + ref_spec = {} # (reference, n) → its spectrum + over = [] + for name, chans, rate in vs: + for ci, c in enumerate(chans): + x = resample(c, rate) + label = name + (f' ch{ci}' if len(chans) > 1 else '') + if len(x) < MIN_SAMPLES or not any(x): + print(f' {"-":>6} {label} ({len(x)} samples at {ANALYSIS_RATE} Hz' + f'{", silent" if not any(x) else ""}: too short or silent to judge)') + continue + px = prefix_sq(x) + best = (0.0, None, 0) + cache = {} + for path, y, py in rs: + n = 1 << (len(x) + len(y) - 1).bit_length() + if n not in cache: + cache[n] = spectrum(x, n) + if (path, n) not in ref_spec: + ref_spec[(path, n)] = spectrum(y, n) + r, lag = best_ncc(x, cache[n], px, y, ref_spec[(path, n)], py, n) + if r > best[0]: + best = (r, path, lag) + flag = 'FAIL' if best[0] > a.threshold else 'ok' + print(f' {best[0]:6.3f} {label} (rate {rate}; nearest {os.path.basename(best[1] or "-")} ' + f'at lag {best[2]}) {flag}') + if best[0] > a.threshold: + over.append(f'{label}: {best[0]:.3f} with {best[1]}') + for f in fails: + print(f' FAIL: {f}') + for o in over: + print(f' FAIL: {o}') + if over or fails: + print(f'audio provenance: FAILED — {len(over)} signal(s) above {a.threshold}, {len(fails)} uncovered') + return 1 + print('audio provenance: ok — no committed audio vector correlates with a reference above the threshold') + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/wai/tools/wai_int_audio_export.py b/wai/tools/wai_int_audio_export.py index 4ad513c0f..ef7085cbd 100644 --- a/wai/tools/wai_int_audio_export.py +++ b/wai/tools/wai_int_audio_export.py @@ -3,77 +3,302 @@ INTEGER-exact decoder — the audio analog of `int_hyper`, extending "conformance is a hash" to learned audio. -A neural audio codec (SoundStream/EnCodec-class) decodes through a FLOAT network, -so a ~1e-5 round-off makes the reconstructed waveform — and its hash — diverge -between machines: never receiptable. Here the synthesis is a stack of integer -1-D transposed convolutions + integer IGDN (per-out-channel int12 weights, -fixed-point activations, all i64), so the reconstructed PCM is byte-identical on -every machine (NeuralIntegerExact) — *sample-equivalence*, a portable PCM hash. +A neural audio codec decodes through a FLOAT network, so a ~1e-5 round-off makes the +reconstructed waveform — and its hash — diverge between machines: never receiptable. +Here the synthesis is a stack of integer 1-D transposed convolutions + integer IGDN +(per-out-channel int12 weights, fixed-point activations, all i64), so the +reconstructed PCM is byte-identical on every machine (NeuralIntegerExact) — +*sample-equivalence*, a portable PCM hash. + +**The signal is generated here; the tool reads no audio file.** Both the training +signal and the test signal (the clip the vector codes) are written by `signal()` +below from a seed: 16 kHz, mono, signed 16-bit, a sequence of segments, each a tone +(one to three partials with an attack and a release), a chirp (a linear frequency +sweep), a noise burst (white noise, low-passed by a moving average or high-passed by +a difference) or a speech-like formant sweep (a harmonic series on a gliding, +vibrato-modulated pitch, shaped by three formant resonances that move between two +vowel targets), sometimes two overlaid, with short gaps. The generator is integer +arithmetic only — a SplitMix64 counter stream, a 32-bit phase accumulator, a +4096-entry sine table built by Bhaskara I's rational approximation — so a seed gives +the same samples on every machine and every Python. The training signal is stream 0 +of the seed and the test signal stream 1: the codec is trained on one signal and +codes another of the same kind. Pipeline (mirrors wai_sota_int_synth_export, in 1-D): - 1. train g_a/g_s (Conv1d+GDN / ConvTranspose1d+IGDN) on a real clip (float; this - is the encoder, float is fine here — only the DECODE must be integer); - 2. quantize the latent per channel (factorized prior, data-fit CDF + rANS, the - byte-exact match of the Rust RansDecoder); - 3. PTQ g_s → integer (WAM1 binary), run an INTEGER reference decode (numpy, - mirroring the planned Rust i64 arithmetic) → int16 PCM → FNV-1a hash. + 1. generate the training and test signals from the seed; + 2. train g_a/g_s (Conv1d+GDN / ConvTranspose1d+IGDN) on the training signal (float; + this is the encoder, float is fine here — only the DECODE must be integer), with + torch seeded from the same seed, deterministic algorithms and a fixed thread + count, so a run on one host and torch build gives the same weights every time; + 3. quantize the test signal's latent per channel (factorized prior, data-fit CDF + + rANS, the byte-exact match of the Rust RansDecoder); + 4. PTQ g_s → integer (WAM1 binary), run an INTEGER reference decode (numpy, + mirroring the Rust i64 arithmetic) → int16 PCM → FNV-1a hash. - ffmpeg -i .aiff -ac 1 -ar 16000 -f s16le audio.s16 - python tools/wai_int_audio_export.py audio.s16 + python tools/wai_int_audio_export.py [--seed S] [--channels C] + [--samples N] [--steps K] [--train-seconds T] + python tools/wai_int_audio_export.py --signal-only [--seed S] [--stream 0|1] + [--samples N] + +`--signal-only` writes one generated signal as raw s16le and needs nothing beyond the +Python standard library. The committed vector (`wai-rs/tests/vectors/int_audio/`) and +the site's demo clip are both exports of this tool; their commands are in that +directory's README. """ -import sys, os, json, math, base64 -import numpy as np, torch, torch.nn as nn, torch.nn.functional as F, warnings -warnings.filterwarnings('ignore') -from compressai.ans import RansEncoder +import argparse +import base64 +import json +import math +import os +import struct +import sys AFRAC = 12; WBITS = 12; SHIFT = 24; GF = 16 # same fixed-point as int_hyper -SR = 16000; T = 2048; C = 32; K = 5; STR = 2; NLAY = 4 # codec shape (16x downsample) -STEPS = 3500; MAXS = 0 # train steps; MAXS>0 caps #samples (small committed vector) +SR = 16000; T = 2048; K = 5; STR = 2; NLAY = 4 # codec shape (16x downsample) +LR = 1.5e-3; RATE_W = 8e-6 +THREADS = 4 # fixed: CPU reductions depend on it + +# ---- the generated signal (integer arithmetic only) ---------------------------------- +M64 = (1 << 64) - 1 +GOLDEN = 0x9E3779B97F4A7C15 +TAB_BITS = 12 +TAB_N = 1 << TAB_BITS + + +def _mix64(x): + """SplitMix64's finalizer.""" + x &= M64 + x = ((x ^ (x >> 30)) * 0xBF58476D1CE4E5B9) & M64 + x = ((x ^ (x >> 27)) * 0x94D049BB133111EB) & M64 + return x ^ (x >> 31) + + +class Rng: + """A SplitMix64 stream, keyed by (seed, stream).""" + + def __init__(self, seed, stream): + self.s = _mix64(seed * GOLDEN + stream * 0xD1B54A32D192ED03) + + def next(self): + self.s = (self.s + GOLDEN) & M64 + return _mix64(self.s) + + def int(self, lo, hi): + """Uniform integer in [lo, hi].""" + return lo + self.next() % (hi - lo + 1) + + def s16(self): + """Uniform signed 16-bit value.""" + return (self.next() >> 48) - 32768 + + +def _sine_table(): + """sin(2π i / TAB_N) × 32767, by Bhaskara I's approximation, rounded half up: + sin(πp) ≈ 16 p (1 − p) / (5 − 4 p (1 − p)), p = u / half.""" + half = TAB_N // 2 + out = [] + for i in range(TAB_N): + u = i % half + a = u * (half - u) + den = 5 * half * half - 4 * a + v = (2 * 32767 * 16 * a + den) // (2 * den) + out.append(v if i < half else -v) + return out + + +SINE = _sine_table() + + +def _inc(f_q8): + """32-bit phase increment per sample for a frequency in Hz × 256.""" + return (f_q8 << 24) // SR + + +def _osc(phase): + return SINE[(phase & 0xFFFFFFFF) >> (32 - TAB_BITS)] + + +def _envelope(n, attack, release): + """Linear attack and release, Q15, length n.""" + attack, release = max(1, min(attack, n // 2)), max(1, min(release, n // 2)) + return [min(32767, (32767 * (i + 1)) // attack, (32767 * (n - i)) // release) for i in range(n)] + + +def _root64(x): + """2^(x/64) in Q16, floored: the largest y with y^64 ≤ 2^(x + 16·64). Exact.""" + lo, hi = 1 << 16, 1 << 17 + while lo < hi: + mid = (lo + hi + 1) // 2 + if mid ** 64 <= 1 << (x + 1024): + lo = mid + else: + hi = mid - 1 + return lo + + +ROOT64 = [_root64(x) for x in range(64)] + + +def _log_freq(r, lo, hi): + """A frequency in Hz × 256, log-uniform in [lo, hi] Hz: lo · 2^(k/64) for a random + k, drawn again while it is above hi.""" + octaves = 0 + while (lo << octaves) < hi: + octaves += 1 + while True: + o, j = divmod(r.int(0, 64 * octaves), 64) + f = ((lo << o) * ROOT64[j]) >> 8 + if f <= hi << 8: + return f + + +# Formant targets (F1, F2, F3) in Hz for five vowels. +VOWELS = [(730, 1090, 2440), (270, 2290, 3010), (300, 870, 2240), (530, 1840, 2480), (570, 840, 2410)] + + +def _tone(r, n): + out = [0] * n + for _ in range(r.int(1, 3)): + inc, amp, ph = _inc(_log_freq(r, 80, 3500)), r.int(4000, 16000), r.next() & 0xFFFFFFFF + for i in range(n): + out[i] += (amp * _osc(ph)) >> 15 + ph += inc + env = _envelope(n, r.int(SR // 500, SR // 30), r.int(SR // 50, SR // 6)) + return [(v * e) >> 15 for v, e in zip(out, env)] + + +def _chirp(r, n): + i0, i1 = _inc(_log_freq(r, 100, 6000)), _inc(_log_freq(r, 100, 6000)) + amp, ph, out = r.int(8000, 20000), r.next() & 0xFFFFFFFF, [] + for i in range(n): + out.append((amp * _osc(ph)) >> 15) + ph += i0 + (i1 - i0) * i // n + env = _envelope(n, r.int(SR // 500, SR // 50), r.int(SR // 100, SR // 20)) + return [(v * e) >> 15 for v, e in zip(out, env)] + + +def _noise(r, n): + w = [r.s16() for _ in range(n + 16)] + kind = r.int(0, 2) + if kind == 0: # white + x = w[:n] + elif kind == 1: # low-passed: moving average + L = [2, 3, 5, 8, 13][r.int(0, 4)] + x = [sum(w[i:i + L]) // L for i in range(n)] + else: # high-passed: first difference + x = [(w[i + 1] - w[i]) >> 1 for i in range(n)] + env = _envelope(n, r.int(SR // 1000, SR // 200), r.int(SR // 50, SR // 5)) + amp = r.int(4000, 14000) + return [(((v * amp) >> 15) * e) >> 15 for v, e in zip(x, env)] + + +def _formant(r, n): + """A harmonic series on a gliding pitch with vibrato, each harmonic weighted by + three resonances 1 / (1 + ((f − F)/B)^2) whose centres move between two vowels.""" + f0a = r.int(90, 240) << 8 + f0b = (f0a * r.int(80, 125)) // 100 + va, vb = VOWELS[r.int(0, 4)], VOWELS[r.int(0, 4)] + bw = (r.int(60, 110), r.int(90, 160), r.int(120, 220)) + vib_inc, vib_depth = _inc(r.int(4, 7) << 8), r.int(5, 25) # depth in ‰ + nh = max(1, (4800 << 8) // max(f0a, f0b)) + ph0, vph, out = r.next() & 0xFFFFFFFF, r.next() & 0xFFFFFFFF, [] + amps = [0] * nh + for i in range(n): + f0 = f0a + (f0b - f0a) * i // n + f0 += (f0 * vib_depth * _osc(vph)) // (1000 * 32767) + vph += vib_inc + if i % 32 == 0: # the weights at block rate + F = [va[j] + (vb[j] - va[j]) * i // n for j in range(3)] + for k in range(nh): + fk = ((k + 1) * f0) >> 8 + g = 0 + for j in range(3): + d = ((fk - F[j]) << 10) // bw[j] + g += (1 << 20) // (1024 + ((d * d) >> 10)) # Q10 resonance gain + amps[k] = (g * 1024) // (1024 + 256 * k) # spectral tilt + ph0 += _inc(f0) + acc = 0 + for k in range(nh): + acc += amps[k] * _osc((k + 1) * ph0) + out.append(acc >> 15) + env = _envelope(n, r.int(SR // 200, SR // 20), r.int(SR // 50, SR // 10)) + return [(v * e) >> 15 for v, e in zip(out, env)] + + +KINDS = [_tone, _chirp, _noise, _formant] + + +def signal(seed, stream, n): + """n samples of the generated signal (a list of ints in the int16 range).""" + r = Rng(seed, stream) + out = [] + while len(out) < n: + m = r.int(SR * 8 // 100, SR // 2) # a segment: 80–500 ms + seg = KINDS[r.int(0, 3)](r, m) + if r.int(0, 3) == 0: # sometimes a second, quieter kind + over = KINDS[r.int(0, 3)](r, m) + seg = [a + (b >> 2) for a, b in zip(seg, over)] + peak = max(1, max(abs(v) for v in seg)) + target = r.int(6000, 28000) + seg = [(v * target) // peak for v in seg] + out += seg + [0] * r.int(0, SR // 25) # a gap: 0–40 ms + return [max(-32768, min(32767, v)) for v in out[:n]] + + +def s16le(x): + return struct.pack(f'<{len(x)}h', *x) # ---- learned model (float; the encoder) ------------------------------------- -class GDN1d(nn.Module): - """1-D (inverse) generalized divisive normalization with a full C×C gamma. - Forward divides by sqrt(beta + gamma·x²); inverse multiplies — the exact form - crate::int_transform::igdn reproduces in integer (out = x·isqrt(norm)).""" - def __init__(self, ch, inverse): - super().__init__() - self.inverse = inverse - self.rg = nn.Parameter(torch.randn(ch, ch) * 0.01) # raw → softplus → γ≥0 - self.rb = nn.Parameter(torch.zeros(ch)) # raw → softplus → β≈1 +def build_codec(C): + import torch + import torch.nn as nn + import torch.nn.functional as F - def gb(self): - return F.softplus(self.rg), F.softplus(self.rb + 1.0) + class GDN1d(nn.Module): + """1-D (inverse) generalized divisive normalization with a full C×C gamma. + Forward divides by sqrt(beta + gamma·x²); inverse multiplies — the exact form + crate::int_transform::igdn reproduces in integer (out = x·isqrt(norm)).""" + def __init__(self, ch, inverse): + super().__init__() + self.inverse = inverse + self.rg = nn.Parameter(torch.randn(ch, ch) * 0.01) # raw → softplus → γ≥0 + self.rb = nn.Parameter(torch.zeros(ch)) # raw → softplus → β≈1 - def forward(self, x): # x [N,C,T] - g, b = self.gb() - norm = b[None, :, None] + torch.einsum('ij,njt->nit', g, x * x) - return x * torch.sqrt(norm) if self.inverse else x / torch.sqrt(norm) + def gb(self): + return F.softplus(self.rg), F.softplus(self.rb + 1.0) + def forward(self, x): # x [N,C,T] + g, b = self.gb() + norm = b[None, :, None] + torch.einsum('ij,njt->nit', g, x * x) + return x * torch.sqrt(norm) if self.inverse else x / torch.sqrt(norm) -class Codec(nn.Module): - def __init__(self): - super().__init__() - ga, gs = [], [] - for i in range(NLAY): - ga += [nn.Conv1d(1 if i == 0 else C, C, K, STR, K // 2), GDN1d(C, False)] - for i in range(NLAY): - last = i == NLAY - 1 - gs += [nn.ConvTranspose1d(C, 1 if last else C, K, STR, K // 2, output_padding=STR - 1)] - if not last: - gs += [GDN1d(C, True)] - self.g_a, self.g_s = nn.Sequential(*ga), nn.Sequential(*gs) + class Codec(nn.Module): + def __init__(self): + super().__init__() + ga, gs = [], [] + for i in range(NLAY): + ga += [nn.Conv1d(1 if i == 0 else C, C, K, STR, K // 2), GDN1d(C, False)] + for i in range(NLAY): + last = i == NLAY - 1 + gs += [nn.ConvTranspose1d(C, 1 if last else C, K, STR, K // 2, output_padding=STR - 1)] + if not last: + gs += [GDN1d(C, True)] + self.g_a, self.g_s = nn.Sequential(*ga), nn.Sequential(*gs) - def forward(self, x): - y = self.g_a(x) - y_hat = y + (torch.rand_like(y) - 0.5) # STE quantization noise - return self.g_s(y_hat), y + def forward(self, x): + y = self.g_a(x) + y_hat = y + (torch.rand_like(y) - 0.5) # STE quantization noise + return self.g_s(y_hat), y + + return Codec() # ---- integer reference decode (numpy; mirrors the Rust i64 arithmetic) ------- def conv_t_1d_int(xi, wi, mult, bias, stride, pad, opad): """Integer 1-D transposed conv + per-out-channel (mult>>SHIFT)+bias — byte-for- - byte what Rust int_audio::conv_t_1d will do. xi [cin,T]; wi [cin,cout,k].""" + byte what Rust int_audio::conv_t_1d does. xi [cin,T]; wi [cin,cout,k].""" + import numpy as np cin, cout, k = wi.shape; Tin = xi.shape[1] To = (Tin - 1) * stride + k + opad - 2 * pad Tp = To + 2 * pad @@ -88,6 +313,7 @@ def conv_t_1d_int(xi, wi, mult, bias, stride, pad, opad): def igdn_int_1d(x, gi, bi): """Integer IGDN, the EXACT arithmetic of crate::int_transform::igdn (per-term >>GF before the sum, isqrt at 2^AFRAC, (x·s)>>AFRAC). x [C,T] python-int.""" + import numpy as np Cc, Tt = x.shape x2 = x * x # 2^(2·AFRAC) norm = np.tile(np.array(bi, dtype=object)[:, None], (1, Tt)) # 2^(2·AFRAC) @@ -98,6 +324,7 @@ def igdn_int_1d(x, gi, bi): def run_g_s_int(xi, layers): + import numpy as np x = xi.astype(object) for L in layers: if L['type'] == 'convT': @@ -113,6 +340,7 @@ def run_g_s_int(xi, layers): def to_pcm_i16(x_fixed): """[1,T] @ 2^AFRAC (≈[-1,1]) → int16 PCM, round-half-up, clamp. Matches Rust.""" + import numpy as np v = x_fixed[0] num = v * 32767 + (1 << (AFRAC - 1)) pcm = num >> AFRAC # python>> floors (== i64 arith shift) @@ -121,12 +349,14 @@ def to_pcm_i16(x_fixed): # ---- PTQ + entropy helpers --------------------------------------------------- def q_weight_pc(w): # ConvTranspose1d [in,out,k] → per-OUT int12 + import numpy as np s = np.abs(w).max(axis=(0, 2), keepdims=True) / (2 ** (WBITS - 1) - 1) + 1e-12 wi = np.round(w / s).clip(-(2 ** (WBITS - 1)), 2 ** (WBITS - 1) - 1).astype(np.int64) return wi, s.reshape(-1) def fit_cdf(sym, lo, hi): + import numpy as np n = hi - lo + 1 hist = np.bincount(np.asarray(sym, np.int64) - lo, minlength=n).astype(np.float64) total = 1 << 16 @@ -146,33 +376,41 @@ def fnv1a(buf): return h -def main(src, outdir): - os.makedirs(outdir, exist_ok=True) - torch.manual_seed(0) - raw = np.fromfile(src, np.int16).astype(np.float32) / 32768.0 - if MAXS: - raw = raw[:MAXS] - n = (len(raw) // T) * T - raw = raw[:n] - frames = torch.from_numpy(raw.reshape(-1, 1, T)) # [Nf,1,T] +def main(outdir, seed, C, nsamp, steps, train_seconds): + import numpy as np + import torch + import torch.nn.functional as F + from compressai.ans import RansEncoder - m = Codec() - opt = torch.optim.Adam(m.parameters(), 1.5e-3) + assert nsamp % T == 0, f'--samples must be a multiple of {T}' + os.makedirs(outdir, exist_ok=True) + torch.manual_seed(seed) + torch.use_deterministic_algorithms(True) + torch.set_num_threads(THREADS) + + ntrain = (train_seconds * SR // T) * T + train = np.array(signal(seed, 0, ntrain), np.float32) / 32768.0 + test_i16 = signal(seed, 1, nsamp) + raw = np.array(test_i16, np.float32) / 32768.0 + frames = torch.from_numpy(train.reshape(-1, 1, T)) # [Nf,1,T] + + m = build_codec(C) + opt = torch.optim.Adam(m.parameters(), LR) # Keep the rate term tiny: the latent must stay O(1+) so integer rounding (the # quantizer) preserves it — too much rate pressure collapses it below the grid. - for step in range(STEPS): + for step in range(steps): xr, y = m(frames) - loss = F.mse_loss(xr, frames) + 8e-6 * y.abs().mean() + loss = F.mse_loss(xr, frames) + RATE_W * y.abs().mean() opt.zero_grad(); loss.backward(); opt.step() m.eval() with torch.no_grad(): xr_f, _ = m(frames) snr_f = 10 * np.log10((frames ** 2).sum() / ((xr_f - frames) ** 2).sum() + 1e-12) - print(f' (float recon SNR {float(snr_f):.1f} dB)') + print(f' (float recon SNR on the training signal {float(snr_f):.1f} dB)') - # full-signal latent → per-channel factorized symbols (s = round(y - median)) + # the test signal's latent → per-channel factorized symbols (s = round(y - median)) with torch.no_grad(): - xfull = torch.from_numpy(raw.reshape(1, 1, n)) + xfull = torch.from_numpy(raw.reshape(1, 1, nsamp)) y = m.g_a(xfull)[0].numpy() # [C, L] L = y.shape[1] median = np.round(np.median(y, axis=1)) # integer-grid median @@ -214,7 +452,6 @@ def main(src, outdir): bits = enc.encode_with_indexes(syms_flat, idx, cdfs, [len(cdfs[0])] * C, [lo] * C) # WAM1 binary model - import struct mb = bytearray(b'WAM1') mb += struct.pack(' integer-decode SNR {snr:.1f} dB, pcm_hash {pcm_hash}') + open(f'{outdir}/audio_source.s16', 'wb').write(s16le(test_i16)) + print(f'learned 1-D audio codec (seed {seed}): {len(pcm)} samples @ {SR}Hz, C={C} L={L}, ' + f'model {len(mb)}B, rANS {len(bits)}B -> integer-decode SNR {snr:.1f} dB, pcm_hash {pcm_hash}') if __name__ == '__main__': - # optional: C MAXS STEPS (e.g. tiny committed vector: 16 8192 2500) - if len(sys.argv) > 3: - C = int(sys.argv[3]) - if len(sys.argv) > 4: - MAXS = int(sys.argv[4]) - if len(sys.argv) > 5: - STEPS = int(sys.argv[5]) - main(sys.argv[1], sys.argv[2]) + ap = argparse.ArgumentParser(description='Train and export the integer learned audio codec on a ' + 'generated signal (no audio input).') + ap.add_argument('outdir', nargs='?', help='where audio_model.bin, audio_bitstream.json and audio.json go') + ap.add_argument('--seed', type=int, default=1) + ap.add_argument('--channels', type=int, default=32, help='latent channels C') + ap.add_argument('--samples', type=int, default=32768, help='test-signal (coded clip) length, a multiple of 2048') + ap.add_argument('--steps', type=int, default=3500, help='training steps') + ap.add_argument('--train-seconds', type=int, default=8, help='training-signal length in seconds') + ap.add_argument('--signal-only', metavar='OUT.s16', help='write one generated signal and exit') + ap.add_argument('--stream', type=int, default=1, help='with --signal-only: 0 training, 1 test') + a = ap.parse_args() + if a.signal_only: + n = a.samples if a.stream == 1 else (a.train_seconds * SR // T) * T + open(a.signal_only, 'wb').write(s16le(signal(a.seed, a.stream, n))) + sys.exit(0) + if not a.outdir: + ap.error('outdir is required unless --signal-only is given') + main(a.outdir, a.seed, a.channels, a.samples, a.steps, a.train_seconds) diff --git a/wai/volumetric-conformance/README.md b/wai/volumetric-conformance/README.md index 8c2d53641..6fe467aa0 100644 --- a/wai/volumetric-conformance/README.md +++ b/wai/volumetric-conformance/README.md @@ -7,6 +7,8 @@ frame, hence the identical BLAKE3 — on every machine, no tolerance parameter. The temporal counterpart of the static splat, with the single cloud replaced by per-Gaussian keyframed tracks. +**Provenance.** Generated here: `wai_splat4d_vectors gen` writes every vector from clouds and tracks authored in code. No capture. + Each Gaussian carries a track of keyframes (`frame`, position, scale, rotation quaternion, color, opacity, all `wai.det.fixed64`); the sink interpolates linearly in the `Fx` floor at any frame. The frame hash is diff --git a/wai/wai-rs/src/bin/wai_decode_bench.rs b/wai/wai-rs/src/bin/wai_decode_bench.rs index d065f0249..dea393cad 100644 --- a/wai/wai-rs/src/bin/wai_decode_bench.rs +++ b/wai/wai-rs/src/bin/wai_decode_bench.rs @@ -23,8 +23,8 @@ //! not all trained models: //! //! - **trained:** int_synth photo, int_world, int_splat and int_audio (their export -//! tools train the float model, then quantize it; int_world and int_splat train -//! on procedurally generated content); +//! tools train the float model, then quantize it; int_world, int_splat and +//! int_audio train on generated content); //! - **random weights:** the int_hyper tiny conditional codec //! (`tools/wai_int_hyper_tiny_vector.py` draws its integer weights from a seeded //! generator); diff --git a/wai/wai-rs/src/bin/wai_int_payload_vectors.rs b/wai/wai-rs/src/bin/wai_int_payload_vectors.rs index 438162bfc..df9d4aa46 100644 --- a/wai/wai-rs/src/bin/wai_int_payload_vectors.rs +++ b/wai/wai-rs/src/bin/wai_int_payload_vectors.rs @@ -285,17 +285,21 @@ fn fixtures(t: &mut Tree) -> Vec { fx(t, "wih1_order_exhausted_before_value", HYP, Some(short_z), None, "stream_exhausted", "decode"); fx(t, "wih1_unread_word", HYP, edit(&ih, &|b| { let l = get_u32(b, ih_ly); set_u32(b, ih_ly, l + 4); b.extend_from_slice(&[0; 4]) }), None, "stream_noncanonical", "decode"); - // ── WIA1 ── (16 channels × 512, tables of 14) + // ── WIA1 ── (the header's n_ch at 12 and cdf_len at 28; the first table follows the + // n_ch medians, at 32 + 8·n_ch, and its last entry is the one that must be 65536) let au_n = at(&au, wire::parse_wia1(&au).expect("audio parses").rans) - 4; // offset of L + let au_t0 = 32 + 8 * get_u32(&au, 12) as usize; // the first table + let au_cl = u16::from_le_bytes([au[28], au[29]]) as usize; // cdf_len + let au_last = au_t0 + (au_cl - 1) * 4; // the first table's last entry fx(t, "wia1_json_form", AUD, None, Some(format!("{V}/int_audio/audio_bitstream.json")), "bad_magic", "parse"); fx(t, "wia1_under_splat", SPL, Some(au.clone()), None, "format_capability_mismatch", "parse"); fx(t, "wia1_afrac_zero", AUD, edit(&au, &|b| b[5] = 0), None, "field_range", "parse"); fx(t, "wia1_sr_zero", AUD, edit(&au, &|b| set_u32(b, 8, 0)), None, "field_range", "parse"); fx(t, "wia1_median_range", AUD, edit(&au, &|b| b[32..40].copy_from_slice(&((1i64 << 47) + 1).to_le_bytes())), None, "field_range", "parse"); - fx(t, "wia1_cdf_malformed", AUD, edit(&au, &|b| set_u32(b, 160 + 13 * 4, 65_535)), None, "cdf_malformed", "parse"); + fx(t, "wia1_cdf_malformed", AUD, edit(&au, &|b| set_u32(b, au_last, 65_535)), None, "cdf_malformed", "parse"); // §2's order: the tables are read one at a time, each present and then checked, so // a malformed first table is cdf_malformed though the payload ends inside the second. - fx(t, "wia1_order_table_before_length", AUD, edit(&au, &|b| { set_u32(b, 160 + 13 * 4, 65_535); b.truncate(160 + 14 * 4 + 10) }), None, "cdf_malformed", "parse"); + fx(t, "wia1_order_table_before_length", AUD, edit(&au, &|b| { set_u32(b, au_last, 65_535); b.truncate(au_t0 + au_cl * 4 + 10) }), None, "cdf_malformed", "parse"); fx(t, "wia1_too_large", AUD, edit(&au, &|b| { set_u32(b, 12, 65_536); set_u32(b, 16, 2_048) }), None, "too_large", "parse"); fx(t, "wia1_trailing", AUD, edit(&au, &|b| b.push(0)), None, "trailing", "parse"); fx(t, "wia1_afrac_model", AUD, edit(&au, &|b| b[5] = 11), None, "model_mismatch", "decode"); diff --git a/wai/wai-rs/tests/corpus_provenance.rs b/wai/wai-rs/tests/corpus_provenance.rs new file mode 100644 index 000000000..34d5e0782 --- /dev/null +++ b/wai/wai-rs/tests/corpus_provenance.rs @@ -0,0 +1,73 @@ +//! Every corpus states where its content comes from (CONTRIBUTING, *Corpus content*). +//! +//! Each `wai/*-conformance/` directory has a `README.md` with a line that begins +//! `**Provenance.**`, and `tests/vectors/README.md` has a row for every directory under +//! `tests/vectors/` whose provenance cell is not empty. A provenance line names the +//! generator that writes the content and the generator's input; an input not generated +//! in this repository is named with its source and its licence. The test holds the +//! line's presence, not its truth: that is a reviewer's, and for audio also +//! `tools/wai_audio_provenance_check.py`'s. + +use std::path::PathBuf; + +fn wai() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") +} + +/// The line a README states its provenance in, if it has one with text after the label. +fn provenance_line(readme: &str) -> Option<&str> { + readme + .lines() + .map(str::trim_start) + .find(|l| l.starts_with("**Provenance.**") && l.len() > "**Provenance.**".len() + 1) +} + +#[test] +fn every_conformance_corpus_states_its_provenance() { + // OER/2 is settled: no change edits its documents or its corpus. + let settled = ["oer-conformance"]; + let mut dirs: Vec = std::fs::read_dir(wai()) + .unwrap() + .flatten() + .filter(|e| e.path().is_dir()) + .map(|e| e.file_name().to_string_lossy().into_owned()) + .filter(|n| n.ends_with("-conformance")) + .collect(); + dirs.sort(); + assert!(dirs.len() > 10, "found {} corpora", dirs.len()); + let mut missing = Vec::new(); + for d in &dirs { + if settled.contains(&d.as_str()) { + continue; + } + match std::fs::read_to_string(wai().join(d).join("README.md")) { + Ok(text) if provenance_line(&text).is_some() => {} + Ok(_) => missing.push(format!("{d}/README.md has no `**Provenance.**` line")), + Err(_) => missing.push(format!("{d} has no README.md")), + } + } + assert!(missing.is_empty(), "corpora without a provenance line:\n {}", missing.join("\n ")); +} + +#[test] +fn every_vector_directory_states_its_provenance() { + let root = wai().join("wai-rs/tests/vectors"); + let index = std::fs::read_to_string(root.join("README.md")).expect("tests/vectors/README.md"); + let mut dirs: Vec = std::fs::read_dir(&root) + .unwrap() + .flatten() + .filter(|e| e.path().is_dir()) + .map(|e| e.file_name().to_string_lossy().into_owned()) + .collect(); + dirs.sort(); + let mut missing = Vec::new(); + for d in &dirs { + // A row: | `/` | | … + let row = index.lines().find(|l| l.trim_start().starts_with(&format!("| `{d}/` |"))); + let cell = row.and_then(|r| r.split('|').nth(2)).map(str::trim).unwrap_or(""); + if cell.is_empty() { + missing.push(d.clone()); + } + } + assert!(missing.is_empty(), "tests/vectors/README.md states no provenance for: {missing:?}"); +} diff --git a/wai/wai-rs/tests/vectors/README.md b/wai/wai-rs/tests/vectors/README.md new file mode 100644 index 000000000..a64cf72b5 --- /dev/null +++ b/wai/wai-rs/tests/vectors/README.md @@ -0,0 +1,21 @@ +# Committed vectors + +The vectors the reference implementation's tests decode, and the conformance corpora +under `wai/*-conformance/` and the cross-architecture goldens are built on. A vector +changes only through its generator (CONTRIBUTING, *Vectors*). + +**Provenance.** Each directory's row names the generator that writes it and the +generator's input. `tests/corpus_provenance.rs` requires a row for every directory. + +| directory | provenance | +|---|---| +| `generate/` | Generated: `tools/wai_generate_model.py` writes the spectral model from an integer sine table and a word list. | +| `int_audio/` | Generated: `tools/wai_int_audio_export.py` trains the codec on a signal it generates from seed 1 and codes another signal from the same seed; it reads no audio file ([`int_audio/README.md`](int_audio/README.md)). | +| `int_hyper/` | Generated: `tools/wai_int_hyper_tiny_vector.py` draws the tiny conditional codec's integer weights from a seeded generator. No image. | +| `int_mlicpp/` | Generated: `tools/wai_int_mlicpp_export.py` (seed 20260929) codes an integer pattern of ramps, an xor texture and a disc with seeded weights; `tools/wai_int_mlicpp_set_form.py` writes its set-form twin. | +| `int_splat/` | Generated: `tools/wai_int_splat_export.py` trains on a seeded procedural cloud. | +| `int_video/` | `int_video.*` is generated by `tools/wai_int_video_encode.py` (a disc over a gradient), and `video_nk.*` by `tools/wai_int_video_keyframe_encode.py` (the `int_hyper` tiny keyframe, panned). `video_real.*` is **not** generated: `tools/wai_int_video_real_encode.py` pans over a third-party picture that was not made for this repository and has no recorded licence for redistribution. It is to be replaced by generated content. | +| `int_world/` | Generated: `tools/wai_int_world_export.py` trains on a seeded procedural world. | +| `int_world2/` | Generated: `tools/wai_int_world2_export.py` trains on seeded procedural backgrounds. | +| `jpegai/` | `test256.bin` is the output of the ISO/IEC 6048-1 reference encoder (BSD-3) for a 256×256 generated test image of gradients and blocks ([`jpegai/README.md`](jpegai/README.md)). | +| `staged_bounds/` | Constructed here for the bound tests: grey frames from a residual stream of zeros, and envelopes whose capability is refinement-only or companion-only ([`staged_bounds/README.md`](staged_bounds/README.md)). No image. | diff --git a/wai/wai-rs/tests/vectors/int_audio/README.md b/wai/wai-rs/tests/vectors/int_audio/README.md new file mode 100644 index 000000000..dd587826e --- /dev/null +++ b/wai/wai-rs/tests/vectors/int_audio/README.md @@ -0,0 +1,39 @@ +# `wai.audio.int_codec` — the committed vector + +**Provenance.** Generated here. The signal the codec is trained on, and the clip the +vector codes, are written by `tools/wai_int_audio_export.py` itself from a seed: 16 kHz, +mono, signed 16-bit, a sequence of tones, chirps, noise bursts and speech-like formant +sweeps. The tool reads no audio file. Apache-2.0, like the rest of the repository. + +- `audio_model.bin` — the integer synthesis (`WAM1`): 16 latent channels, four + transposed convolutions with integer IGDN between them. +- `audio_bitstream.json` — the export's JSON form: the latent (16 × 512), its + per-channel tables and its rANS stream. `audio_bitstream.wia1` is the same bitstream + as a `WIA1` payload, converted by `wai_int_payload_vectors gen`. +- `audio.json` — the decoded PCM's FNV-1a, its sample count (8192) and its rate. +- `sha256.json` — the decoded PCM's SHA-256 (SPEC §7 *Digests*), written by + `WAI_WRITE_SHA256_SIDECARS=1 cargo test --test int_kernels_vectors int_audio_clip` + after the decode has reproduced `audio.json`'s FNV-1a. + +**How it was made.** From `wai/`, with torch 2.14.1, numpy 1.26.4 and CompressAI 1.2.8 +on an arm64 host: + + python tools/wai_int_audio_export.py --seed 1 --channels 16 --samples 8192 --steps 2500 + +then `audio_model.bin`, `audio_bitstream.json` and `audio.json` copied from ``. +The seed is **1**: stream 0 of the seed is the training signal (8 s), stream 1 the coded +clip. Training is seeded from the same seed, with deterministic algorithms and four +threads. Two runs gave byte-identical files. A different host or torch build may train +to different weights; the committed files are what conformance checks, and their +decode is integer and the same everywhere. + +The signal alone needs only the Python standard library: + + python3 tools/wai_int_audio_export.py --signal-only clip.s16 --seed 1 --stream 1 --samples 8192 + +The site's demo clip is an export of the same tool with the same seed and the +command's defaults (32 channels, 32768 samples, 3500 steps). + +**Checked.** `tools/wai_audio_provenance_check.py` correlates the decoded clip (the +staged-delivery case `audio_int_codec_1l`'s stage 0) against the reference recordings +the CI host has, and fails above 0.5. diff --git a/wai/wai-rs/tests/vectors/int_audio/audio.json b/wai/wai-rs/tests/vectors/int_audio/audio.json index 3b9d75582..43bbfb9d0 100644 --- a/wai/wai-rs/tests/vectors/int_audio/audio.json +++ b/wai/wai-rs/tests/vectors/int_audio/audio.json @@ -1 +1 @@ -{"pcm_fnv1a": "085b193909b3042a", "n_samples": 8192, "sample_rate": 16000} \ No newline at end of file +{"pcm_fnv1a": "e5ff66ad1b86117c", "n_samples": 8192, "sample_rate": 16000} \ No newline at end of file diff --git a/wai/wai-rs/tests/vectors/int_audio/audio_bitstream.json b/wai/wai-rs/tests/vectors/int_audio/audio_bitstream.json index 20bc71440..946a084ca 100644 --- a/wai/wai-rs/tests/vectors/int_audio/audio_bitstream.json +++ b/wai/wai-rs/tests/vectors/int_audio/audio_bitstream.json @@ -1 +1 @@ -{"n_ch": 16, "lat_len": 512, "t_out": 8192, "afrac": 12, "lo": -6, "cdfs": [[0, 1, 2, 3, 4, 5, 6, 65404, 65531, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 5, 6, 65530, 65531, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 5, 6, 65530, 65531, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 5, 6, 65530, 65531, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 5, 6, 65530, 65531, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 5, 6, 65530, 65531, 65532, 65533, 65534, 65535, 65536], [0, 255, 1790, 4477, 8060, 11899, 15994, 50564, 52995, 56322, 60929, 64128, 65535, 65536], [0, 1, 2, 2433, 6528, 11135, 14590, 49541, 56579, 60290, 63873, 65408, 65535, 65536], [0, 1, 2, 3, 4, 5, 6, 65530, 65531, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 5, 6, 65530, 65531, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 5, 6, 65530, 65531, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 5, 6, 65530, 65531, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 5, 6, 65530, 65531, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 5, 6, 65530, 65531, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 5, 6, 41472, 65531, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 5, 6, 65530, 65531, 65532, 65533, 65534, 65535, 65536]], "median_q": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -4096, 0], "rans_b64": "etYG9QEAAAAZ0vuSWzgg8GEZB5zW3oezESVuYoARWGd+yezeXdBdidkZayqq2n1f3P8iNWEgLHGQ7iWl4NSxAj8O9necDFtFYOYAKo/DzaLmOkzC6tfuO63y92k3EcIwRRMHjdQjAi1+K5BDivfp3ycuhq+d4HuaPyWFGWD6p4n+HsK6uU/9u5x2IxyWMCs5K0VqX+5dNsWEXFF4Sm8Y/Mt04/sYeTs9d3CIvljRV7lk6dRvYmz7e7/KeCHN0RmtwsSvTHQ1acqT47otwcBwGCUAG0kn7e4bnu8fnvD7+tB78335QQspZbgXaJN35Su21jMBpUhzCYVdq8I5Vwy1dgQTLe/Bxg7OVviBB/rIYr5hy95NqexAdoohKfoOwsddftjaHPHWuwf5uNFFsZl9wFupJqFnc1HFlXLaOUFfdx7KOpF/HyHjKjXBBFCy5b1kNfSeTkawYtjBtw4WqRQ7uwCy2gPE0zpVm/wfQdKIfdVDB6X8TQoQ980+yzZCfoCo"} \ No newline at end of file +{"n_ch": 16, "lat_len": 512, "t_out": 8192, "afrac": 12, "lo": -6, "cdfs": [[0, 1, 2, 3, 4, 5, 6, 65529, 65530, 65531, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 5, 7811, 64123, 65530, 65531, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 5, 6, 65529, 65530, 65531, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 5, 1924, 63355, 65530, 65531, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 5, 6, 65529, 65530, 65531, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 2050, 5505, 19582, 45698, 58239, 63869, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 5, 1028, 32772, 52224, 65405, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 4483, 18304, 35324, 53633, 62463, 65278, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 9474, 22271, 39171, 54144, 64893, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 2178, 3329, 19965, 47105, 61694, 64893, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 5, 6, 65529, 65530, 65531, 65532, 65533, 65534, 65535, 65536], [0, 3967, 7934, 11133, 15996, 21626, 26233, 33015, 40309, 45939, 53762, 56577, 60544, 65535, 65536], [0, 1, 2, 3, 1922, 10880, 22653, 41986, 53631, 63229, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 5, 11650, 35197, 63612, 65531, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 7681, 17663, 26237, 39546, 54784, 65405, 65532, 65533, 65534, 65535, 65536], [0, 1, 2, 3, 4, 5, 2436, 33282, 57213, 65531, 65532, 65533, 65534, 65535, 65536]], "median_q": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -4096, 4096, -4096], "rans_b64": "bh7fxT8AAAATELnUjf3O5eE8XGMF/sJWJaIlOwjg5Q6m3Q4X3ewuSNs28cUZsE7mhJR3RNRP6UC7HDfTHQEwCLUGqBry9mB0xLaXxuuSBjkKI4XjzUIn0v0jgDCAdGXp47PxXfaChT4s0U7/mA+KjLRJzGcawpnA0xRwgfVHzl8yEQs3Lz0gEbrnDP1eH5LgYuGhm3rr9ONe/0Pzgg8ueDYTkgYgCHsUsmhOnpumjTpkc28Mblcnf1+S5owQr+KX4Ydh75ejd7k0wE0JmWyyZknIAYWTrC9fcSnwZfuIGs2eyHzNW5KPKBKtByoGVF4In/cqV1zywnhyqf5oij87K11yE4IsWsImI3Ay346C0rG+Q5KVhST/USnzxkPt/izl6/z4ts4h/C4WEQ9o2T9VVbiDVo24XWNNjpSTw2kRJLx3fizkkcYUxUnC+FmepYb6kaLS/n/2lxquX/Ujgplb2pqDpGrWwAgCHcTNij96PGFHDfu19VBmsizQZsvlos5Opwk7LtTfJr+2bofB9thKPueG9uLEnf5OVt0PzSEMRYJ+t1SQaV/s5G0LDXq1v14RKevagbsJIRYLSFm8QbTV2IcBvCv7X6fWfe1zEsYuQXBSYzHVlb2XG1Rgw34AaL5jheA5URliccP9ciehjZEieG0hbkC6U7bHm1iL0aqjoW6Lpj4DKOrcPqo7BPa9vyxrxlLqbZThUA6oAghHtYkFLwa+gOJnfhkZwCg9zquMzPJ2+h3BmfjEyPFAqvtAEz95X48iBUfv5qxBjIaObD3AX88ui7hM4Z8nOnxgw4k/qeGCSI3dVd0TeE5HLb8SDqkgY8QiwxjHroAz4vNb6P7hK6r4fMTzXeupbgn6X5x+Ez5fFMaHzMzhZRjYt5Tf2gPi4WRkZhLvrACOsGugwTWVQLszQnApN6zDol5C9dQOBrxv9mmkjGTMx/isoAveVgr6ysJuSnkm/tbyyamfgS41GmrdBdRzTR6UNriuSO1s+lxnoBqdI3SLbnpRryifit2mRywFk7xzlvIfus/tTIDCFZa5AZzmRA9EvbZHYh8uWMHim1+eLmyzds3OEFEHLYuL9s7GcOg5IsxcjDUNlq5KEXEfTQ69U8WlIGckTwjJLnASv6rcfJQ/0p9r+NMcpKZuH7eqiKY7XTjZjkTPn5gqlNSYG0jY6eLK+NdkIk9d/K5OJTBS3BjvdkwFY2lJBhXsaCfFXxPmMp8V//U/+PnuRJf6TTxkQmM60SH5m9IcilxNBKpOKSTObD9C4skikkKDirdgGIr0QQQp8cC4Ne5kzBOvZlfUcd9B8E3rlQw8x7cPH/8VobcJqRTotUaIpnjBE1fbSFrEVQ750bTT2UsHM137BCZoovofJlma3puAtqd9HggazBj81hH9u5f1HPkmbtQrIOTyEfscB8VRJSuZf9KsKSPkWhyNbTesD16MopfA1WSGCuU9qjjMSGCCO91vkRsFQGFBW8v6xr3htOh/8l60kmDrY3JNQykSgKiWRbh9rlRTN4w4cAYtbErSYTZfwbmOD0HABwpUt7dZ7cmJBI8kdAXw/ziMMImTwu0KSxn2D+BXBCtyul08lcc7UNt35ebuFy1RpZ9yVsfaTQXwNLcqs5zCCCHJINEG8iOIuR6w2YSkFp5WcarOz7tRgN07A6NoebHOdgdkfH5AVKpLBsW6Fn9oZNCwRYgg641XDL8ld9S3GbGa3cj4mP/rqGjYBOtrgVqMqrCvU1hS4Vo/GdYkjn/1ZevBJJhD0YLcppAjFJJLA4WFCmmgLrkw0tjr86PmHeOznGLxMMk5Dph+4M7BOYGxIAkLxnlBDPeZCy2OF01wZME8wMdL0gHVHj0hld/asUQm8CBxyt9oA5pqSag+9Jdc6e8MGCqcytXMnZ7/E54KfP0niIlBxg0wODrdkJWvLBnpXGW7NrPqMBTek7UTwSyTOJePsIGcOdDWhjxTxnoDVY8DtYd+p5fJmg1j3s3HoifvmmwXppyJWrUf0F2OJmns5pxPE6W71dOB5WqP9hvW"} \ No newline at end of file diff --git a/wai/wai-rs/tests/vectors/int_audio/audio_bitstream.wia1 b/wai/wai-rs/tests/vectors/int_audio/audio_bitstream.wia1 index 05d5af267..f50153003 100644 Binary files a/wai/wai-rs/tests/vectors/int_audio/audio_bitstream.wia1 and b/wai/wai-rs/tests/vectors/int_audio/audio_bitstream.wia1 differ diff --git a/wai/wai-rs/tests/vectors/int_audio/audio_model.bin b/wai/wai-rs/tests/vectors/int_audio/audio_model.bin index d202073c3..7585817c7 100644 Binary files a/wai/wai-rs/tests/vectors/int_audio/audio_model.bin and b/wai/wai-rs/tests/vectors/int_audio/audio_model.bin differ diff --git a/wai/wai-rs/tests/vectors/int_audio/sha256.json b/wai/wai-rs/tests/vectors/int_audio/sha256.json index 1820a6355..481110d70 100644 --- a/wai/wai-rs/tests/vectors/int_audio/sha256.json +++ b/wai/wai-rs/tests/vectors/int_audio/sha256.json @@ -1,6 +1,6 @@ { "audio": { "buffer": "PCM: signed 16-bit little-endian samples in output order, channels interleaved", - "sha256": "b8ff83a442c112661ec7a41d6e51cb32559510a699a0e9bb37fa301f8952978a" + "sha256": "4b0344c87c67fc30b7d977ae2077fc310822199598e5d2f9f8fc06314b3a2875" } } diff --git a/wai/world-conformance/README.md b/wai/world-conformance/README.md index 9902d5671..dac36485c 100644 --- a/wai/world-conformance/README.md +++ b/wai/world-conformance/README.md @@ -6,6 +6,8 @@ MUST produce the identical BLAKE3 state hash at every checkpoint, on every machine, under every permitted arrival order. Verification is hash equality — there is no tolerance parameter. +**Provenance.** Generated here: `wai_world_vectors gen` writes every world from scenes authored in code. No capture. + Vectors run the registered sim capabilities `wai.world.sim.kinematic0`, `wai.world.sim.ballistic0`, and `wai.world.sim.truss0` (the solver-backed registrant: position-based dynamics, 8 Gauss–Seidel