wai.media.exploration: archives that replay, default-distance, a homogenization meter, and taste as a declared, owned objective (CO2, CO3) #406
18 changed files with 9066 additions and 1 deletions
27
.github/workflows/wai-conformance.yml
vendored
27
.github/workflows/wai-conformance.yml
vendored
|
|
@ -141,6 +141,10 @@ on:
|
|||
# reports, settlement_verify.py reads that table, and the registry's wire identifiers test
|
||||
# reads its sections.
|
||||
- 'wai/extensions/settlement.md'
|
||||
# tests/exploration.rs requires this extension's §13 to list the codes the reference
|
||||
# reports and its §3.2 table to name the reference's descriptors, exploration_verify.py
|
||||
# reads §13, and the registry's wire identifiers test reads its sections.
|
||||
- 'wai/extensions/exploration.md'
|
||||
- 'wai/wai-web/**'
|
||||
# The audio provenance check below reads the corpora above with this tool.
|
||||
- 'wai/tools/wai_audio_provenance_check.py'
|
||||
|
|
@ -1279,6 +1283,29 @@ jobs:
|
|||
(cd wai-rs && cargo test --no-default-features --features settlement --lib settlement::)
|
||||
(cd wai-rs && cargo test --no-default-features --features settlement --test settlement)
|
||||
python3 -I live-conformance/settlement_verify.py live-conformance/settlement
|
||||
# wai.media.exploration (extensions/exploration.md): the reference module's unit tests (the
|
||||
# descriptors of flat, single-row and checkerboard pictures, grid bins at both ends, the
|
||||
# sample distance's zero, the taste score, derived prompts, and a schedule that replays and
|
||||
# keeps the first comer without an objective) and tests/exploration.rs (vectors.json
|
||||
# regenerates byte for byte from its fixed seeds and no other file is there, wai-rs gives
|
||||
# every generation, descriptor vector and case its stated outcome, §13 lists exactly the
|
||||
# reference's codes, each of which the corpus gives, and §3.2 names the reference's
|
||||
# descriptors); then the corpus's second implementation, exploration_verify.py, written from
|
||||
# the extension's text and sharing no code with wai-rs, which makes every picture with the
|
||||
# generator as the extension's Appendix A restates it, computes the descriptors, replays
|
||||
# every archive's schedule, reads usage statements with ai_usage_verify.py, verifies every
|
||||
# record with WAI's strict Ed25519 rule and BLAKE3 from claim-conformance, reads the
|
||||
# extension's reason table, requires every mutated copy of the corpus to fail, and drops each
|
||||
# of its own checks in turn and requires the corpus to catch the weakened verifier; standard
|
||||
# library only, run isolated (-I).
|
||||
- name: exploration · wai-rs and exploration_verify.py
|
||||
working-directory: wai
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
(cd wai-rs && cargo test --no-default-features --features exploration --lib exploration::)
|
||||
(cd wai-rs && cargo test --no-default-features --features exploration --test exploration)
|
||||
python3 -I live-conformance/exploration_verify.py live-conformance/exploration
|
||||
# wai.live.e2ee (extensions/confidentiality.md): the corpus's second implementation,
|
||||
# e2ee_verify.py, written from the extension's text and the documents it reads and
|
||||
# sharing no code with the reference (joule-moq's secure_objects and joule-moq-mls,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,52 @@ wire format may change and such changes are recorded here.
|
|||
|
||||
## v0.4.0 — current
|
||||
|
||||
### `wai.media.exploration`: archives that replay, default-distance, a homogenization meter, and taste as a declared, owned objective
|
||||
|
||||
- **The extension** ([exploration](extensions/exploration.md)) registers the record
|
||||
`wai.media.exploration`. It builds on the exactness of `wai.generate.spectral`: what elsewhere
|
||||
can only be estimated about a generator's outputs is recomputed here.
|
||||
- **Descriptors.** Six integer derivations over a picture's `RGB8` samples, each from 0 to 1024:
|
||||
mean luma, chroma, detail (the mean luma step between neighbours), palette diversity (the
|
||||
Gini–Simpson index of a 64-colour quantisation) and the two coordinates of the luma centroid.
|
||||
No learned model: they place a picture and judge nothing. A grid of one to four axes bins them
|
||||
into niches.
|
||||
- **Archives.** A generation request may ask for an archive instead of one picture:
|
||||
MAP-Elites over a declared grid, through the prompt and up to eight modifiers the person
|
||||
declares, run by the pinned schedule `wai-exploration:map-elites/1` from a declared search
|
||||
seed. Each elite names its genome (seed, steps, modifier mask) and regenerates; the archive
|
||||
replays bit for bit, so an elite dropped or chosen after the fact is refused
|
||||
(`archive_replay_mismatch`). The placement rule is `none` (the first trial to reach a niche
|
||||
keeps it) or a taste prior.
|
||||
- **Default-distance.** A prompt's default set is seeds 0 to 15 at four steps. A work's distance
|
||||
from it, in descriptors and in samples (0 exactly when the work is a default), is a signed
|
||||
`measured` figure for the exact generator, an `estimate` for any other, and `unknown` with
|
||||
every figure `null`, never 0.
|
||||
- **Collection statements.** The descriptor coverage of a grid and the distribution of pairwise
|
||||
descriptor distances of a gallery, a room's outputs, a session's candidates or an archive,
|
||||
recomputed from the samples.
|
||||
- **Taste priors.** An integer ranking model over the descriptors (`rise`, `fall`, `near` and
|
||||
`far` terms, unsigned throughout), a one-file parameter set pinned as SPEC §3.1 pins one,
|
||||
signed by its owner (`taste_not_owner` otherwise) and carrying the owner's `wai.ai.usage`
|
||||
statement; a ranking under it reproduces, and states the use it made of the prior, which the
|
||||
owner's statement may disallow. Personal and community priors are the same kind of object, and
|
||||
none is a default.
|
||||
- **Disclosure.** Every archive, ranking and pick that an aesthetic or reward model steered names
|
||||
it by digest or states `none`. A missing disclosure is reported undisclosed, never `none`; a
|
||||
policy may require it (`objective_undisclosed`), and a pick from an archive a prior steered must
|
||||
name that prior (`objective_mismatch`). A pick from an archive inherits the elite's genome: the
|
||||
slot the contribution record fills to point a `candidates` step at an archive.
|
||||
- **Reference and corpus.** `wai-rs` feature `exploration` (`src/exploration.rs`), and
|
||||
`live-conformance/exploration/`, written from fixed seeds by `wai_exploration_vectors gen`:
|
||||
generations that check the generator's restatement, descriptor vectors, archives, distances,
|
||||
collections, rankings and picks, accepted and refused at each rule. Checked by
|
||||
`tests/exploration.rs` and by `live-conformance/exploration_verify.py`, a second implementation
|
||||
that shares no code with `wai-rs`, implements the generator from the extension's Appendix A,
|
||||
requires every mutated copy of the corpus to fail and every weakened copy of itself to be
|
||||
caught. `wai_exploration_vectors measure` runs an archive against plain seed sampling and
|
||||
against uniform sampling of the same genome space at equal compute. The three priors of the
|
||||
corpus are written in code with hand-set weights; nothing is trained on anyone's choices.
|
||||
|
||||
### `wai.live.ad_presentation`: a player's signed claim that an ad was presented, and a spend that counts only presented impressions
|
||||
|
||||
- **The extension** ([ad-presentation](extensions/ad-presentation.md)) registers the record
|
||||
|
|
|
|||
|
|
@ -63,7 +63,8 @@ they serve ([jwp-receipts](extensions/jwp-receipts.md) §7),
|
|||
[packaging](extensions/packaging.md), [live-events](extensions/live-events.md),
|
||||
[live-rooms](extensions/live-rooms.md), [capture-binding](extensions/capture-binding.md),
|
||||
[live-compose](extensions/live-compose.md), [live-recording](extensions/live-recording.md),
|
||||
[timed-text](extensions/timed-text.md) and [ai-usage](extensions/ai-usage.md) put on the wire outside a
|
||||
[timed-text](extensions/timed-text.md), [ai-usage](extensions/ai-usage.md) and
|
||||
[exploration](extensions/exploration.md) put on the wire outside a
|
||||
manifest — magic numbers, a media type, `codecs` prefixes, paths, playlist tags and
|
||||
attributes, document kinds, catalog fields, MOQT Setup Options and HTTP fields — each with its kind, the section that
|
||||
defines it, and whether it is defined, provisional (test material only until
|
||||
|
|
@ -210,6 +211,7 @@ and a role are fixed once registered.
|
|||
| `wai.live.steering` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content |
|
||||
| `wai.live.watermark` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content |
|
||||
| `wai.media.contribution` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content |
|
||||
| `wai.media.exploration` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content |
|
||||
| `wai.media.synthesis` | record | registered | — | — | — | — | — | §5 Interactive, temporal & computed content |
|
||||
| `wai.mesh.draco` | payload | registered | volumetric | standalone | royalty-free | standard-unclassified | — | §5 Immersive audio, cinema, volumetric, haptics, HDR |
|
||||
| `wai.mesh.glb` | payload | registered | volumetric | standalone | royalty-free | standard-unclassified | — | §5 Interactive, temporal & computed content |
|
||||
|
|
@ -369,15 +371,18 @@ and a role are fixed once registered.
|
|||
| `urn:wai:event:1` | catalog | defined | live-events §8.1 |
|
||||
| `wai` | catalog | defined | moq-streaming-format §3 |
|
||||
| `wai-access-completeness` | document-kind | defined | access-services §7.1 |
|
||||
| `wai-archive` | document-kind | defined | exploration §5.3 |
|
||||
| `wai-authorship-map` | document-kind | defined | contribution §7.3 |
|
||||
| `wai-c2pa` | catalog | defined | live-provenance §10.2 |
|
||||
| `wai-caption-quality` | document-kind | defined | access-services §4.7 |
|
||||
| `wai-caption-reference` | document-kind | defined | access-services §4.3 |
|
||||
| `wai-caption-sources` | document-kind | defined | access-services §4.2 |
|
||||
| `wai-collection` | document-kind | defined | exploration §7.1 |
|
||||
| `wai-contribution` | document-kind | defined | contribution §6 |
|
||||
| `wai-derivation` | document-kind | defined | deriving-edge §5.1 |
|
||||
| `wai-description` | document-kind | defined | access-services §5.1 |
|
||||
| `wai-detection` | document-kind | defined | synthesis §7.3 |
|
||||
| `wai-distance` | document-kind | defined | exploration §6.3 |
|
||||
| `wai-key-status` | document-kind | defined | jwp-receipts §7.9 |
|
||||
| `wai-label-policy` | document-kind | defined | synthesis §10.1 |
|
||||
| `wai-marking` | document-kind | defined | synthesis §7.2 |
|
||||
|
|
@ -385,7 +390,9 @@ and a role are fixed once registered.
|
|||
| `wai-module-grant` | document-kind | defined | jwp-receipts §7.9 |
|
||||
| `wai-module-grants` | document-kind | defined | http-delivery §8.4 |
|
||||
| `wai-origin` | document-kind | defined | synthesis §3.1 |
|
||||
| `wai-pick` | document-kind | defined | exploration §10.2 |
|
||||
| `wai-player` | document-kind | defined | synthesis §10.3 |
|
||||
| `wai-ranking` | document-kind | defined | exploration §9.1 |
|
||||
| `wai-receipt-index` | document-kind | defined | http-delivery §8.2 |
|
||||
| `wai-receipts` | catalog | defined | moq-streaming-format §4.1 |
|
||||
| `wai-review` | document-kind | defined | synthesis §9.1 |
|
||||
|
|
@ -393,6 +400,7 @@ and a role are fixed once registered.
|
|||
| `wai-signing` | document-kind | defined | access-services §6.1 |
|
||||
| `wai-signing-review` | document-kind | defined | access-services §6.4 |
|
||||
| `wai-step` | document-kind | defined | contribution §4.1 |
|
||||
| `wai-taste` | document-kind | defined | exploration §8.1 |
|
||||
| `wai1` | codecs-prefix | defined | http-delivery §4.1 |
|
||||
| `wai1` | fourcc | provisional | http-delivery Appendix B |
|
||||
| `wai2` | codecs-prefix | defined | http-delivery §4.1 |
|
||||
|
|
|
|||
17
wai/SPEC.md
17
wai/SPEC.md
|
|
@ -1232,6 +1232,23 @@ the amount under a declared tariff in integer micro-units; a verifier recomputes
|
|||
the acknowledgements and refuses a statement that does not recompute. Acknowledgements map onto CDNI
|
||||
logging records (RFC 7937), whose fields carry no counter-signature, and a line kind is reserved for
|
||||
a payee that is not a hop: [`extensions/settlement.md`](extensions/settlement.md).
|
||||
And for **exploration** with the exact generator, and **taste** as a declared, owned objective —
|
||||
`wai.media.exploration`: six integer descriptors over a picture's `RGB8` samples (mean luma,
|
||||
chroma, detail, palette diversity and the luma centroid's two coordinates) place it without
|
||||
judging it. An archive is MAP-Elites over a declared grid of descriptors, run over
|
||||
`wai.generate.spectral` by a pinned schedule from a declared search seed, through the prompt and
|
||||
the modifiers a person declares: every elite names its genome and regenerates, and the archive
|
||||
replays bit for bit (`archive_replay_mismatch` otherwise). A prompt's default set is seeds 0 to
|
||||
15 at four steps, and a work's default-distance from it, in descriptors and in samples (0 exactly
|
||||
when the work is a default), is a signed measurement for the exact generator and an estimate,
|
||||
never a measurement, for any other; an unknown figure is never written as 0. A collection
|
||||
statement gives a gallery's, a room's or a session's descriptor coverage and pairwise distances.
|
||||
A taste prior is an integer ranking model over the descriptors, a one-file parameter set pinned as
|
||||
§3.1 pins one, signed by its owner (`taste_not_owner` otherwise) and carrying the owner's
|
||||
`wai.ai.usage` statement; a ranking under it reproduces. Every archive, ranking and pick that an
|
||||
aesthetic or reward model steered names it by digest or states `none`; a policy may require the
|
||||
disclosure of every pick, a pick from an archive a prior steered names that prior, and no default
|
||||
objective exists: [`extensions/exploration.md`](extensions/exploration.md).
|
||||
|
||||
### Payloads of the WAI-native integer codecs
|
||||
|
||||
|
|
|
|||
830
wai/extensions/exploration.md
Normal file
830
wai/extensions/exploration.md
Normal file
|
|
@ -0,0 +1,830 @@
|
|||
# WAI Extension: Exploration and Declared Taste
|
||||
|
||||
> Status: Draft. Registers the record `wai.media.exploration`. `wai.generate.spectral` is exact:
|
||||
> a model file, a prompt, a seed and a number of steps give the same bytes on every machine
|
||||
> ([SPEC](../SPEC.md) §5, *Payload of `wai.generate.spectral`*). This extension builds on that.
|
||||
> Six **descriptors**, each an integer derivation over a picture's `RGB8` samples, place a
|
||||
> picture: how bright, how colourful, how busy, how many colours, and where its light sits. An
|
||||
> **archive** asks the generator for many pictures that differ, not one: MAP-Elites over a
|
||||
> declared grid of descriptors, run by a pinned schedule from a declared search seed, so every
|
||||
> elite regenerates from the genome it names and the whole archive replays bit for bit. The
|
||||
> **default set** of a prompt is what the model makes on its own, seeds 0 to 15 at four steps; a
|
||||
> work's **default-distance** is how far it sits from the nearest default, a signed measurement
|
||||
> for the exact generator and an estimate, never a measurement, for any other. A **collection
|
||||
> statement** gives a gallery's, a room's or a session's descriptor coverage and pairwise
|
||||
> distances: a homogenization meter that recomputes. **Taste** is a declared, owned objective: a
|
||||
> **taste prior** is an integer ranking model over the descriptors, pinned by digest as a WAI
|
||||
> prior, signed by its owner and carrying its owner's usage statement
|
||||
> ([ai-usage](ai-usage.md)); a **ranking** under it reproduces; and every archive, ranking and
|
||||
> **pick** an aesthetic or reward model steered names that model by digest, or states `none`. A
|
||||
> policy may require the disclosure, and a selection from a steered archive cannot drop it. No
|
||||
> default objective exists: a personal prior and a community prior are the same kind of thing.
|
||||
> No new transport. The reference implementation is the `exploration` module of `wai-rs`
|
||||
> (feature `exploration`); the corpus is
|
||||
> [`live-conformance/exploration/`](../live-conformance/exploration/), and its second
|
||||
> implementation is `live-conformance/exploration_verify.py`.
|
||||
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
|
||||
|
||||
## 1. Scope
|
||||
|
||||
In scope: the records and their encodings (§2); the descriptors, their distance and grids (§3);
|
||||
the generator path, modifiers and genomes (§4); archives and their schedule (§5); the default set
|
||||
and default-distance (§6); collection statements (§7); taste priors (§8) and rankings (§9); the
|
||||
disclosure of what steered a record, and the pick record (§10); the slot this extension leaves
|
||||
the contribution record (§11); verification (§12).
|
||||
|
||||
Out of scope: a user interface; how a person chooses a prompt, a modifier or a pick; training a
|
||||
taste prior from anyone's choices (the corpus's priors are written in code with hand-set
|
||||
weights, §14); a learned embedding (the descriptors are arithmetic, §3.3); and any statement of
|
||||
what a picture is worth.
|
||||
|
||||
### 1.1 Why
|
||||
|
||||
Help from a generator can make one person's work more inventive while making many people's work
|
||||
more alike: different people asking for the same thing receive the same few answers, and
|
||||
different generators converge on similar answers to open prompts. A person shown one example
|
||||
tends to stay near it, and a generator asked again with an edited prompt tends to repeat itself.
|
||||
A single aesthetic objective applied to everyone narrows what is made further: it ranks one
|
||||
taste above others by construction and refuses requests outside it. Rewarding a result for
|
||||
differing from what others produce, rather than for matching one objective, restores variety.
|
||||
|
||||
Each of these is a statement about outputs, and for most generators it can only be estimated.
|
||||
For an exact generator it can be checked. This extension makes four such statements checkable:
|
||||
how widely an archive explored (§5), how far a work is from what the model does on its own (§6),
|
||||
how alike a collection is (§7), and which objective, if any, chose what a person was shown
|
||||
(§8–§10).
|
||||
|
||||
### 1.2 Relationships
|
||||
|
||||
Relationship to the core spec: `wai.media.exploration` is a **record** ([SPEC](../SPEC.md) §5): a
|
||||
signed record a WAI tool emits and verifies. It names no payload format and is not
|
||||
dispatchable. A taste prior is a parameter set in SPEC §3.1's sense, pinned by a pin of SPEC
|
||||
§3.1's members (§8.3).
|
||||
|
||||
Relationship to `wai.generate.spectral`: every picture this extension recomputes is made by that
|
||||
capability's reference generator, with the model file the record names by SHA-256 (§4.1).
|
||||
Appendix A restates the generator for a second implementation.
|
||||
|
||||
Relationship to [ai-usage](ai-usage.md): a taste prior carries its owner's usage statement, read
|
||||
by ai-usage §3.6, and a ranking states the use it made of the prior in ai-usage §6.1's form; the
|
||||
preference is decided by ai-usage §5.2 rule 2 (§9.3).
|
||||
|
||||
Relationship to [synthesis](synthesis.md): a picture an archive holds is generated; its origin
|
||||
claim, where its publisher signs one, is synthesis §3's, step `generate`. Nothing here changes
|
||||
a class.
|
||||
|
||||
Relationship to the contribution record, `wai.media.contribution`: §11 names the slot a
|
||||
contribution session's `candidates` and `select` steps fill to point at an archive, and the
|
||||
disclosure member they carry.
|
||||
|
||||
## 2. Records
|
||||
|
||||
Every record of this extension is a JSON object (RFC 8259) under the encodings of
|
||||
[jwp-receipts](jwp-receipts.md) §2.4.3: byte strings in lowercase hexadecimal; integers unsigned,
|
||||
at most 2^53 − 1, in plain decimal; no other number; no boolean; a **term** and a **date-time**
|
||||
as defined there. A record is read as jwp-receipts §3.1 step 1 reads a claim: RFC 8259's grammar,
|
||||
no member named twice in one object, at most 32 levels of nesting. Each record has exactly the
|
||||
members its section lists, no member missing and none added, at any level, except where a
|
||||
section marks a member OPTIONAL, and a `kind` naming it; one that does not, or whose member is
|
||||
of another type or outside its range, is `record_malformed`.
|
||||
|
||||
Every record carries `issued_at` (a date-time: when its signer signed it), `signer_pubkey` (32
|
||||
bytes, the Ed25519 key of RFC 8032 that signs it) and `sig` (64 bytes):
|
||||
|
||||
```
|
||||
sig = Ed25519(signer_key, "wai:exploration\x01" || JCS(record without "sig"))
|
||||
link_hash = BLAKE3("wai:exploration-link\x01" || JCS(record))
|
||||
```
|
||||
|
||||
JCS is RFC 8785, which on these values reduces to what jwp-receipts §2.4.3 states. The signature
|
||||
is verified strictly ([SPEC](../SPEC.md) §7 *Signatures*): a key that fails step 1 or 2 is
|
||||
`bad_key`, and a signature that fails a later step `bad_signature`. A record is named by its
|
||||
link hash wherever another names it.
|
||||
|
||||
A **picture** is named by its samples: the SHA-256 (FIPS 180-4) of its `RGB8` buffer as
|
||||
[reconstruction-binding](reconstruction-binding.md) §2 defines it (rows top to bottom, pixels
|
||||
left to right, three bytes a pixel, no padding), its **samples digest**. A picture object is
|
||||
`{"samples", "width", "height"}`, and `{"samples", "width", "height", "descriptors"}` where a
|
||||
table says it carries descriptors: a samples digest, a width and a height each from 1 to 4096
|
||||
with their product at most 2^23 (the ranges of an instruction object, SPEC §5), and a descriptor
|
||||
vector (§3.4).
|
||||
|
||||
A record names the descriptor set it uses in `descriptors`: `wai-exploration:image6`, the one
|
||||
this revision defines. Another value is `record_malformed`.
|
||||
|
||||
## 3. Descriptors
|
||||
|
||||
### 3.1 Luma
|
||||
|
||||
The descriptors are computed from a picture's `RGB8` buffer of *w* × *h* pixels, *n* = *w* · *h*,
|
||||
in integer arithmetic; ⌊ ⌋ is the floor of an exact rational, and no step rounds otherwise. The
|
||||
**luma** of a pixel with bytes *R*, *G*, *B* is
|
||||
|
||||
*Y* = (77 · *R* + 150 · *G* + 29 · *B* + 128) >> 8,
|
||||
|
||||
an integer from 0 to 255 (the weights sum to 256, so a grey pixel's luma is its value).
|
||||
|
||||
### 3.2 The six descriptors
|
||||
|
||||
Each descriptor is an integer from 0 to 1024. The **descriptor vector** is the six, in this
|
||||
order:
|
||||
|
||||
| # | name | value |
|
||||
|---|---|---|
|
||||
| 1 | `luma` | ⌊1024 · Σ *Y* / (255 · *n*)⌋ |
|
||||
| 2 | `chroma` | ⌊1024 · Σ (max(*R*, *G*, *B*) − min(*R*, *G*, *B*)) / (255 · *n*)⌋ |
|
||||
| 3 | `detail` | with *P* = (*w* − 1) · *h* + *w* · (*h* − 1) and *D* the sum of \|*Y*(*x* + 1, *y*) − *Y*(*x*, *y*)\| over every horizontal pair and \|*Y*(*x*, *y* + 1) − *Y*(*x*, *y*)\| over every vertical pair: 0 when *P* is 0, otherwise min(1024, ⌊1024 · *D* / (16 · *P*)⌋) |
|
||||
| 4 | `palette` | each pixel's colour is (*R* >> 6) · 16 + (*G* >> 6) · 4 + (*B* >> 6), one of 64; with *k*ᵢ the number of pixels of colour *i*: ⌊1024 · (*n*² − Σ *k*ᵢ²) / *n*²⌋ |
|
||||
| 5 | `centroid_x` | with *V* = *Y* + 1 at each pixel: ⌊1024 · Σ (2*x* + 1) · *V* / (2 · *w* · Σ *V*)⌋ |
|
||||
| 6 | `centroid_y` | ⌊1024 · Σ (2*y* + 1) · *V* / (2 · *h* · Σ *V*)⌋ |
|
||||
|
||||
Sums run over every pixel unless the row says otherwise; *x* runs from 0 to *w* − 1 left to
|
||||
right and *y* from 0 to *h* − 1 top to bottom. Every intermediate value fits in 64 unsigned bits
|
||||
for every size §2 admits.
|
||||
|
||||
### 3.3 Why these six
|
||||
|
||||
Each answers a question a viewer asks of a picture, in one number: how bright it is (`luma`); how
|
||||
colourful (`chroma`, the mean spread of a pixel's three channels); how busy (`detail`, the mean
|
||||
luma step between neighbours, sixteen levels a step counting as full); how many colours it uses
|
||||
and how evenly (`palette`, the probability that two pixels drawn at random differ in their
|
||||
64-colour quantisation, the Gini–Simpson index); and where its light sits (`centroid_x` and
|
||||
`centroid_y`, the luma-weighted centre, 512 being the frame's centre; *V* is *Y* + 1 so that a
|
||||
black picture has a centre). They are arithmetic over the samples, with no learned model and so
|
||||
no population's judgement inside them: they place a picture, they do not rank it. Each is scaled
|
||||
to the same range so that a distance (§3.4) weighs no axis above another by its units; weighing
|
||||
one above another is a taste, which §8 makes a declared, owned object instead.
|
||||
|
||||
### 3.4 Distance
|
||||
|
||||
The **descriptor distance** of two vectors is Σ \|*a*ᵢ − *b*ᵢ\| over the six, an integer from 0 to
|
||||
6144. In JSON a vector is an array of six integers, each from 0 to 1024.
|
||||
|
||||
### 3.5 Grids
|
||||
|
||||
A **grid** is an array of 1 to 4 **axes**, each `{"descriptor", "lo", "hi", "bins"}`: a descriptor
|
||||
name of §3.2, no name twice; 0 ≤ `lo` < `hi` ≤ 1024; `bins` from 1 to 64; and the product of
|
||||
every axis's `bins`, the grid's **cells**, at most 4096. The **bin** of a value *d* on an axis is
|
||||
0 when *d* < `lo`, `bins` − 1 when *d* ≥ `hi`, and ⌊(*d* − `lo`) · `bins` / (`hi` − `lo`)⌋
|
||||
otherwise. The **niche** of a vector is its bins read as a mixed-radix number, the first axis
|
||||
most significant: *k* = 0, then *k* = *k* · `bins` + bin for each axis in order.
|
||||
|
||||
## 4. The generator path
|
||||
|
||||
### 4.1 Generation
|
||||
|
||||
`gen(model, prompt, seed, steps, w, h)` is the picture SPEC §5 has a sink make for an
|
||||
instruction object of `wai.generate.spectral`: the reference generator's
|
||||
`generate(model, prompt, seed, steps, h, w)`, `w` · `h` · 3 bytes of `RGB8`, with the model file
|
||||
whose SHA-256 the record names as `sha256:` and 64 lowercase hexadecimal digits. A prompt is what
|
||||
an instruction object's `prompt` is: 0 to 256 bytes, each 0x20 to 0x7E other than `"` and `\`.
|
||||
A seed is from 0 to 2^32 − 1 and `steps` from 1 to 64. Appendix A restates the generator.
|
||||
|
||||
A verifier holds model files and finds one by its SHA-256. A record that asks it to generate with
|
||||
a model file it does not hold is `model_unheld`.
|
||||
|
||||
A generator other than `wai.generate.spectral` is named by a term and is not recomputed here
|
||||
(§6.3, §10.2).
|
||||
|
||||
### 4.2 Modifiers and derived prompts
|
||||
|
||||
An archive declares 0 to 8 **modifiers**, each 1 to 32 bytes of `a`–`z` and `0`–`9`: words the
|
||||
search may add to the prompt. A **modifier mask** is an integer below 2^*M*, *M* the number of
|
||||
modifiers. The **derived prompt** of a mask is the prompt followed, for each *i* from 0 to *M* − 1
|
||||
whose bit *i* is set, by one space (0x20) and modifier *i*. The derived prompt of the full mask
|
||||
MUST be a prompt (§4.1). The modifiers are the person's: the archive explores the space they
|
||||
declare, and the prompt they wrote is always the first word.
|
||||
|
||||
### 4.3 Genomes
|
||||
|
||||
A **genome** is `(seed, steps, mods)`; its picture is `gen(model, derived(prompt, mods), seed,
|
||||
steps, w, h)` for the archive's model, prompt, modifiers and size.
|
||||
|
||||
## 5. Archives
|
||||
|
||||
### 5.1 The request
|
||||
|
||||
An archive record carries what was asked:
|
||||
|
||||
| member | value |
|
||||
|---|---|
|
||||
| `generator` | `wai.generate.spectral` |
|
||||
| `model` | `sha256:` and the model file's SHA-256 |
|
||||
| `prompt` | a prompt (§4.1) |
|
||||
| `modifiers` | 0 to 8 modifiers (§4.2) |
|
||||
| `width`, `height` | the pictures' size (§2) |
|
||||
| `steps` | from 1 to 64: the steps every initial trial uses |
|
||||
| `descriptors` | `wai-exploration:image6` |
|
||||
| `grid` | a grid (§3.5) |
|
||||
| `search` | `{"schedule", "seed", "trials", "init"}`: `schedule` `wai-exploration:map-elites/1`; `seed` an integer, the search seed; `trials` from 1 to 4096; `init` from 1 to `trials` |
|
||||
| `objective` | `"none"`, or a taste objective (§10.1): what decides between two pictures of one niche |
|
||||
|
||||
An objective of type `model` is `record_malformed` here: a schedule steered by a model a
|
||||
verifier cannot run cannot be replayed.
|
||||
|
||||
### 5.2 The schedule `wai-exploration:map-elites/1`
|
||||
|
||||
The schedule draws from **splitmix64**, a 64-bit state *s* that starts at the search seed; each
|
||||
draw sets *s* to *s* + 0x9E3779B97F4A7C15 modulo 2^64, then *z* = *s*, *z* = (*z* ⊕ (*z* >> 30)) ·
|
||||
0xBF58476D1CE4E5B9, *z* = (*z* ⊕ (*z* >> 27)) · 0x94D049BB133111EB, each product modulo 2^64,
|
||||
and returns *z* ⊕ (*z* >> 31). The archive *A* maps a niche to its **elite**; the visits *V* map
|
||||
a niche to the number of trials that reached it. Both start empty. For each trial *t* from 0 to
|
||||
`trials` − 1, in order:
|
||||
|
||||
1. **Genome.** When *t* < `init`: seed = draw mod 2^32, `steps` the request's, mods 0. Otherwise:
|
||||
- with *O* the occupied niches in ascending order, *a* = *O*[draw mod \|*O*\|], then
|
||||
*b* = *O*[draw mod \|*O*\|]; the parent is *A*[*b*] when *V*[*b*] < *V*[*a*], and *A*[*a*]
|
||||
otherwise;
|
||||
- mods = the parent's; when *M* > 0, for each *i* from 0 to *M* − 1 in order, bit *i* is
|
||||
flipped when draw mod *M* is 0;
|
||||
- with *d* = draw mod 8, steps = the parent's steps plus *d* − 4 when *d* < 4 and *d* − 3
|
||||
otherwise, clamped to 1 to 64;
|
||||
- seed = draw mod 2^32.
|
||||
2. **Picture.** The genome's picture (§4.3), its descriptor vector and its niche *k* in the grid;
|
||||
*V*[*k*] increases by 1.
|
||||
3. **Score.** With a taste objective, the score of the vector under the prior (§8.2); with
|
||||
`none`, no score.
|
||||
4. **Placement.** When *k* is not occupied, the trial becomes its elite. When it is, the trial
|
||||
replaces the elite only under a taste objective and only with a strictly greater score; a tie
|
||||
keeps the earlier trial, and with `none` the first trial to reach a niche keeps it.
|
||||
|
||||
The parent is the less-visited of two occupied niches drawn at random: the search spends its
|
||||
trials where few have landed. A child keeps its parent's recipe close (each modifier flips with
|
||||
probability 1/*M*, the steps move by one to four) and draws a new seed. The archive is therefore a
|
||||
function of the request alone.
|
||||
|
||||
### 5.3 The record
|
||||
|
||||
An archive record, `kind` `"wai-archive"`, has the members of §5.1 and:
|
||||
|
||||
| member | type | meaning |
|
||||
|---|---|---|
|
||||
| `elites` | array | every occupied niche's elite, in strictly ascending order of `niche`, at least one |
|
||||
| `coverage` | object | `{"occupied", "cells"}`: the number of elites and the grid's cells |
|
||||
| `issued_at`, `signer_pubkey`, `sig` | | §2 |
|
||||
|
||||
An elite is `{"niche", "trial", "seed", "steps", "mods", "samples", "descriptors", "score"}`: its
|
||||
niche, below the grid's cells; the trial that placed it, below `trials`, no trial twice; its
|
||||
genome (`seed` below 2^32, `steps` from 1 to 64, `mods` below 2^*M*); its picture's samples
|
||||
digest and descriptor vector; and its score, an integer under a taste objective and `null`
|
||||
under `none`.
|
||||
|
||||
### 5.4 Verifying an archive
|
||||
|
||||
In this order:
|
||||
|
||||
1. The record reads (§2, §5.1, §5.3) and its signature verifies.
|
||||
2. `coverage` states the number of elites and the grid's cells: otherwise `coverage_mismatch`.
|
||||
3. A taste objective's prior resolves and verifies (§8.3, §8.4).
|
||||
4. The verifier holds the model (`model_unheld`).
|
||||
5. For each elite in order: its genome's picture has its samples digest
|
||||
(`generation_mismatch`), the picture's descriptor vector is its `descriptors`
|
||||
(`descriptor_mismatch`), that vector's niche is its `niche` (`niche_mismatch`), and its score
|
||||
is the prior's score of the vector, or `null` with `none` (`score_mismatch`).
|
||||
6. Under a policy that requires replay (§12.1): the schedule of §5.2, run from the request,
|
||||
leaves exactly these elites, member for member: otherwise `archive_replay_mismatch`.
|
||||
|
||||
An archive that passes step 5 is **elite-checked**: every picture it lists is what its genome
|
||||
makes, and sits where it says. One that passes step 6 is **replayed**: the archive is the
|
||||
schedule's, every niche's elite is the one the schedule keeps, and no trial was left out or
|
||||
chosen after the fact. Replay costs `trials` generations; step 5 costs one per elite.
|
||||
|
||||
## 6. Default-distance
|
||||
|
||||
### 6.1 The default set
|
||||
|
||||
The **default set** of a model, a prompt and a size is the 16 pictures `gen(model, prompt, k, 4,
|
||||
w, h)` for *k* from 0 to 15: what the model makes for the prompt on its own, at the steps of the
|
||||
registered conformance object (SPEC §5) and the first sixteen seeds. They stand for what a person
|
||||
who chooses nothing receives, and they are the same sixteen for every verifier.
|
||||
|
||||
### 6.2 Two distances
|
||||
|
||||
A work's **descriptor distance** to the default set is the least descriptor distance (§3.4)
|
||||
between its vector and a default's; `nearest_descriptor` is the lowest *k* that attains it. Its
|
||||
**sample distance** is the least ⌈1024 · Σ \|*a* − *b*\| / (765 · *n*)⌉ between its buffer and a
|
||||
default's, the sum over every byte; `nearest_sample` is the lowest *k* that attains it.
|
||||
|
||||
Both are given because they answer different questions. The descriptor distance says how
|
||||
different a *kind* of picture the work is from what the model does on its own, and is blind to
|
||||
which exact draw it is. The sample distance says whether the work *is* a default: it is 0 exactly
|
||||
when the work's bytes are a default's (the ceiling keeps any difference above 0), and otherwise
|
||||
how far in samples. A work that is seed 3 of the prompt as written has sample distance 0; a work
|
||||
regenerated with other words, other steps or another seed, or edited afterwards, does not.
|
||||
|
||||
### 6.3 The record and its class
|
||||
|
||||
A distance record, `kind` `"wai-distance"`:
|
||||
|
||||
| member | type | meaning |
|
||||
|---|---|---|
|
||||
| `work` | picture with descriptors | the work |
|
||||
| `generator` | term | `wai.generate.spectral`, or a term naming another generator |
|
||||
| `model` | term | for `wai.generate.spectral`, `sha256:` and the model file's SHA-256; otherwise a term naming the model |
|
||||
| `prompt` | string | a prompt (§4.1) |
|
||||
| `descriptors` | string | `wai-exploration:image6` |
|
||||
| `defaults` | array | 0 to 16 entries `{"samples", "descriptors"}`: a default's samples digest or `null`, and its vector |
|
||||
| `class` | string | `measured`, `estimate` or `unknown` |
|
||||
| `descriptor_distance`, `nearest_descriptor`, `sample_distance`, `nearest_sample` | integer or `null` | §6.2 |
|
||||
| `issued_at`, `signer_pubkey`, `sig` | | §2 |
|
||||
|
||||
The class says what kind of figure it is:
|
||||
|
||||
- **`measured`**: the generator is exact, the default set is recomputed, and the figure is a
|
||||
measurement anyone holding the model and the work repeats. `defaults` has 16 entries, every
|
||||
`samples` a digest, in seed order, and all four figures are integers.
|
||||
- **`estimate`**: the generator is not exact. Its defaults are the signer's statement of what
|
||||
the generator made (their vectors, and their samples where the signer has them), and the
|
||||
figure is the descriptor distance from those: an estimate of how far the work is from the
|
||||
generator's defaults, never a measurement of it. `defaults` has 1 to 16 entries,
|
||||
`descriptor_distance` and `nearest_descriptor` are integers, and `sample_distance` and
|
||||
`nearest_sample` are `null`.
|
||||
- **`unknown`**: no default set is known. `defaults` is empty and all four figures are `null`.
|
||||
Unknown is never zero: a record that writes 0 for a figure it does not have states that the
|
||||
work is a default.
|
||||
|
||||
A record whose members break its class's shape is `record_malformed`. A `measured` record over a
|
||||
generator other than `wai.generate.spectral`, or a record over `wai.generate.spectral` of
|
||||
another class, is `distance_class_invalid`: the exact generator's figure is always a
|
||||
measurement, and no other generator's ever is.
|
||||
|
||||
### 6.4 Verifying a distance
|
||||
|
||||
In this order, after the record reads and its signature verifies:
|
||||
|
||||
1. The class agrees with the generator (`distance_class_invalid`).
|
||||
2. Held samples of the work are `width` · `height` · 3 bytes (`samples_mismatch`).
|
||||
3. `measured`: the verifier holds the model (`model_unheld`); the default set at the work's size
|
||||
is made, and each entry's samples digest and vector are the default's
|
||||
(`default_mismatch`); the verifier holds the work's samples (`work_unheld`), and their vector
|
||||
is the work's `descriptors` (`descriptor_mismatch`); all four figures are computed by §6.2.
|
||||
4. `estimate`: where the work's samples are held, their vector is its `descriptors`
|
||||
(`descriptor_mismatch`); the descriptor distance and the nearest default are computed from
|
||||
the work's vector and the stated vectors.
|
||||
5. `unknown`: where the work's samples are held, their vector is its `descriptors`
|
||||
(`descriptor_mismatch`).
|
||||
6. The figures computed are the figures stated (`distance_mismatch`).
|
||||
|
||||
A verifier reports the class with the figures, and reports an `unknown` figure as unknown,
|
||||
never as 0.
|
||||
|
||||
## 7. Collection statements
|
||||
|
||||
### 7.1 The record
|
||||
|
||||
A collection statement, `kind` `"wai-collection"`, is the homogenization meter of a set of
|
||||
pictures:
|
||||
|
||||
| member | type | meaning |
|
||||
|---|---|---|
|
||||
| `scope` | string | `gallery`, `room` (a room's outputs), `session` (a session's candidates) or `archive` |
|
||||
| `subject` | term | what the collection is: a gallery's URI, a room's id, an archive's link hash |
|
||||
| `descriptors` | string | `wai-exploration:image6` |
|
||||
| `grid` | array | a grid (§3.5) |
|
||||
| `members` | array | 2 to 256 pictures with descriptors; a picture may appear more than once |
|
||||
| `coverage` | object | `{"occupied", "cells"}` |
|
||||
| `distances` | object | §7.2 |
|
||||
| `issued_at`, `signer_pubkey`, `sig` | | §2 |
|
||||
|
||||
### 7.2 The figures
|
||||
|
||||
- `coverage`: `occupied`, the number of distinct niches the members' vectors fall in; `cells`,
|
||||
the grid's.
|
||||
- `distances`: over every unordered pair of members (*i* < *j*), the descriptor distance of
|
||||
their vectors: `pairs`, their number; `sum`, their total, so the mean is `sum` / `pairs`
|
||||
exactly; `min` and `max`; `median`, the element at position ⌊(`pairs` − 1) / 2⌋ of the
|
||||
distances in ascending order; `width`, from 1 to 6144; and `histogram`, 16 counts, a distance
|
||||
*d* counted in bin min(⌊*d* / `width`⌋, 15).
|
||||
|
||||
A collection whose members are all alike has few occupied cells and small distances; one that
|
||||
explores has many and large ones. The figures say nothing of quality.
|
||||
|
||||
### 7.3 Verifying a collection
|
||||
|
||||
After the record reads and its signature verifies: for each member in order, the verifier holds
|
||||
its samples (`member_unheld`), of its size (`samples_mismatch`), whose vector is its
|
||||
`descriptors` (`descriptor_mismatch`); then `coverage` is computed from the vectors
|
||||
(`coverage_mismatch`), and then `distances` with the stated `width` (`distances_mismatch`).
|
||||
Every figure is computed from samples the verifier holds; a statement over pictures it does not
|
||||
hold is not checked, and is not accepted.
|
||||
|
||||
## 8. Taste priors
|
||||
|
||||
### 8.1 The prior
|
||||
|
||||
A **taste prior** is a file: the canonical form (JCS) of a signed record, `kind` `"wai-taste"`:
|
||||
|
||||
| member | type | meaning |
|
||||
|---|---|---|
|
||||
| `owner` | 32 B hex | the Ed25519 key of the person or community whose taste it is |
|
||||
| `scope` | string | `personal` or `community` |
|
||||
| `descriptors` | string | `wai-exploration:image6` |
|
||||
| `terms` | array | 1 to 32 terms (§8.2) |
|
||||
| `usage` | object or `null` | the owner's usage statement ([ai-usage](ai-usage.md) §3), or `null` |
|
||||
| `issued_at`, `signer_pubkey`, `sig` | | §2 |
|
||||
|
||||
A file whose bytes are not the canonical form of the record they hold is `record_malformed`,
|
||||
and so is a `usage` that does not read by ai-usage §3.6. The prior's **digest** is the SHA-256
|
||||
of the file.
|
||||
|
||||
### 8.2 The score
|
||||
|
||||
A term is `{"descriptor", "shape", "at", "weight"}`: a descriptor name; a shape; `at` from 0 to
|
||||
1024, which is 0 for `rise` and `fall`; and `weight` from 1 to 1024. Its value at a vector whose
|
||||
descriptor is *d*:
|
||||
|
||||
| shape | value |
|
||||
|---|---|
|
||||
| `rise` | `weight` · *d* |
|
||||
| `fall` | `weight` · (1024 − *d*) |
|
||||
| `near` | `weight` · (1024 − \|*d* − `at`\|) |
|
||||
| `far` | `weight` · \|*d* − `at`\| |
|
||||
|
||||
The **score** of a vector is the sum of its terms' values, an integer from 0 to 2^25. Every
|
||||
value is non-negative, so the arithmetic is unsigned throughout and the same on every machine. A
|
||||
prior that likes calm, dark pictures says `fall` on `detail` and `luma`; one that likes a
|
||||
mid-grey says `near` at 512; one that wants pictures unlike the usual says `far` from where
|
||||
the usual sits. The score is a few integer operations over six numbers, stated in full here,
|
||||
so it is not expressed as an integer-operator graph ([integer-operators](integer-operators.md)).
|
||||
|
||||
### 8.3 Carriage as a WAI prior
|
||||
|
||||
A taste prior is a parameter set of one file in SPEC §3.1's sense: what a ranking's output
|
||||
depends on, besides its input. A record names it by a **pin** of SPEC §3.1's members `id` (a
|
||||
name for people, 1 to 256 bytes), `sha256` (the prior's digest) and `determinism` (in words,
|
||||
1 to 256 bytes), and no other member: the form is undeclared, and the digest covers the one file.
|
||||
A verifier holds priors and finds one by its digest, as a sink resolves a pin: nothing but the
|
||||
digest selects it, not the `id`. A pin that names a prior the verifier does not hold is
|
||||
`objective_unresolved`.
|
||||
|
||||
### 8.4 Ownership and usage
|
||||
|
||||
A prior is its owner's: it **MUST** be signed by `owner`. One signed by any other key is
|
||||
`taste_not_owner`, whatever its signature. The rule is one of attribution: a taste is ascribed
|
||||
to a key only by that key's signature, so no tool can present a prior as someone's who did not
|
||||
sign it. The checks run in this order: the file reads, the signature verifies (`bad_key`,
|
||||
`bad_signature`), and the signer is the owner.
|
||||
|
||||
The owner's usage statement governs the prior as it governs any content
|
||||
([ai-usage](ai-usage.md) §5): it is the prior's signed statement, signed with it by the owner. A
|
||||
ranking states the use it made of the prior (§9.3), and a use the statement disallows is refused.
|
||||
A prior that travels in a WAI envelope carries the same statement in the envelope's manifest
|
||||
(ai-usage §4.1), and an agent that consumes it claims its use there as for any object.
|
||||
|
||||
This extension sets no consent requirement for a service's own mechanics: making an archive,
|
||||
measuring a distance, ranking with the prior a person chose. Using a distributor's WAI service is
|
||||
the person's agreement to how that service works. The usage statement is the owner's preference,
|
||||
read as ai-usage reads any statement. Where a regime asks for a lawful basis for processing a
|
||||
personal prior, the service records it in a `lawful-basis` claim
|
||||
([jwp-receipts](jwp-receipts.md) §2.4.6).
|
||||
|
||||
### 8.5 No default objective
|
||||
|
||||
This extension defines no taste prior of its own, ships none as a default, and ranks nothing
|
||||
without one. A personal prior and a community prior are the same kind of object: a community's
|
||||
taste is owned by the community's key, and sits beside each member's own, not above it. No prior
|
||||
is privileged by the specification, by a verifier or by a registry: what ranks a set is always a
|
||||
named prior someone owns, and anyone may compare what two priors do with the same pictures. A
|
||||
taste is as legitimate when it prefers what most priors reject: `far` makes an anti-aesthetic as
|
||||
expressible as an aesthetic.
|
||||
|
||||
## 9. Rankings
|
||||
|
||||
### 9.1 The record
|
||||
|
||||
A ranking, `kind` `"wai-ranking"`, says what a taste prior made of a set of pictures:
|
||||
|
||||
| member | type | meaning |
|
||||
|---|---|---|
|
||||
| `prior` | object | the prior's pin (§8.3) |
|
||||
| `descriptors` | string | `wai-exploration:image6` |
|
||||
| `candidates` | array | 1 to 256 pictures with descriptors |
|
||||
| `scores` | array | one integer per candidate, in their order |
|
||||
| `order` | array | the candidates' indices, best first: a permutation of 0 to *n* − 1 |
|
||||
| `usage` | array | 0 to 3 entries `{"vocabulary", "term"}`: the use made of the prior, in ai-usage §6.1's form, in strictly ascending order of `vocabulary` |
|
||||
| `issued_at`, `signer_pubkey`, `sig` | | §2 |
|
||||
|
||||
A `usage` entry's `vocabulary` is `aipref`, `cawg` or `tdmrep`, and its `term` a term of that
|
||||
vocabulary (ai-usage §6.2 rule 4) or `null`, a use outside it. Anything else is
|
||||
`record_malformed`. A ranking is signed by whoever ran it; the prior's owner signed the prior.
|
||||
|
||||
### 9.2 The order
|
||||
|
||||
Each candidate's score is the prior's score of its vector. The order is by score, greatest
|
||||
first; candidates of equal score keep their order in `candidates`, the lower index first.
|
||||
|
||||
### 9.3 The prior's usage
|
||||
|
||||
The prior's usage statement, where it has one, governs the use. For every vocabulary it carries,
|
||||
`usage` has an entry (`prior_usage_unanswered`); for each entry whose `term` is not `null`, the
|
||||
statement's preference for the term (ai-usage §3.2, §3.4, §3.5) is not disallow
|
||||
(`prior_usage_disallowed`). A ranking states the use it made in each vocabulary as a
|
||||
consumption claim does (ai-usage §6.1): running a prior to order pictures is the ranking signer's
|
||||
use, which it names. A prior with no statement governs nothing; a ranking MAY still state its
|
||||
use.
|
||||
|
||||
### 9.4 Verifying a ranking
|
||||
|
||||
After the record reads and its signature verifies:
|
||||
|
||||
1. The prior resolves and verifies (§8.3, §8.4).
|
||||
2. For each candidate whose samples the verifier holds: they are of its size
|
||||
(`samples_mismatch`), and their vector is its `descriptors` (`descriptor_mismatch`). A
|
||||
candidate whose samples are not held is scored from its stated vector.
|
||||
3. The scores and the order are those of §9.2 (`ranking_mismatch`).
|
||||
4. The usage is answered and allowed (§9.3).
|
||||
|
||||
A ranking that passes reproduces: any party holding the prior gets the same scores and the same
|
||||
order from the same vectors. The verifier reports whether every vector was recomputed from
|
||||
samples or some were taken as stated.
|
||||
|
||||
## 10. Disclosure of objectives
|
||||
|
||||
### 10.1 The member `objective`
|
||||
|
||||
A record of a generation, a selection or a ranking says which aesthetic or reward model steered
|
||||
it, or that none did, in the member `objective`:
|
||||
|
||||
| value | meaning |
|
||||
|---|---|
|
||||
| `"none"` | no aesthetic or reward model steered the record |
|
||||
| `{"type": "taste", "prior"}` | a taste prior of §8, by its pin: a verifier resolves it and runs it |
|
||||
| `{"type": "model", "sha256", "id"}` | another aesthetic or reward model, by the SHA-256 of its parameter set (SPEC §3.1) and a name for people: named, not run here |
|
||||
|
||||
A **taste objective** is a value of type `taste`. An archive's `objective` (§5.1) is the
|
||||
disclosure of its placement rule, and a ranking's `prior` is its objective: both are required
|
||||
members. A pick (§10.2) that a model steered **MUST** carry `objective` naming the model, and one
|
||||
that nothing steered **SHOULD** carry `"none"`. A record of another extension that a model may
|
||||
steer carries the same member where that extension adds it (§11).
|
||||
|
||||
### 10.2 The pick
|
||||
|
||||
A pick, `kind` `"wai-pick"`, says that a party generated or selected a work, and what steered it:
|
||||
|
||||
| member | type | meaning |
|
||||
|---|---|---|
|
||||
| `act` | string | `generate` or `select` |
|
||||
| `work` | picture | the work |
|
||||
| `from` | object | where it came from (below) |
|
||||
| `objective` | | OPTIONAL: §10.1 |
|
||||
| `issued_at`, `signer_pubkey`, `sig` | | §2 |
|
||||
|
||||
`from` is one of:
|
||||
|
||||
- `{"type": "archive", "archive", "niche"}`: a `select` of the elite of `niche` in the archive
|
||||
whose link hash is `archive`;
|
||||
- `{"type": "set", "set"}`: a `select` of one of 1 to 256 samples digests;
|
||||
- `{"type": "generation", "generator", "model", "prompt", "seed", "steps"}`: a `generate`, by a
|
||||
generator term, a model term (for `wai.generate.spectral`, `sha256:` and the model file's
|
||||
SHA-256), a prompt (§4.1), and a seed (below 2^32) and steps (1 to 64), each `null` where the
|
||||
generator has none.
|
||||
|
||||
A `generate` whose `from` is not a generation, or a `select` whose `from` is one, is
|
||||
`record_malformed`.
|
||||
|
||||
A pick from an archive **inherits** the elite's genome: its seed, steps and modifier mask, and so
|
||||
its derived prompt. A pick from an archive whose objective is a taste prior was steered by that
|
||||
prior, since the prior chose every elite: its `objective` MUST name the same prior
|
||||
(`objective_mismatch`), absent or not.
|
||||
|
||||
### 10.3 Policy
|
||||
|
||||
A verifier sees steering only where a record shows it: through an archive a pick names (§10.2),
|
||||
and otherwise not. So a missing `objective` is never read as `none`: the record is
|
||||
**undisclosed**, and a verifier reports it so. A policy MAY require the member of every pick
|
||||
(§12.1); under it, a pick without `objective` is `objective_undisclosed`. Every archive and ranking carries its objective by
|
||||
construction. A universal aesthetic objective therefore cannot steer a record silently: it is
|
||||
named by digest, or the record says that nothing steered it, or the record is undisclosed and a
|
||||
policy that requires disclosure refuses it.
|
||||
|
||||
### 10.4 Verifying a pick
|
||||
|
||||
After the record reads and its signature verifies, in this order:
|
||||
|
||||
1. Without `objective`, under a policy that requires it: `objective_undisclosed`.
|
||||
2. A taste objective's prior resolves and verifies (§8.3, §8.4).
|
||||
3. Held samples of the work are of its size (`samples_mismatch`).
|
||||
4. From an archive: the verifier holds the archive, verified earlier in the same case
|
||||
(`archive_unheld`); the archive has an elite at `niche` whose samples digest is the work's, and
|
||||
the work's size is the archive's (`pick_not_in_archive`); and where the archive's
|
||||
objective is a taste prior, the pick's `objective` names that prior's digest
|
||||
(`objective_mismatch`).
|
||||
5. From a set: the work's samples digest is one of the set (`pick_not_in_set`).
|
||||
6. From a generation by `wai.generate.spectral` with a seed and steps: the verifier holds the
|
||||
model (`model_unheld`), and `gen` at the work's size has the work's samples digest
|
||||
(`generation_mismatch`). Any other generation is taken as stated.
|
||||
|
||||
The verifier reports the objective (`none`, the type and digest it names, or `undisclosed`),
|
||||
how the work was checked (`in-archive`, `in-set`, `regenerated` or `stated`) and, for a pick
|
||||
from an archive or a regenerated generation, the genome.
|
||||
|
||||
## 11. The slot contribution fills
|
||||
|
||||
The contribution record, `wai.media.contribution`, records a creative session step by step: its
|
||||
`candidates` step commits a set of pictures by samples digest and seed, and its `select` step
|
||||
picks one. This revision leaves it one slot and one member, which that extension fills:
|
||||
|
||||
- **`archive`**: a `candidates` step MAY name an archive record by its link hash. The set it
|
||||
commits is then the archive's elites in ascending niche order: each member's samples digest is
|
||||
the elite's `samples`, and its generation is the elite's genome, the seed, the steps and the
|
||||
derived prompt (§4.3), at the archive's model and size. A `select` of a member of such a set
|
||||
inherits the elite's genome as a pick from the archive does (§10.2). How the member is written
|
||||
in a step, and how a step that names an archive is checked against it, are contribution's.
|
||||
- **`objective`**: a step that a model steered carries the member of §10.1 with its meaning
|
||||
there; a `select` from a set an archive steered by a taste prior carries that prior.
|
||||
|
||||
Nothing here depends on contribution: an archive, a pick and their checks stand alone.
|
||||
|
||||
## 12. Verification
|
||||
|
||||
### 12.1 Inputs
|
||||
|
||||
A verifier is given a **case**: records in an order, as texts; what it holds, each found by its
|
||||
SHA-256 (model files, taste prior files, and `RGB8` sample buffers); and its policy:
|
||||
|
||||
- `replay`: `required`, every archive is replayed (§5.4 step 6); or `elites`, it is
|
||||
elite-checked;
|
||||
- `objective`: `required`, every pick carries `objective` (§10.3); or `optional`.
|
||||
|
||||
### 12.2 Steps
|
||||
|
||||
The records are verified in their order, each by its section: §5.4, §6.4, §7.3, §9.4 or §10.4,
|
||||
by its `kind`. A record whose `kind` is none of `wai-archive`, `wai-distance`,
|
||||
`wai-collection`, `wai-ranking` and `wai-pick`, or that does not read, is `record_malformed`.
|
||||
Each section's checks follow reading and the signature (§2), in the order the section gives;
|
||||
the first that fails is the case's refusal, with the record's position. An archive that a pick
|
||||
names is held only once it has been verified, earlier in the case.
|
||||
|
||||
### 12.3 What an accepted case states
|
||||
|
||||
For each record, in order, its `kind` and:
|
||||
|
||||
- an archive: its link hash, `replayed` or `elites`, `occupied`, `cells`, and its objective
|
||||
(`none`, or `{"type", "sha256"}`);
|
||||
- a distance: its link hash, its class and the four figures, `null` where unknown;
|
||||
- a collection: its link hash, the number of members, `occupied`, `cells` and `distances`;
|
||||
- a ranking: its link hash, the prior's digest, owner and scope, the order, `recomputed` or
|
||||
`stated`, and for each `usage` entry its vocabulary, term and preference (`allow`, `unknown`,
|
||||
`outside` for a `null` term, or `absent` when the prior's statement does not carry the
|
||||
vocabulary);
|
||||
- a pick: its link hash, its act, its objective, its check, and its genome: `seed`, `steps`,
|
||||
`mods` (0 for a generation) and the derived `prompt`, or `null` for a pick from a set or a
|
||||
generation taken as stated.
|
||||
|
||||
## 13. Reasons
|
||||
|
||||
The codes this extension adds, each with where it is raised:
|
||||
|
||||
| reason | raised at |
|
||||
|---|---|
|
||||
| `record_malformed` | §2, §5.1, §6.3, §8.1, §9.1, §10.2, §12.2 |
|
||||
| `bad_key` | §2 |
|
||||
| `bad_signature` | §2 |
|
||||
| `model_unheld` | §4.1, §5.4 step 4, §6.4 step 3, §10.4 step 6 |
|
||||
| `objective_unresolved` | §8.3 |
|
||||
| `taste_not_owner` | §8.4 |
|
||||
| `objective_undisclosed` | §10.3, §10.4 step 1 |
|
||||
| `objective_mismatch` | §10.2, §10.4 step 4 |
|
||||
| `generation_mismatch` | §5.4 step 5, §10.4 step 6 |
|
||||
| `samples_mismatch` | §6.4 step 2, §7.3, §9.4 step 2, §10.4 step 3 |
|
||||
| `descriptor_mismatch` | §5.4 step 5, §6.4, §7.3, §9.4 step 2 |
|
||||
| `niche_mismatch` | §5.4 step 5 |
|
||||
| `score_mismatch` | §5.4 step 5 |
|
||||
| `coverage_mismatch` | §5.4 step 2, §7.3 |
|
||||
| `archive_replay_mismatch` | §5.4 step 6 |
|
||||
| `distance_class_invalid` | §6.3, §6.4 step 1 |
|
||||
| `default_mismatch` | §6.4 step 3 |
|
||||
| `work_unheld` | §6.4 step 3 |
|
||||
| `distance_mismatch` | §6.4 step 6 |
|
||||
| `member_unheld` | §7.3 |
|
||||
| `distances_mismatch` | §7.3 |
|
||||
| `ranking_mismatch` | §9.4 step 3 |
|
||||
| `prior_usage_unanswered` | §9.3 |
|
||||
| `prior_usage_disallowed` | §9.3 |
|
||||
| `archive_unheld` | §10.4 step 4 |
|
||||
| `pick_not_in_archive` | §10.4 step 4 |
|
||||
| `pick_not_in_set` | §10.4 step 5 |
|
||||
|
||||
## 14. Conformance
|
||||
|
||||
[`live-conformance/exploration/`](../live-conformance/exploration/) holds the corpus, written from
|
||||
fixed seeds by `wai_exploration_vectors gen` as `vectors.json`. It has:
|
||||
|
||||
- **Generations**: pictures of `wai.generate.spectral` at small sizes with their samples digests,
|
||||
among them prompts whose words select each component type, a negative orientation and a
|
||||
gradient, and a derived prompt: the check of Appendix A.
|
||||
- **Descriptors**: buffers with their vectors, among them a single pixel, a single row and a
|
||||
single column, flat buffers, a checkerboard and generated pictures.
|
||||
- **Archives**: archives without an objective and under a taste prior, with modifiers and
|
||||
without, replayed and elite-checked; and one refused for each defect of §5.4, among them a
|
||||
niche whose descriptor does not recompute, a seed that does not regenerate its picture, an
|
||||
elite dropped (which every elite check passes and replay refuses), and a prior signed by a key
|
||||
other than its owner's.
|
||||
- **Distances**: a measured distance of a work pulled from the defaults and of a work that is a
|
||||
default (sample distance 0), an estimate and an unknown; and one refused for each defect of
|
||||
§6.4, among them a distance stated wrong, a measured figure over a generator that is not exact,
|
||||
and an unknown written as 0.
|
||||
- **Collections**: the default set and an archive's elites as galleries; and one refused for each
|
||||
defect of §7.3.
|
||||
- **Taste priors and rankings**: a personal prior, a community prior with a usage statement and
|
||||
an anti-aesthetic prior, each ranking the same pictures, all three written in code with
|
||||
hand-set weights; a ranking from stated vectors; and one refused for each defect of §9.4,
|
||||
among them a ranking that does not reproduce, a prior signed by a non-owner, a use the owner's
|
||||
statement disallows and a prior file not in canonical form.
|
||||
- **Picks**: picks from an archive (inheriting the elite's genome), from a set and from a
|
||||
generation, under each kind of objective and undisclosed; and one refused for each defect of
|
||||
§10.4, among them a pick without `objective` under a policy that requires it and a pick from a
|
||||
prior-steered archive that states `none`.
|
||||
|
||||
A conforming verifier gives every case the outcome, the reason, the record and the statements
|
||||
the corpus states. `wai_exploration_vectors verify` and the test `tests/exploration.rs` check
|
||||
that the file regenerates byte for byte, that `wai-rs` gives every case its outcome, that every
|
||||
reason of §13 appears, and that §13 lists exactly the reference's codes.
|
||||
`live-conformance/exploration_verify.py` is the second implementation, written from this text:
|
||||
it shares no code with `wai-rs`; it implements the generator from Appendix A and the descriptors
|
||||
from §3, reads usage statements with the AI usage corpus's second implementation
|
||||
(`ai_usage_verify.py`), checks every signature with WAI's strict rule
|
||||
(`claim-conformance/_ed25519.py`) and every link hash with BLAKE3 from
|
||||
`claim-conformance/_blake3.py`; it mutates copies of the corpus and requires every copy to fail,
|
||||
and drops each of its own checks in turn and requires the corpus to catch the weakened verifier.
|
||||
|
||||
## 15. Security and privacy considerations
|
||||
|
||||
- **Cost.** Replaying an archive costs `trials` generations at its size, and making a default
|
||||
set sixteen. A verifier bounds what it will spend before it starts; a case it will not
|
||||
replay under a policy that requires replay is declined, not accepted elite-checked.
|
||||
- **What is proved.** An elite-checked or replayed archive proves what the generator makes from
|
||||
each genome, and, replayed, that the schedule kept those pictures. A measured distance proves
|
||||
how far a work is from the defaults. Neither says who made the work, which the contribution
|
||||
record and [synthesis](synthesis.md) record.
|
||||
- **Gaming a distance.** A work far from the defaults is not thereby original: a distance is a
|
||||
measurement, not a judgement, and a collection statement shows whether many works moved away
|
||||
from the defaults in the same direction.
|
||||
- **Taste describes a person.** A personal prior says what one person likes. It is ascribed to a
|
||||
person only by that person's signature (§8.4), and its owner's usage statement travels with it.
|
||||
A party that holds a ranking learns how a prior ordered those pictures, which is what a ranking
|
||||
is for. Where a regime asks for a lawful basis for processing it, that is a `lawful-basis`
|
||||
claim's (§8.4).
|
||||
- **Undisclosed steering.** A pick without `objective` is reported undisclosed, never as `none`.
|
||||
A party that steers a selection with a model and states `none` makes a false signed statement;
|
||||
where the steering came from an archive the pick names, the verifier sees it (§10.2).
|
||||
- **Descriptors are coarse.** Six numbers do not describe a picture. Two pictures that differ in
|
||||
every way a viewer cares about can share a vector; coverage counts niches of a grid, not
|
||||
ideas.
|
||||
|
||||
## Appendix A. The generator, restated
|
||||
|
||||
This appendix restates the reference generator of `wai.generate.spectral`, `generate.rs`, for a
|
||||
second implementation. The reference governs; the corpus's generations check that the two agree
|
||||
(§14). Integers here are unbounded or 64-bit as stated; ÷ divides and truncates toward zero;
|
||||
*a* mod *b* for *b* > 0 is the remainder in 0 to *b* − 1; >> on a negative integer is the floor
|
||||
of division by a power of two.
|
||||
|
||||
**A.1 The model file** is a JSON object with: `sine_lut`, an array of `lut_size` integers;
|
||||
`lut_size` (*T*); `lut_bits` (14 when absent); `freq_max`; `amp_bits`; `color_offsets`, three
|
||||
integers; `base_components`; `step_components`; and `lexicon`, an object mapping a word to an
|
||||
array of up to ten integers, read as `[r, g, b, freq, orient, radial, mirror, gradient, angular,
|
||||
spiral]` with absent entries 0.
|
||||
|
||||
**A.2 The intent.** The prompt's ASCII letters are lowered; its **tokens** are its maximal runs of
|
||||
ASCII letters and digits, in order, each counted every time it occurs. Over the tokens that are
|
||||
words of the lexicon, sum each of the ten entries; *n* is the number of such tokens. When *n* is
|
||||
0 the intent is hue (256, 256, 256), freq 256, and every other value 0. Otherwise, with *m* the
|
||||
greatest of the three colour sums and at least 1: hue*c* = sum*c* · 256 ÷ *m*; freq = sum ÷ *n*;
|
||||
orient and gradient = sum ÷ *n* clamped to −256 to 256; radial, mirror, angular and spiral = sum
|
||||
÷ *n* clamped to 0 to 256.
|
||||
|
||||
**A.3 The draw.** *K* = `base_components` + `steps` · `step_components`. With orient < 0,
|
||||
(*sx*, *sy*) = (max(256 + orient, 0), 256); otherwise (256, max(256 − orient, 0)). *F* =
|
||||
max(`freq_max` · freq ÷ 256, 1); span = 2*F* + 1; *A* = 2^`amp_bits`. The PRNG is §5.2's
|
||||
splitmix64, with its state starting at fnv(prompt) ⊕ seed ⊕ 0xD1B54A32D192ED03, where fnv is
|
||||
64-bit FNV-1a over the prompt's bytes (offset basis 14695981039346656037, prime 1099511628211).
|
||||
With *t*₁ … *t*₄ = radial, mirror, angular, spiral, *P* = max(256 − Σ*t*, 48) and total = *P* +
|
||||
Σ*t*, each of the *K* components draws, in this order: pick = draw mod total; *fx* = (draw mod
|
||||
span) − *F*; *fy* = (draw mod span) − *F*; amp = (draw mod *A*) + 1; phase = draw mod *T*. Its
|
||||
type is plane when pick < *P*, and otherwise the first *i* from 1 to 4 with pick < *P* + *t*₁ + … +
|
||||
*t*ᵢ: 1 radial, 2 mirror, 3 angular, 4 spiral. Its frequencies are: radial (max(\|*fx*\|, 2), 0);
|
||||
angular (\|*fx*\| mod 11 + 2, 0); spiral (\|*fx*\| mod 9 + 2, max(\|*fy*\|, 2)); plane and mirror
|
||||
(*fx* · *sx* ÷ 256, *fy* · *sy* ÷ 256).
|
||||
|
||||
**A.4 The field.** *cx* = ⌊*w* / 2⌋, *cy* = ⌊*h* / 2⌋; gscale = (2^`lut_bits` · *A* · *K*) ÷ (2048 ·
|
||||
max(*cy*, 1)). For each channel *c* with offset *o* = `color_offsets`[*c*] and each pixel (*x*,
|
||||
*y*), with *dx* = *x* − *cx*, *dy* = *y* − *cy*, *r* = ⌊√(*dx*² + *dy*²)⌋ and θ = iatan2(*dy*, *dx*):
|
||||
the accumulator is gradient · (*cy* − *y*) · gscale plus, over the components, amp ·
|
||||
`sine_lut`[arg mod *T*], where arg is *fx* · *x* + *fy* · *y* + phase + *o* for a plane,
|
||||
*fx* · *r* + phase + *o* for a radial, *fx* · \|*dx*\| + *fy* · \|*dy*\| + phase + *o* for a mirror,
|
||||
*fx* · θ + phase + *o* for an angular and *fx* · θ + *fy* · *r* + phase + *o* for a spiral component.
|
||||
|
||||
**A.5 iatan2(*dy*, *dx*)**, an angle in 0 to *T* − 1: when *dx* = 0, it is 0 for *dy* = 0, *T* ÷ 4
|
||||
for *dy* > 0 and 3*T* ÷ 4 otherwise; when *dy* = 0, 0 for *dx* > 0 and *T* ÷ 2 otherwise. Else, *X*
|
||||
= \|*dx*\| · 2^12, *Y* = \|*dy*\| · 2^12, ang = 0, and for *i* from 0 to 13 with the steps (128, 76,
|
||||
40, 20, 10, 5, 3, 1, 1, 0, 0, 0, 0, 0): when *Y* > 0, (*X*, *Y*) = (*X* + (*Y* >> *i*), *Y* − (*X* >>
|
||||
*i*)) and ang increases by the step; when *Y* < 0, (*X*, *Y*) = (*X* − (*Y* >> *i*), *Y* + (*X* >>
|
||||
*i*)) and ang decreases by it. The angle is ang for *dx* ≥ 0 and *dy* ≥ 0, *T* ÷ 2 − ang for *dx* < 0
|
||||
and *dy* ≥ 0, *T* ÷ 2 + ang for *dx* < 0 and *dy* < 0, and *T* − ang for *dx* ≥ 0 and *dy* < 0, each
|
||||
taken mod *T*.
|
||||
|
||||
**A.6 The bytes.** With *g*₀ and *g*₁ the least and greatest of all 3*n* accumulators and range =
|
||||
max(*g*₁ − *g*₀, 1), and *e*_c = 64 + hue_c · 192 ÷ 256: each accumulator *v* of channel *c* gives
|
||||
*q* = ((*v* − *g*₀) · 255 + range ÷ 2) ÷ range and the byte min(max(*q* · *e*_c ÷ 256, 0), 255), at
|
||||
the pixel's position in the `RGB8` buffer.
|
||||
|
|
@ -24,6 +24,7 @@ and the independent verifier of each, beside it.
|
|||
| [`settlement/`](settlement/) | [settlement](../extensions/settlement.md) (`wai.delivery.settlement`) | `wai_settlement_vectors gen` | `settlement_verify.py` |
|
||||
| [`access/`](access/) | [access-services](../extensions/access-services.md) (`wai.access.services`) | `wai_access_services_vectors gen` | `access_services_verify.py` |
|
||||
| [`presentation/`](presentation/) | [ad-presentation](../extensions/ad-presentation.md) (`wai.live.ad_presentation`) | `wai_ad_presentation_vectors gen` | `ad_presentation_verify.py` |
|
||||
| [`exploration/`](exploration/) | [exploration](../extensions/exploration.md) (`wai.media.exploration`) | `wai_exploration_vectors gen` | `exploration_verify.py` |
|
||||
|
||||
**Provenance.** Generated here: each directory's corpus is written by its row's generator
|
||||
(`gen`, in `wai/wai-rs`), from records authored in code and from fixed seeds, and each
|
||||
|
|
@ -59,4 +60,5 @@ python3 synthesis_verify.py synthesis
|
|||
python3 ai_usage_verify.py usage
|
||||
python3 contribution_verify.py contribution
|
||||
python3 settlement_verify.py settlement
|
||||
python3 exploration_verify.py exploration
|
||||
```
|
||||
|
|
|
|||
37
wai/live-conformance/exploration/README.md
Normal file
37
wai/live-conformance/exploration/README.md
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# live-conformance/exploration — exploration and declared taste
|
||||
|
||||
The corpus of [`extensions/exploration.md`](../../extensions/exploration.md) §14, in one file,
|
||||
`vectors.json`:
|
||||
|
||||
- `model`: the model file of `wai.generate.spectral` every generation uses, by its `path` below
|
||||
`wai/` and its `sha256`;
|
||||
- `generations`: a `prompt`, `seed`, `steps`, `width` and `height`, and the `sha256` of the
|
||||
picture the generator makes (the check of the extension's Appendix A);
|
||||
- `descriptors`: a buffer, given as `hex` or as a `generate` instruction (`prompt`, `seed`,
|
||||
`steps`, `width`, `height`), its `width` and `height`, and its descriptor vector (§3);
|
||||
- `cases`: each a `policy` (`replay`: `required` or `elites`; `objective`: `required` or
|
||||
`optional`), its `records` in order as JSON texts, and what the verifier `held`: `models` (the
|
||||
SHA-256 of each model file it holds; the one file above, or none), `priors` (each taste prior
|
||||
file's text, held by the SHA-256 of its UTF-8 bytes) and `samples` (each buffer, as `hex` or a
|
||||
`generate` instruction, held by its SHA-256).
|
||||
|
||||
**Exploration equivalence.** For each case, a conforming verifier reaches the outcome it states:
|
||||
|
||||
- `refuse`: the `reason` of §13 and the `record`, the position of the record refused;
|
||||
- `accept`: the `statements` of §12.3, one per record, exactly as written.
|
||||
|
||||
A verifier that reaches another outcome, reason, record or statement does not conform.
|
||||
|
||||
**Provenance.** `wai_exploration_vectors gen` writes `vectors.json` from records authored in
|
||||
code: every key is SHA-256 of the corpus's label and an index, every picture is made by
|
||||
`wai.generate.spectral` from the committed model file `wai-rs/tests/vectors/generate/generate_spectral_model.json`
|
||||
or derived from one by a rule written in the generator (a corner inverted), the remaining
|
||||
buffers (a pixel, a row, a column, flat, a checkerboard, a ramp) are written as formulas there,
|
||||
every time is a constant, and Ed25519 signing is deterministic, so the file is the same bytes on
|
||||
every machine. The three taste priors are written in code with hand-set weights; none is trained
|
||||
on anyone's choices. The generator `urn:example:generator:float-1` and the aesthetic model a pick
|
||||
names stand for any generator and model that are not exact: nothing in the corpus runs them.
|
||||
|
||||
```
|
||||
python3 -I ../exploration_verify.py .
|
||||
```
|
||||
3705
wai/live-conformance/exploration/vectors.json
Normal file
3705
wai/live-conformance/exploration/vectors.json
Normal file
File diff suppressed because it is too large
Load diff
1230
wai/live-conformance/exploration_verify.py
Normal file
1230
wai/live-conformance/exploration_verify.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -840,6 +840,20 @@
|
|||
"edition": null,
|
||||
"registered": "2026-10-21"
|
||||
},
|
||||
{
|
||||
"capability": "wai.media.exploration",
|
||||
"kind": "record",
|
||||
"status": "registered",
|
||||
"media": null,
|
||||
"role": null,
|
||||
"tier": null,
|
||||
"licence": null,
|
||||
"replicate": null,
|
||||
"sets": [],
|
||||
"spec_section": "§5 Interactive, temporal & computed content",
|
||||
"edition": null,
|
||||
"registered": "2026-10-21"
|
||||
},
|
||||
{
|
||||
"capability": "wai.media.synthesis",
|
||||
"kind": "record",
|
||||
|
|
@ -2445,6 +2459,13 @@
|
|||
"section": "§7.1",
|
||||
"status": "defined"
|
||||
},
|
||||
{
|
||||
"name": "wai-archive",
|
||||
"kind": "document-kind",
|
||||
"document": "extensions/exploration.md",
|
||||
"section": "§5.3",
|
||||
"status": "defined"
|
||||
},
|
||||
{
|
||||
"name": "wai-authorship-map",
|
||||
"kind": "document-kind",
|
||||
|
|
@ -2480,6 +2501,13 @@
|
|||
"section": "§4.2",
|
||||
"status": "defined"
|
||||
},
|
||||
{
|
||||
"name": "wai-collection",
|
||||
"kind": "document-kind",
|
||||
"document": "extensions/exploration.md",
|
||||
"section": "§7.1",
|
||||
"status": "defined"
|
||||
},
|
||||
{
|
||||
"name": "wai-contribution",
|
||||
"kind": "document-kind",
|
||||
|
|
@ -2508,6 +2536,13 @@
|
|||
"section": "§7.3",
|
||||
"status": "defined"
|
||||
},
|
||||
{
|
||||
"name": "wai-distance",
|
||||
"kind": "document-kind",
|
||||
"document": "extensions/exploration.md",
|
||||
"section": "§6.3",
|
||||
"status": "defined"
|
||||
},
|
||||
{
|
||||
"name": "wai-key-status",
|
||||
"kind": "document-kind",
|
||||
|
|
@ -2557,6 +2592,13 @@
|
|||
"section": "§3.1",
|
||||
"status": "defined"
|
||||
},
|
||||
{
|
||||
"name": "wai-pick",
|
||||
"kind": "document-kind",
|
||||
"document": "extensions/exploration.md",
|
||||
"section": "§10.2",
|
||||
"status": "defined"
|
||||
},
|
||||
{
|
||||
"name": "wai-player",
|
||||
"kind": "document-kind",
|
||||
|
|
@ -2564,6 +2606,13 @@
|
|||
"section": "§10.3",
|
||||
"status": "defined"
|
||||
},
|
||||
{
|
||||
"name": "wai-ranking",
|
||||
"kind": "document-kind",
|
||||
"document": "extensions/exploration.md",
|
||||
"section": "§9.1",
|
||||
"status": "defined"
|
||||
},
|
||||
{
|
||||
"name": "wai-receipt-index",
|
||||
"kind": "document-kind",
|
||||
|
|
@ -2613,6 +2662,13 @@
|
|||
"section": "§4.1",
|
||||
"status": "defined"
|
||||
},
|
||||
{
|
||||
"name": "wai-taste",
|
||||
"kind": "document-kind",
|
||||
"document": "extensions/exploration.md",
|
||||
"section": "§8.1",
|
||||
"status": "defined"
|
||||
},
|
||||
{
|
||||
"name": "wai1",
|
||||
"kind": "codecs-prefix",
|
||||
|
|
|
|||
|
|
@ -653,6 +653,13 @@ wai:wai.media.contribution a wai:Capability ;
|
|||
wai:specSection "§5 Interactive, temporal & computed content" ;
|
||||
wai:registeredOn "2026-10-21"^^xsd:date .
|
||||
|
||||
wai:wai.media.exploration a wai:Capability ;
|
||||
rdfs:label "wai.media.exploration" ;
|
||||
wai:kind wai:kind-record ;
|
||||
wai:status wai:status-registered ;
|
||||
wai:specSection "§5 Interactive, temporal & computed content" ;
|
||||
wai:registeredOn "2026-10-21"^^xsd:date .
|
||||
|
||||
wai:wai.media.synthesis a wai:Capability ;
|
||||
rdfs:label "wai.media.synthesis" ;
|
||||
wai:kind wai:kind-record ;
|
||||
|
|
|
|||
|
|
@ -330,6 +330,15 @@ ai_usage = ["live_room"]
|
|||
# declared bytes, per-class joules from live-metering's WMH1 and integer amount a verifier
|
||||
# recomputes. SHA-256, BLAKE3, Ed25519 and the room's records only; no new dependency.
|
||||
settlement = ["live_metering", "jwp_hash"]
|
||||
# Exploration (extensions/exploration): `wai.media.exploration` — six integer descriptors over a
|
||||
# picture's RGB8 samples; quality-diversity archives (MAP-Elites over a declared grid, run by a
|
||||
# pinned schedule over wai.generate.spectral, so every elite regenerates and the archive replays
|
||||
# bit for bit); the default set of a prompt and a work's default-distance (measured for the exact
|
||||
# generator, an estimate otherwise, unknown never zero); collection statements (coverage and
|
||||
# pairwise distances); taste priors (integer ranking models, signed by their owners, carrying a
|
||||
# wai.ai.usage statement), rankings under them, and the disclosure of what steered a pick.
|
||||
# SHA-256, BLAKE3, Ed25519 and ai_usage's statement reader; no new dependency.
|
||||
exploration = ["generate", "ai_usage"]
|
||||
# Timed text and live captions (extensions/timed-text): `wai.text.timed` — the cue model,
|
||||
# live caption revisions (`caption` events of wai.live.events with the WTX1 annotation),
|
||||
# and their exact mappings onto WebVTT documents and segments, the IMSC1 Text Profile,
|
||||
|
|
@ -845,6 +854,11 @@ name = "wai_settlement_vectors"
|
|||
path = "src/bin/wai_settlement_vectors.rs"
|
||||
required-features = ["settlement"]
|
||||
|
||||
[[bin]]
|
||||
name = "wai_exploration_vectors"
|
||||
path = "src/bin/wai_exploration_vectors.rs"
|
||||
required-features = ["exploration"]
|
||||
|
||||
[[bin]]
|
||||
name = "wai_timed_text_vectors"
|
||||
path = "src/bin/wai_timed_text_vectors.rs"
|
||||
|
|
@ -1585,6 +1599,11 @@ name = "settlement"
|
|||
path = "tests/settlement.rs"
|
||||
required-features = ["settlement"]
|
||||
|
||||
[[test]]
|
||||
name = "exploration"
|
||||
path = "tests/exploration.rs"
|
||||
required-features = ["exploration"]
|
||||
|
||||
[[test]]
|
||||
name = "timed_text"
|
||||
path = "tests/timed_text.rs"
|
||||
|
|
|
|||
139
wai/wai-rs/src/bin/wai_exploration_vectors.rs
Normal file
139
wai/wai-rs/src/bin/wai_exploration_vectors.rs
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
//! Conformance corpus for exploration (`live-conformance/exploration/`, extensions/exploration.md §14).
|
||||
//!
|
||||
//! wai_exploration_vectors gen <corpus-dir> write vectors.json from fixed seeds
|
||||
//! wai_exploration_vectors verify <corpus-dir> every file regenerates byte for byte, no other
|
||||
//! file but README.md is there, and every case
|
||||
//! gets the outcome it states
|
||||
//! wai_exploration_vectors measure [trials] the archive against plain seed sampling and
|
||||
//! uniform genome sampling at equal compute
|
||||
//!
|
||||
//! The generator and this verifier are one implementation. The corpus's second
|
||||
//! implementation is `live-conformance/exploration_verify.py`, written from the extension's
|
||||
//! text and sharing no code with this binary.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
use wai::exploration::{self, corpus, Axis, Objective, Request};
|
||||
use wai::generate::SpectralModel;
|
||||
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
match (a.get(1).map(String::as_str), a.get(2)) {
|
||||
(Some("gen"), Some(d)) => {
|
||||
if let Err(e) = std::fs::create_dir_all(d) {
|
||||
eprintln!("create {d}: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
for (name, body) in corpus::generate() {
|
||||
let p = Path::new(d).join(&name);
|
||||
if let Err(e) = std::fs::write(&p, body) {
|
||||
eprintln!("write {}: {e}", p.display());
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
println!("wrote {d}");
|
||||
}
|
||||
(Some("verify"), Some(d)) => std::process::exit(verify(Path::new(d))),
|
||||
(Some("measure"), n) => measure(n.and_then(|s| s.parse().ok()).unwrap_or(512)),
|
||||
_ => {
|
||||
eprintln!("usage: wai_exploration_vectors gen|verify <corpus-dir> | measure [trials]");
|
||||
std::process::exit(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn verify(dir: &Path) -> i32 {
|
||||
let mut files = BTreeMap::new();
|
||||
let entries = match std::fs::read_dir(dir) {
|
||||
Ok(e) => e,
|
||||
Err(e) => {
|
||||
eprintln!("read {}: {e}", dir.display());
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
for e in entries.flatten() {
|
||||
let name = e.file_name().to_string_lossy().into_owned();
|
||||
if name == "README.md" {
|
||||
continue;
|
||||
}
|
||||
match std::fs::read_to_string(e.path()) {
|
||||
Ok(s) => {
|
||||
files.insert(name, s);
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("read {name}: {err}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
let generated: BTreeMap<String, String> = corpus::generate().into_iter().collect();
|
||||
let same = files == generated;
|
||||
println!("{} every file regenerates byte for byte from its seeds, and no other file is there", if same { "PASS" } else { "FAIL" });
|
||||
let fails = corpus::verify(&files);
|
||||
for f in &fails {
|
||||
println!("FAIL {f}");
|
||||
}
|
||||
println!("{} every case gets the outcome the corpus states", if fails.is_empty() { "PASS" } else { "FAIL" });
|
||||
(!same || !fails.is_empty()) as i32
|
||||
}
|
||||
|
||||
/// The measurement the pull request reports: for each prompt, the cells of one grid that plain
|
||||
/// seed sampling (seeds 0, 1, 2, … at the request's steps, the prompt as written), uniform sampling
|
||||
/// of the archive's genome space (a random seed, steps 1 to 16 and any modifier mask) and the
|
||||
/// archive's schedule reach with the same number of generations, each of the last two summed over
|
||||
/// three search seeds; and the mean pairwise descriptor distance of one representative per cell
|
||||
/// reached. Informative: it changes no conformance outcome.
|
||||
fn measure(trials: u64) {
|
||||
let p = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/vectors/generate/generate_spectral_model.json");
|
||||
let bytes = std::fs::read(&p).expect("the committed model");
|
||||
let m = SpectralModel::from_value(&serde_json::from_slice(&bytes).expect("JSON"));
|
||||
let modifiers: Vec<String> = ["bright", "dark", "calm", "busy", "warm", "cool", "spiral", "mirror"].map(String::from).to_vec();
|
||||
let grid = vec![
|
||||
Axis { descriptor: 0, lo: 128, hi: 896, bins: 8 },
|
||||
Axis { descriptor: 1, lo: 128, hi: 896, bins: 8 },
|
||||
Axis { descriptor: 2, lo: 0, hi: 512, bins: 8 },
|
||||
];
|
||||
let prompts = [
|
||||
"a quiet forest at night",
|
||||
"ocean spiral vortex at dusk",
|
||||
"a red rose",
|
||||
"neon city grid",
|
||||
"golden sunrise over the desert",
|
||||
"portrait of a cat",
|
||||
];
|
||||
const RUNS: u64 = 3;
|
||||
println!("{trials} generations each, 64x64, grid luma x chroma x detail (8 x 8 x 8 = 512 cells), modifiers {modifiers:?}");
|
||||
println!("cells reached, each column summed over {RUNS} search seeds (plain seed sampling has none: it is the same each time)");
|
||||
println!("| prompt | seeds 0..{} | uniform genome | archive | mean pair distance: seeds | uniform | archive |", trials - 1);
|
||||
println!("|---|---|---|---|---|---|---|");
|
||||
let mut tot = [0u64; 3];
|
||||
for (i, prompt) in prompts.iter().enumerate() {
|
||||
let mut cells = [0u64; 3];
|
||||
let mut means = [0u64; 3];
|
||||
for run in 0..RUNS {
|
||||
let r = Request {
|
||||
model: exploration::digest(&bytes),
|
||||
prompt: prompt.to_string(),
|
||||
modifiers: modifiers.clone(),
|
||||
width: 64,
|
||||
height: 64,
|
||||
steps: 4,
|
||||
grid: grid.clone(),
|
||||
seed: 1 + i as u64 + 100 * run,
|
||||
trials,
|
||||
init: (trials / 8).max(1),
|
||||
objective: Objective::None,
|
||||
};
|
||||
let reach = [exploration::reach_seeds(&m, &r), exploration::reach_random(&m, &r), exploration::reach_archive(&m, &r)];
|
||||
for (k, x) in reach.iter().enumerate() {
|
||||
cells[k] += x.occupied;
|
||||
means[k] += x.spread.as_ref().map_or(0, |s| s.sum / s.pairs);
|
||||
}
|
||||
}
|
||||
println!("| {prompt} | {} | {} | {} | {} | {} | {} |", cells[0], cells[1], cells[2], means[0] / RUNS, means[1] / RUNS, means[2] / RUNS);
|
||||
for (t, c) in tot.iter_mut().zip(cells) {
|
||||
*t += c;
|
||||
}
|
||||
}
|
||||
println!("| all | {} | {} | {} | | | |", tot[0], tot[1], tot[2]);
|
||||
}
|
||||
|
|
@ -229,6 +229,7 @@ pub const CAP_AI_USAGE: &str = "wai.ai.usage";
|
|||
pub const CAP_MEDIA_CONTRIBUTION: &str = "wai.media.contribution";
|
||||
pub const CAP_DELIVERY_SETTLEMENT: &str = "wai.delivery.settlement";
|
||||
pub const CAP_ACCESS_SERVICES: &str = "wai.access.services";
|
||||
pub const CAP_MEDIA_EXPLORATION: &str = "wai.media.exploration";
|
||||
|
||||
/// One row per string SPEC §5 registers that is a payload, companion or record:
|
||||
/// `(string, SPEC section, edition, registered)`.
|
||||
|
|
@ -326,6 +327,7 @@ const ROWS: &[(&str, &str, Option<&str>, &str)] = &[
|
|||
(CAP_MEDIA_CONTRIBUTION, S_INTERACTIVE, None, "2026-10-21"),
|
||||
(CAP_DELIVERY_SETTLEMENT, S_INTERACTIVE, None, "2026-10-21"),
|
||||
(CAP_ACCESS_SERVICES, S_INTERACTIVE, None, "2026-10-21"),
|
||||
(CAP_MEDIA_EXPLORATION, S_INTERACTIVE, None, "2026-10-21"),
|
||||
(CAP_AUDIO_IAMF, S_IMMERSIVE, None, "2026-06-17"),
|
||||
(CAP_AUDIO_AC4, S_IMMERSIVE, None, "2026-06-17"),
|
||||
(CAP_AUDIO_IVAS, S_IMMERSIVE, None, "2026-06-17"),
|
||||
|
|
@ -386,6 +388,7 @@ const RECORDS: &[&str] = &[
|
|||
CAP_MEDIA_CONTRIBUTION,
|
||||
CAP_DELIVERY_SETTLEMENT,
|
||||
CAP_ACCESS_SERVICES,
|
||||
CAP_MEDIA_EXPLORATION,
|
||||
];
|
||||
|
||||
/// A named identifier SPEC uses that has the shape of a capability string but is
|
||||
|
|
@ -550,6 +553,7 @@ const SYNTHESIS: &str = "extensions/synthesis.md";
|
|||
const CONTRIBUTION: &str = "extensions/contribution.md";
|
||||
const SETTLEMENT: &str = "extensions/settlement.md";
|
||||
const ACCESS_SERVICES: &str = "extensions/access-services.md";
|
||||
const EXPLORATION: &str = "extensions/exploration.md";
|
||||
|
||||
const fn wire(name: &'static str, kind: WireKind, document: &'static str, section: &'static str,
|
||||
status: WireStatus) -> WireIdentifier {
|
||||
|
|
@ -675,6 +679,12 @@ pub const WIRE_IDENTIFIERS: &[WireIdentifier] = &[
|
|||
wire("wai-signing", WireKind::DocumentKind, ACCESS_SERVICES, "§6.1", WireStatus::Defined),
|
||||
wire("wai-signing-review", WireKind::DocumentKind, ACCESS_SERVICES, "§6.4", WireStatus::Defined),
|
||||
wire("wai-access-completeness", WireKind::DocumentKind, ACCESS_SERVICES, "§7.1", WireStatus::Defined),
|
||||
wire("wai-archive", WireKind::DocumentKind, EXPLORATION, "§5.3", WireStatus::Defined),
|
||||
wire("wai-distance", WireKind::DocumentKind, EXPLORATION, "§6.3", WireStatus::Defined),
|
||||
wire("wai-collection", WireKind::DocumentKind, EXPLORATION, "§7.1", WireStatus::Defined),
|
||||
wire("wai-taste", WireKind::DocumentKind, EXPLORATION, "§8.1", WireStatus::Defined),
|
||||
wire("wai-ranking", WireKind::DocumentKind, EXPLORATION, "§9.1", WireStatus::Defined),
|
||||
wire("wai-pick", WireKind::DocumentKind, EXPLORATION, "§10.2", WireStatus::Defined),
|
||||
];
|
||||
|
||||
/// A string with the shape of a capability that the reference implementation uses
|
||||
|
|
|
|||
1714
wai/wai-rs/src/exploration.rs
Normal file
1714
wai/wai-rs/src/exploration.rs
Normal file
File diff suppressed because it is too large
Load diff
1137
wai/wai-rs/src/exploration/corpus.rs
Normal file
1137
wai/wai-rs/src/exploration/corpus.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -535,6 +535,14 @@ pub mod ai_usage;
|
|||
/// cbindgen:ignore
|
||||
#[cfg(feature = "settlement")]
|
||||
pub mod settlement;
|
||||
// Exploration (extensions/exploration.md): `wai.media.exploration` — the descriptors of a
|
||||
// picture, quality-diversity archives over the exact generator that replay bit for bit, a work's
|
||||
// default-distance, collection statements, and taste as a declared, owned objective: taste
|
||||
// priors, rankings under them and the disclosure of what steered a pick. Rust-only, like the
|
||||
// live modules: its constants must not become macros in include/wai.h.
|
||||
/// cbindgen:ignore
|
||||
#[cfg(feature = "exploration")]
|
||||
pub mod exploration;
|
||||
// Timed text and live captions (extensions/timed-text.md): `wai.text.timed` — the cue model
|
||||
// both WebVTT and the IMSC1 Text Profile express, live caption revisions as `caption`
|
||||
// events of `wai.live.events` with the WTX1 annotation, and the exact mappings onto WebVTT
|
||||
|
|
|
|||
73
wai/wai-rs/tests/exploration.rs
Normal file
73
wai/wai-rs/tests/exploration.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
//! `wai.media.exploration` (extensions/exploration.md): the committed corpus
|
||||
//! `live-conformance/exploration/` regenerates byte for byte from its fixed seeds, holds no other
|
||||
//! file, every generation, descriptor vector and case in it gets the outcome it states, and the
|
||||
//! extension's §13 lists exactly the reasons the reference reports. The generator itself asserts
|
||||
//! that the corpus gives every one of them.
|
||||
#![cfg(feature = "exploration")]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
use wai::exploration::{corpus, Reason, DESCRIPTORS};
|
||||
|
||||
fn wai_dir() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..")
|
||||
}
|
||||
|
||||
fn committed() -> BTreeMap<String, String> {
|
||||
let dir = wai_dir().join("live-conformance/exploration");
|
||||
std::fs::read_dir(&dir)
|
||||
.expect("the committed corpus")
|
||||
.flatten()
|
||||
.map(|e| (e.file_name().to_string_lossy().into_owned(), e.path()))
|
||||
.filter(|(n, _)| n != "README.md")
|
||||
.map(|(n, p)| (n, std::fs::read_to_string(p).expect("a text file")))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn section(text: &str, heading: &str) -> String {
|
||||
let at = text.find(heading).unwrap_or_else(|| panic!("a section {heading}"));
|
||||
let end = text[at + 1..].find("\n## ").map_or(text.len(), |e| at + 1 + e);
|
||||
text[at..end].to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_committed_corpus_regenerates_from_its_seeds() {
|
||||
let files = committed();
|
||||
let generated: BTreeMap<String, String> = corpus::generate().into_iter().collect();
|
||||
let extra: Vec<&String> = files.keys().filter(|k| !generated.contains_key(*k)).collect();
|
||||
let missing: Vec<&String> = generated.keys().filter(|k| !files.contains_key(*k)).collect();
|
||||
let differ: Vec<&String> = generated.iter().filter(|(k, v)| files.get(*k).is_some_and(|f| f != *v)).map(|(k, _)| k).collect();
|
||||
assert!(extra.is_empty() && missing.is_empty() && differ.is_empty(), "extra {extra:?}, missing {missing:?}, differ {differ:?}: run `wai_exploration_vectors gen`");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_case_gets_its_stated_outcome() {
|
||||
let fails = corpus::verify(&committed());
|
||||
assert!(fails.is_empty(), "{}", fails.join("\n"));
|
||||
}
|
||||
|
||||
/// §13 of the extension lists exactly the codes `Reason::ALL` has, each once, in order.
|
||||
#[test]
|
||||
fn the_extension_lists_the_reasons() {
|
||||
let text = std::fs::read_to_string(wai_dir().join("extensions/exploration.md")).expect("the extension");
|
||||
let listed: Vec<String> = section(&text, "\n## 13. Reasons")
|
||||
.lines()
|
||||
.filter(|l| l.starts_with("| `"))
|
||||
.filter_map(|l| l.split('`').nth(1).map(String::from))
|
||||
.collect();
|
||||
let want: Vec<String> = Reason::ALL.iter().map(|r| r.code().to_string()).collect();
|
||||
assert_eq!(listed, want, "§13's table and Reason::ALL differ");
|
||||
}
|
||||
|
||||
/// §3.2's table names the descriptors `DESCRIPTORS` holds, in its order.
|
||||
#[test]
|
||||
fn the_extension_names_the_descriptors() {
|
||||
let text = std::fs::read_to_string(wai_dir().join("extensions/exploration.md")).expect("the extension");
|
||||
let s = section(&text, "\n### 3.2 ");
|
||||
let named: Vec<String> = s
|
||||
.lines()
|
||||
.filter(|l| l.starts_with("| ") && l.split('|').nth(1).is_some_and(|c| c.trim().parse::<u32>().is_ok()))
|
||||
.filter_map(|l| l.split('`').nth(1).map(String::from))
|
||||
.collect();
|
||||
assert_eq!(named, DESCRIPTORS.map(String::from).to_vec(), "§3.2's table and DESCRIPTORS differ");
|
||||
}
|
||||
Loading…
Reference in a new issue