Candidate, Artifact, Work
A generative system can produce endless images and still fail to produce public work. The hard part is not only rendering. It is moving from candidate to artifact to selected work without losing the token, seed, source model, dimensions, image bytes, or editorial decision that made the output worth publishing.
Lignes treats the central technical object as that path. A sketch can keep changing. A token identifies a generation. A render writes image bytes and a sidecar receipt. A selection file turns artifacts into a public set. Validation checks that the archive still agrees with the files on disk.
The experiment is publication after abundance. The system does not automate taste. It makes taste durable enough to inspect.



Determinism Is Editorial Infrastructure
Token choice is not only metadata. It controls the render. The renderer derives a seed from the token, creates a deterministic random source, and threads that source through the p5 runtime and model helpers.
That makes curation repeatable. When a token is selected, the system can render the same work again, validate the bytes, and keep the public archive tied to a stable generation identity instead of a screenshot from an interactive session.
export function createP5ModelRenderer({
dimensions,
keyboardMap,
logger,
model,
token,
}: RenderOptions) {
const seed = seedFromToken(token);
const randomSource = createMulberry32(seed);
return (baseP5: p5) => {
const p = baseP5 as LignesP5;
const defaultFilename = `${model.meta.id}-${token}.png`;
const done = (extra?: Record<string, unknown>) => {
const meta = {
modelId: model.meta.id,
token,
seed,
modelVersion: model.meta.version,
height: p.height,
width: p.width,
metadata: extra,
};
};
};
}The Render Pipeline Writes a Receipt
The headless renderer turns browser art into a publication artifact. It drives the render route, waits for the canvas payload, decodes image bytes, writes the PNG, and writes a sidecar JSON record beside it. High-quality publication renders use a long edge measured in thousands of pixels, not preview screenshots.
The receipt is the important object. It records artifact version, creation time, sketch decisions, generation metadata, byte count, MIME type, relative path, SHA, model metadata, runtime adapter, filename, width, and height. That is enough information to treat the image as a durable work instead of a loose asset.
const sidecar = {
artifactVersion: SHOWCASE_ARTIFACT_VERSION,
createdAt,
decisions: payload.meta.metadata ?? {},
generation,
image: {
bytes: imageBuffer.byteLength,
mimeType: "image/png",
relativePath: rawImageRelativePath,
sha256: crypto.createHash("sha256").update(imageBuffer).digest("hex"),
},
model: payload.model,
render: {
adapter: "headless",
runtime: "p5",
filename: payload.filename,
height: payload.meta.height,
width: payload.meta.width,
},
};Selection Files Are the Edit
Publication is not automatic. Candidate renders can be abundant, but the public set is explicit. Selection files name exact modelId:token pairs. Publishing resolves those pairs against the artifact index and fails if a selected token has no corresponding render artifact.
That makes curation operational without pretending the system can decide quality. The decision stays human. The selected state becomes data. The archive can be rebuilt from the recorded edit.
if (options.selection) {
const artifactIndex = new Map(
artifacts.map((artifact) => [
`${artifact.generation.modelId}:${artifact.generation.id}`,
artifact,
]),
);
const selected: LignesRenderArtifact[] = [];
for (const item of options.selection.items) {
const key = `${item.modelId}:${item.token}`;
const artifact = artifactIndex.get(key);
if (!artifact) {
throw new Error(`Selection entry ${key} not found.`);
}
selected.push(artifact);
}
}Validation Keeps the Archive Honest
The archive is only as trustworthy as the agreement between manifest and bytes. The checker verifies that each image exists, stays below the Workers asset limit, matches the recorded SHA, matches the recorded byte count, and uses the expected public URL.
This is the technical side of a taste system. The public page can be beautiful, but the underlying work still needs file integrity, provenance, and stale-manifest detection. Otherwise the archive becomes a mood board with no receipt.
const actualHash = sha256(imageBuffer);
if (actualHash !== entry.image.sha256) {
throw new Error(
`${entry.image.relativePath} hash mismatch. Expected ${entry.image.sha256}, got ${actualHash}`,
);
}
if (imageStat.size !== entry.image.bytes) {
throw new Error(
`${entry.image.relativePath} byte size mismatch. Expected ${entry.image.bytes}, got ${imageStat.size}`,
);
}
if (entry.generation.url !== `/${entry.image.relativePath}`) {
throw new Error(
`${entry.generation.modelId}:${entry.generation.id} has a mismatched public URL`,
);
}Public Pages Show the Receipt
The public work page treats each image as an inspectable artifact. It surfaces the sketch id, token, seed, dimensions, model version, publish time, and emitted decisions when they exist. Collections then become editorial overlays over manifest entries rather than storage folders.
There are honest bounds. Many model versions remain 0.0.0, so the archive is token-stable before it is semver-stable. Some generated works emit richer decision metadata than others. Those gaps are visible because the pipeline keeps the receipt close to the work.
{
"artifactVersion": 1,
"generation": {
"id": "599591c0-fb79-4aab-8668-b1223439e047",
"modelId": "graviton",
"meta": {
"seed": 1728653598,
"height": 3840,
"width": 2880
}
},
"image": {
"bytes": 9025225,
"relativePath": "generated/graviton/599591c0-fb79-4aab-8668-b1223439e047.png",
"sha256": "d529529a88cac9afbe79666d2ba2f4eb4f98d37a3d1b507d055f9a48d94c6167"
}
}