open-standards/wai/extensions/exploration.md
Transaction Science 12b39d399b
All checks were successful
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 5s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 5s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 5s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m46s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m26s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 58s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 5s
WAI conformance / does this change touch it (pull_request) Successful in 5s
WAI energy receipts / does this change touch it (pull_request) Successful in 5s
wai-ops-gpu integer operators on GPUs / does this change touch it (pull_request) Successful in 5s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 4s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 4s
wai-web wasm32 / does this change touch it (pull_request) Successful in 5s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m9s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 36s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 1m2s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 10m9s
wai-ops-gpu integer operators on GPUs / builds · no adapter is not a pass (linux) (pull_request) Successful in 1m53s
wai-pack / wai-pack · linux (pull_request) Successful in 1m24s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 19m19s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 2m31s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 4m5s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 5m19s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 48s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 25s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 41s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 8m28s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 2m41s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 23m28s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 10m45s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 2m2s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 29s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 1m18s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 5m19s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 39m39s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 12m12s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 20m37s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 29s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 58s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 10m28s
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Successful in 4m17s
wai-pack / wai-pack · macos (pull_request) Successful in 1m22s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 3m44s
exploration: no consent requirement for a service's own mechanics; a lawful basis is a lawful-basis claim's
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-21 14:42:30 -04:00

830 lines
48 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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