# 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.