pixelkiln 0.85.0 → 0.86.1

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.
package/PROVIDERS.md CHANGED
@@ -114,13 +114,13 @@ only the part that fits its existing still-image pipeline.
114
114
 
115
115
  | Decision | Scenario through PixelKiln |
116
116
  |---|---|
117
- | Best fit today | A controlled one-asset spike using a hosted model or project LoRA with a hard CU ceiling |
118
- | PixelKiln generator | `map` stills |
117
+ | Best fit today | Large or illustrative art rendered big then reduced (render, Pixelate, cut out, refine), image-to-image edits of existing art, and a chain of models across styles, each with a hard CU ceiling. Poor for small native-size sprites. |
118
+ | PixelKiln generator | `map` stills, with `styleImages` and `image-to-image` revisions as reference uploads, and `revision.fromStyle` to chain styles |
119
119
  | Output | One to four PNG candidates, 16–4096px; each model has its own bounds |
120
120
  | Cost model | Manifest `maxComputeUnits`, command budget, then free authoritative preflight before each paid request |
121
121
  | Recovery | Durable job and asset IDs refresh temporary signed original-file URLs |
122
122
  | Account lifecycle | Read-only connectivity check; no balance, list, adopt, salvage, tag, or purge yet |
123
- | Confidence | BFL Flux 2 Dev live-tested through quote, billing, single/two-output jobs, human selection, PNG download, and provider-backed restore; other model schemas remain unverified |
123
+ | Confidence | BFL Flux 2 Dev live-tested through quote, billing, single/two-output jobs, human selection, PNG download, and provider-backed restore. Ten generation models, six tool models, reference-image edits, a three-style chain, and a game's landmarks and cover have each been run once; see the [Scenario page](./docs/SCENARIO.md). Other model schemas remain unverified. |
124
124
 
125
125
  Scenario model schemas differ. The current adapter sends prompt, width, height,
126
126
  output count, optional seed, and explicitly declared JSON parameters. Verify
@@ -134,8 +134,8 @@ background. That distinction matters more than raw canvas size.
134
134
 
135
135
  | Asset type | PixelLab | Retro Diffusion | ComfyUI | Scenario |
136
136
  |---|---|---|---|---|
137
- | Isolated house, building, mountain, or landmark | Start with `map`: arbitrary dimensions up to 400×400 and a measured one-generation cost. Live benchmark outputs had opaque backgrounds, so plan for cleanup. Use `1dir` only when references or candidate variety justify 20–40 generations and a square canvas. | Start with `rd_plus__topdown_asset`, `rd_plus__isometric_asset`, or `rd_tile__scene_object`, depending on perspective. `rd_tile__scene_object` is intended for 64–384px objects placed on tile maps. | Choose a checkpoint or LoRA trained for the intended perspective, then keep background removal or segmentation in the workflow. For the tested Pixel Art XL stack, target 48–128px native components even though the adapter accepts larger working canvases. | The live BFL smoke produced a readable 512px keep, but it was opaque and used 19,619 colors. Use that profile for concepts or refinement input, not a finished limited-palette asset. A project-specific model may improve consistency but needs its own smoke. |
138
- | Full scenic background | Use `pixflux` with `noBackground: false` when an exact palette matters, or `map` for a simple scene; both top out at 400×400. `imagePro` reaches 792 wide or 688 tall for a flat 40 generations. | `rd_plus__environment` targets one-point-perspective scenes; `rd_plus__topdown_map` targets 3/4 top-down maps. These styles support up to 384×384. | Use composition controls only to establish the scene. Recover and review native components, then compose them at 1× with one grid and palette. A large model canvas is not a large native pixel-art canvas. | The BFL profile accepts canvases up to 2048px, but no Scenario scenic brief has passed the shared benchmark. Treat that as model-canvas capacity, not native pixel resolution. Start with one 512px concept before raising size or steps. |
137
+ | Isolated house, building, mountain, or landmark | Start with `map`: arbitrary dimensions up to 400×400 and a measured one-generation cost. Live benchmark outputs had opaque backgrounds, so plan for cleanup. Use `1dir` only when references or candidate variety justify 20–40 generations and a square canvas. | Start with `rd_plus__topdown_asset`, `rd_plus__isometric_asset`, or `rd_tile__scene_object`, depending on perspective. `rd_tile__scene_object` is intended for 64–384px objects placed on tile maps. | Choose a checkpoint or LoRA trained for the intended perspective, then keep background removal or segmentation in the workflow. For the tested Pixel Art XL stack, target 48–128px native components even though the adapter accepts larger working canvases. | Raw model output is opaque and anti-aliased (the BFL smoke keep used 19,619 colors), but a chain of render at four times the size, Pixelate to the target grid, Birefnet, and `refine` took a 960 px render of an alpine lodge to a 240×208 native grid at high confidence with 63 colors, for 18 CU (a 1 CU render can be nearly as good). Details and the Pixelate grid caveat are in [Set up Scenario](./docs/SCENARIO.md#large-and-illustrative-art-render-big-pixelate-refine). One sample per subject. |
138
+ | Full scenic background | Use `pixflux` with `noBackground: false` when an exact palette matters, or `map` for a simple scene; both top out at 400×400. `imagePro` reaches 792 wide or 688 tall for a flat 40 generations. | `rd_plus__environment` targets one-point-perspective scenes; `rd_plus__topdown_map` targets 3/4 top-down maps. These styles support up to 384×384. | Use composition controls only to establish the scene. Recover and review native components, then compose them at 1× with one grid and palette. A large model canvas is not a large native pixel-art canvas. | GPT Image 2 at four times the target size, then Pixelate, turned a course-cover brief into a convincing pixel scene with 31 colors and legible title text. It has not been put through the shared benchmark. A large model canvas is not native pixel resolution: pixelate to the grid you need and check it with `refine`. |
139
139
  | Style consistency across a set | `1dir` accepts a style reference and returns size-dependent candidates, but it is more expensive and capped at the square-object range. | RD Pro accepts up to nine references and has stronger prompt following, but its common styles top out at 256×256 and cost $0.18 per image. Environment-specific RD Plus styles trade references for a larger 384px canvas. | LoRAs, reference adapters, ControlNet, and shared latent settings can live in the committed workflow. Reproducibility also depends on external model and custom-node versions. | Scenario's project models and LoRAs are the main reason to use it for a set. PixelKiln can pin the model ID and parameters, but its first live run covers only the public BFL profile. |
140
140
  | Very large final scene | Generate reusable objects, terrain, and background layers separately; assemble them deterministically and integer-upscale the result. | Use the same layered approach. The API has a 512px overall ceiling, but the useful environment and scene-object styles currently cap at 384px. | The graph can tile, upscale, or composite beyond hosted-provider limits, but memory and seam quality become workflow concerns. Prefer reusable layers unless the scene truly needs one render. | Scenario can request a larger raster from a compatible model, but the same rule applies: generate reusable layers at their useful native detail, compose at 1×, and integer-upscale only the final scene. |
141
141
 
package/dist/cli.js CHANGED
@@ -777,6 +777,7 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
777
777
  revision: spec.revision ? {
778
778
  mode: spec.revision.mode,
779
779
  from: spec.revision.sourceAssetId,
780
+ fromStyle: spec.revision.sourceStyleId,
780
781
  sourceSha256: spec.revision.sourceSha256,
781
782
  maskSha256: spec.revision.maskSha256,
782
783
  strength: spec.revision.strength,
@@ -1126,8 +1127,16 @@ var RevisionDitheringSchema = z.enum(["none", "2x2", "4x4", "8x8"]);
1126
1127
  var RevisionSchema = z.object({
1127
1128
  /** What kind of controlled change the provider workflow performs. */
1128
1129
  mode: RevisionModeSchema,
1129
- /** Parent asset id in the same style. */
1130
+ /** Parent asset id. It lives in this asset's own style unless `fromStyle` names another. */
1130
1131
  from: z.string().min(1),
1132
+ /**
1133
+ * The style the parent lives in, when it is not this asset's own. The
1134
+ * parent is generated by that style's provider and model, and this
1135
+ * revision starts from its output file, so a chain (generate with one
1136
+ * model, clean up with another) is declared instead of restated by hand.
1137
+ * Styles may depend on one another but not in a cycle.
1138
+ */
1139
+ fromStyle: z.string().min(1).optional(),
1131
1140
  /** Manifest-relative black/white PNG. Required only for masked inpainting. */
1132
1141
  mask: z.string().min(1).optional(),
1133
1142
  /** Provider-neutral edit strength. The active adapter must bind it explicitly. */
@@ -1988,6 +1997,7 @@ var LockEntrySchema = z.object({
1988
1997
  revision: z.object({
1989
1998
  mode: RevisionModeSchema,
1990
1999
  sourceAssetId: z.string().min(1),
2000
+ sourceStyleId: z.string().min(1).optional(),
1991
2001
  sourceSha256: z.string().regex(/^[0-9a-f]{64}$/),
1992
2002
  maskSha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
1993
2003
  strength: z.number().min(0).max(1).optional(),
@@ -4405,7 +4415,7 @@ async function inspectRevision(spec, lock, seen) {
4405
4415
  if (!dependency.ready) {
4406
4416
  return {
4407
4417
  ready: false,
4408
- reason: `revision source ${spec.styleId}/${revision.sourceAssetId} is not ready: ${dependency.reason}`
4418
+ reason: `revision source ${revision.sourceStyleId ?? spec.styleId}/${revision.sourceAssetId} is not ready: ${dependency.reason}`
4409
4419
  };
4410
4420
  }
4411
4421
  if (revision.sourceMembers) {
@@ -10836,13 +10846,24 @@ ${issues}`);
10836
10846
  }
10837
10847
  }
10838
10848
  if (asset.revision) {
10839
- if (!parsed.data.assets[asset.revision.from]) {
10849
+ const parent = parsed.data.assets[asset.revision.from];
10850
+ if (!parent) {
10840
10851
  unknownReferences.push(
10841
10852
  `assets.${assetId}.revision.from: unknown asset "${asset.revision.from}"`
10842
10853
  );
10843
10854
  } else if (asset.revision.from === assetId) {
10844
10855
  unknownReferences.push(`assets.${assetId}.revision.from: an asset cannot revise itself`);
10845
10856
  }
10857
+ const fromStyle = asset.revision.fromStyle;
10858
+ if (fromStyle) {
10859
+ if (!styleIds.has(fromStyle)) {
10860
+ unknownReferences.push(`assets.${assetId}.revision.fromStyle: unknown style "${fromStyle}"`);
10861
+ } else if (parent && parent.styles.length && !parent.styles.includes(fromStyle)) {
10862
+ unknownReferences.push(
10863
+ `assets.${assetId}.revision.fromStyle: "${asset.revision.from}" is not in style "${fromStyle}"`
10864
+ );
10865
+ }
10866
+ }
10846
10867
  }
10847
10868
  for (const shape of ["state", "animation"]) {
10848
10869
  const of = asset[shape]?.of;
@@ -10958,7 +10979,7 @@ async function resolveSpecs(loaded, filter) {
10958
10979
  return provider;
10959
10980
  };
10960
10981
  const specs = [];
10961
- const styleIds = Object.keys(manifest.styles).filter(
10982
+ const requestedStyleIds = Object.keys(manifest.styles).filter(
10962
10983
  (id) => !filter?.styles?.length || filter.styles.includes(id)
10963
10984
  );
10964
10985
  for (const unknownStyle of filter?.styles ?? []) {
@@ -10983,6 +11004,39 @@ async function resolveSpecs(loaded, filter) {
10983
11004
  current = parentAssetId(manifest.assets[current]);
10984
11005
  }
10985
11006
  }
11007
+ const styleDependencies = /* @__PURE__ */ new Map();
11008
+ for (const styleId of Object.keys(manifest.styles)) {
11009
+ const needed = /* @__PURE__ */ new Set();
11010
+ for (const assetId of resolutionAssetIds) {
11011
+ const asset = manifest.assets[assetId];
11012
+ if (asset?.styles.length && !asset.styles.includes(styleId)) continue;
11013
+ const fromStyle = asset?.revision?.fromStyle;
11014
+ if (fromStyle && fromStyle !== styleId) needed.add(fromStyle);
11015
+ }
11016
+ styleDependencies.set(styleId, needed);
11017
+ }
11018
+ const styleIds = [];
11019
+ const emitStyles = new Set(requestedStyleIds);
11020
+ {
11021
+ const placed = /* @__PURE__ */ new Set();
11022
+ const visiting = [];
11023
+ const place = (styleId) => {
11024
+ if (placed.has(styleId)) return;
11025
+ const cycleStart = visiting.indexOf(styleId);
11026
+ if (cycleStart >= 0) {
11027
+ throw new Error(
11028
+ `Styles revise each other in a cycle: ${[...visiting.slice(cycleStart), styleId].join(" -> ")}. revision.fromStyle must form a chain, not a loop.`
11029
+ );
11030
+ }
11031
+ visiting.push(styleId);
11032
+ for (const dependency of styleDependencies.get(styleId) ?? []) place(dependency);
11033
+ visiting.pop();
11034
+ placed.add(styleId);
11035
+ styleIds.push(styleId);
11036
+ };
11037
+ for (const styleId of requestedStyleIds) place(styleId);
11038
+ }
11039
+ const styleFinalizers = /* @__PURE__ */ new Map();
10986
11040
  const styleImageCache = /* @__PURE__ */ new Map();
10987
11041
  async function loadStyleImage(rel, what = "Style image") {
10988
11042
  const abs = path14.resolve(root, rel);
@@ -11370,7 +11424,8 @@ async function resolveSpecs(loaded, filter) {
11370
11424
  `Provider "${activeProvider.id}" does not support ${asset.revision.mode} revisions`
11371
11425
  );
11372
11426
  }
11373
- const sourceSpec = await finalize(asset.revision.from);
11427
+ const sourceStyleId = asset.revision.fromStyle && asset.revision.fromStyle !== styleId ? asset.revision.fromStyle : void 0;
11428
+ const sourceSpec = sourceStyleId ? await styleFinalizers.get(sourceStyleId)(asset.revision.from) : await finalize(asset.revision.from);
11374
11429
  const sourceStem = sourceSpec.quality?.outFile ?? (sourceSpec.source ? path14.resolve(root, sourceSpec.source) : sourceSpec.outFile);
11375
11430
  const sourceMembers = await revisionSourceMembers(sourceStem, `revision source ${asset.revision.from}`);
11376
11431
  const sourceFile = sourceMembers?.[0]?.file ?? sourceStem;
@@ -11392,6 +11447,7 @@ async function resolveSpecs(loaded, filter) {
11392
11447
  resolved.revision = {
11393
11448
  mode: asset.revision.mode,
11394
11449
  sourceAssetId: asset.revision.from,
11450
+ ...sourceStyleId ? { sourceStyleId } : {},
11395
11451
  sourceFile,
11396
11452
  sourceSha256: sourceImage?.hash ?? null,
11397
11453
  sourceWidth: sourceImage?.width ?? null,
@@ -11513,6 +11569,8 @@ async function resolveSpecs(loaded, filter) {
11513
11569
  finalized.add(assetId);
11514
11570
  return resolved;
11515
11571
  };
11572
+ styleFinalizers.set(styleId, finalize);
11573
+ if (!emitStyles.has(styleId)) continue;
11516
11574
  for (const assetId of Object.keys(manifest.assets)) {
11517
11575
  if (!requestedAssetIds.has(assetId) || !styleSpecs.has(assetId)) continue;
11518
11576
  specs.push(await finalize(assetId));
@@ -15913,7 +15971,7 @@ async function buildGallerySnapshot(opts) {
15913
15971
  recordedSpecHash: entry?.specHash ?? null,
15914
15972
  currentSpecHash: spec.specHash,
15915
15973
  revision: entry?.revision ?? null,
15916
- revisionParentKey: spec.revision ? lockKey(spec.styleId, spec.revision.sourceAssetId) : entry?.revision ? lockKey(spec.styleId, entry.revision.sourceAssetId) : null,
15974
+ revisionParentKey: spec.revision ? lockKey(spec.revision.sourceStyleId ?? spec.styleId, spec.revision.sourceAssetId) : entry?.revision ? lockKey(entry.revision.sourceStyleId ?? spec.styleId, entry.revision.sourceAssetId) : null,
15917
15975
  mirrorOfKey: spec.mirror ? lockKey(spec.styleId, spec.mirror.sourceAssetId) : entry?.mirror ? lockKey(spec.styleId, entry.mirror.sourceAssetId) : null,
15918
15976
  skeleton: spec.revision?.mode === "animate-skeleton" && spec.revision.keypointsFile ? {
15919
15977
  keypointsFile: portableOutputPath(spec.revision.keypointsFile, root),
@@ -15983,7 +16041,7 @@ async function buildGallerySnapshot(opts) {
15983
16041
  recordedSpecHash: entry.specHash,
15984
16042
  currentSpecHash: null,
15985
16043
  revision: entry.revision,
15986
- revisionParentKey: entry.revision ? lockKey(entry.styleId, entry.revision.sourceAssetId) : null,
16044
+ revisionParentKey: entry.revision ? lockKey(entry.revision.sourceStyleId ?? entry.styleId, entry.revision.sourceAssetId) : null,
15987
16045
  mirrorOfKey: entry.mirror ? lockKey(entry.styleId, entry.mirror.sourceAssetId) : null,
15988
16046
  skeleton: null,
15989
16047
  character: describeCharacter(void 0, entry),
@@ -16919,6 +16977,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
16919
16977
  revision: spec.revision ? {
16920
16978
  mode: spec.revision.mode,
16921
16979
  sourceAssetId: spec.revision.sourceAssetId,
16980
+ ...spec.revision.sourceStyleId ? { sourceStyleId: spec.revision.sourceStyleId } : {},
16922
16981
  sourceSha256: spec.revision.sourceSha256,
16923
16982
  ...spec.revision.maskSha256 ? { maskSha256: spec.revision.maskSha256 } : {},
16924
16983
  ...spec.revision.strength == null ? {} : { strength: spec.revision.strength },