Architecture
The state machine, provider boundary, output identity, and durable writes.
PixelKiln separates provider mechanics from the project state machine:
manifest + lock + planning + review + recovery + artifact pipelines
──────────────────── Provider interface ─────────────────────────
PixelLabProvider RetroDiffusionProvider FakeProvider future adapters
Everything above the provider boundary is backend-neutral. URL shapes, auth headers, request/response schemas, and optional account capabilities remain in the adapter.
Manifest and resolved specs
The committed manifest is intent. Resolution combines one style and one asset, loads/reference-hashes style images, applies overrides, chooses a provider- supported generator, and computes a deterministic spec hash. The hash excludes project root, output location, and tags but includes every pixel-affecting setting.
See manifest reference.
Lockfile
pixelkiln.lock.json is the committed paid-work record. Version 2 entries
retain:
- style/asset identity and spec hash;
- provider and remote object/job ids;
- explicit lifecycle status and errors;
- source URLs/candidates/selections;
outputs[]with portable path, SHA-256, optional structural role, and optional PNG/GIF media type;- provider-specific metadata under a provider-id namespace;
- successful submission cost and cost unit.
Lock keys are styleId/assetId. Output paths use manifest-relative /
separators so a clone or moved checkout remains valid. Legacy absolute v2 paths
are rebased in memory and rewritten portably on the next save. The current
manifest remains destination authority; a stale lock path cannot redirect
restore into an unrelated project file.
Cost units that differ are never summed. Built-in adapters currently use
generations, usd, and free; custom adapters may register another
non-empty unit. Candidate count also belongs to the provider estimate rather
than being assumed globally.
State machine
The provider pipeline is deliberately resumable:
plan → submit → poll → pick (when needed) → fetch
└──────── structural/inline outputs ────────┘
Remote ids are saved immediately after submission. Generation and download
failures remain separate, so paid work with a temporary CDN failure is
recoverable at zero generation cost. Each stage can be rerun independently;
gen is only their everyday orchestration.
Provider responses are runtime-validated before entering lock state. A 2xx response with a missing object id, malformed URL set, invalid estimate, or changed field type becomes an explicit adapter error rather than corrupted durable state.
Output identity
One manifest asset may produce multiple structural members. Roles such as
tile-03 are load-bearing identity, not presentation. Audits, packs, mounts,
and exporters all use the same role model. A consumer must request a role when
there is no unambiguous primary output.
PNG ingestion validates signature, chunks, CRCs, palettes, compressed data, scanlines, dimensions, and supported color modes. GIF ingestion walks the logical screen, color tables, extensions, image-data blocks, and trailer. Both formats are validated before bytes become durable output or recovery cache data.
Concurrency and lock saves
Lock writes use a same-directory temporary file and rename. An advisory writer lock serializes separate processes. In-process saves queue per path, and field-level dirty patches merge separate snapshots so updates to different assets, or to different fields of one asset, do not silently lose the earlier write. Stale advisory locks are recoverable after their safety window.
Derived artifact transactions
Pack, mount, and export use a separate managed bundle writer:
- Recover an interrupted prior transaction.
- Validate provenance ownership and manual edits.
- Compare every desired member and skip identical bytes.
- Write an immutable transaction journal.
- Stage all changing members beside their destinations.
- Move existing members to unique backups.
- Promote every stage.
- Write a durable commit marker.
- Remove backups, stages, journal, and marker.
Before the marker, recovery restores the old complete set. After the marker, recovery keeps the new complete set and finishes cleanup. Journal paths are validated against current destinations and reserved same-directory temp names. See derived artifacts.
Caches
The project content cache is keyed by output SHA-256. The account cache maps provider object ids to remote content hashes for adoption/salvage. Neither is authoritative or committed; both can be deleted and rebuilt. Every recovery byte is structurally validated before use.
Provider capability boundary
Providers are selected from a registry by the manifest's top-level provider
id. Required members cover support/estimate, submit, poll, and download;
candidate selection is required only when an adapter can return alternatives.
Account-wide listing, tagging, deletion, and balance are optional. Commands
such as adopt or salvage report a capability gap rather than failing through
an undefined method.
PixelLabProvider is production and live-tested. RetroDiffusionProvider is
an experimental still, tileset, and animation adapter. Authenticated RD Fast
and RD Plus single-candidate still lifecycles have passed end to end. Its
multi-candidate, tileset, GIF, and spritesheet paths retain mocked coverage
pending paid live smokes. FakeProvider implements the same contract in memory, which
keeps the paid pipeline testable without credentials or network access. See
library API and
PixelLab vs. Retro Diffusion.