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.js CHANGED
@@ -128,11 +128,45 @@ var CharacterAnimationSchema = z.object({
128
128
  /** v3 only: keep the resting pose as frame 0, so `frames` generated frames land as `frames + 1` files. */
129
129
  keepFirstFrame: z.boolean().default(true),
130
130
  /** `template` when a template is named, otherwise `v3`; `pro` for the sequential high-quality engine. */
131
- mode: CharacterAnimationModeSchema.optional()
131
+ mode: CharacterAnimationModeSchema.optional(),
132
+ /**
133
+ * v3 only: a manifest-relative image of the pose to start from, instead
134
+ * of the character's rotation for this direction. Up to 256px.
135
+ */
136
+ startFrame: z.string().min(1).optional(),
137
+ /**
138
+ * v3 only: a pose to animate toward. The loop then interpolates from the
139
+ * start frame (the rotation, or `startFrame`) to this image, which must
140
+ * be the same size as the start frame.
141
+ */
142
+ endFrame: z.string().min(1).optional(),
143
+ /** What is being animated, when the character's own description would mislead the model. */
144
+ subject: z.string().min(1).optional(),
145
+ /** Template mode only: style hints for this loop, over the character's own. */
146
+ outline: z.string().min(1).optional(),
147
+ shading: z.string().min(1).optional(),
148
+ detail: z.string().min(1).optional(),
149
+ /** v3 text loops only: let PixelLab expand the action into a fuller motion description first. */
150
+ enhancePrompt: z.boolean().optional()
132
151
  }).strict().superRefine((animation, context) => {
133
152
  if (animation.frames !== void 0 && animation.frames % 2 !== 0) {
134
153
  context.addIssue({ code: z.ZodIssueCode.custom, message: "frames must be even", path: ["frames"] });
135
154
  }
155
+ const mode2 = animation.mode ?? (animation.template ? "template" : "v3");
156
+ for (const key of ["startFrame", "endFrame", "enhancePrompt"]) {
157
+ if (animation[key] !== void 0 && mode2 !== "v3") {
158
+ context.addIssue({
159
+ code: z.ZodIssueCode.custom,
160
+ message: `${key} is for v3 loops; a ${mode2} loop ${mode2 === "template" ? "starts from the character's rotation and follows its template" : "takes neither"}`,
161
+ path: [key]
162
+ });
163
+ }
164
+ }
165
+ for (const key of ["outline", "shading", "detail"]) {
166
+ if (animation[key] !== void 0 && mode2 !== "template") {
167
+ context.addIssue({ code: z.ZodIssueCode.custom, message: `${key} overrides apply to template loops only`, path: [key] });
168
+ }
169
+ }
136
170
  if (animation.template && animation.mode && animation.mode !== "template") {
137
171
  context.addIssue({
138
172
  code: z.ZodIssueCode.custom,
@@ -270,6 +304,14 @@ var StyleObjectSchema = z.object({
270
304
  textGuidanceScale: z.number().min(1).max(20).optional(),
271
305
  /** `character` only. Draw `standard` bases and every loop in isometric view. */
272
306
  isometric: z.boolean().optional(),
307
+ /** `character` only, `v3` bases: let PixelLab expand the prompt into a fuller one before drawing. */
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(),
273
315
  /** Fixed seed for reproducibility where the endpoint supports it. */
274
316
  seed: z.number().int().optional(),
275
317
  /**
@@ -442,6 +484,15 @@ var AssetSchema = z.object({
442
484
  * still guides the result.
443
485
  */
444
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(),
445
496
  /**
446
497
  * Another asset of the same style flipped left to right, made locally
447
498
  * from that asset's downloaded files at no generation cost.
@@ -523,6 +574,20 @@ var AssetSchema = z.object({
523
574
  path: ["reference"]
524
575
  });
525
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
+ }
526
591
  if (typeof asset.reference === "object" && !Object.keys(asset.reference).length) {
527
592
  context.addIssue({ code: z.ZodIssueCode.custom, message: "a reference needs at least a south image", path: ["reference"] });
528
593
  }
@@ -1194,7 +1259,8 @@ var PixelLabClient = class {
1194
1259
  ...args.outline ? { outline: args.outline } : {},
1195
1260
  ...args.detail ? { detail: args.detail } : {},
1196
1261
  ...args.seed != null ? { seed: args.seed } : {},
1197
- ...south ? { reference_image: encode(south) } : {}
1262
+ ...south ? { reference_image: encode(south) } : {},
1263
+ ...args.enhancePrompt ? { enhance_prompt: true } : {}
1198
1264
  };
1199
1265
  } else if (args.mode === "pro") {
1200
1266
  path33 = "/create-character-pro";
@@ -1203,10 +1269,12 @@ var PixelLabClient = class {
1203
1269
  image_size: imageSize,
1204
1270
  template_id: args.template,
1205
1271
  no_background: args.noBackground ?? true,
1206
- method: south ? "rotate_character" : "create_with_style",
1272
+ method: south ? "rotate_character" : args.concept ? "create_from_concept" : "create_with_style",
1207
1273
  ...args.view ? { view: args.view } : {},
1208
1274
  ...args.seed != null ? { seed: args.seed } : {},
1209
- ...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 } : {}
1210
1278
  };
1211
1279
  } else {
1212
1280
  path33 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
@@ -1251,6 +1319,7 @@ var PixelLabClient = class {
1251
1319
  * the action text; pro is the sequential engine. One job per direction.
1252
1320
  */
1253
1321
  async animateCharacter(args) {
1322
+ const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
1254
1323
  const raw = await this.request("/animate-character", {
1255
1324
  method: "POST",
1256
1325
  body: JSON.stringify({
@@ -1260,10 +1329,18 @@ var PixelLabClient = class {
1260
1329
  directions: args.directions,
1261
1330
  ...args.template ? { template_animation_id: args.template } : {},
1262
1331
  ...args.actionDescription ? { action_description: args.actionDescription } : {},
1332
+ ...args.description ? { description: args.description } : {},
1263
1333
  ...args.mode === "v3" && args.frameCount ? { frame_count: args.frameCount } : {},
1264
1334
  ...args.mode === "v3" && args.keepFirstFrame === false ? { keep_first_frame: false } : {},
1335
+ ...args.mode === "v3" && args.startFrame ? { custom_start_frame: encode(args.startFrame) } : {},
1336
+ ...args.mode === "v3" && args.endFrame ? { end_frame: encode(args.endFrame) } : {},
1337
+ ...args.mode === "v3" && args.enhancePrompt ? { enhance_prompt: true } : {},
1265
1338
  ...args.mode === "template" && args.textGuidanceScale !== void 0 ? { text_guidance_scale: args.textGuidanceScale } : {},
1339
+ ...args.mode === "template" && args.outline ? { outline: args.outline } : {},
1340
+ ...args.mode === "template" && args.shading ? { shading: args.shading } : {},
1341
+ ...args.mode === "template" && args.detail ? { detail: args.detail } : {},
1266
1342
  ...args.isometric !== void 0 ? { isometric: args.isometric } : {},
1343
+ ...args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {},
1267
1344
  ...args.seed != null ? { seed: args.seed } : {}
1268
1345
  })
1269
1346
  });
@@ -1860,6 +1937,34 @@ function skipSubBlocks(bytes, start) {
1860
1937
  function cacheFileName(hash, mediaType = MediaType.PNG) {
1861
1938
  return `${hash}${mediaExtension(mediaType)}`;
1862
1939
  }
1940
+ function imageMetadata(buf) {
1941
+ if (buf.length >= 24 && buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834 && buf.toString("ascii", 12, 16) === "IHDR") {
1942
+ return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20), format: "png" };
1943
+ }
1944
+ if (buf.length < 4 || buf[0] !== 255 || buf[1] !== 216) return null;
1945
+ let offset = 2;
1946
+ while (offset + 3 < buf.length) {
1947
+ if (buf[offset] !== 255) return null;
1948
+ while (buf[offset] === 255) offset++;
1949
+ const marker = buf[offset++];
1950
+ if (marker == null || marker === 217 || marker === 218) break;
1951
+ if (marker === 1 || marker >= 208 && marker <= 215) continue;
1952
+ if (offset + 2 > buf.length) return null;
1953
+ const length = buf.readUInt16BE(offset);
1954
+ if (length < 2 || offset + length > buf.length) return null;
1955
+ const isStartOfFrame = marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker);
1956
+ if (isStartOfFrame) {
1957
+ if (length < 7) return null;
1958
+ return {
1959
+ width: buf.readUInt16BE(offset + 5),
1960
+ height: buf.readUInt16BE(offset + 3),
1961
+ format: "jpeg"
1962
+ };
1963
+ }
1964
+ offset += length;
1965
+ }
1966
+ return null;
1967
+ }
1863
1968
 
1864
1969
  // src/providers/pixellab.ts
1865
1970
  import { mkdirSync, existsSync, readFileSync, writeFileSync } from "fs";
@@ -1920,12 +2025,20 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
1920
2025
  parent: spec.character.parentAssetId,
1921
2026
  parentSha256: spec.character.parentSha256 ?? null,
1922
2027
  state: spec.character.state,
1923
- animation: spec.character.animation,
2028
+ // Pose images enter as their hashes, never their paths.
2029
+ animation: spec.character.animation ? {
2030
+ ...spec.character.animation,
2031
+ startFrame: spec.character.animation.startFrame?.sha256,
2032
+ endFrame: spec.character.animation.endFrame?.sha256
2033
+ } : void 0,
1924
2034
  // Absent keys keep the hashes of manifests that never set these.
1925
2035
  proportions: spec.character.proportions,
1926
2036
  textGuidanceScale: spec.character.textGuidanceScale,
1927
2037
  isometric: spec.character.isometric,
1928
- reference: spec.character.reference ? Object.fromEntries(Object.entries(spec.character.reference).map(([direction, image]) => [direction, image.sha256])) : void 0
2038
+ enhancePrompt: spec.character.enhancePrompt,
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
1929
2042
  } : void 0,
1930
2043
  // A mirror's bytes come from its source's recorded outputs; the plan
1931
2044
  // compares those directly, so only the choice of source is identity.
@@ -2093,6 +2206,38 @@ var PixelLabProvider = class _PixelLabProvider {
2093
2206
  throw new Error(`${label}: proportions apply to the mannequin template; ${character.template} is a quadruped`);
2094
2207
  }
2095
2208
  }
2209
+ if (character.kind === "base" && character.enhancePrompt !== void 0 && character.mode !== "v3") {
2210
+ throw new Error(`${label}: enhancePrompt applies to v3 bases; the ${character.mode} engine does not take it`);
2211
+ }
2212
+ if (character.animation) {
2213
+ const { startFrame, endFrame } = character.animation;
2214
+ for (const [name, image] of [["start frame", startFrame], ["end frame", endFrame]]) {
2215
+ if (image && (image.width > 256 || image.height > 256)) {
2216
+ throw new Error(`${label}: ${name} is ${image.width}x${image.height}; PixelLab v3 takes up to 256px`);
2217
+ }
2218
+ }
2219
+ if (endFrame) {
2220
+ const start = startFrame ?? (character.parentFile && existsSync(character.parentFile) ? imageMetadata(readFileSync(character.parentFile)) : null);
2221
+ if (start && (start.width !== endFrame.width || start.height !== endFrame.height)) {
2222
+ throw new Error(
2223
+ `${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`
2224
+ );
2225
+ }
2226
+ }
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
+ }
2096
2241
  if (character.reference) {
2097
2242
  const given = Object.keys(character.reference);
2098
2243
  if (!character.reference.south) throw new Error(`${label}: a reference needs a south-facing sprite`);
@@ -2193,7 +2338,10 @@ var PixelLabProvider = class _PixelLabProvider {
2193
2338
  textGuidanceScale: character.textGuidanceScale,
2194
2339
  isometric: character.isometric,
2195
2340
  reference: character.reference ? readReference(spec, character.reference) : void 0,
2196
- styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0
2341
+ styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0,
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
2197
2345
  });
2198
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 } } };
2199
2347
  }
@@ -2237,7 +2385,15 @@ var PixelLabProvider = class _PixelLabProvider {
2237
2385
  directions: [animation.direction],
2238
2386
  seed: spec.seed,
2239
2387
  textGuidanceScale: character.textGuidanceScale,
2240
- isometric: character.isometric
2388
+ isometric: character.isometric,
2389
+ startFrame: animation.startFrame ? readPose(spec, "start frame", animation.startFrame) : void 0,
2390
+ endFrame: animation.endFrame ? readPose(spec, "end frame", animation.endFrame) : void 0,
2391
+ description: animation.subject,
2392
+ outline: animation.outline,
2393
+ shading: animation.shading,
2394
+ detail: animation.detail,
2395
+ enhancePrompt: animation.enhancePrompt,
2396
+ paletteSwatchBase64: spec.palette.length ? paletteSwatch(spec.palette).toString("base64") : void 0
2241
2397
  });
2242
2398
  const job = { characterId: parentId, name, direction: animation.direction, jobIds: res.background_job_ids };
2243
2399
  return {
@@ -2605,6 +2761,21 @@ function readReference(spec, reference) {
2605
2761
  }
2606
2762
  return images;
2607
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
+ }
2772
+ function readPose(spec, what, image) {
2773
+ const bytes = readFileSync(image.path);
2774
+ if (sha256(bytes) !== image.sha256) {
2775
+ throw new Error(`${spec.styleId}/${spec.assetId}: ${what} changed after the manifest was resolved: ${image.path}`);
2776
+ }
2777
+ return { base64: bytes.toString("base64"), format: image.format };
2778
+ }
2608
2779
  function rotationSources(character) {
2609
2780
  const order = character.directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
2610
2781
  const sources = [];
@@ -5297,6 +5468,18 @@ async function resolveSpecs(loaded, filter) {
5297
5468
  finalized.add(assetId);
5298
5469
  return resolved;
5299
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
+ }
5300
5483
  const characterParent = asset.state?.of ?? asset.animation?.of;
5301
5484
  if (resolved.character && characterParent) {
5302
5485
  const parentSpec = await finalize(characterParent);
@@ -5435,34 +5618,6 @@ async function resolveStyleImages(loaded, styleId) {
5435
5618
  async function styleImagesBase64(loaded, styleId) {
5436
5619
  return (await resolveStyleImages(loaded, styleId)).map((img) => img.base64);
5437
5620
  }
5438
- function imageMetadata(buf) {
5439
- if (buf.length >= 24 && buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834 && buf.toString("ascii", 12, 16) === "IHDR") {
5440
- return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20), format: "png" };
5441
- }
5442
- if (buf.length < 4 || buf[0] !== 255 || buf[1] !== 216) return null;
5443
- let offset = 2;
5444
- while (offset + 3 < buf.length) {
5445
- if (buf[offset] !== 255) return null;
5446
- while (buf[offset] === 255) offset++;
5447
- const marker = buf[offset++];
5448
- if (marker == null || marker === 217 || marker === 218) break;
5449
- if (marker === 1 || marker >= 208 && marker <= 215) continue;
5450
- if (offset + 2 > buf.length) return null;
5451
- const length = buf.readUInt16BE(offset);
5452
- if (length < 2 || offset + length > buf.length) return null;
5453
- const isStartOfFrame = marker >= 192 && marker <= 207 && ![196, 200, 204].includes(marker);
5454
- if (isStartOfFrame) {
5455
- if (length < 7) return null;
5456
- return {
5457
- width: buf.readUInt16BE(offset + 5),
5458
- height: buf.readUInt16BE(offset + 3),
5459
- format: "jpeg"
5460
- };
5461
- }
5462
- offset += length;
5463
- }
5464
- return null;
5465
- }
5466
5621
  async function resolveCharacterShape(asset, style, kind, files) {
5467
5622
  const mode2 = style.mode ?? "standard";
5468
5623
  const directions = mode2 === "standard" ? style.directions ?? 8 : 8;
@@ -5474,8 +5629,13 @@ async function resolveCharacterShape(asset, style, kind, files) {
5474
5629
  template: style.template ?? "mannequin",
5475
5630
  ...proportions !== void 0 ? { proportions } : {},
5476
5631
  ...style.textGuidanceScale !== void 0 ? { textGuidanceScale: style.textGuidanceScale } : {},
5477
- ...style.isometric !== void 0 ? { isometric: style.isometric } : {}
5632
+ ...style.isometric !== void 0 ? { isometric: style.isometric } : {},
5633
+ ...style.enhancePrompt !== void 0 ? { enhancePrompt: style.enhancePrompt } : {}
5478
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
+ }
5479
5639
  if (asset.reference) {
5480
5640
  const byDirection = typeof asset.reference === "string" ? { south: asset.reference } : asset.reference;
5481
5641
  shape.reference = {};
@@ -5498,13 +5658,24 @@ async function resolveCharacterShape(asset, style, kind, files) {
5498
5658
  }
5499
5659
  if (asset.animation) {
5500
5660
  const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
5661
+ const pose = async (rel, what) => {
5662
+ const image = await files.load(rel, what);
5663
+ return { path: path5.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
5664
+ };
5501
5665
  shape.animation = {
5502
5666
  mode: animationMode,
5503
5667
  ...asset.animation.template ? { template: asset.animation.template } : {},
5504
5668
  direction: asset.animation.direction,
5505
5669
  frames: asset.animation.frames ?? 8,
5506
5670
  fps: asset.animation.fps,
5507
- keepFirstFrame: asset.animation.keepFirstFrame
5671
+ keepFirstFrame: asset.animation.keepFirstFrame,
5672
+ ...asset.animation.startFrame ? { startFrame: await pose(asset.animation.startFrame, "Animation start frame") } : {},
5673
+ ...asset.animation.endFrame ? { endFrame: await pose(asset.animation.endFrame, "Animation end frame") } : {},
5674
+ ...asset.animation.subject ? { subject: asset.animation.subject } : {},
5675
+ ...asset.animation.outline ? { outline: asset.animation.outline } : {},
5676
+ ...asset.animation.shading ? { shading: asset.animation.shading } : {},
5677
+ ...asset.animation.detail ? { detail: asset.animation.detail } : {},
5678
+ ...asset.animation.enhancePrompt !== void 0 ? { enhancePrompt: asset.animation.enhancePrompt } : {}
5508
5679
  };
5509
5680
  }
5510
5681
  return shape;
@@ -6984,9 +7155,24 @@ import path11 from "path";
6984
7155
  async function inspectRevisionReadiness(spec, lock) {
6985
7156
  if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
6986
7157
  if (spec.character?.parentSpec) return inspectCharacterParent(spec, lock, /* @__PURE__ */ new Set());
7158
+ if (spec.character?.styleAnchor) return inspectStyleAnchor(spec, lock, /* @__PURE__ */ new Set());
6987
7159
  if (!spec.revision) return null;
6988
7160
  return inspectRevision(spec, lock, /* @__PURE__ */ new Set());
6989
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
+ }
6990
7176
  async function inspectMirror(spec, lock, seen) {
6991
7177
  const source = spec.mirror.sourceSpec;
6992
7178
  const label = `mirror source ${spec.styleId}/${spec.mirror.sourceAssetId}`;
@@ -7058,6 +7244,10 @@ async function inspectParent(spec, lock, seen) {
7058
7244
  const inputs = await inspectCharacterParent(spec, lock, nextSeen);
7059
7245
  if (!inputs.ready) return inputs;
7060
7246
  }
7247
+ if (spec.character?.styleAnchor) {
7248
+ const inputs = await inspectStyleAnchor(spec, lock, nextSeen);
7249
+ if (!inputs.ready) return inputs;
7250
+ }
7061
7251
  if (spec.quality) {
7062
7252
  const quality = await inspectQualityProfile(spec, lock);
7063
7253
  return quality?.state === "approved" ? { ready: true, reason: "approved quality output is current" } : {
@@ -7097,6 +7287,14 @@ async function anyOutputModified(entry, spec) {
7097
7287
  }
7098
7288
  return false;
7099
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
+ }
7100
7298
  function resumeCommandForStatus(status) {
7101
7299
  if (status === "pending" || status === "processing") return "poll";
7102
7300
  if (status === "review") return "pick";
@@ -7205,16 +7403,19 @@ async function buildPlan(specs, lock, opts = {}) {
7205
7403
  const stateOf = new Map(items.map((i) => [i.key, i.state]));
7206
7404
  for (const item of items) {
7207
7405
  if (!isActionable(item)) continue;
7208
- const parent = item.spec.mirror?.sourceSpec ?? item.spec.revision?.sourceSpec ?? item.spec.character?.parentSpec;
7209
- if (!parent) continue;
7210
- const parentKey = lockKey(parent.styleId, parent.assetId);
7211
- const parentState = stateOf.get(parentKey);
7212
- if (parentState && isActionable({ state: parentState })) {
7213
- item.state = "blocked";
7214
- item.reason = `parent ${parentKey} is being generated in this run; this follows in the next wave`;
7215
- } else if (item.spec.mirror && (parentState === "recoverable" || parentState === "in-flight")) {
7216
- item.state = "blocked";
7217
- 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
+ }
7218
7419
  }
7219
7420
  }
7220
7421
  const actionable = items.filter(isActionable);
@@ -9693,10 +9894,13 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
9693
9894
  try {
9694
9895
  const refs = styleImages.get(spec.styleId) ?? [];
9695
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;
9696
9899
  const replacedMetadata = previousEntry?.providerMetadata?.[provider.id];
9697
9900
  const { jobId, metadata } = await provider.submit(spec, refs, {
9698
9901
  ...previousJobId ? { previousJobId } : {},
9699
9902
  ...parentEntry?.objectId ? { parentObjectId: parentEntry.objectId } : {},
9903
+ ...anchorEntry?.objectId ? { styleObjectId: anchorEntry.objectId } : {},
9700
9904
  ...replacedMetadata ? { replacedMetadata } : {},
9701
9905
  ...previousMetadata ? { previousMetadata } : {},
9702
9906
  checkpoint: async (checkpoint) => {
@@ -12305,7 +12509,7 @@ var pin_default = {
12305
12509
  godot: "4.7.2",
12306
12510
  extensionsApi: 9,
12307
12511
  protocol: 4,
12308
- release: "editor-pixelorama-v1.2.2-pk.18",
12512
+ release: "editor-pixelorama-v1.2.2-pk.26",
12309
12513
  files: {
12310
12514
  "index.apple-touch-icon.png": {
12311
12515
  sha256: "ee5434873e0fed91e9a888c3ea5a8b79beac25999308be7a6eb41bf4ce640d27",
@@ -12320,7 +12524,7 @@ var pin_default = {
12320
12524
  bytes: 7298
12321
12525
  },
12322
12526
  "index.html": {
12323
- sha256: "d9adf06036660c4b76ef5386e872bc03dc0d5b0628fd6ac660d480893ee1f47a",
12527
+ sha256: "326101ab02bb4a29a7964214c9307c7dff26e9d66b8ebaaf6e7a3987e84a05b2",
12324
12528
  bytes: 5442
12325
12529
  },
12326
12530
  "index.icon.png": {
@@ -12332,8 +12536,8 @@ var pin_default = {
12332
12536
  bytes: 279815
12333
12537
  },
12334
12538
  "index.pck": {
12335
- sha256: "a41d9c3a39aa99ea5ee49ddcedfeb8b092006aa75c0de67139f13297c17f42e4",
12336
- bytes: 6431692
12539
+ sha256: "e26292650bb996965acd1ba70e74d2b4020b063d14f1276701312c1d34cb0a83",
12540
+ bytes: 6431932
12337
12541
  },
12338
12542
  "index.png": {
12339
12543
  sha256: "6e3ca3eb4a99e4c07bdd89ebc873b440ec0ffd66ce1955160da9e7ed80d538d7",