WAI Extension: Exploration and Declared Taste
Mirrored from the canonical text at commit b1681667 ().
Status: Draft. Registers the record
wai.media.exploration.wai.generate.spectralis exact: a model file, a prompt, a seed and a number of steps give the same bytes on every machine (SPEC §5, Payload ofwai.generate.spectral). This extension builds on that. Six descriptors, each an integer derivation over a picture’sRGB8samples, 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); a ranking under it reproduces; and every archive, ranking and pick an aesthetic or reward model steered names that model by digest, or statesnone. 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 theexplorationmodule ofwai-rs(featureexploration); the corpus islive-conformance/exploration/, and its second implementation islive-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 §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: 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: 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 §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 §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 §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 · Σ (2x + 1) · V / (2 · w · Σ V)⌋ |
| 6 | centroid_y | ⌊1024 · Σ (2y + 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:
- Genome. When t <
init: seed = draw mod 2^32,stepsthe 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.
- Picture. The genome’s picture (§4.3), its descriptor vector and its niche k in the grid; V[k] increases by 1.
- Score. With a taste objective, the score of the vector under the prior (§8.2); with
none, no score. - 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
nonethe 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:
- The record reads (§2, §5.1, §5.3) and its signature verifies.
coveragestates the number of elites and the grid’s cells: otherwisecoverage_mismatch.- A taste objective’s prior resolves and verifies (§8.3, §8.4).
- The verifier holds the model (
model_unheld). - For each elite in order: its genome’s picture has its samples digest
(
generation_mismatch), the picture’s descriptor vector is itsdescriptors(descriptor_mismatch), that vector’s niche is itsniche(niche_mismatch), and its score is the prior’s score of the vector, ornullwithnone(score_mismatch). - 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.defaultshas 16 entries, everysamplesa 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.defaultshas 1 to 16 entries,descriptor_distanceandnearest_descriptorare integers, andsample_distanceandnearest_samplearenull.unknown: no default set is known.defaultsis empty and all four figures arenull. 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:
- The class agrees with the generator (
distance_class_invalid). - Held samples of the work are
width·height· 3 bytes (samples_mismatch). 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’sdescriptors(descriptor_mismatch); all four figures are computed by §6.2.estimate: where the work’s samples are held, their vector is itsdescriptors(descriptor_mismatch); the descriptor distance and the nearest default are computed from the work’s vector and the stated vectors.unknown: where the work’s samples are held, their vector is itsdescriptors(descriptor_mismatch).- 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 issum/pairsexactly;minandmax;median, the element at position ⌊(pairs− 1) / 2⌋ of the distances in ascending order;width, from 1 to 6144; andhistogram, 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 §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).
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 §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 §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:
- The prior resolves and verifies (§8.3, §8.4).
- For each candidate whose samples the verifier holds: they are of its size
(
samples_mismatch), and their vector is itsdescriptors(descriptor_mismatch). A candidate whose samples are not held is scored from its stated vector. - The scores and the order are those of §9.2 (
ranking_mismatch). - 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"}: aselectof the elite ofnichein the archive whose link hash isarchive;{"type": "set", "set"}: aselectof one of 1 to 256 samples digests;{"type": "generation", "generator", "model", "prompt", "seed", "steps"}: agenerate, by a generator term, a model term (forwai.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), eachnullwhere 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:
- Without
objective, under a policy that requires it:objective_undisclosed. - A taste objective’s prior resolves and verifies (§8.3, §8.4).
- Held samples of the work are of its size (
samples_mismatch). - From an archive: the verifier holds the archive, verified earlier in the same case
(
archive_unheld); the archive has an elite atnichewhose 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’sobjectivenames that prior’s digest (objective_mismatch). - From a set: the work’s samples digest is one of the set (
pick_not_in_set). - From a generation by
wai.generate.spectralwith a seed and steps: the verifier holds the model (model_unheld), andgenat 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: acandidatesstep 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’ssamples, 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. Aselectof 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; aselectfrom 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); orelites, it is elite-checked;objective:required, every pick carriesobjective(§10.3); oroptional.
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,
replayedorelites,occupied,cells, and its objective (none, or{"type", "sha256"}); - a distance: its link hash, its class and the four figures,
nullwhere unknown; - a collection: its link hash, the number of members,
occupied,cellsanddistances; - a ranking: its link hash, the prior’s digest, owner and scope, the order,
recomputedorstated, and for eachusageentry its vocabulary, term and preference (allow,unknown,outsidefor anullterm, orabsentwhen 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 derivedprompt, ornullfor 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/ holds the corpus, written from
fixed seeds by wai_exploration_vectors gen as vectors.json. It has:
- Generations: pictures of
wai.generate.spectralat 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
objectiveunder a policy that requires it and a pick from a prior-steered archive that statesnone.
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
trialsgenerations 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 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-basisclaim’s (§8.4). - Undisclosed steering. A pick without
objectiveis reported undisclosed, never asnone. A party that steers a selection with a model and statesnonemakes 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: huec = sumc · 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 = 2F + 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 3T ÷ 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 3n 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.