Guides

Derived artifacts

Refine, pack, mount, export, hand-edit companions, approval provenance, and recovery.

PixelKiln turns validated source PNGs into application-ready bundles without a provider call:

  • pack: deterministic grid sheet plus frame atlas, or Aseprite sheet JSON, or a Godot SpriteFrames resource;
  • mount: declared stable cells in a new or existing sheet, same formats;
  • export: structural tile atlas plus generic, Tiled, or Godot metadata;
  • refine: native-grid PNG or ordered frame set plus palette, audit, and human-review record.

Every CLI bundle includes generated output, metadata, and a <base>.pixelkiln.json provenance companion.

Pack

pixelkiln pack --style neon
pixelkiln pack --style ground --output-role tile-00 --output-role tile-01
pixelkiln pack --style mixed --primary-only

Manifest mode normally reads the outputs recorded by the lockfile. An asset with a source (or a sourceByStyle entry for this style) is packed from that file instead (a hand edit made with pixelkiln edit, a palette remap, an alignment fix), and an asset with a source but no lock entry is packed from it too, the same rule mount applies. Structural sets keep their lock outputs. When a style declares quality, it instead requires every selected refinement record to be current and approved, then packs those derived PNGs. Frames sort by asset id for byte-stable layouts. Structural sets preserve provider order and qualify ids by role (terrain/tile-03). The grid cell is the largest source sprite; smaller frames retain their real dimensions at the cell's top-left — except for a character style, whose frames are bottom-centred in the cell instead, so a base's rotations and a taller state or v3-interpolated animation frame (a crouch loop can come back on a canvas larger than the rotations; see docs/ENDPOINTS.md, "Characters, measured") share a floor rather than a top edge. --format aseprite records that offset as a genuine trim (sourceSize the cell, spriteSourceSize the frame's placement within it) so an engine reading the sheet lands every frame on the same floor too.

All standard non-interlaced PNG color modes and bit depths are decoded and normalized to RGBA. Corrupt/interlaced inputs are reported as skipped. If no input is readable, packing fails rather than emitting an empty sheet.

The generic atlas lists sets when a multi-output asset is on the sheet:

"sets": [
  { "id": "dancer", "kind": "frames", "fps": 12, "frames": ["dancer/frame-00", "dancer/frame-01"] },
  { "id": "terrain", "kind": "members", "frames": ["terrain/tile-00", "terrain/tile-01"] }
]

--format aseprite writes that same information as Aseprite's sheet JSON (each set a frameTag, frame durations from the fps) and --format godot as a SpriteFrames .tres (each frame set a looping animation at its fps, every other frame a one-frame animation). The sheet PNG is the same file in every format; the companion records options.format.

Explicit mode packs sources from any project and requires no manifest:

pixelkiln pack --inputs sprites.json --out dist/sheet
[
  { "id": "effect_euphoric", "path": "art/effect_euphoric.png" },
  { "id": "aura_golden", "path": "../shared/aura_golden.png" }
]

Paths resolve from the inputs JSON directory. Duplicate ids fail before file decoding because consumers must have one unambiguous frame per id.

Refine

refine can run the profiles declared in a manifest or process one --from PNG. Both modes call a pinned local Pixel Art Fixer installation, reconstruct the detected native grid, apply a closed palette without dithering, and write the PNG beside a quality companion.

Manifest mode owns the output path and policy. ComfyUI frames writes numbered role-stable PNGs under one set-level record:

pixelkiln refine --style environment
pixelkiln refine check --style environment

It preserves a current pending or approved record on repeat. A profile or source change rebuilds the result and resets approval; --force deliberately does the same for current work.

Path mode is useful for one-off input:

pixelkiln refine \
  --from candidates/fortress.png \
  --out art/fortress-native.png \
  --palette "#141b1e,#23312a,#4e3b34,#865c45,#c6a766,#f1bb70" \
  --fixer-python .pixelkiln/pixelfixer/bin/python

The companion uses artifact kind refine. Its options store the tool revision, detected step, phase, native dimensions, confidence, exact palette, no-dither policy, audit thresholds and result, plus pending or approved review state. A frame record also stores fps, ordered roles, and every frame's measurements. pixelkiln refine check verifies the fingerprint and current source/output hashes. pixelkiln refine approve rewrites only the companion after a named person completes the native-scale review.

An actual rebuild resets approval to pending. A manual PNG edit, palette or tool metadata edit, changed source, or altered approval invalidates the record. For one-off refinement, use the PNG as an asset source when mounting it. A manifest quality profile routes approved output into pack and mount automatically and binds the quality record into their provenance.

Mount

pack moves cells when the sorted input set changes. mount is for sheets whose cell coordinates are already load-bearing in scene files, saved data, or engine configuration.

Declare style.mount and per-asset cell values in the manifest. A base sheet is optional and may equal the output. Only declared cells are cleared/replaced; all other base pixels survive byte-for-byte. A sprite larger than its cell is reported and skipped rather than cropped. Two assets cannot own one cell.

Use asset source (or sourceByStyle for one style) when mounting a palette-remapped, aligned, hand-touched, or otherwise post-processed file instead of the raw lock output; pixelkiln edit sets it up for you. A style quality profile takes precedence for participating cells, but only after approval. Use outputRole to choose one member of a structural set. See manifest reference. The same rule applies to an approved ComfyUI frame set: pack includes every frame, while mount needs one explicit outputRole for the declared cell.

Export

pixelkiln export --style ground --only terrain --format generic
pixelkiln export --style ground --only terrain --format tiled
pixelkiln export --style ground --only terrain --format godot

Generic JSON retains all provider rules and normalized masks. Tiled and Godot translate recognized four-edge/four-corner semantics and reject unknown or lossy mappings. See TILES.md for the format contracts.

Hand-edit companion

A hand edit saved from the gallery's in-browser editor leaves a small record beside it, <edit>.edit.json (beside the stem for a frame set), that is not a provenance companion and is not transactional:

{
  "version": 2,
  "editor": "pixelorama@v1.2.2-stable",
  "protocol": 3,
  "savedAt": "2026-09-12T10:00:00.000Z",
  "project": "walk.pxo",
  "outputs": [
    { "role": "frame-00", "basedOn": "<sha256 of the generated frame>", "sha256": "<sha256 as saved>" },
    { "role": "frame-01", "basedOn": "…", "sha256": "…" }
  ]
}

One outputs entry per file, in output order; a single image has one with role: null. basedOn lets the gallery report regenerated-since by comparing hashes rather than modification times (null when the edit started from untracked art); sha256 shows when another tool rewrote a file after the editor did; project names the layered Pixelorama file kept beside the edit, which the next in-browser edit opens so layers survive. A version 1 companion (one image, flat basedOn/sha256) is still read. An edit made with a desktop editor has no companion and is judged by file times. Commit the companion and the .pxo with the edit if you want that history; nothing else reads them.

Provenance companion

The companion is engine-neutral and versioned:

{
  "format": "pixelkiln-artifact-bundle",
  "version": 1,
  "kind": "pack",
  "fingerprint": "…",
  "sources": [
    { "id": "anvil", "path": "../art/anvil.png", "sha256": "…", "included": true }
  ],
  "options": { "columns": 8, "order": "id", "style": "base" },
  "outputs": [
    { "path": "sheet.png", "sha256": "…" },
    { "path": "sheet.json", "sha256": "…" }
  ]
}

Source paths are relative to the companion. Options are recursively key-sorted before hashing. The fingerprint covers sources, options, and output hashes. Manifest-driven bundles conservatively include the project manifest and lockfile, so newly declared or recorded sources make an older artifact stale. Profile-driven pack and mount bundles also include one $quality/<assetId> source per refinement record. Changing an approval or its bound bytes makes the downstream bundle stale without hiding which gate changed.

Verify without rebuilding:

import { verifyArtifactBundle } from "pixelkiln"

const verification = await verifyArtifactBundle("dist/sheet.pixelkiln.json")
if (!verification.current) {
  console.error(verification.changedSources, verification.changedOutputs)
}

Ownership and manual edits

PixelKiln never silently claims a differing existing destination:

  • No companion + identical desired bytes: adopt it, preserve its mtime, and add provenance.
  • No companion + differing bytes: refuse the entire bundle.
  • Valid companion + matching recorded output: update normally.
  • Valid companion + manually changed output: refuse the entire bundle.
  • Invalid or altered companion: refuse takeover.

After reviewing the difference, --force explicitly replaces and takes ownership of every changing member:

pixelkiln pack --style neon --force

Library consumers get the same policy from writeManagedArtifactBundle(..., { force: true }).

Transactional writes

Changed members are compared first, staged beside their destinations, and only then promoted. Duplicate normalized destinations fail before mutation. Byte-identical members are not rewritten. If an ordinary write/promotion error occurs, newly promoted files are removed and previous backups are restored. Any recovery failure retains the backup and reports its exact path.

Abrupt termination recovery

Managed writes create an immutable <companion>.transaction journal before staging. After every member is promoted, a separate durable commit marker is created before cleanup.

On the next invocation:

  • journal without marker: restore the prior complete bundle and remove stages;
  • journal with marker: retain the fully committed new bundle and finish cleanup;
  • live owning process: refuse concurrent recovery;
  • destination or temp path outside the current bundle: refuse unsafe recovery and retain the journal for inspection.

The journal is short-lived and should not normally appear in Git status. A hard-exit integration test covers both sides of the commit boundary.

Library persistence

packSprites, packStyle, mountSprites, mountStyle, and exportTileset return bytes, metadata, skipped-input details, and source hashes. Use writeManagedArtifactBundle for the CLI's ownership, provenance, transaction, and recovery policy. writeArtifactBundle is the lower-level staged/rollback primitive for callers that supply their own ownership policy.