pixelkiln 0.61.0 → 0.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -296,6 +296,45 @@ who holds a sword in the right hand holds it in the left when facing the
296
296
  mirrored way. Most games accept that; if yours does not, generate both
297
297
  sides. `adopt` skips mirrors, since there is nothing upstream to adopt.
298
298
 
299
+ ### One loop, several directions
300
+
301
+ Writing each direction out is five near-identical loops and three mirrors
302
+ for a full walk. `directions` on an animation says the same thing once:
303
+
304
+ ```json
305
+ "bot.walk": {
306
+ "prompt": "walking forward in place, short legs stepping, body bobbing slightly",
307
+ "animation": { "of": "bot", "frames": 12, "fps": 12, "directions": ["south", "west", "north", "south-west", "north-west"] }
308
+ }
309
+ ```
310
+
311
+ When the manifest loads, this becomes exactly the assets it stands for: one
312
+ loop per named direction (`bot.walk.south`, `bot.walk.west`, ...), each with
313
+ every field of the shorthand and its own `direction`, plus a mirror for each
314
+ direction whose flip is not named (`bot.walk.east` mirrors `bot.walk.west`,
315
+ `bot.walk.south-east` mirrors `bot.walk.south-west`, `bot.walk.north-east`
316
+ mirrors `bot.walk.north-west`). Eight directions for five generations. Name
317
+ both sides of a pair (`["west", "east"]`) to generate both instead of
318
+ mirroring, which is the fix for a character whose handedness matters. The
319
+ mirrors carry the shorthand's `category`, `tags`, and `styles`.
320
+
321
+ - The expanded ids are ordinary assets: `plan`, the gallery, the lockfile,
322
+ and `pack` all see `bot.walk.west` and `bot.walk.east`, never `bot.walk`.
323
+ Rewriting the shorthand into explicit assets by hand changes no identity
324
+ and regenerates nothing.
325
+ - `--only bot.walk` selects the whole family; `--only bot.walk.west` one
326
+ member of it.
327
+ - `direction` and `directions` are mutually exclusive, and fields that name
328
+ one output (`file`, `cell`, `remoteId`, `source`, `sourceByStyle`,
329
+ `outputRole`) are refused on a shorthand, since every direction would
330
+ claim them. An expanded id that is already declared is an error, not an
331
+ override.
332
+ - The gallery's `--edit` changes the shorthand, not one direction: editing
333
+ `bot.walk.west` there is refused with a pointer to `bot.walk`. A revision,
334
+ state, or new loop can still name an expanded asset as its parent. To
335
+ change one direction alone, write it out as its own asset and drop it
336
+ from `directions`.
337
+
299
338
  Engines that flip sprites at draw time (Godot's `flip_h`, Unity's
300
339
  `flipX`) do not need mirrored files at all. Declare only the directions
301
340
  you generate and flip in the engine; mirrors are for pipelines that want
package/docs/CLI.md CHANGED
@@ -860,6 +860,62 @@ pixelkiln export --style ground --only terrain --format tiled --columns 8
860
860
 
861
861
  See [derived artifacts](./ARTIFACTS.md) and [tiles](./TILES.md).
862
862
 
863
+ ## PixelLab utilities
864
+
865
+ Two commands call PixelLab on loose files rather than manifest assets. Neither
866
+ reads the manifest or touches the lockfile; both load `PIXELLAB_API_KEY` from
867
+ the environment or a `.env.local`/`.env` beside `--manifest` or in the working
868
+ directory, the same as every other command.
869
+
870
+ ### `unzoom`
871
+
872
+ ```bash
873
+ pixelkiln unzoom --from refs/knight-512.png --out refs/knight.png
874
+ ```
875
+
876
+ Shrinks an upscaled piece of pixel art (a 32x32 sprite saved at 512x512, every
877
+ art pixel a 16x16 block) back onto its native grid with PixelLab's `/unzoom`.
878
+ Run it on outside art before listing it in `styleImages` or as a
879
+ `reference`: PixelLab's own API guide names upscaled reference art as the most
880
+ common cause of disappointing output from every endpoint that takes one.
881
+
882
+ - `--out` defaults to `<input>.unzoomed.png` beside the input, and the input
883
+ is never overwritten. An existing `--out` is replaced only with `--force`.
884
+ - `--quantize <n>`: `0` (default) auto-detects a palette, `-1` keeps every
885
+ color the downsample produces, `2`-`256` quantizes to exactly that many.
886
+ - The input must be at least 256x256 (the grid detector needs room) and at
887
+ most 2048x2048 worth of pixels; both are checked before anything is sent.
888
+ - **The result is opaque**: PixelLab composites transparency onto white before
889
+ it detects the grid, so a cut-out sprite needs its background removed again.
890
+ - `--json` prints the detected zoom factor and both sizes; `--dry-run` sends
891
+ nothing.
892
+
893
+ ### `font`
894
+
895
+ ```bash
896
+ pixelkiln font --description "warm orange arcade font" --weight Bold --glyph-px 16 --out fonts/arcade
897
+ ```
898
+
899
+ Generates a pixel font with PixelLab's `/generate-font-pro` and writes two
900
+ files: `<out>.ttf`, ready to load in an engine, and `<out>.png`, the 80-glyph
901
+ atlas (A-Z, a-z, 0-9, and common game-UI punctuation). A trailing `.ttf` or
902
+ `.png` on `--out` is treated as part of the base, so `--out fonts/arcade.ttf`
903
+ writes the same two files.
904
+
905
+ - `--weight` is `Bold` or `Regular` (default); `--glyph-px` is the native glyph
906
+ size, `8`, `16` (default), `32`, or `64`; `--name` sets the font family name
907
+ (PixelLab otherwise uses the description plus the weight).
908
+ - Costs PixelLab's documented 25 generations, not yet confirmed against a
909
+ live bill. The command prints that and asks first; `--yes` skips the
910
+ question, `--budget <n>` refuses when `n` is below it, and `--dry-run` sends
911
+ nothing.
912
+ - It waits for the job (PixelLab documents 30-80 s) and then downloads both
913
+ files. Existing files are replaced only with `--force`.
914
+
915
+ A font is deliberately not a manifest generator: it has no style suffix to
916
+ share, no prompt-per-asset, no candidates to pick, and its main output is not
917
+ an image, so there is nothing for the lockfile's provenance model to hold.
918
+
863
919
  ## Utility commands
864
920
 
865
921
  ### `help`
@@ -894,13 +950,13 @@ Print the package version. `-v` is an alias.
894
950
  | `--provider <id>` | balance/adopt/salvage/purge/workspace add | Select the account provider for a mixed manifest, or set the workspace catalog's default provider hint. |
895
951
  | `--account <label>` | workspace add | Free-form account label, e.g. distinguishing sandboxes. |
896
952
  | `--all` | salvage dry run | List every unclaimed object rather than the first 30. |
897
- | `--from <path>` | init/refine/quality check | Existing source tree for init; source PNG or quality record for one-file refine mode; baseline for quality check. Omit it to use manifest `style.quality`. |
953
+ | `--from <path>` | init/refine/quality check/unzoom | Existing source tree for init; source PNG or quality record for one-file refine mode; baseline for quality check; the upscaled image for unzoom. Omit it to use manifest `style.quality`. |
898
954
  | `--exclude <names>` | init | Directory/name fragments to exclude; repeatable. |
899
955
  | `--generator <name>` | init | Generator assigned to the scaffolded style. |
900
- | `--name <name>` | init | Project name for the scaffolded manifest. |
956
+ | `--name <name>` | init/font | Project name for the scaffolded manifest, or the generated font's family name. |
901
957
  | `--write-prompts` | adopt | Recover provider prompts into the manifest. |
902
958
  | `--port <n>` | pick/salvage/gallery | Local review or gallery server port; otherwise chooses a free port. |
903
- | `--out <path>` | pack/export/refine/recipe install/quality snapshot | Output base override, final native PNG for path-mode refine, exact recipe destination, or quality baseline path. Export requires one selected tileset. |
959
+ | `--out <path>` | pack/export/refine/recipe install/quality snapshot/unzoom/font | Output base override, final native PNG for path-mode refine, exact recipe destination, quality baseline path, the unzoomed PNG, or the font's `<base>.ttf`/`<base>.png`. Export requires one selected tileset. |
904
960
  | `--inputs <path>` | pack/quality snapshot | JSON input array; requires `--out`. Quality cases use `{ id, path, record?, tolerances? }`. |
905
961
  | `--columns <n>` | pack/export | Grid columns, 1–1024; default is near-square. |
906
962
  | `--format <name>` | export, pack, mount | `export`: `generic` (default), `tiled`, or `godot` (TileSet). `pack` and `mount`: `generic` (default), `aseprite` (sheet JSON), or `godot` (SpriteFrames). |
@@ -918,6 +974,10 @@ Print the package version. `-v` is an alias.
918
974
  | `--reviewer <name>` | refine approve | Human reviewer stored in the quality companion. |
919
975
  | `--note <text>` | refine approve | Optional review note stored in the quality companion. |
920
976
  | `--model-root <path>` | recipe verify | ComfyUI `models` directory. Enables streamed hash checks for every external model declared by the recipe. |
977
+ | `--quantize <n>` | unzoom | `0` auto-detects a palette (default), `-1` keeps every color, `2`-`256` quantizes to exactly that many. |
978
+ | `--description <text>` | font | The font's style, e.g. `"warm orange arcade font"`. |
979
+ | `--weight <w>` | font | `Bold` or `Regular` (default). |
980
+ | `--glyph-px <n>` | font | Native glyph size: `8`, `16` (default), `32`, or `64`. |
921
981
  | `--generation <n\|hash>` | restore | Bring a replaced generation back: `1` is the most recently replaced, or a prefix of one of its output hashes. One asset at a time. |
922
982
 
923
983
  ## Exit and output contract
package/docs/ENDPOINTS.md CHANGED
@@ -169,9 +169,22 @@ were the correct source instead. Wired up as the `revision` asset shape's
169
169
  `reduce-colors`/`correct-pixelart` modes; see [Controlled asset
170
170
  revisions](./REVISIONS.md#cleanup-reduce-colors-and-correct-pixelart). Only
171
171
  confirmed at this one size — whether it stays flat at larger canvases (unlike
172
- `inpaint`'s canvas-tiered cost) is not yet known, and multi-frame batch input
173
- (both endpoints are built to quantize/clean several frames in one call) is
174
- unmeasured and unmodeled.
172
+ `inpaint`'s canvas-tiered cost) is not yet known. Multi-frame input (both
173
+ endpoints quantize/clean several same-size frames in one call, onto one
174
+ shared palette) is now modeled: a revision whose parent is a set sends every
175
+ member together; see [Revising a whole set at
176
+ once](./REVISIONS.md#revising-a-whole-set-at-once). Its price per call is
177
+ unmeasured.
178
+
179
+ `/unzoom` (same `Cleanup` tag, also synchronous) recovers native-resolution
180
+ pixel art from an upscaled image: at least 256×256 in, at most 2048×2048 in
181
+ area, and the result is opaque (transparency is composited onto white before
182
+ grid detection). PixelLab's own API overview tells integrators to run it on
183
+ any user-supplied reference art first. Wired up as `pixelkiln unzoom`, a
184
+ standalone command on a loose file rather than a revision mode, since the
185
+ art it is for (a reference pulled from outside) is not a manifest asset; see
186
+ [the CLI reference](./CLI.md#unzoom). Cost unmeasured; PixelLab's MCP tool
187
+ description claims 0.1 generations, the same as the rest of the tier.
175
188
 
176
189
  ---
177
190
 
@@ -533,13 +546,25 @@ Listed so the gaps are known rather than assumed away:
533
546
  required — are now wired up as the `revision` asset shape's `animate` and
534
547
  `animate-pixminimax` modes; see
535
548
  [Animation and interpolation](./REVISIONS.md#animation-and-interpolation).
536
- Both are schema-only for the *request* (live OpenAPI document, not an
549
+ `/interpolation-v2` ("Interpolate (Pro)", in-betweens from one keyframe to
550
+ another) and `/edit-animation-v2` ("Edit animation (Pro)", one text edit
551
+ across every frame of a set) are wired the same way, as the `interpolate`
552
+ and `edit-animation` modes; see
553
+ [Interpolate between two keyframes](./REVISIONS.md#interpolate-between-two-keyframes).
554
+ All four are schema-only for the *request* (live OpenAPI document, not an
537
555
  observed call); the *completed job* response shape is a genuine unknown —
538
556
  no usage example exists for either endpoint at all, only the generic
539
557
  background-job one — so `pollAnimateRevision` checks several plausible
540
558
  field names defensively rather than assume one. Cost borrows `character`'s
541
559
  own measured v3-loop formula as a placeholder, for the same reason
542
- `objectPro` borrowed `character` pro-flash's.
560
+ `objectPro` borrowed `character` pro-flash's; `interpolate` and
561
+ `edit-animation` borrow the 20/25/40 Pro canvas tiers instead, since
562
+ neither schema carries a usage example.
563
+ - `/generate-font-pro` (+ `GET /generate-font-pro/{job_id}`) — an 80-glyph
564
+ atlas PNG plus a `.ttf` from a style description, `weight`
565
+ (`Bold`/`Regular`), and `glyph_px` (8/16/32/64). Documented at 25
566
+ generations, unmeasured. Wired up as `pixelkiln font`, outside the
567
+ manifest; see [the CLI reference](./CLI.md#font).
543
568
  - Everything else PixelLab's own tutorials demonstrate that this file has no
544
569
  entry for at all — "animation to animation" motion transfer,
545
570
  skeleton-animation's rig/template-style controls, Object Creator, and Map
@@ -555,7 +580,8 @@ Listed so the gaps are known rather than assumed away:
555
580
  (the standalone isometric tile endpoint alongside it now has one real
556
581
  measured data point — see above)
557
582
  - `generate-font-pro`, job-based, with a response shape not in the simple
558
- `{usage, image}` form
583
+ `{usage, image}` form (wrapped by `pixelkiln font`, but its cost is not
584
+ yet measured)
559
585
  - the character family beyond what the `character` generator uses: portraits
560
586
  (`portrait-character-pro` both ways, `/characters/{id}/portrait`), outfit
561
587
  transfer (`transfer-outfit-v2`), lip-sync, skeleton animation, and
@@ -16,6 +16,7 @@ account; [ENDPOINTS.md](./ENDPOINTS.md) contains the detailed experiments.
16
16
  | Controlled pose/expression sequence in ComfyUI | `frames` | 0 `free` provider units | one atomic ordered set |
17
17
  | A character facing 4 or 8 directions, its poses, and its animations | `character` | 1 per base (standard), 20–40 per pose, 1 per template loop | one set of directions, or one ordered loop |
18
18
  | UI chrome — panels, buttons, health bars, toolbars | `uiAsset` | 20 generations (measured once, at 256x192) | 1 composited image |
19
+ | One UI element from a description, 16px and up, optionally guided by a concept image | `uiElement` | 20–40 generations (unmeasured) | 1 image |
19
20
 
20
21
  Start with `map` unless a required capability points elsewhere. Forty `map`
21
22
  re-rolls cost the same as one 64×64 `1dir` call.
@@ -399,6 +400,50 @@ price at an area well above where `1dir`/`tiles` would bill the ceiling.
399
400
  Left as an over-read for `--budget` until a second size is measured; see
400
401
  [ENDPOINTS.md](./ENDPOINTS.md) for the full writeup.
401
402
 
403
+ ## `uiElement`
404
+
405
+ `uiElement` wraps PixelLab's `/generate-ui-v2` ("Generate UI (Pro)"): one UI
406
+ element — a button, a health bar, an inventory slot, a dialogue box — from
407
+ the prompt alone. Where `uiAsset` lays a panel out from `pieces`/`elements`
408
+ on a 192px-and-up canvas, `uiElement` has no layout vocabulary and goes down
409
+ to 16×16, which suits icons and small widgets:
410
+
411
+ ```jsonc
412
+ {
413
+ "styles": {
414
+ "hud": {
415
+ "generator": "uiElement",
416
+ "uiColorPalette": "brown and gold",
417
+ "styleImages": [{ "path": "refs/hud-concept.png" }],
418
+ "outDir": "art/ui"
419
+ }
420
+ },
421
+ "assets": {
422
+ "slot": { "prompt": "wooden inventory slot with metal corners", "width": 32, "height": 32 },
423
+ "hp-bar": { "prompt": "health bar with a red fill", "width": 96, "height": 16 }
424
+ }
425
+ }
426
+ ```
427
+
428
+ - `width`/`height` default to 256×256 and can be non-square: 16 up to 792
429
+ wide and 688 tall, with the exact ceiling set by aspect ratio upstream
430
+ (square tops out at 512×512, 16:9 at 688×384).
431
+ - The style's single `styleImages` entry, if any, is sent as the endpoint's
432
+ `concept_image`: design guidance for the element, not a strict style
433
+ transfer. More than one is refused.
434
+ - `uiColorPalette` is sent as `color_palette`, the same free-text hint
435
+ `uiAsset` takes. PixelLab removes the background by default; set
436
+ `noBackground: false` on the style to keep it.
437
+ - One call is expected to return one image, recorded straight away. If the
438
+ endpoint ever returns several, they go to candidate review like
439
+ `imagePro`'s.
440
+
441
+ **Unmeasured.** The schema carries no usage example and no call has been
442
+ billed yet. The plan prices it on the same 20/25/40 canvas tiers as
443
+ `1dir`/`tiles`, the safe over-read for a Pro endpoint, and the completed
444
+ job's shape is read defensively (`pollUiElement` names the keys it got if
445
+ the shape is not the documented `images` list).
446
+
402
447
  ## Style variants
403
448
 
404
449
  Styles are namespaces. Add another style to re-derive the same asset ids into a
package/docs/MANIFEST.md CHANGED
@@ -66,7 +66,7 @@ not merely a label edit.
66
66
  | `remoteId` | string | The provider's own id for art that already exists on the account, so `adopt` maps it without matching bytes: an object id, a character id, or `<character id>#<animation group id>`. Not part of the spec's identity. |
67
67
  | `revision` | object | Controlled image-to-image or inpaint dependency. See [controlled revisions](REVISIONS.md). |
68
68
  | `state` | object | `character` styles: a pose or outfit of another character asset. See [Characters](./CHARACTERS.md). |
69
- | `animation` | object | `character` styles: a loop of another character asset in one direction. See [Characters](./CHARACTERS.md). |
69
+ | `animation` | object | `character` styles: a loop of another character asset in one direction. `directions` (a list, instead of `direction`) declares the loop for several directions at once and fills the unnamed flips with mirrors; see [One loop, several directions](./CHARACTERS.md#one-loop-several-directions). |
70
70
  | `mirror` | string | Another asset of the same style flipped left to right, made locally at no cost. See [Mirrors](./CHARACTERS.md#mirrors). |
71
71
  | `batch` | object | `1dir` styles: rides along on another `1dir` asset's `item_descriptions` submission instead of generating on its own — `{ "of": "<leader asset id>", "index": <1-based slot> }`. See [`1dir`](./GENERATORS.md#1dir). |
72
72
  | `pieces` | array | `uiAsset` styles: exact shape regions (`rounded_rect`/`circle`/`polygon`) the panel is composited from, each with a unique `id`. See [`uiAsset`](./GENERATORS.md#uiasset). |
@@ -116,7 +116,7 @@ One asset using the fields most projects reach for:
116
116
  |---|---|---|
117
117
  | `extends` | style id | Optional parent style. The child inherits resolved settings but must declare its own `outDir`. |
118
118
  | `provider` | top-level default | Provider registry id for this style. Assets cannot override it. |
119
- | `generator` | `map` | `map`, `1dir`, `pixflux`, `tiles`, `terrain`, `isometricTile`, `imagePro`, `character`, `objectPro`, `uiAsset`, or provider-specific `animation`/`frames`. See [GENERATORS.md](./GENERATORS.md). |
119
+ | `generator` | `map` | `map`, `1dir`, `pixflux`, `tiles`, `terrain`, `isometricTile`, `imagePro`, `character`, `objectPro`, `uiAsset`, `uiElement`, or provider-specific `animation`/`frames`. See [GENERATORS.md](./GENERATORS.md). |
120
120
  | `outDir` | string, required | Output directory relative to the manifest. |
121
121
  | `promptPrefix` | `""` | Prepended to every participating asset prompt. |
122
122
  | `promptSuffix` | `""` | Appended to every participating asset prompt. |
@@ -466,17 +466,17 @@ their own page: [Characters](./CHARACTERS.md) and
466
466
 
467
467
  | Revision field | Type | Meaning |
468
468
  |---|---|---|
469
- | `mode` | enum, required | `image-to-image`, `inpaint`, `outpaint`, `reduce-colors`, `correct-pixelart`, `animate`, or `animate-pixminimax`. The selected provider must opt into the mode. |
470
- | `from` | asset id, required | Parent asset in the same style. Self-references, unknown ids, and cycles are rejected. |
469
+ | `mode` | enum, required | `image-to-image`, `inpaint`, `outpaint`, `reduce-colors`, `correct-pixelart`, `animate`, `animate-pixminimax`, `interpolate`, or `edit-animation`. The selected provider must opt into the mode. |
470
+ | `from` | asset id, required | Parent asset in the same style. Self-references, unknown ids, and cycles are rejected. A parent written as a set (a character's directions, an animation's frames) is read as every member: `reduce-colors`, `correct-pixelart`, and `edit-animation` take the whole set in one call; the other modes refuse it. See [Revising a whole set at once](./REVISIONS.md#revising-a-whole-set-at-once). |
471
471
  | `mask` | string | Manifest-relative PNG required for `inpaint`; rejected for the other modes. Its dimensions must match an available source. |
472
- | `strength` | number 0–1 | Workflow edit/denoise strength (`image-to-image`, `correct-pixelart`); rejected for `reduce-colors`, `animate`, and `animate-pixminimax`. Interpretation is provider- and model-specific. |
472
+ | `strength` | number 0–1 | Workflow edit/denoise strength (`image-to-image`, `correct-pixelart`); rejected for `reduce-colors`, `animate`, `animate-pixminimax`, `interpolate`, and `edit-animation`. Interpretation is provider- and model-specific. |
473
473
  | `numColors` | integer 2–256 | `reduce-colors` only. Target palette size; mutually exclusive with `paletteImage`. |
474
474
  | `paletteImage` | string | `reduce-colors` only. Manifest-relative image whose colors become the palette; unlike `mask`, no size relationship to the source is required. |
475
475
  | `dithering` | enum | `reduce-colors` only. `none` (default), `2x2`, `4x4`, or `8x8`. |
476
476
  | `ditheringStrength` | number 0–10 | `reduce-colors` only. Ignored when `dithering` is `none`. |
477
477
  | `frames` | integer 4–40, even | `animate`/`animate-pixminimax` only. `animate` caps at 16; `animate-pixminimax` allows up to 40. |
478
- | `fps` | integer 1–60 | `animate`/`animate-pixminimax` only. Playback rate recorded with the frames; the provider does not store one. |
479
- | `lastFrame` | string | `animate`/`animate-pixminimax` only. Manifest-relative image pinning where the motion ends, turning an open-ended animation into an interpolation. |
478
+ | `fps` | integer 1–60 | `animate`/`animate-pixminimax`/`interpolate`/`edit-animation` only. Playback rate recorded with the frames; the provider does not store one. Defaults to the parent's own rate when it is a loop, else 8. |
479
+ | `lastFrame` | string | `animate`/`animate-pixminimax`: manifest-relative image pinning where the motion ends, turning an open-ended animation into an interpolation. `interpolate`: required, the ending keyframe (the parent is the start); it must match the parent's size. |
480
480
  | `direction` | enum | `animate-pixminimax` only. The sprite's facing, used only alongside `enhancePrompt`. |
481
481
  | `enhancePrompt` | boolean | `animate`/`animate-pixminimax` only. Lets the provider expand the prompt into a fuller motion description first, for an extra documented +0.05-generation surcharge. |
482
482
 
package/docs/PIXELLAB.md CHANGED
@@ -164,10 +164,13 @@ reference sprites, concept images, style anchors, mirrors, and every
164
164
  `animate-character` control; see [Characters](./CHARACTERS.md). Masked
165
165
  inpainting and whole-image editing are the `revision` asset shape, not the
166
166
  `character` generator; see [Controlled asset revisions](./REVISIONS.md).
167
- Object Creator (generic 8-direction object rotation, object states and
168
- animation), UI element and RPG UI-kit generation, fonts, and Map Workshop
167
+ The same revision shape animates, interpolates, recolors, and cleans up any
168
+ asset, a whole character or loop at once where the endpoint takes a set.
169
+ Object Creator is the `objectPro` generator, UI panels and elements are
170
+ `uiAsset` and `uiElement`, and fonts and unzooming reference art are the
171
+ standalone `pixelkiln font` and `pixelkiln unzoom` commands. Map Workshop
169
172
  scene composition (placing objects and characters into a map, in-scene
170
- inpainting, export) are separate PixelLab product surfaces this adapter does
173
+ inpainting, export) is a separate PixelLab product surface this adapter does
171
174
  not model at all — including PixelKiln's own `map` generator, which is
172
175
  unrelated to PixelLab's "Map Workshop" despite the shared name: `map` returns
173
176
  one static prop in one generation, with no scene or canvas concept. Use the
package/docs/REVISIONS.md CHANGED
@@ -7,10 +7,11 @@ dependencies before submission, and records the lineage in the lockfile.
7
7
 
8
8
  The manifest and pipeline are provider-neutral. ComfyUI and PixelLab implement
9
9
  it; PixelLab covers `image-to-image`, `inpaint`, `reduce-colors`,
10
- `correct-pixelart`, `animate`, and `animate-pixminimax`, not `outpaint` (its
11
- API has no canvas-expansion endpoint). ComfyUI covers every mode generically
12
- except `animate`/`animate-pixminimax`, which need an ordered frame set its
13
- revision path cannot produce. Retro Diffusion and Scenario reject revision
10
+ `correct-pixelart`, `animate`, `animate-pixminimax`, `interpolate`, and
11
+ `edit-animation`, not `outpaint` (its API has no canvas-expansion endpoint).
12
+ ComfyUI covers every mode generically except the four frame-set modes
13
+ (`animate`, `animate-pixminimax`, `interpolate`, `edit-animation`), which need
14
+ an ordered frame set its revision path cannot produce. Retro Diffusion and Scenario reject revision
14
15
  work during offline resolution instead of silently starting a fresh
15
16
  text-to-image job.
16
17
 
@@ -138,14 +139,47 @@ photographs and 3-D renders, not for reprocessing" pixel art):
138
139
  `strength` (0–1, PixelLab's own default 0.1) is how far the model may move
139
140
  from the source; start low and only raise it for real repair.
140
141
 
141
- Neither mode supports the source being a frame set (a character animation, an
142
- `objectPro` loop) yet — each processes exactly one image per revision, the
143
- same as `image-to-image`. PixelLab's own `/reduce-colors` and
144
- `/correct-pixelart` endpoints are built to take several frames in one call
145
- specifically so an animation's frames (or a character's eight directions)
146
- share one consistent palette/cleanup pass instead of drifting frame by frame;
147
- pixelkiln does not expose that yet — revise each frame as its own asset for
148
- now, and expect some frame-to-frame drift from doing so independently.
142
+ ### Revising a whole set at once
143
+
144
+ Both modes also take a parent that is a **set** rather than one image: a
145
+ character's or object's directions, an animation loop, or another frame-set
146
+ revision. Every member goes to PixelLab in one call, which is the point of
147
+ both endpoints: an animation's frames (or a character's eight directions) come
148
+ back on one shared palette, or through one consistent cleanup pass, instead of
149
+ drifting frame by frame the way revising each member separately would.
150
+
151
+ ```jsonc
152
+ {
153
+ "assets": {
154
+ "hero-walk-east": { "prompt": "walking", "animation": { "of": "hero", "direction": "east" } },
155
+ "hero-walk-east-16c": {
156
+ "prompt": "16-color walk",
157
+ "revision": { "mode": "reduce-colors", "from": "hero-walk-east", "numColors": 16 }
158
+ }
159
+ }
160
+ }
161
+ ```
162
+
163
+ Nothing new is declared: when the parent has no file at its own path but has
164
+ `<stem>-<role>.png` members beside it, those members are the source. Two role
165
+ vocabularies are recognized: a full 8- or 4-direction rotation (`-south.png`,
166
+ `-south-east.png`, ...), or numbered frames (`-frame-00.png` upward). The
167
+ child's output keeps the parent's roles, so `hero-walk-east-16c-frame-03.png`
168
+ is the reduced version of `hero-walk-east-frame-03.png`, and a frame set keeps
169
+ its playback rate.
170
+
171
+ - Every member's hash goes into the child's identity, so a change to any one
172
+ frame (not only the first) makes the child stale. The lock records one
173
+ `sourceSha256` over every member hash in order, the same formula a mirror
174
+ records over its source.
175
+ - Before spending, the member files found on disk are checked against the
176
+ parent's own lock entry. A `-frame-11.png` left over from an older, longer
177
+ generation blocks the revision with a message naming the count mismatch
178
+ rather than being sent as if it belonged.
179
+ - Members must all be the same size. `reduce-colors`' 512×512 limit is for the
180
+ whole call: sixteen 64×64 frames fit, five 256×256 do not.
181
+ - Every other mode except `edit-animation` (below) reads exactly one image and
182
+ refuses a set parent at plan time, naming the modes that take one.
149
183
 
150
184
  ## Animation and interpolation
151
185
 
@@ -216,9 +250,70 @@ already takes for `image-to-image`/`inpaint`. Cost is likewise unmeasured:
216
250
  near-identical) as a placeholder, plus `enhancePrompt`'s own documented
217
251
  +0.05-generation surcharge, which is a real schema number rather than a guess.
218
252
 
219
- ComfyUI does not support either mode: its revision path always writes a
220
- single output image, and an animation is a frame set — a structural gap
221
- rather than a missing binding, so `supportsRevision` refuses it up front
253
+ ### Interpolate between two keyframes
254
+
255
+ `interpolate` calls `/interpolation-v2`, PixelLab's dedicated "Interpolate
256
+ (Pro)" tool: the parent is the start keyframe, `lastFrame` (required here) is
257
+ the end keyframe, and the asset's `prompt` describes the transition. Unlike
258
+ `lastFrame` on `animate`, the frame count is the model's own choice (PixelLab
259
+ documents "typically 4-8"); there is no `frames` field.
260
+
261
+ ```jsonc
262
+ {
263
+ "assets": {
264
+ "knight-hit-fall": {
265
+ "prompt": "knocked backward off his feet by a heavy blow",
266
+ "revision": {
267
+ "mode": "interpolate",
268
+ "from": "knight-hit-airborne",
269
+ "lastFrame": "art/knight-hit-landed.png",
270
+ "fps": 12
271
+ }
272
+ }
273
+ }
274
+ }
275
+ ```
276
+
277
+ This is the "key poses first, then interpolate the gap" pattern from
278
+ PixelLab's own tutorials, for motion one `animate` pass cannot produce
279
+ cleanly: build each key pose as a plain `image-to-image` revision (an
280
+ airborne "just hit" frame, then a "landed" frame edited from it), then let
281
+ `interpolate` fill the motion between them. `lastFrame` can be any earlier
282
+ asset's own generated output file, and the plan holds the revision as
283
+ `blocked` until that file exists. Both keyframes must be the same size, 16 to
284
+ 128 pixels per side.
285
+
286
+ ### Edit every frame of an animation at once
287
+
288
+ `edit-animation` calls `/edit-animation-v2`: one text edit ("add a red cape")
289
+ applied across a whole frame set in a single call, so the change stays
290
+ consistent from frame to frame. It is `image-to-image` for an entire
291
+ animation. The parent must be a set (see
292
+ [Revising a whole set at once](#revising-a-whole-set-at-once)); a
293
+ single-image parent is refused. The result keeps the parent's roles, so a
294
+ character's eight directions edited together stay eight directions.
295
+
296
+ ```jsonc
297
+ {
298
+ "revision": { "mode": "edit-animation", "from": "hero-walk-east" }
299
+ }
300
+ ```
301
+
302
+ Frames are 16 to 256 pixels per side, and because PixelLab packs every frame
303
+ into one grid before editing, the frame ceiling falls as frames grow: 16
304
+ frames up to 64px, 9 up to 80px, 4 up to 256px. Both are checked at plan
305
+ time.
306
+
307
+ `interpolate` and `edit-animation` land in candidate review like `animate`,
308
+ take `fps` (defaulting to the parent's own rate when it has one, else 8), and
309
+ reject `strength`. Neither has been exercised against a live account: request
310
+ fields come from PixelLab's live OpenAPI document, the completed job is read
311
+ by the same defensive frame-list lookup `animate` uses, and cost is an
312
+ estimate (see [PixelLab](#pixellab) below).
313
+
314
+ ComfyUI supports none of the four frame-set modes: its revision path always
315
+ writes a single output image, and an animation is a frame set — a structural
316
+ gap rather than a missing binding, so `supportsRevision` refuses it up front
222
317
  rather than failing partway through submission.
223
318
 
224
319
  ## Dependency gate
@@ -330,9 +425,20 @@ further — there is nothing to ask.
330
425
  recorded as `billed` on every call regardless
331
426
  (`context.metadata.revisionUsage`, since there is no later background job
332
427
  to ask the way `billedForJob` asks for every other adapter call).
333
- - `reduce-colors`'s total size limit is 512×512 worth of pixels (262144px²);
334
- `correct-pixelart`'s is 1024 pixels per side. Both are checked before a
335
- request is sent, the same as `inpaint`'s 32–512px floor/ceiling.
428
+ - `reduce-colors`'s total size limit is 512×512 worth of pixels (262144px²)
429
+ across every member sent; `correct-pixelart`'s is 1024 pixels per side.
430
+ Both are checked before a request is sent, the same as `inpaint`'s
431
+ 32–512px floor/ceiling. The 0.1-generation cost above was measured on one
432
+ image; a whole member set is still one call, but whether its price stays
433
+ flat is unmeasured.
434
+ - `interpolate` (`/interpolation-v2`) and `edit-animation`
435
+ (`/edit-animation-v2`) are both documented "Pro" endpoints with no usage
436
+ example in the schema and no live measurement. `estimate()` borrows the
437
+ same 20/25/40 canvas tiers the other unmeasured Pro edits use, sized on the
438
+ keyframe for `interpolate` and on the packed frame grid for
439
+ `edit-animation` (four 32×32 frames price as a 64×64 canvas, the 40 tier).
440
+ Over-reading is the safe direction for `--budget`; expect a live bill at or
441
+ below the plan.
336
442
  - `animate` calls `/animate-with-text-v3`, `animate-pixminimax` calls
337
443
  `/animate-pixminimax` — both real async background jobs, unlike the two
338
444
  Cleanup-tier modes above, since they generate new frames rather than
@@ -414,7 +520,10 @@ further — there is nothing to ask.
414
520
  The lock entry records revision mode, parent id, parent hash, optional mask
415
521
  hash, strength, and — for `reduce-colors` — `numColors`, the palette image's
416
522
  hash, and the dithering settings, or — for `animate`/`animate-pixminimax` —
417
- `frames`, `fps`, the last frame's hash, `direction`, and `enhancePrompt`.
523
+ `frames`, `fps`, the last frame's hash, `direction`, and `enhancePrompt`
524
+ (`interpolate` records `fps` and the end keyframe's hash; `edit-animation`
525
+ records `fps`). For a set parent, the parent hash is one hash over every
526
+ member's hash in order.
418
527
  ComfyUI provider metadata also retains the same lineage beside the workflow
419
528
  hash.
420
529
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixelkiln",
3
- "version": "0.61.0",
3
+ "version": "0.63.0",
4
4
  "description": "Manifest-driven pixel-art generation, review, recovery, and packaging with deterministic provenance.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -45,7 +45,8 @@
45
45
  "imagePro",
46
46
  "isometricTile",
47
47
  "objectPro",
48
- "uiAsset"
48
+ "uiAsset",
49
+ "uiElement"
49
50
  ],
50
51
  "default": "map"
51
52
  },
@@ -501,7 +502,8 @@
501
502
  "imagePro",
502
503
  "isometricTile",
503
504
  "objectPro",
504
- "uiAsset"
505
+ "uiAsset",
506
+ "uiElement"
505
507
  ],
506
508
  "default": "map"
507
509
  },
@@ -1007,7 +1009,9 @@
1007
1009
  "reduce-colors",
1008
1010
  "correct-pixelart",
1009
1011
  "animate",
1010
- "animate-pixminimax"
1012
+ "animate-pixminimax",
1013
+ "interpolate",
1014
+ "edit-animation"
1011
1015
  ]
1012
1016
  },
1013
1017
  "from": {
@@ -1149,6 +1153,23 @@
1149
1153
  ],
1150
1154
  "default": "south"
1151
1155
  },
1156
+ "directions": {
1157
+ "type": "array",
1158
+ "items": {
1159
+ "type": "string",
1160
+ "enum": [
1161
+ "south",
1162
+ "south-east",
1163
+ "east",
1164
+ "north-east",
1165
+ "north",
1166
+ "north-west",
1167
+ "west",
1168
+ "south-west"
1169
+ ]
1170
+ },
1171
+ "minItems": 1
1172
+ },
1152
1173
  "frames": {
1153
1174
  "type": "integer",
1154
1175
  "minimum": 4,
@@ -120,7 +120,8 @@
120
120
  "imagePro",
121
121
  "isometricTile",
122
122
  "objectPro",
123
- "uiAsset"
123
+ "uiAsset",
124
+ "uiElement"
124
125
  ],
125
126
  "default": "map"
126
127
  },
@@ -48,7 +48,9 @@ hashes.
48
48
  dependent asset the same way: one `pixelkiln gen` runs the waves in order
49
49
  and stops when the budget cannot cover the next one. Before declaring the
50
50
  east-facing loop of a character, check whether a `mirror` of the west one
51
- does the job for nothing; before drawing a base from text, ask whether the
51
+ does the job for nothing (an animation's `directions` list declares the
52
+ generated sides once and mirrors the rest; `--only <shorthand id>`
53
+ selects the whole family); before drawing a base from text, ask whether the
52
54
  user has a south-facing sprite to `reference` instead (1 generation on
53
55
  `pro-flash` at 64px). Mirrors swap handedness.
54
56
  - Treat every ComfyUI output as source material until it passes the native-grid,