Skip to main content

WAI Extension: Exploration and Declared Taste

Mirrored from the canonical text at commit b1681667 ().

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 §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); 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/, 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 §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:

#namevalue
1luma⌊1024 · Σ Y / (255 · n)⌋
2chroma⌊1024 · Σ (max(R, G, B) − min(R, G, B)) / (255 · n)⌋
3detailwith 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)⌋)
4paletteeach 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²⌋
5centroid_xwith V = Y + 1 at each pixel: ⌊1024 · Σ (2x + 1) · V / (2 · w · Σ V)⌋
6centroid_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:

membervalue
generatorwai.generate.spectral
modelsha256: and the model file’s SHA-256
prompta prompt (§4.1)
modifiers0 to 8 modifiers (§4.2)
width, heightthe pictures’ size (§2)
stepsfrom 1 to 64: the steps every initial trial uses
descriptorswai-exploration:image6
grida 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:

membertypemeaning
elitesarrayevery occupied niche’s elite, in strictly ascending order of niche, at least one
coverageobject{"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":

membertypemeaning
workpicture with descriptorsthe work
generatortermwai.generate.spectral, or a term naming another generator
modeltermfor wai.generate.spectral, sha256: and the model file’s SHA-256; otherwise a term naming the model
promptstringa prompt (§4.1)
descriptorsstringwai-exploration:image6
defaultsarray0 to 16 entries {"samples", "descriptors"}: a default’s samples digest or null, and its vector
classstringmeasured, estimate or unknown
descriptor_distance, nearest_descriptor, sample_distance, nearest_sampleinteger or null§6.2
issued_at, signer_pubkey, sig§2

The class says what kind of figure it is:

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:

membertypemeaning
scopestringgallery, room (a room’s outputs), session (a session’s candidates) or archive
subjecttermwhat the collection is: a gallery’s URI, a room’s id, an archive’s link hash
descriptorsstringwai-exploration:image6
gridarraya grid (§3.5)
membersarray2 to 256 pictures with descriptors; a picture may appear more than once
coverageobject{"occupied", "cells"}
distancesobject§7.2
issued_at, signer_pubkey, sig§2

7.2 The figures

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":

membertypemeaning
owner32 B hexthe Ed25519 key of the person or community whose taste it is
scopestringpersonal or community
descriptorsstringwai-exploration:image6
termsarray1 to 32 terms (§8.2)
usageobject or nullthe 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:

shapevalue
riseweight · d
fallweight · (1024 − d)
nearweight · (1024 − |d − at|)
farweight · |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:

membertypemeaning
priorobjectthe prior’s pin (§8.3)
descriptorsstringwai-exploration:image6
candidatesarray1 to 256 pictures with descriptors
scoresarrayone integer per candidate, in their order
orderarraythe candidates’ indices, best first: a permutation of 0 to n − 1
usagearray0 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:

valuemeaning
"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:

membertypemeaning
actstringgenerate or select
workpicturethe work
fromobjectwhere it came from (below)
objectiveOPTIONAL: §10.1
issued_at, signer_pubkey, sig§2

from is one of:

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:

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:

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:

13. Reasons

The codes this extension adds, each with where it is raised:

reasonraised 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:

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

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.