pixelkiln 0.44.0 → 0.45.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.
package/dist/index.js CHANGED
@@ -306,6 +306,12 @@ var StyleObjectSchema = z.object({
306
306
  isometric: z.boolean().optional(),
307
307
  /** `character` only, `v3` bases: let PixelLab expand the prompt into a fuller one before drawing. */
308
308
  enhancePrompt: z.boolean().optional(),
309
+ /**
310
+ * `character` only, `pro` bases: the asset id of a generated 8-direction
311
+ * character in this style whose look every base drawn from text or a
312
+ * concept follows. An asset may override it; the anchor itself ignores it.
313
+ */
314
+ styleCharacter: z.string().min(1).optional(),
309
315
  /** Fixed seed for reproducibility where the endpoint supports it. */
310
316
  seed: z.number().int().optional(),
311
317
  /**
@@ -478,6 +484,15 @@ var AssetSchema = z.object({
478
484
  * still guides the result.
479
485
  */
480
486
  reference: z.union([z.string().min(1), z.record(CharacterDirectionSchema, z.string().min(1))]).optional(),
487
+ /**
488
+ * `character` styles, `pro` bases only: a manifest-relative concept
489
+ * image (a painting, a photo, a sketch; up to 1024px) that seeds the
490
+ * design instead of the prompt alone. A style image or `styleCharacter`
491
+ * may still anchor the look.
492
+ */
493
+ concept: z.string().min(1).optional(),
494
+ /** `character` styles, `pro` bases: this base's style anchor, over the style's. */
495
+ styleCharacter: z.string().min(1).optional(),
481
496
  /**
482
497
  * Another asset of the same style flipped left to right, made locally
483
498
  * from that asset's downloaded files at no generation cost.
@@ -559,6 +574,20 @@ var AssetSchema = z.object({
559
574
  path: ["reference"]
560
575
  });
561
576
  }
577
+ if (asset.concept && (asset.state || asset.animation || asset.mirror)) {
578
+ context.addIssue({
579
+ code: z.ZodIssueCode.custom,
580
+ message: "a concept image belongs on a base; a state, animation, or mirror takes its look from its parent",
581
+ path: ["concept"]
582
+ });
583
+ }
584
+ if (asset.concept && asset.reference) {
585
+ context.addIssue({
586
+ code: z.ZodIssueCode.custom,
587
+ message: "concept and reference are mutually exclusive: a concept seeds a new design, a reference is the sprite to rotate",
588
+ path: ["concept"]
589
+ });
590
+ }
562
591
  if (typeof asset.reference === "object" && !Object.keys(asset.reference).length) {
563
592
  context.addIssue({ code: z.ZodIssueCode.custom, message: "a reference needs at least a south image", path: ["reference"] });
564
593
  }
@@ -1240,10 +1269,12 @@ var PixelLabClient = class {
1240
1269
  image_size: imageSize,
1241
1270
  template_id: args.template,
1242
1271
  no_background: args.noBackground ?? true,
1243
- method: south ? "rotate_character" : "create_with_style",
1272
+ method: south ? "rotate_character" : args.concept ? "create_from_concept" : "create_with_style",
1244
1273
  ...args.view ? { view: args.view } : {},
1245
1274
  ...args.seed != null ? { seed: args.seed } : {},
1246
- ...south ? { reference_image: encode(south) } : args.styleReference ? { reference_image: encode(args.styleReference) } : {}
1275
+ ...south ? { reference_image: encode(south) } : args.styleReference ? { reference_image: encode(args.styleReference) } : {},
1276
+ ...!south && args.concept ? { concept_image: encode(args.concept) } : {},
1277
+ ...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
1247
1278
  };
1248
1279
  } else {
1249
1280
  path33 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
@@ -2005,7 +2036,9 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
2005
2036
  textGuidanceScale: spec.character.textGuidanceScale,
2006
2037
  isometric: spec.character.isometric,
2007
2038
  enhancePrompt: spec.character.enhancePrompt,
2008
- reference: spec.character.reference ? Object.fromEntries(Object.entries(spec.character.reference).map(([direction, image]) => [direction, image.sha256])) : void 0
2039
+ reference: spec.character.reference ? Object.fromEntries(Object.entries(spec.character.reference).map(([direction, image]) => [direction, image.sha256])) : void 0,
2040
+ concept: spec.character.concept?.sha256,
2041
+ styleAnchor: spec.character.styleAnchor ? { of: spec.character.styleAnchor.assetId, sha256: spec.character.styleAnchor.sha256 } : void 0
2009
2042
  } : void 0,
2010
2043
  // A mirror's bytes come from its source's recorded outputs; the plan
2011
2044
  // compares those directly, so only the choice of source is identity.
@@ -2192,6 +2225,19 @@ var PixelLabProvider = class _PixelLabProvider {
2192
2225
  }
2193
2226
  }
2194
2227
  }
2228
+ if (character.concept) {
2229
+ if (character.mode !== "pro") throw new Error(`${label}: a concept image is a pro input; the ${character.mode} engine draws from text or a reference`);
2230
+ if (character.concept.width > 1024 || character.concept.height > 1024) {
2231
+ throw new Error(`${label}: concept image is ${character.concept.width}x${character.concept.height}; PixelLab pro takes up to 1024px`);
2232
+ }
2233
+ }
2234
+ if (character.styleAnchor) {
2235
+ if (character.mode !== "pro") throw new Error(`${label}: styleCharacter is a pro input; the ${character.mode} engine has no style anchor`);
2236
+ if (character.reference) throw new Error(`${label}: a pro base rotating its own reference takes no styleCharacter`);
2237
+ if (character.styleAnchor.spec.character?.directions !== 8) {
2238
+ throw new Error(`${label}: styleCharacter ${character.styleAnchor.assetId} has ${character.styleAnchor.spec.character?.directions} directions; PixelLab wants an 8-direction anchor`);
2239
+ }
2240
+ }
2195
2241
  if (character.reference) {
2196
2242
  const given = Object.keys(character.reference);
2197
2243
  if (!character.reference.south) throw new Error(`${label}: a reference needs a south-facing sprite`);
@@ -2293,7 +2339,9 @@ var PixelLabProvider = class _PixelLabProvider {
2293
2339
  isometric: character.isometric,
2294
2340
  reference: character.reference ? readReference(spec, character.reference) : void 0,
2295
2341
  styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0,
2296
- enhancePrompt: character.enhancePrompt
2342
+ enhancePrompt: character.enhancePrompt,
2343
+ concept: character.concept ? readPose(spec, "concept image", character.concept) : void 0,
2344
+ styleCharacterId: character.styleAnchor ? requireStyleObjectId(spec, context) : void 0
2297
2345
  });
2298
2346
  return { jobId: res2.character_id, metadata: { character: { kind: "base", characterId: res2.character_id, mode: character.mode, directions: character.directions, backgroundJobId: res2.background_job_id } } };
2299
2347
  }
@@ -2713,6 +2761,14 @@ function readReference(spec, reference) {
2713
2761
  }
2714
2762
  return images;
2715
2763
  }
2764
+ function requireStyleObjectId(spec, context) {
2765
+ if (!context?.styleObjectId) {
2766
+ throw new Error(
2767
+ `${spec.styleId}/${spec.assetId} anchors its style on ${spec.character.styleAnchor.assetId}, which has no PixelLab character id in the lockfile yet; generate the anchor first`
2768
+ );
2769
+ }
2770
+ return context.styleObjectId;
2771
+ }
2716
2772
  function readPose(spec, what, image) {
2717
2773
  const bytes = readFileSync(image.path);
2718
2774
  if (sha256(bytes) !== image.sha256) {
@@ -5412,6 +5468,18 @@ async function resolveSpecs(loaded, filter) {
5412
5468
  finalized.add(assetId);
5413
5469
  return resolved;
5414
5470
  }
5471
+ const anchorId = asset.styleCharacter ?? style.styleCharacter;
5472
+ if (resolved.character?.kind === "base" && anchorId && anchorId !== assetId) {
5473
+ const anchorSpec = await finalize(anchorId);
5474
+ if (!anchorSpec.character || anchorSpec.character.kind === "animation") {
5475
+ throw new Error(`assets.${assetId}: styleCharacter ${anchorId} is not a character base or state`);
5476
+ }
5477
+ const file = memberPath(anchorSpec.outFile, "south", 0, anchorSpec.character.directions, MediaType.PNG);
5478
+ resolved.character = {
5479
+ ...resolved.character,
5480
+ styleAnchor: { assetId: anchorId, spec: anchorSpec, file, sha256: existsSync4(file) ? await sha256File(file) : null }
5481
+ };
5482
+ }
5415
5483
  const characterParent = asset.state?.of ?? asset.animation?.of;
5416
5484
  if (resolved.character && characterParent) {
5417
5485
  const parentSpec = await finalize(characterParent);
@@ -5564,6 +5632,10 @@ async function resolveCharacterShape(asset, style, kind, files) {
5564
5632
  ...style.isometric !== void 0 ? { isometric: style.isometric } : {},
5565
5633
  ...style.enhancePrompt !== void 0 ? { enhancePrompt: style.enhancePrompt } : {}
5566
5634
  };
5635
+ if (asset.concept) {
5636
+ const image = await files.load(asset.concept, "Concept image");
5637
+ shape.concept = { path: path5.resolve(files.root, asset.concept), sha256: image.hash, width: image.width, height: image.height, format: image.format };
5638
+ }
5567
5639
  if (asset.reference) {
5568
5640
  const byDirection = typeof asset.reference === "string" ? { south: asset.reference } : asset.reference;
5569
5641
  shape.reference = {};
@@ -7083,9 +7155,24 @@ import path11 from "path";
7083
7155
  async function inspectRevisionReadiness(spec, lock) {
7084
7156
  if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
7085
7157
  if (spec.character?.parentSpec) return inspectCharacterParent(spec, lock, /* @__PURE__ */ new Set());
7158
+ if (spec.character?.styleAnchor) return inspectStyleAnchor(spec, lock, /* @__PURE__ */ new Set());
7086
7159
  if (!spec.revision) return null;
7087
7160
  return inspectRevision(spec, lock, /* @__PURE__ */ new Set());
7088
7161
  }
7162
+ async function inspectStyleAnchor(spec, lock, seen) {
7163
+ const anchor = spec.character.styleAnchor;
7164
+ const label = `style anchor ${spec.styleId}/${anchor.assetId}`;
7165
+ const dependency = await inspectParent(anchor.spec, lock, seen);
7166
+ if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
7167
+ if (!anchor.sha256 || !existsSync7(anchor.file)) return { ready: false, reason: `${label} has no generated south-facing file yet` };
7168
+ if (await sha256File(anchor.file) !== anchor.sha256) {
7169
+ return { ready: false, reason: `${label} changed after the manifest was resolved` };
7170
+ }
7171
+ if (!lock.entries[lockKey(anchor.spec.styleId, anchor.spec.assetId)]?.objectId) {
7172
+ return { ready: false, reason: `${label} has no PixelLab character id recorded` };
7173
+ }
7174
+ return { ready: true, reason: `${label} is current` };
7175
+ }
7089
7176
  async function inspectMirror(spec, lock, seen) {
7090
7177
  const source = spec.mirror.sourceSpec;
7091
7178
  const label = `mirror source ${spec.styleId}/${spec.mirror.sourceAssetId}`;
@@ -7157,6 +7244,10 @@ async function inspectParent(spec, lock, seen) {
7157
7244
  const inputs = await inspectCharacterParent(spec, lock, nextSeen);
7158
7245
  if (!inputs.ready) return inputs;
7159
7246
  }
7247
+ if (spec.character?.styleAnchor) {
7248
+ const inputs = await inspectStyleAnchor(spec, lock, nextSeen);
7249
+ if (!inputs.ready) return inputs;
7250
+ }
7160
7251
  if (spec.quality) {
7161
7252
  const quality = await inspectQualityProfile(spec, lock);
7162
7253
  return quality?.state === "approved" ? { ready: true, reason: "approved quality output is current" } : {
@@ -7196,6 +7287,14 @@ async function anyOutputModified(entry, spec) {
7196
7287
  }
7197
7288
  return false;
7198
7289
  }
7290
+ function dependencies(spec) {
7291
+ return [
7292
+ spec.mirror?.sourceSpec,
7293
+ spec.revision?.sourceSpec,
7294
+ spec.character?.parentSpec,
7295
+ spec.character?.styleAnchor?.spec
7296
+ ].filter((parent) => Boolean(parent));
7297
+ }
7199
7298
  function resumeCommandForStatus(status) {
7200
7299
  if (status === "pending" || status === "processing") return "poll";
7201
7300
  if (status === "review") return "pick";
@@ -7304,16 +7403,19 @@ async function buildPlan(specs, lock, opts = {}) {
7304
7403
  const stateOf = new Map(items.map((i) => [i.key, i.state]));
7305
7404
  for (const item of items) {
7306
7405
  if (!isActionable(item)) continue;
7307
- const parent = item.spec.mirror?.sourceSpec ?? item.spec.revision?.sourceSpec ?? item.spec.character?.parentSpec;
7308
- if (!parent) continue;
7309
- const parentKey = lockKey(parent.styleId, parent.assetId);
7310
- const parentState = stateOf.get(parentKey);
7311
- if (parentState && isActionable({ state: parentState })) {
7312
- item.state = "blocked";
7313
- item.reason = `parent ${parentKey} is being generated in this run; this follows in the next wave`;
7314
- } else if (item.spec.mirror && (parentState === "recoverable" || parentState === "in-flight")) {
7315
- item.state = "blocked";
7316
- item.reason = `mirror source ${parentKey} is ${parentState}; this follows in the next wave`;
7406
+ for (const parent of dependencies(item.spec)) {
7407
+ const parentKey = lockKey(parent.styleId, parent.assetId);
7408
+ const parentState = stateOf.get(parentKey);
7409
+ if (parentState && isActionable({ state: parentState })) {
7410
+ item.state = "blocked";
7411
+ item.reason = `parent ${parentKey} is being generated in this run; this follows in the next wave`;
7412
+ break;
7413
+ }
7414
+ if (item.spec.mirror && (parentState === "recoverable" || parentState === "in-flight")) {
7415
+ item.state = "blocked";
7416
+ item.reason = `mirror source ${parentKey} is ${parentState}; this follows in the next wave`;
7417
+ break;
7418
+ }
7317
7419
  }
7318
7420
  }
7319
7421
  const actionable = items.filter(isActionable);
@@ -9792,10 +9894,13 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
9792
9894
  try {
9793
9895
  const refs = styleImages.get(spec.styleId) ?? [];
9794
9896
  const parentEntry = spec.character?.parentSpec ? lock.entries[lockKey(spec.character.parentSpec.styleId, spec.character.parentSpec.assetId)] : void 0;
9897
+ const anchor = spec.character?.styleAnchor;
9898
+ const anchorEntry = anchor ? lock.entries[lockKey(anchor.spec.styleId, anchor.spec.assetId)] : void 0;
9795
9899
  const replacedMetadata = previousEntry?.providerMetadata?.[provider.id];
9796
9900
  const { jobId, metadata } = await provider.submit(spec, refs, {
9797
9901
  ...previousJobId ? { previousJobId } : {},
9798
9902
  ...parentEntry?.objectId ? { parentObjectId: parentEntry.objectId } : {},
9903
+ ...anchorEntry?.objectId ? { styleObjectId: anchorEntry.objectId } : {},
9799
9904
  ...replacedMetadata ? { replacedMetadata } : {},
9800
9905
  ...previousMetadata ? { previousMetadata } : {},
9801
9906
  checkpoint: async (checkpoint) => {
@@ -12404,7 +12509,7 @@ var pin_default = {
12404
12509
  godot: "4.7.2",
12405
12510
  extensionsApi: 9,
12406
12511
  protocol: 4,
12407
- release: "editor-pixelorama-v1.2.2-pk.18",
12512
+ release: "editor-pixelorama-v1.2.2-pk.26",
12408
12513
  files: {
12409
12514
  "index.apple-touch-icon.png": {
12410
12515
  sha256: "ee5434873e0fed91e9a888c3ea5a8b79beac25999308be7a6eb41bf4ce640d27",
@@ -12419,7 +12524,7 @@ var pin_default = {
12419
12524
  bytes: 7298
12420
12525
  },
12421
12526
  "index.html": {
12422
- sha256: "d9adf06036660c4b76ef5386e872bc03dc0d5b0628fd6ac660d480893ee1f47a",
12527
+ sha256: "326101ab02bb4a29a7964214c9307c7dff26e9d66b8ebaaf6e7a3987e84a05b2",
12423
12528
  bytes: 5442
12424
12529
  },
12425
12530
  "index.icon.png": {
@@ -12431,8 +12536,8 @@ var pin_default = {
12431
12536
  bytes: 279815
12432
12537
  },
12433
12538
  "index.pck": {
12434
- sha256: "a41d9c3a39aa99ea5ee49ddcedfeb8b092006aa75c0de67139f13297c17f42e4",
12435
- bytes: 6431692
12539
+ sha256: "e26292650bb996965acd1ba70e74d2b4020b063d14f1276701312c1d34cb0a83",
12540
+ bytes: 6431932
12436
12541
  },
12437
12542
  "index.png": {
12438
12543
  sha256: "6e3ca3eb4a99e4c07bdd89ebc873b440ec0ffd66ce1955160da9e7ed80d538d7",