pixelkiln 0.43.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.cjs CHANGED
@@ -427,11 +427,45 @@ var CharacterAnimationSchema = import_zod.z.object({
427
427
  /** v3 only: keep the resting pose as frame 0, so `frames` generated frames land as `frames + 1` files. */
428
428
  keepFirstFrame: import_zod.z.boolean().default(true),
429
429
  /** `template` when a template is named, otherwise `v3`; `pro` for the sequential high-quality engine. */
430
- mode: CharacterAnimationModeSchema.optional()
430
+ mode: CharacterAnimationModeSchema.optional(),
431
+ /**
432
+ * v3 only: a manifest-relative image of the pose to start from, instead
433
+ * of the character's rotation for this direction. Up to 256px.
434
+ */
435
+ startFrame: import_zod.z.string().min(1).optional(),
436
+ /**
437
+ * v3 only: a pose to animate toward. The loop then interpolates from the
438
+ * start frame (the rotation, or `startFrame`) to this image, which must
439
+ * be the same size as the start frame.
440
+ */
441
+ endFrame: import_zod.z.string().min(1).optional(),
442
+ /** What is being animated, when the character's own description would mislead the model. */
443
+ subject: import_zod.z.string().min(1).optional(),
444
+ /** Template mode only: style hints for this loop, over the character's own. */
445
+ outline: import_zod.z.string().min(1).optional(),
446
+ shading: import_zod.z.string().min(1).optional(),
447
+ detail: import_zod.z.string().min(1).optional(),
448
+ /** v3 text loops only: let PixelLab expand the action into a fuller motion description first. */
449
+ enhancePrompt: import_zod.z.boolean().optional()
431
450
  }).strict().superRefine((animation, context) => {
432
451
  if (animation.frames !== void 0 && animation.frames % 2 !== 0) {
433
452
  context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: "frames must be even", path: ["frames"] });
434
453
  }
454
+ const mode2 = animation.mode ?? (animation.template ? "template" : "v3");
455
+ for (const key of ["startFrame", "endFrame", "enhancePrompt"]) {
456
+ if (animation[key] !== void 0 && mode2 !== "v3") {
457
+ context.addIssue({
458
+ code: import_zod.z.ZodIssueCode.custom,
459
+ message: `${key} is for v3 loops; a ${mode2} loop ${mode2 === "template" ? "starts from the character's rotation and follows its template" : "takes neither"}`,
460
+ path: [key]
461
+ });
462
+ }
463
+ }
464
+ for (const key of ["outline", "shading", "detail"]) {
465
+ if (animation[key] !== void 0 && mode2 !== "template") {
466
+ context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: `${key} overrides apply to template loops only`, path: [key] });
467
+ }
468
+ }
435
469
  if (animation.template && animation.mode && animation.mode !== "template") {
436
470
  context.addIssue({
437
471
  code: import_zod.z.ZodIssueCode.custom,
@@ -569,6 +603,14 @@ var StyleObjectSchema = import_zod.z.object({
569
603
  textGuidanceScale: import_zod.z.number().min(1).max(20).optional(),
570
604
  /** `character` only. Draw `standard` bases and every loop in isometric view. */
571
605
  isometric: import_zod.z.boolean().optional(),
606
+ /** `character` only, `v3` bases: let PixelLab expand the prompt into a fuller one before drawing. */
607
+ enhancePrompt: import_zod.z.boolean().optional(),
608
+ /**
609
+ * `character` only, `pro` bases: the asset id of a generated 8-direction
610
+ * character in this style whose look every base drawn from text or a
611
+ * concept follows. An asset may override it; the anchor itself ignores it.
612
+ */
613
+ styleCharacter: import_zod.z.string().min(1).optional(),
572
614
  /** Fixed seed for reproducibility where the endpoint supports it. */
573
615
  seed: import_zod.z.number().int().optional(),
574
616
  /**
@@ -741,6 +783,15 @@ var AssetSchema = import_zod.z.object({
741
783
  * still guides the result.
742
784
  */
743
785
  reference: import_zod.z.union([import_zod.z.string().min(1), import_zod.z.record(CharacterDirectionSchema, import_zod.z.string().min(1))]).optional(),
786
+ /**
787
+ * `character` styles, `pro` bases only: a manifest-relative concept
788
+ * image (a painting, a photo, a sketch; up to 1024px) that seeds the
789
+ * design instead of the prompt alone. A style image or `styleCharacter`
790
+ * may still anchor the look.
791
+ */
792
+ concept: import_zod.z.string().min(1).optional(),
793
+ /** `character` styles, `pro` bases: this base's style anchor, over the style's. */
794
+ styleCharacter: import_zod.z.string().min(1).optional(),
744
795
  /**
745
796
  * Another asset of the same style flipped left to right, made locally
746
797
  * from that asset's downloaded files at no generation cost.
@@ -822,6 +873,20 @@ var AssetSchema = import_zod.z.object({
822
873
  path: ["reference"]
823
874
  });
824
875
  }
876
+ if (asset.concept && (asset.state || asset.animation || asset.mirror)) {
877
+ context.addIssue({
878
+ code: import_zod.z.ZodIssueCode.custom,
879
+ message: "a concept image belongs on a base; a state, animation, or mirror takes its look from its parent",
880
+ path: ["concept"]
881
+ });
882
+ }
883
+ if (asset.concept && asset.reference) {
884
+ context.addIssue({
885
+ code: import_zod.z.ZodIssueCode.custom,
886
+ message: "concept and reference are mutually exclusive: a concept seeds a new design, a reference is the sprite to rotate",
887
+ path: ["concept"]
888
+ });
889
+ }
825
890
  if (typeof asset.reference === "object" && !Object.keys(asset.reference).length) {
826
891
  context.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: "a reference needs at least a south image", path: ["reference"] });
827
892
  }
@@ -1493,7 +1558,8 @@ var PixelLabClient = class {
1493
1558
  ...args.outline ? { outline: args.outline } : {},
1494
1559
  ...args.detail ? { detail: args.detail } : {},
1495
1560
  ...args.seed != null ? { seed: args.seed } : {},
1496
- ...south ? { reference_image: encode(south) } : {}
1561
+ ...south ? { reference_image: encode(south) } : {},
1562
+ ...args.enhancePrompt ? { enhance_prompt: true } : {}
1497
1563
  };
1498
1564
  } else if (args.mode === "pro") {
1499
1565
  path33 = "/create-character-pro";
@@ -1502,10 +1568,12 @@ var PixelLabClient = class {
1502
1568
  image_size: imageSize,
1503
1569
  template_id: args.template,
1504
1570
  no_background: args.noBackground ?? true,
1505
- method: south ? "rotate_character" : "create_with_style",
1571
+ method: south ? "rotate_character" : args.concept ? "create_from_concept" : "create_with_style",
1506
1572
  ...args.view ? { view: args.view } : {},
1507
1573
  ...args.seed != null ? { seed: args.seed } : {},
1508
- ...south ? { reference_image: encode(south) } : args.styleReference ? { reference_image: encode(args.styleReference) } : {}
1574
+ ...south ? { reference_image: encode(south) } : args.styleReference ? { reference_image: encode(args.styleReference) } : {},
1575
+ ...!south && args.concept ? { concept_image: encode(args.concept) } : {},
1576
+ ...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
1509
1577
  };
1510
1578
  } else {
1511
1579
  path33 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
@@ -1550,6 +1618,7 @@ var PixelLabClient = class {
1550
1618
  * the action text; pro is the sequential engine. One job per direction.
1551
1619
  */
1552
1620
  async animateCharacter(args) {
1621
+ const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
1553
1622
  const raw = await this.request("/animate-character", {
1554
1623
  method: "POST",
1555
1624
  body: JSON.stringify({
@@ -1559,10 +1628,18 @@ var PixelLabClient = class {
1559
1628
  directions: args.directions,
1560
1629
  ...args.template ? { template_animation_id: args.template } : {},
1561
1630
  ...args.actionDescription ? { action_description: args.actionDescription } : {},
1631
+ ...args.description ? { description: args.description } : {},
1562
1632
  ...args.mode === "v3" && args.frameCount ? { frame_count: args.frameCount } : {},
1563
1633
  ...args.mode === "v3" && args.keepFirstFrame === false ? { keep_first_frame: false } : {},
1634
+ ...args.mode === "v3" && args.startFrame ? { custom_start_frame: encode(args.startFrame) } : {},
1635
+ ...args.mode === "v3" && args.endFrame ? { end_frame: encode(args.endFrame) } : {},
1636
+ ...args.mode === "v3" && args.enhancePrompt ? { enhance_prompt: true } : {},
1564
1637
  ...args.mode === "template" && args.textGuidanceScale !== void 0 ? { text_guidance_scale: args.textGuidanceScale } : {},
1638
+ ...args.mode === "template" && args.outline ? { outline: args.outline } : {},
1639
+ ...args.mode === "template" && args.shading ? { shading: args.shading } : {},
1640
+ ...args.mode === "template" && args.detail ? { detail: args.detail } : {},
1565
1641
  ...args.isometric !== void 0 ? { isometric: args.isometric } : {},
1642
+ ...args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {},
1566
1643
  ...args.seed != null ? { seed: args.seed } : {}
1567
1644
  })
1568
1645
  });
@@ -2159,6 +2236,34 @@ function skipSubBlocks(bytes, start) {
2159
2236
  function cacheFileName(hash, mediaType = MediaType.PNG) {
2160
2237
  return `${hash}${mediaExtension(mediaType)}`;
2161
2238
  }
2239
+ function imageMetadata(buf) {
2240
+ if (buf.length >= 24 && buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834 && buf.toString("ascii", 12, 16) === "IHDR") {
2241
+ return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20), format: "png" };
2242
+ }
2243
+ if (buf.length < 4 || buf[0] !== 255 || buf[1] !== 216) return null;
2244
+ let offset = 2;
2245
+ while (offset + 3 < buf.length) {
2246
+ if (buf[offset] !== 255) return null;
2247
+ while (buf[offset] === 255) offset++;
2248
+ const marker = buf[offset++];
2249
+ if (marker == null || marker === 217 || marker === 218) break;
2250
+ if (marker === 1 || marker >= 208 && marker <= 215) continue;
2251
+ if (offset + 2 > buf.length) return null;
2252
+ const length = buf.readUInt16BE(offset);
2253
+ if (length < 2 || offset + length > buf.length) return null;
2254
+ const isStartOfFrame = marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker);
2255
+ if (isStartOfFrame) {
2256
+ if (length < 7) return null;
2257
+ return {
2258
+ width: buf.readUInt16BE(offset + 5),
2259
+ height: buf.readUInt16BE(offset + 3),
2260
+ format: "jpeg"
2261
+ };
2262
+ }
2263
+ offset += length;
2264
+ }
2265
+ return null;
2266
+ }
2162
2267
 
2163
2268
  // src/providers/pixellab.ts
2164
2269
  var import_node_fs2 = require("fs");
@@ -2219,12 +2324,20 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
2219
2324
  parent: spec.character.parentAssetId,
2220
2325
  parentSha256: spec.character.parentSha256 ?? null,
2221
2326
  state: spec.character.state,
2222
- animation: spec.character.animation,
2327
+ // Pose images enter as their hashes, never their paths.
2328
+ animation: spec.character.animation ? {
2329
+ ...spec.character.animation,
2330
+ startFrame: spec.character.animation.startFrame?.sha256,
2331
+ endFrame: spec.character.animation.endFrame?.sha256
2332
+ } : void 0,
2223
2333
  // Absent keys keep the hashes of manifests that never set these.
2224
2334
  proportions: spec.character.proportions,
2225
2335
  textGuidanceScale: spec.character.textGuidanceScale,
2226
2336
  isometric: spec.character.isometric,
2227
- reference: spec.character.reference ? Object.fromEntries(Object.entries(spec.character.reference).map(([direction, image]) => [direction, image.sha256])) : void 0
2337
+ enhancePrompt: spec.character.enhancePrompt,
2338
+ reference: spec.character.reference ? Object.fromEntries(Object.entries(spec.character.reference).map(([direction, image]) => [direction, image.sha256])) : void 0,
2339
+ concept: spec.character.concept?.sha256,
2340
+ styleAnchor: spec.character.styleAnchor ? { of: spec.character.styleAnchor.assetId, sha256: spec.character.styleAnchor.sha256 } : void 0
2228
2341
  } : void 0,
2229
2342
  // A mirror's bytes come from its source's recorded outputs; the plan
2230
2343
  // compares those directly, so only the choice of source is identity.
@@ -2392,6 +2505,38 @@ var PixelLabProvider = class _PixelLabProvider {
2392
2505
  throw new Error(`${label}: proportions apply to the mannequin template; ${character.template} is a quadruped`);
2393
2506
  }
2394
2507
  }
2508
+ if (character.kind === "base" && character.enhancePrompt !== void 0 && character.mode !== "v3") {
2509
+ throw new Error(`${label}: enhancePrompt applies to v3 bases; the ${character.mode} engine does not take it`);
2510
+ }
2511
+ if (character.animation) {
2512
+ const { startFrame, endFrame } = character.animation;
2513
+ for (const [name, image] of [["start frame", startFrame], ["end frame", endFrame]]) {
2514
+ if (image && (image.width > 256 || image.height > 256)) {
2515
+ throw new Error(`${label}: ${name} is ${image.width}x${image.height}; PixelLab v3 takes up to 256px`);
2516
+ }
2517
+ }
2518
+ if (endFrame) {
2519
+ const start = startFrame ?? (character.parentFile && (0, import_node_fs2.existsSync)(character.parentFile) ? imageMetadata((0, import_node_fs2.readFileSync)(character.parentFile)) : null);
2520
+ if (start && (start.width !== endFrame.width || start.height !== endFrame.height)) {
2521
+ throw new Error(
2522
+ `${label}: end frame is ${endFrame.width}x${endFrame.height} but the ${startFrame ? "start frame" : "character's rotation"} is ${start.width}x${start.height}; PixelLab interpolates between frames of one size`
2523
+ );
2524
+ }
2525
+ }
2526
+ }
2527
+ if (character.concept) {
2528
+ 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`);
2529
+ if (character.concept.width > 1024 || character.concept.height > 1024) {
2530
+ throw new Error(`${label}: concept image is ${character.concept.width}x${character.concept.height}; PixelLab pro takes up to 1024px`);
2531
+ }
2532
+ }
2533
+ if (character.styleAnchor) {
2534
+ if (character.mode !== "pro") throw new Error(`${label}: styleCharacter is a pro input; the ${character.mode} engine has no style anchor`);
2535
+ if (character.reference) throw new Error(`${label}: a pro base rotating its own reference takes no styleCharacter`);
2536
+ if (character.styleAnchor.spec.character?.directions !== 8) {
2537
+ throw new Error(`${label}: styleCharacter ${character.styleAnchor.assetId} has ${character.styleAnchor.spec.character?.directions} directions; PixelLab wants an 8-direction anchor`);
2538
+ }
2539
+ }
2395
2540
  if (character.reference) {
2396
2541
  const given = Object.keys(character.reference);
2397
2542
  if (!character.reference.south) throw new Error(`${label}: a reference needs a south-facing sprite`);
@@ -2492,7 +2637,10 @@ var PixelLabProvider = class _PixelLabProvider {
2492
2637
  textGuidanceScale: character.textGuidanceScale,
2493
2638
  isometric: character.isometric,
2494
2639
  reference: character.reference ? readReference(spec, character.reference) : void 0,
2495
- styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0
2640
+ styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0,
2641
+ enhancePrompt: character.enhancePrompt,
2642
+ concept: character.concept ? readPose(spec, "concept image", character.concept) : void 0,
2643
+ styleCharacterId: character.styleAnchor ? requireStyleObjectId(spec, context) : void 0
2496
2644
  });
2497
2645
  return { jobId: res2.character_id, metadata: { character: { kind: "base", characterId: res2.character_id, mode: character.mode, directions: character.directions, backgroundJobId: res2.background_job_id } } };
2498
2646
  }
@@ -2536,7 +2684,15 @@ var PixelLabProvider = class _PixelLabProvider {
2536
2684
  directions: [animation.direction],
2537
2685
  seed: spec.seed,
2538
2686
  textGuidanceScale: character.textGuidanceScale,
2539
- isometric: character.isometric
2687
+ isometric: character.isometric,
2688
+ startFrame: animation.startFrame ? readPose(spec, "start frame", animation.startFrame) : void 0,
2689
+ endFrame: animation.endFrame ? readPose(spec, "end frame", animation.endFrame) : void 0,
2690
+ description: animation.subject,
2691
+ outline: animation.outline,
2692
+ shading: animation.shading,
2693
+ detail: animation.detail,
2694
+ enhancePrompt: animation.enhancePrompt,
2695
+ paletteSwatchBase64: spec.palette.length ? paletteSwatch(spec.palette).toString("base64") : void 0
2540
2696
  });
2541
2697
  const job = { characterId: parentId, name, direction: animation.direction, jobIds: res.background_job_ids };
2542
2698
  return {
@@ -2904,6 +3060,21 @@ function readReference(spec, reference) {
2904
3060
  }
2905
3061
  return images;
2906
3062
  }
3063
+ function requireStyleObjectId(spec, context) {
3064
+ if (!context?.styleObjectId) {
3065
+ throw new Error(
3066
+ `${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`
3067
+ );
3068
+ }
3069
+ return context.styleObjectId;
3070
+ }
3071
+ function readPose(spec, what, image) {
3072
+ const bytes = (0, import_node_fs2.readFileSync)(image.path);
3073
+ if (sha256(bytes) !== image.sha256) {
3074
+ throw new Error(`${spec.styleId}/${spec.assetId}: ${what} changed after the manifest was resolved: ${image.path}`);
3075
+ }
3076
+ return { base64: bytes.toString("base64"), format: image.format };
3077
+ }
2907
3078
  function rotationSources(character) {
2908
3079
  const order = character.directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
2909
3080
  const sources = [];
@@ -5596,6 +5767,18 @@ async function resolveSpecs(loaded, filter) {
5596
5767
  finalized.add(assetId);
5597
5768
  return resolved;
5598
5769
  }
5770
+ const anchorId = asset.styleCharacter ?? style.styleCharacter;
5771
+ if (resolved.character?.kind === "base" && anchorId && anchorId !== assetId) {
5772
+ const anchorSpec = await finalize(anchorId);
5773
+ if (!anchorSpec.character || anchorSpec.character.kind === "animation") {
5774
+ throw new Error(`assets.${assetId}: styleCharacter ${anchorId} is not a character base or state`);
5775
+ }
5776
+ const file = memberPath(anchorSpec.outFile, "south", 0, anchorSpec.character.directions, MediaType.PNG);
5777
+ resolved.character = {
5778
+ ...resolved.character,
5779
+ styleAnchor: { assetId: anchorId, spec: anchorSpec, file, sha256: (0, import_node_fs5.existsSync)(file) ? await sha256File(file) : null }
5780
+ };
5781
+ }
5599
5782
  const characterParent = asset.state?.of ?? asset.animation?.of;
5600
5783
  if (resolved.character && characterParent) {
5601
5784
  const parentSpec = await finalize(characterParent);
@@ -5734,34 +5917,6 @@ async function resolveStyleImages(loaded, styleId) {
5734
5917
  async function styleImagesBase64(loaded, styleId) {
5735
5918
  return (await resolveStyleImages(loaded, styleId)).map((img) => img.base64);
5736
5919
  }
5737
- function imageMetadata(buf) {
5738
- if (buf.length >= 24 && buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834 && buf.toString("ascii", 12, 16) === "IHDR") {
5739
- return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20), format: "png" };
5740
- }
5741
- if (buf.length < 4 || buf[0] !== 255 || buf[1] !== 216) return null;
5742
- let offset = 2;
5743
- while (offset + 3 < buf.length) {
5744
- if (buf[offset] !== 255) return null;
5745
- while (buf[offset] === 255) offset++;
5746
- const marker = buf[offset++];
5747
- if (marker == null || marker === 217 || marker === 218) break;
5748
- if (marker === 1 || marker >= 208 && marker <= 215) continue;
5749
- if (offset + 2 > buf.length) return null;
5750
- const length = buf.readUInt16BE(offset);
5751
- if (length < 2 || offset + length > buf.length) return null;
5752
- const isStartOfFrame = marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker);
5753
- if (isStartOfFrame) {
5754
- if (length < 7) return null;
5755
- return {
5756
- width: buf.readUInt16BE(offset + 5),
5757
- height: buf.readUInt16BE(offset + 3),
5758
- format: "jpeg"
5759
- };
5760
- }
5761
- offset += length;
5762
- }
5763
- return null;
5764
- }
5765
5920
  async function resolveCharacterShape(asset, style, kind, files) {
5766
5921
  const mode2 = style.mode ?? "standard";
5767
5922
  const directions = mode2 === "standard" ? style.directions ?? 8 : 8;
@@ -5773,8 +5928,13 @@ async function resolveCharacterShape(asset, style, kind, files) {
5773
5928
  template: style.template ?? "mannequin",
5774
5929
  ...proportions !== void 0 ? { proportions } : {},
5775
5930
  ...style.textGuidanceScale !== void 0 ? { textGuidanceScale: style.textGuidanceScale } : {},
5776
- ...style.isometric !== void 0 ? { isometric: style.isometric } : {}
5931
+ ...style.isometric !== void 0 ? { isometric: style.isometric } : {},
5932
+ ...style.enhancePrompt !== void 0 ? { enhancePrompt: style.enhancePrompt } : {}
5777
5933
  };
5934
+ if (asset.concept) {
5935
+ const image = await files.load(asset.concept, "Concept image");
5936
+ shape.concept = { path: import_node_path5.default.resolve(files.root, asset.concept), sha256: image.hash, width: image.width, height: image.height, format: image.format };
5937
+ }
5778
5938
  if (asset.reference) {
5779
5939
  const byDirection = typeof asset.reference === "string" ? { south: asset.reference } : asset.reference;
5780
5940
  shape.reference = {};
@@ -5797,13 +5957,24 @@ async function resolveCharacterShape(asset, style, kind, files) {
5797
5957
  }
5798
5958
  if (asset.animation) {
5799
5959
  const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
5960
+ const pose = async (rel, what) => {
5961
+ const image = await files.load(rel, what);
5962
+ return { path: import_node_path5.default.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
5963
+ };
5800
5964
  shape.animation = {
5801
5965
  mode: animationMode,
5802
5966
  ...asset.animation.template ? { template: asset.animation.template } : {},
5803
5967
  direction: asset.animation.direction,
5804
5968
  frames: asset.animation.frames ?? 8,
5805
5969
  fps: asset.animation.fps,
5806
- keepFirstFrame: asset.animation.keepFirstFrame
5970
+ keepFirstFrame: asset.animation.keepFirstFrame,
5971
+ ...asset.animation.startFrame ? { startFrame: await pose(asset.animation.startFrame, "Animation start frame") } : {},
5972
+ ...asset.animation.endFrame ? { endFrame: await pose(asset.animation.endFrame, "Animation end frame") } : {},
5973
+ ...asset.animation.subject ? { subject: asset.animation.subject } : {},
5974
+ ...asset.animation.outline ? { outline: asset.animation.outline } : {},
5975
+ ...asset.animation.shading ? { shading: asset.animation.shading } : {},
5976
+ ...asset.animation.detail ? { detail: asset.animation.detail } : {},
5977
+ ...asset.animation.enhancePrompt !== void 0 ? { enhancePrompt: asset.animation.enhancePrompt } : {}
5807
5978
  };
5808
5979
  }
5809
5980
  return shape;
@@ -7283,9 +7454,24 @@ var import_node_path11 = __toESM(require("path"), 1);
7283
7454
  async function inspectRevisionReadiness(spec, lock) {
7284
7455
  if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
7285
7456
  if (spec.character?.parentSpec) return inspectCharacterParent(spec, lock, /* @__PURE__ */ new Set());
7457
+ if (spec.character?.styleAnchor) return inspectStyleAnchor(spec, lock, /* @__PURE__ */ new Set());
7286
7458
  if (!spec.revision) return null;
7287
7459
  return inspectRevision(spec, lock, /* @__PURE__ */ new Set());
7288
7460
  }
7461
+ async function inspectStyleAnchor(spec, lock, seen) {
7462
+ const anchor = spec.character.styleAnchor;
7463
+ const label = `style anchor ${spec.styleId}/${anchor.assetId}`;
7464
+ const dependency = await inspectParent(anchor.spec, lock, seen);
7465
+ if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
7466
+ if (!anchor.sha256 || !(0, import_node_fs8.existsSync)(anchor.file)) return { ready: false, reason: `${label} has no generated south-facing file yet` };
7467
+ if (await sha256File(anchor.file) !== anchor.sha256) {
7468
+ return { ready: false, reason: `${label} changed after the manifest was resolved` };
7469
+ }
7470
+ if (!lock.entries[lockKey(anchor.spec.styleId, anchor.spec.assetId)]?.objectId) {
7471
+ return { ready: false, reason: `${label} has no PixelLab character id recorded` };
7472
+ }
7473
+ return { ready: true, reason: `${label} is current` };
7474
+ }
7289
7475
  async function inspectMirror(spec, lock, seen) {
7290
7476
  const source = spec.mirror.sourceSpec;
7291
7477
  const label = `mirror source ${spec.styleId}/${spec.mirror.sourceAssetId}`;
@@ -7357,6 +7543,10 @@ async function inspectParent(spec, lock, seen) {
7357
7543
  const inputs = await inspectCharacterParent(spec, lock, nextSeen);
7358
7544
  if (!inputs.ready) return inputs;
7359
7545
  }
7546
+ if (spec.character?.styleAnchor) {
7547
+ const inputs = await inspectStyleAnchor(spec, lock, nextSeen);
7548
+ if (!inputs.ready) return inputs;
7549
+ }
7360
7550
  if (spec.quality) {
7361
7551
  const quality = await inspectQualityProfile(spec, lock);
7362
7552
  return quality?.state === "approved" ? { ready: true, reason: "approved quality output is current" } : {
@@ -7396,6 +7586,14 @@ async function anyOutputModified(entry, spec) {
7396
7586
  }
7397
7587
  return false;
7398
7588
  }
7589
+ function dependencies(spec) {
7590
+ return [
7591
+ spec.mirror?.sourceSpec,
7592
+ spec.revision?.sourceSpec,
7593
+ spec.character?.parentSpec,
7594
+ spec.character?.styleAnchor?.spec
7595
+ ].filter((parent) => Boolean(parent));
7596
+ }
7399
7597
  function resumeCommandForStatus(status) {
7400
7598
  if (status === "pending" || status === "processing") return "poll";
7401
7599
  if (status === "review") return "pick";
@@ -7504,16 +7702,19 @@ async function buildPlan(specs, lock, opts = {}) {
7504
7702
  const stateOf = new Map(items.map((i) => [i.key, i.state]));
7505
7703
  for (const item of items) {
7506
7704
  if (!isActionable(item)) continue;
7507
- const parent = item.spec.mirror?.sourceSpec ?? item.spec.revision?.sourceSpec ?? item.spec.character?.parentSpec;
7508
- if (!parent) continue;
7509
- const parentKey = lockKey(parent.styleId, parent.assetId);
7510
- const parentState = stateOf.get(parentKey);
7511
- if (parentState && isActionable({ state: parentState })) {
7512
- item.state = "blocked";
7513
- item.reason = `parent ${parentKey} is being generated in this run; this follows in the next wave`;
7514
- } else if (item.spec.mirror && (parentState === "recoverable" || parentState === "in-flight")) {
7515
- item.state = "blocked";
7516
- item.reason = `mirror source ${parentKey} is ${parentState}; this follows in the next wave`;
7705
+ for (const parent of dependencies(item.spec)) {
7706
+ const parentKey = lockKey(parent.styleId, parent.assetId);
7707
+ const parentState = stateOf.get(parentKey);
7708
+ if (parentState && isActionable({ state: parentState })) {
7709
+ item.state = "blocked";
7710
+ item.reason = `parent ${parentKey} is being generated in this run; this follows in the next wave`;
7711
+ break;
7712
+ }
7713
+ if (item.spec.mirror && (parentState === "recoverable" || parentState === "in-flight")) {
7714
+ item.state = "blocked";
7715
+ item.reason = `mirror source ${parentKey} is ${parentState}; this follows in the next wave`;
7716
+ break;
7717
+ }
7517
7718
  }
7518
7719
  }
7519
7720
  const actionable = items.filter(isActionable);
@@ -9992,10 +10193,13 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
9992
10193
  try {
9993
10194
  const refs = styleImages.get(spec.styleId) ?? [];
9994
10195
  const parentEntry = spec.character?.parentSpec ? lock.entries[lockKey(spec.character.parentSpec.styleId, spec.character.parentSpec.assetId)] : void 0;
10196
+ const anchor = spec.character?.styleAnchor;
10197
+ const anchorEntry = anchor ? lock.entries[lockKey(anchor.spec.styleId, anchor.spec.assetId)] : void 0;
9995
10198
  const replacedMetadata = previousEntry?.providerMetadata?.[provider.id];
9996
10199
  const { jobId, metadata } = await provider.submit(spec, refs, {
9997
10200
  ...previousJobId ? { previousJobId } : {},
9998
10201
  ...parentEntry?.objectId ? { parentObjectId: parentEntry.objectId } : {},
10202
+ ...anchorEntry?.objectId ? { styleObjectId: anchorEntry.objectId } : {},
9999
10203
  ...replacedMetadata ? { replacedMetadata } : {},
10000
10204
  ...previousMetadata ? { previousMetadata } : {},
10001
10205
  checkpoint: async (checkpoint) => {
@@ -12604,7 +12808,7 @@ var pin_default = {
12604
12808
  godot: "4.7.2",
12605
12809
  extensionsApi: 9,
12606
12810
  protocol: 4,
12607
- release: "editor-pixelorama-v1.2.2-pk.18",
12811
+ release: "editor-pixelorama-v1.2.2-pk.26",
12608
12812
  files: {
12609
12813
  "index.apple-touch-icon.png": {
12610
12814
  sha256: "ee5434873e0fed91e9a888c3ea5a8b79beac25999308be7a6eb41bf4ce640d27",
@@ -12619,7 +12823,7 @@ var pin_default = {
12619
12823
  bytes: 7298
12620
12824
  },
12621
12825
  "index.html": {
12622
- sha256: "d9adf06036660c4b76ef5386e872bc03dc0d5b0628fd6ac660d480893ee1f47a",
12826
+ sha256: "326101ab02bb4a29a7964214c9307c7dff26e9d66b8ebaaf6e7a3987e84a05b2",
12623
12827
  bytes: 5442
12624
12828
  },
12625
12829
  "index.icon.png": {
@@ -12631,8 +12835,8 @@ var pin_default = {
12631
12835
  bytes: 279815
12632
12836
  },
12633
12837
  "index.pck": {
12634
- sha256: "a41d9c3a39aa99ea5ee49ddcedfeb8b092006aa75c0de67139f13297c17f42e4",
12635
- bytes: 6431692
12838
+ sha256: "e26292650bb996965acd1ba70e74d2b4020b063d14f1276701312c1d34cb0a83",
12839
+ bytes: 6431932
12636
12840
  },
12637
12841
  "index.png": {
12638
12842
  sha256: "6e3ca3eb4a99e4c07bdd89ebc873b440ec0ffd66ce1955160da9e7ed80d538d7",