pixelkiln 0.53.4 → 0.55.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/cli.js +704 -31
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +706 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +599 -115
- package/dist/index.d.ts +599 -115
- package/dist/index.js +705 -31
- package/dist/index.js.map +1 -1
- package/docs/CHARACTERS.md +6 -0
- package/docs/ENDPOINTS.md +47 -2
- package/docs/GENERATORS.md +89 -0
- package/docs/MANIFEST.md +1 -1
- package/package.json +1 -1
- package/schema/manifest.schema.json +50 -2
- package/schema/recipe.schema.json +25 -1
- package/skills/pixelkiln/references/pixellab-roadmap.md +40 -11
- package/skills/pixelkiln/references/pixellab.md +57 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/types.ts
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
var MediaTypeSchema = z.enum(["image/png", "image/gif"]);
|
|
4
|
-
var GeneratorSchema = z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character", "terrain", "imagePro"]);
|
|
4
|
+
var GeneratorSchema = z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character", "terrain", "imagePro", "isometricTile", "objectPro"]);
|
|
5
5
|
var GridConfidenceSchema = z.enum(["low", "medium", "high"]);
|
|
6
6
|
var RevisionModeSchema = z.enum(["image-to-image", "inpaint", "outpaint"]);
|
|
7
7
|
function tileVariationCount(descriptions) {
|
|
@@ -35,6 +35,9 @@ function tilesCost(tileSize, variations) {
|
|
|
35
35
|
if (px <= 2048) return 25;
|
|
36
36
|
return 40;
|
|
37
37
|
}
|
|
38
|
+
function isometricTileCost() {
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
38
41
|
function terrainTileCount(transitionSize) {
|
|
39
42
|
return transitionSize === 1 ? 25 : 16;
|
|
40
43
|
}
|
|
@@ -364,6 +367,28 @@ var StyleObjectSchema = z.object({
|
|
|
364
367
|
/** `terrain` generator only. Camera angle; the API default is `high
|
|
365
368
|
* top-down`. */
|
|
366
369
|
terrainView: z.enum(["low top-down", "high top-down"]).optional(),
|
|
370
|
+
/**
|
|
371
|
+
* `isometricTile` generator only. `/create-isometric-tile`'s own tile
|
|
372
|
+
* grid size, distinct from `size` (the generation canvas, 16-64px — the
|
|
373
|
+
* API's own guidance is that sizes above 24px "often produce better
|
|
374
|
+
* quality results"). The API default is 16.
|
|
375
|
+
*/
|
|
376
|
+
isometricTileSize: z.union([z.literal(16), z.literal(32)]).optional(),
|
|
377
|
+
/**
|
|
378
|
+
* `isometricTile` generator only. Vertical thickness, i.e. how much
|
|
379
|
+
* height/elevation variation the tile can represent on a map: `"thin
|
|
380
|
+
* tile"` (~15% of canvas height), `"thick tile"` (~25%), or `"block"`
|
|
381
|
+
* (~50%, the API default).
|
|
382
|
+
*/
|
|
383
|
+
isometricTileShape: z.enum(["thin tile", "thick tile", "block"]).optional(),
|
|
384
|
+
/**
|
|
385
|
+
* `objectPro` generator only. Rotations captured for a base: 1 (the API
|
|
386
|
+
* still draws and stores a single facing) or the full 8. The API default
|
|
387
|
+
* is 8. Unlike `character`, `objectPro` has no skeleton/template modes
|
|
388
|
+
* and so no `mode`/`template` fields of its own — `/create-object-pro-flash`
|
|
389
|
+
* is the only base-creation path.
|
|
390
|
+
*/
|
|
391
|
+
objectDirections: z.union([z.literal(1), z.literal(8)]).optional(),
|
|
367
392
|
/**
|
|
368
393
|
* `pixflux` only. Whether to strip the generated background.
|
|
369
394
|
*
|
|
@@ -568,25 +593,41 @@ var AssetSchema = z.object({
|
|
|
568
593
|
revision: RevisionSchema.optional(),
|
|
569
594
|
/**
|
|
570
595
|
* The provider's own id for art that already exists on the account, so
|
|
571
|
-
* `adopt` can map it without matching bytes: an object id
|
|
572
|
-
*
|
|
573
|
-
*
|
|
596
|
+
* `adopt` can map it without matching bytes: an object id (an
|
|
597
|
+
* `objectPro` base and state included, since they share PixelLab's
|
|
598
|
+
* `/objects` identity space), a character id, or `<id>#<animation group
|
|
599
|
+
* id>` for a character or `objectPro` animation. Not part of the spec's
|
|
600
|
+
* identity.
|
|
574
601
|
*/
|
|
575
602
|
remoteId: z.string().min(1).optional(),
|
|
576
|
-
/**
|
|
603
|
+
/**
|
|
604
|
+
* `character` or `objectPro` styles: this asset is a pose or edit of
|
|
605
|
+
* another asset in the same family. `objectPro` ignores
|
|
606
|
+
* `paletteFromReference`/`canvas` — the API has no equivalent, and
|
|
607
|
+
* always inherits the parent's exact canvas.
|
|
608
|
+
*/
|
|
577
609
|
state: CharacterStateSchema.optional(),
|
|
578
|
-
/**
|
|
610
|
+
/**
|
|
611
|
+
* `character` or `objectPro` styles: this asset is a loop of another
|
|
612
|
+
* asset in the same family, in one direction. `objectPro` has no
|
|
613
|
+
* skeleton/template concept, so `template`/`subject`/`outline`/
|
|
614
|
+
* `shading`/`detail` are rejected on an `objectPro` animation rather
|
|
615
|
+
* than silently ignored — describe the motion in the asset's own
|
|
616
|
+
* `prompt` instead.
|
|
617
|
+
*/
|
|
579
618
|
animation: CharacterAnimationSchema.optional(),
|
|
580
619
|
/** `character` styles, `standard` humanoid bases: this character's proportions, over the style's. */
|
|
581
620
|
proportions: CharacterProportionsSchema.optional(),
|
|
582
621
|
/**
|
|
583
|
-
* `character` styles, bases only: the
|
|
584
|
-
* PixelLab rotates into the other directions instead of
|
|
585
|
-
* the prompt. A manifest-relative PNG or JPEG of the
|
|
586
|
-
* sprite, or an object keyed by direction (`{ "south":
|
|
587
|
-
* ... }`); quadrupeds in `standard` mode need
|
|
588
|
-
* `standard` takes more than south. `
|
|
589
|
-
*
|
|
622
|
+
* `character` or `objectPro` styles, bases only: the subject's own
|
|
623
|
+
* sprite, which PixelLab rotates into the other directions instead of
|
|
624
|
+
* drawing from the prompt. A manifest-relative PNG or JPEG of the
|
|
625
|
+
* south-facing sprite, or an object keyed by direction (`{ "south":
|
|
626
|
+
* ..., "east": ... }`); quadrupeds in `standard` character mode need
|
|
627
|
+
* south and east, and only `standard` takes more than south. `objectPro`
|
|
628
|
+
* only ever reads the south image, since `/create-object-pro-flash`
|
|
629
|
+
* rotates from one frame. `standard` wants each image at the style's
|
|
630
|
+
* size; v3 accepts up to 256px, pro up to 168px. The prompt
|
|
590
631
|
* still guides the result.
|
|
591
632
|
*/
|
|
592
633
|
reference: z.union([z.string().min(1), z.record(CharacterDirectionSchema, z.string().min(1))]).optional(),
|
|
@@ -1068,13 +1109,36 @@ var TilesetGetSchema = z2.object({
|
|
|
1068
1109
|
}).passthrough(),
|
|
1069
1110
|
usage: UsageSchema
|
|
1070
1111
|
}).passthrough();
|
|
1112
|
+
var IsometricTileSubmitSchema = z2.object({
|
|
1113
|
+
tile_id: z2.string().min(1),
|
|
1114
|
+
background_job_id: z2.string().min(1),
|
|
1115
|
+
status: z2.literal("processing").default("processing")
|
|
1116
|
+
}).passthrough();
|
|
1117
|
+
var IsometricTileGetSchema = z2.object({
|
|
1118
|
+
image: z2.object({ base64: z2.string().min(1), format: z2.string().default("png") }).passthrough(),
|
|
1119
|
+
usage: UsageSchema
|
|
1120
|
+
}).passthrough();
|
|
1071
1121
|
var RevisionJobSubmitSchema = z2.object({
|
|
1072
1122
|
background_job_id: z2.string().min(1),
|
|
1073
1123
|
status: z2.string().default("processing")
|
|
1074
1124
|
}).passthrough();
|
|
1125
|
+
var ObjectAnimationGroupSchema = z2.object({
|
|
1126
|
+
animation_group_id: z2.string().min(1),
|
|
1127
|
+
display_name: z2.string().nullable().default(null),
|
|
1128
|
+
description: z2.string().default(""),
|
|
1129
|
+
frame_count: z2.number().int().default(0),
|
|
1130
|
+
directions: z2.array(
|
|
1131
|
+
z2.object({
|
|
1132
|
+
direction: z2.string(),
|
|
1133
|
+
storage_urls: z2.record(z2.unknown()).default({}),
|
|
1134
|
+
created_at: z2.string()
|
|
1135
|
+
}).passthrough()
|
|
1136
|
+
).default([])
|
|
1137
|
+
}).passthrough();
|
|
1075
1138
|
var PixelLabObjectSchema = z2.object({
|
|
1076
1139
|
id: z2.string().min(1),
|
|
1077
1140
|
name: z2.string().nullable().default(null),
|
|
1141
|
+
state_name: z2.string().nullable().optional(),
|
|
1078
1142
|
prompt: z2.string().default(""),
|
|
1079
1143
|
size: z2.object({ width: z2.number().int().positive(), height: z2.number().int().positive() }),
|
|
1080
1144
|
directions: z2.number().default(0),
|
|
@@ -1086,7 +1150,9 @@ var PixelLabObjectSchema = z2.object({
|
|
|
1086
1150
|
tags: z2.array(z2.string()).default([]),
|
|
1087
1151
|
status: z2.string().nullable().default(null),
|
|
1088
1152
|
progress_percent: z2.number().nullable().optional(),
|
|
1089
|
-
eta_seconds: z2.number().nullable().optional()
|
|
1153
|
+
eta_seconds: z2.number().nullable().optional(),
|
|
1154
|
+
group_id: z2.string().nullable().optional(),
|
|
1155
|
+
animations: z2.array(ObjectAnimationGroupSchema).default([])
|
|
1090
1156
|
}).passthrough();
|
|
1091
1157
|
var MapObjectSchema = z2.object({
|
|
1092
1158
|
object_id: z2.string().min(1),
|
|
@@ -1111,6 +1177,28 @@ var AnimateSubmitSchema = z2.object({
|
|
|
1111
1177
|
status: z2.string().default("processing"),
|
|
1112
1178
|
usage: UsageSchema
|
|
1113
1179
|
}).passthrough();
|
|
1180
|
+
var ObjectProSubmitSchema = z2.object({
|
|
1181
|
+
object_id: z2.string().min(1),
|
|
1182
|
+
background_job_id: z2.string().min(1),
|
|
1183
|
+
status: z2.string().default("processing"),
|
|
1184
|
+
usage: UsageSchema
|
|
1185
|
+
}).passthrough();
|
|
1186
|
+
var DirectionSubmissionSchema = z2.object({
|
|
1187
|
+
direction: z2.string(),
|
|
1188
|
+
status: z2.string(),
|
|
1189
|
+
background_job_id: z2.string().nullable().default(null),
|
|
1190
|
+
animation_id: z2.string().nullable().default(null)
|
|
1191
|
+
}).passthrough();
|
|
1192
|
+
var AnimateObjectSubmitSchema = z2.object({
|
|
1193
|
+
animation_group_id: z2.string().min(1),
|
|
1194
|
+
object_id: z2.string().min(1),
|
|
1195
|
+
mode: z2.string(),
|
|
1196
|
+
frame_count: z2.number().int(),
|
|
1197
|
+
display_name: z2.string().nullable().default(null),
|
|
1198
|
+
description: z2.string().default(""),
|
|
1199
|
+
submissions: z2.array(DirectionSubmissionSchema),
|
|
1200
|
+
usage: UsageSchema
|
|
1201
|
+
}).passthrough();
|
|
1114
1202
|
var CharacterAnimationDirectionSchema = z2.object({
|
|
1115
1203
|
direction: z2.string(),
|
|
1116
1204
|
frame_count: z2.number().int().min(0),
|
|
@@ -1353,6 +1441,33 @@ var PixelLabClient = class {
|
|
|
1353
1441
|
);
|
|
1354
1442
|
return { tileset: res.tileset, usage: res.usage ?? null };
|
|
1355
1443
|
}
|
|
1444
|
+
/** A single standalone isometric tile — no connectable set, no candidates. */
|
|
1445
|
+
async createIsometricTile(args) {
|
|
1446
|
+
const body = { description: args.description };
|
|
1447
|
+
if (args.imageWidth != null && args.imageHeight != null) {
|
|
1448
|
+
body.image_size = { width: args.imageWidth, height: args.imageHeight };
|
|
1449
|
+
}
|
|
1450
|
+
if (args.tileSize != null) body.isometric_tile_size = args.tileSize;
|
|
1451
|
+
if (args.tileShape) body.isometric_tile_shape = args.tileShape;
|
|
1452
|
+
if (args.outline) body.outline = args.outline;
|
|
1453
|
+
if (args.shading) body.shading = args.shading;
|
|
1454
|
+
if (args.detail) body.detail = args.detail;
|
|
1455
|
+
if (args.seed != null) body.seed = args.seed;
|
|
1456
|
+
return validateResponse(
|
|
1457
|
+
IsometricTileSubmitSchema,
|
|
1458
|
+
await this.request("/create-isometric-tile", { method: "POST", body: JSON.stringify(body) }),
|
|
1459
|
+
"create isometric tile"
|
|
1460
|
+
);
|
|
1461
|
+
}
|
|
1462
|
+
/** Throws PixelLabError(423) while the tile is still processing. */
|
|
1463
|
+
async getIsometricTile(tileId) {
|
|
1464
|
+
const res = await validateResponse(
|
|
1465
|
+
IsometricTileGetSchema,
|
|
1466
|
+
await this.request(`/isometric-tiles/${tileId}`),
|
|
1467
|
+
"get isometric tile"
|
|
1468
|
+
);
|
|
1469
|
+
return { image: res.image, usage: res.usage ?? null };
|
|
1470
|
+
}
|
|
1356
1471
|
/**
|
|
1357
1472
|
* Synchronous single-image generation. Returns the PNG inline rather than a
|
|
1358
1473
|
* job id, and is the only endpoint that honours a forced palette;
|
|
@@ -1386,6 +1501,81 @@ var PixelLabClient = class {
|
|
|
1386
1501
|
"get object"
|
|
1387
1502
|
);
|
|
1388
1503
|
}
|
|
1504
|
+
/**
|
|
1505
|
+
* A standalone object with no skeleton, one call draws the south sprite
|
|
1506
|
+
* (or takes the author's) and rotates it with v3 — the character
|
|
1507
|
+
* pro-flash pattern, minus `template_id` (objects have no skeleton) and
|
|
1508
|
+
* with a real `n_directions` choice (1 or 8, character pro-flash is
|
|
1509
|
+
* always 8). Async only: `/create-object-pro-flash` has no sync response.
|
|
1510
|
+
*/
|
|
1511
|
+
async createObjectProFlash(args) {
|
|
1512
|
+
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
1513
|
+
const traits = args.styleTraits;
|
|
1514
|
+
const body = {
|
|
1515
|
+
description: args.description,
|
|
1516
|
+
n_directions: args.directions,
|
|
1517
|
+
...args.reference ? { first_frame: encode(args.reference) } : { image_size: { width: args.size, height: args.size } },
|
|
1518
|
+
...args.view ? { view: args.view } : {},
|
|
1519
|
+
...args.seed != null ? { seed: args.seed } : {},
|
|
1520
|
+
...args.styleReference && args.styleReferenceSize ? {
|
|
1521
|
+
style_image: { image: encode(args.styleReference), size: args.styleReferenceSize },
|
|
1522
|
+
...traits ? {
|
|
1523
|
+
style_options: {
|
|
1524
|
+
...traits.palette !== void 0 ? { color_palette: traits.palette } : {},
|
|
1525
|
+
...traits.outline !== void 0 ? { outline: traits.outline } : {},
|
|
1526
|
+
...traits.detail !== void 0 ? { detail: traits.detail } : {},
|
|
1527
|
+
...traits.shading !== void 0 ? { shading: traits.shading } : {}
|
|
1528
|
+
}
|
|
1529
|
+
} : {}
|
|
1530
|
+
} : {}
|
|
1531
|
+
};
|
|
1532
|
+
return validateResponse(
|
|
1533
|
+
ObjectProSubmitSchema,
|
|
1534
|
+
await this.request("/create-object-pro-flash", { method: "POST", body: JSON.stringify(body) }),
|
|
1535
|
+
"create-object-pro-flash"
|
|
1536
|
+
);
|
|
1537
|
+
}
|
|
1538
|
+
/** A text edit of an existing object, applied to its whole rotation set and stored as a sibling. Always inherits the parent's exact canvas — no size override exists on this endpoint. */
|
|
1539
|
+
async createObjectProState(args) {
|
|
1540
|
+
const raw = await this.request(`/objects/${encodeURIComponent(args.objectId)}/states`, {
|
|
1541
|
+
method: "POST",
|
|
1542
|
+
body: JSON.stringify({
|
|
1543
|
+
edit_description: args.editDescription,
|
|
1544
|
+
...args.stateName ? { state_name: args.stateName } : {},
|
|
1545
|
+
...args.seed != null ? { seed: args.seed } : {}
|
|
1546
|
+
})
|
|
1547
|
+
});
|
|
1548
|
+
return validateResponse(ObjectProSubmitSchema, raw, "create object state");
|
|
1549
|
+
}
|
|
1550
|
+
/**
|
|
1551
|
+
* One direction of one object's animation. Unlike character animation,
|
|
1552
|
+
* this endpoint's own `replace_existing` flag regenerates an
|
|
1553
|
+
* already-animated direction directly (character has no such flag and
|
|
1554
|
+
* pixelkiln instead deletes the prior take itself before resubmitting;
|
|
1555
|
+
* see `submitCharacter`). A 1-direction object must not receive
|
|
1556
|
+
* `directions` at all -- the API 400s if it does.
|
|
1557
|
+
*/
|
|
1558
|
+
async animateObject(args) {
|
|
1559
|
+
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
1560
|
+
const body = {
|
|
1561
|
+
mode: args.mode,
|
|
1562
|
+
...args.directions === 8 ? { directions: [args.direction] } : {},
|
|
1563
|
+
...args.animationDescription ? { animation_description: args.animationDescription } : {},
|
|
1564
|
+
...args.animationGroupId ? { animation_group_id: args.animationGroupId } : {},
|
|
1565
|
+
...args.displayName ? { display_name: args.displayName } : {},
|
|
1566
|
+
...args.frameCount != null ? { frame_count: args.frameCount } : {},
|
|
1567
|
+
...args.keepFirstFrame === false ? { keep_first_frame: false } : {},
|
|
1568
|
+
...args.startFrame ? { custom_start_frame: encode(args.startFrame) } : {},
|
|
1569
|
+
...args.endFrame ? { end_frame: encode(args.endFrame) } : {},
|
|
1570
|
+
...args.enhancePrompt ? { enhance_prompt: true } : {},
|
|
1571
|
+
...args.replaceExisting ? { replace_existing: true } : {}
|
|
1572
|
+
};
|
|
1573
|
+
const raw = await this.request(`/objects/${encodeURIComponent(args.objectId)}/animations`, {
|
|
1574
|
+
method: "POST",
|
|
1575
|
+
body: JSON.stringify(body)
|
|
1576
|
+
});
|
|
1577
|
+
return validateResponse(AnimateObjectSubmitSchema, raw, "animate object");
|
|
1578
|
+
}
|
|
1389
1579
|
async getMapObject(objectId) {
|
|
1390
1580
|
return validateResponse(
|
|
1391
1581
|
MapObjectSchema,
|
|
@@ -2331,6 +2521,8 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
2331
2521
|
tileView: spec.tileView,
|
|
2332
2522
|
tileFeature: spec.tileFeature,
|
|
2333
2523
|
outlineMode: spec.outlineMode,
|
|
2524
|
+
isometricTileSize: spec.isometricTileSize,
|
|
2525
|
+
isometricTileShape: spec.isometricTileShape,
|
|
2334
2526
|
styleImages: styleImageHashes,
|
|
2335
2527
|
// A character family: the engine and rotations of a base, the parent's
|
|
2336
2528
|
// generated bytes for a state or animation (so a regenerated parent
|
|
@@ -2359,6 +2551,23 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
2359
2551
|
concept: spec.character.concept?.sha256,
|
|
2360
2552
|
styleAnchor: spec.character.styleAnchor ? { of: spec.character.styleAnchor.assetId, sha256: spec.character.styleAnchor.sha256 } : void 0
|
|
2361
2553
|
} : void 0,
|
|
2554
|
+
// An objectPro family: same shape as character above, minus the
|
|
2555
|
+
// fields it has none of (mode/template/proportions/isometric/concept/
|
|
2556
|
+
// styleAnchor) — see ResolvedObjectPro.
|
|
2557
|
+
objectPro: spec.objectPro ? {
|
|
2558
|
+
kind: spec.objectPro.kind,
|
|
2559
|
+
directions: spec.objectPro.directions,
|
|
2560
|
+
parent: spec.objectPro.parentAssetId,
|
|
2561
|
+
parentSha256: spec.objectPro.parentSha256 ?? null,
|
|
2562
|
+
state: spec.objectPro.state,
|
|
2563
|
+
animation: spec.objectPro.animation ? {
|
|
2564
|
+
...spec.objectPro.animation,
|
|
2565
|
+
startFrame: spec.objectPro.animation.startFrame?.sha256,
|
|
2566
|
+
endFrame: spec.objectPro.animation.endFrame?.sha256
|
|
2567
|
+
} : void 0,
|
|
2568
|
+
styleTraits: spec.objectPro.styleTraits,
|
|
2569
|
+
reference: spec.objectPro.reference?.sha256
|
|
2570
|
+
} : void 0,
|
|
2362
2571
|
// A mirror's bytes come from its source's recorded outputs; the plan
|
|
2363
2572
|
// compares those directly, so only the choice of source is identity.
|
|
2364
2573
|
mirror: spec.mirror ? { of: spec.mirror.sourceAssetId } : void 0,
|
|
@@ -2426,6 +2635,29 @@ function proFlashCharacterCost(width, height, fromReference) {
|
|
|
2426
2635
|
function characterAnimationName(spec) {
|
|
2427
2636
|
return `pixelkiln:${spec.styleId}/${spec.assetId}`;
|
|
2428
2637
|
}
|
|
2638
|
+
function objectAnimationName(spec) {
|
|
2639
|
+
return `pixelkiln:${spec.styleId}/${spec.assetId}`;
|
|
2640
|
+
}
|
|
2641
|
+
function proFlashObjectCost(width, height, directions, fromReference) {
|
|
2642
|
+
const side = Math.max(32, Math.ceil(Math.max(width, height) / 4) * 4);
|
|
2643
|
+
const rotations = Math.max(1, Math.ceil(side * side * directions / 65536));
|
|
2644
|
+
if (fromReference) return rotations;
|
|
2645
|
+
const image = side <= 96 ? 5 : side <= 208 ? 6 : 9;
|
|
2646
|
+
return image + rotations;
|
|
2647
|
+
}
|
|
2648
|
+
function objectProCost(spec) {
|
|
2649
|
+
const object = spec.objectPro;
|
|
2650
|
+
if (!object) return generationCost(spec.width, spec.height, "1dir");
|
|
2651
|
+
const px = spec.width * spec.height;
|
|
2652
|
+
if (object.kind === "animation") {
|
|
2653
|
+
const animation = object.animation;
|
|
2654
|
+
if (animation.mode === "pro") return generationCost(spec.width, spec.height, "1dir");
|
|
2655
|
+
return Math.max(1, Math.ceil(px * animation.frames / 65536));
|
|
2656
|
+
}
|
|
2657
|
+
if (object.kind === "state") return generationCost(spec.width, spec.height, "1dir");
|
|
2658
|
+
const south = object.reference;
|
|
2659
|
+
return south ? proFlashObjectCost(south.width, south.height, object.directions, true) : proFlashObjectCost(spec.width, spec.height, object.directions, false);
|
|
2660
|
+
}
|
|
2429
2661
|
var CHARACTER_TEMPLATES = ["mannequin", "bear", "cat", "dog", "horse", "lion"];
|
|
2430
2662
|
var PRO_FLASH_TEMPLATES = [...CHARACTER_TEMPLATES, "custom"];
|
|
2431
2663
|
function encodeAnimationJob(job) {
|
|
@@ -2442,6 +2674,20 @@ function decodeAnimationJob(jobId) {
|
|
|
2442
2674
|
}
|
|
2443
2675
|
return null;
|
|
2444
2676
|
}
|
|
2677
|
+
function encodeObjectAnimationJob(job) {
|
|
2678
|
+
return `object-animation:${Buffer.from(JSON.stringify(job)).toString("base64url")}`;
|
|
2679
|
+
}
|
|
2680
|
+
function decodeObjectAnimationJob(jobId) {
|
|
2681
|
+
if (!jobId.startsWith("object-animation:")) return null;
|
|
2682
|
+
try {
|
|
2683
|
+
const value = JSON.parse(Buffer.from(jobId.slice("object-animation:".length), "base64url").toString("utf8"));
|
|
2684
|
+
if (typeof value?.objectId === "string" && typeof value.name === "string" && typeof value.direction === "string") {
|
|
2685
|
+
return { objectId: value.objectId, name: value.name, direction: value.direction, jobId: typeof value.jobId === "string" ? value.jobId : null };
|
|
2686
|
+
}
|
|
2687
|
+
} catch {
|
|
2688
|
+
}
|
|
2689
|
+
return null;
|
|
2690
|
+
}
|
|
2445
2691
|
var PixelLabProvider = class _PixelLabProvider {
|
|
2446
2692
|
constructor(client2) {
|
|
2447
2693
|
this.client = client2;
|
|
@@ -2460,7 +2706,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2460
2706
|
return new _PixelLabProvider(new PixelLabClient("download-only"));
|
|
2461
2707
|
}
|
|
2462
2708
|
supports(generator) {
|
|
2463
|
-
return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "imagePro" || generator === "character";
|
|
2709
|
+
return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "imagePro" || generator === "character" || generator === "isometricTile" || generator === "objectPro";
|
|
2464
2710
|
}
|
|
2465
2711
|
/**
|
|
2466
2712
|
* `inpaint` (`/inpaint-v3`, a mask) and `image-to-image` (`/edit-images-v2`,
|
|
@@ -2496,12 +2742,15 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2496
2742
|
const height = spec.revision.sourceHeight ?? spec.height;
|
|
2497
2743
|
return { unit: "generations", amount: generationCost(width, height, "1dir"), candidates: 1 };
|
|
2498
2744
|
}
|
|
2499
|
-
if (spec.generator === "tiles" || spec.generator === "terrain") {
|
|
2745
|
+
if (spec.generator === "tiles" || spec.generator === "terrain" || spec.generator === "isometricTile") {
|
|
2500
2746
|
return { unit: "generations", amount: spec.cost, candidates: spec.candidates };
|
|
2501
2747
|
}
|
|
2502
2748
|
if (spec.generator === "character") {
|
|
2503
2749
|
return { unit: "generations", amount: characterCost(spec), candidates: 1 };
|
|
2504
2750
|
}
|
|
2751
|
+
if (spec.generator === "objectPro") {
|
|
2752
|
+
return { unit: "generations", amount: objectProCost(spec), candidates: 1 };
|
|
2753
|
+
}
|
|
2505
2754
|
return {
|
|
2506
2755
|
unit: "generations",
|
|
2507
2756
|
amount: generationCost(spec.width, spec.height, spec.generator),
|
|
@@ -2527,19 +2776,19 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2527
2776
|
);
|
|
2528
2777
|
}
|
|
2529
2778
|
if (spec.generator === "map") {
|
|
2530
|
-
requirePixelLabOption("view", spec.view, ["low top-down", "high top-down", "side"]);
|
|
2531
|
-
requirePixelLabOption("outline", spec.outline, [
|
|
2779
|
+
requirePixelLabOption("map", "view", spec.view, ["low top-down", "high top-down", "side"]);
|
|
2780
|
+
requirePixelLabOption("map", "outline", spec.outline, [
|
|
2532
2781
|
"single color outline",
|
|
2533
2782
|
"selective outline",
|
|
2534
2783
|
"lineless"
|
|
2535
2784
|
]);
|
|
2536
|
-
requirePixelLabOption("shading", spec.shading, [
|
|
2785
|
+
requirePixelLabOption("map", "shading", spec.shading, [
|
|
2537
2786
|
"flat shading",
|
|
2538
2787
|
"basic shading",
|
|
2539
2788
|
"medium shading",
|
|
2540
2789
|
"detailed shading"
|
|
2541
2790
|
]);
|
|
2542
|
-
requirePixelLabOption("detail", spec.detail, [
|
|
2791
|
+
requirePixelLabOption("map", "detail", spec.detail, [
|
|
2543
2792
|
"low detail",
|
|
2544
2793
|
"medium detail",
|
|
2545
2794
|
"high detail"
|
|
@@ -2547,7 +2796,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2547
2796
|
}
|
|
2548
2797
|
if (spec.generator === "terrain") {
|
|
2549
2798
|
if (spec.outline) {
|
|
2550
|
-
requirePixelLabOption("outline", spec.outline, [
|
|
2799
|
+
requirePixelLabOption("terrain", "outline", spec.outline, [
|
|
2551
2800
|
"single color black outline",
|
|
2552
2801
|
"single color outline",
|
|
2553
2802
|
"selective outline",
|
|
@@ -2555,7 +2804,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2555
2804
|
]);
|
|
2556
2805
|
}
|
|
2557
2806
|
if (spec.shading) {
|
|
2558
|
-
requirePixelLabOption("shading", spec.shading, [
|
|
2807
|
+
requirePixelLabOption("terrain", "shading", spec.shading, [
|
|
2559
2808
|
"flat shading",
|
|
2560
2809
|
"basic shading",
|
|
2561
2810
|
"medium shading",
|
|
@@ -2564,7 +2813,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2564
2813
|
]);
|
|
2565
2814
|
}
|
|
2566
2815
|
if (spec.detail) {
|
|
2567
|
-
requirePixelLabOption("detail", spec.detail, ["low detail", "medium detail", "highly detailed"]);
|
|
2816
|
+
requirePixelLabOption("terrain", "detail", spec.detail, ["low detail", "medium detail", "highly detailed"]);
|
|
2568
2817
|
}
|
|
2569
2818
|
if (spec.terrainShapeStyle && spec.terrainMode === "pro") {
|
|
2570
2819
|
throw new Error(
|
|
@@ -2585,7 +2834,32 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2585
2834
|
);
|
|
2586
2835
|
}
|
|
2587
2836
|
}
|
|
2837
|
+
if (spec.generator === "isometricTile") {
|
|
2838
|
+
if (spec.outline) {
|
|
2839
|
+
requirePixelLabOption("isometricTile", "outline", spec.outline, [
|
|
2840
|
+
"single color outline",
|
|
2841
|
+
"selective outline",
|
|
2842
|
+
"lineless"
|
|
2843
|
+
]);
|
|
2844
|
+
}
|
|
2845
|
+
if (spec.shading) {
|
|
2846
|
+
requirePixelLabOption("isometricTile", "shading", spec.shading, [
|
|
2847
|
+
"flat shading",
|
|
2848
|
+
"basic shading",
|
|
2849
|
+
"medium shading",
|
|
2850
|
+
"detailed shading",
|
|
2851
|
+
"highly detailed shading"
|
|
2852
|
+
]);
|
|
2853
|
+
}
|
|
2854
|
+
if (spec.detail) {
|
|
2855
|
+
requirePixelLabOption("isometricTile", "detail", spec.detail, ["low detail", "medium detail", "highly detailed"]);
|
|
2856
|
+
}
|
|
2857
|
+
if (spec.width < 16 || spec.width > 64) {
|
|
2858
|
+
throw new Error(`PixelLab isometricTile: size must be 16 to 64 pixels, got ${spec.width}`);
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2588
2861
|
if (spec.generator === "character") this.validateCharacter(spec, styleImages);
|
|
2862
|
+
if (spec.generator === "objectPro") this.validateObjectPro(spec, styleImages);
|
|
2589
2863
|
if ((spec.generator === "map" || spec.generator === "pixflux") && styleImages.length) {
|
|
2590
2864
|
throw new Error(`PixelLab ${spec.generator} does not support style images`);
|
|
2591
2865
|
}
|
|
@@ -2599,6 +2873,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2599
2873
|
"PixelLab imagePro does not support style images yet; /generate-image-v2's own reference_images and style_image are not modeled here"
|
|
2600
2874
|
);
|
|
2601
2875
|
}
|
|
2876
|
+
if (spec.generator === "isometricTile" && styleImages.length) {
|
|
2877
|
+
throw new Error(
|
|
2878
|
+
"PixelLab isometricTile does not support style images; /create-isometric-tile has no such field (its own init_image/color_image are not modeled here yet)"
|
|
2879
|
+
);
|
|
2880
|
+
}
|
|
2602
2881
|
for (const image of styleImages) {
|
|
2603
2882
|
if (image.width > 256 || image.height > 256) {
|
|
2604
2883
|
throw new Error(
|
|
@@ -2741,6 +3020,63 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2741
3020
|
}
|
|
2742
3021
|
}
|
|
2743
3022
|
}
|
|
3023
|
+
validateObjectPro(spec, styleImages = []) {
|
|
3024
|
+
const object = spec.objectPro;
|
|
3025
|
+
if (!object) throw new Error(`${spec.styleId}/${spec.assetId} has no objectPro shape`);
|
|
3026
|
+
const label = `${spec.styleId}/${spec.assetId}`;
|
|
3027
|
+
if (object.kind === "base" && object.reference && styleImages.length) {
|
|
3028
|
+
throw new Error(`${label}: a base rotating its own reference sprite takes no style image`);
|
|
3029
|
+
}
|
|
3030
|
+
if (object.reference && (object.reference.width > 256 || object.reference.height > 256)) {
|
|
3031
|
+
throw new Error(`${label}: reference is ${object.reference.width}x${object.reference.height}; PixelLab objectPro takes up to 256px`);
|
|
3032
|
+
}
|
|
3033
|
+
if (styleImages.length) {
|
|
3034
|
+
if (styleImages.length > 1) throw new Error(`PixelLab objectPro takes one style image`);
|
|
3035
|
+
const image = styleImages[0];
|
|
3036
|
+
if (image.width > 256 || image.height > 256) {
|
|
3037
|
+
throw new Error(`PixelLab objectPro style image is ${image.width}x${image.height}; the limit is 256px`);
|
|
3038
|
+
}
|
|
3039
|
+
if (image.width > spec.width || image.height > spec.height) {
|
|
3040
|
+
throw new Error(
|
|
3041
|
+
`PixelLab objectPro style image is ${image.width}x${image.height} but the object is ${spec.width}x${spec.height}; crop the image to its subject or raise the style's size`
|
|
3042
|
+
);
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
if (object.kind === "base" && (spec.width < 16 || spec.height < 16 || spec.width > 256 || spec.height > 256)) {
|
|
3046
|
+
throw new Error(`PixelLab objectPro bases must be between 16 and 256 pixels`);
|
|
3047
|
+
}
|
|
3048
|
+
if (object.kind === "base" && (spec.width % 4 || spec.height % 4)) {
|
|
3049
|
+
throw new Error(`PixelLab objectPro bases are sized in multiples of 4 pixels; ${spec.width}x${spec.height} is not`);
|
|
3050
|
+
}
|
|
3051
|
+
if (object.kind !== "base" && !object.parentAssetId) {
|
|
3052
|
+
throw new Error(`${spec.styleId}/${spec.assetId} is a ${object.kind} with no parent`);
|
|
3053
|
+
}
|
|
3054
|
+
if (object.animation) {
|
|
3055
|
+
const allowed = object.directions === 1 ? ["south"] : CHARACTER_DIRECTIONS_8;
|
|
3056
|
+
if (!allowed.includes(object.animation.direction)) {
|
|
3057
|
+
throw new Error(
|
|
3058
|
+
`${label} animates "${object.animation.direction}", but its object has ${object.directions} direction${object.directions === 1 ? "" : "s"}: ${allowed.join(", ")}`
|
|
3059
|
+
);
|
|
3060
|
+
}
|
|
3061
|
+
if (!spec.prompt.trim()) {
|
|
3062
|
+
throw new Error(`${label} needs a prompt describing the motion`);
|
|
3063
|
+
}
|
|
3064
|
+
const { startFrame, endFrame } = object.animation;
|
|
3065
|
+
for (const [name, image] of [["start frame", startFrame], ["end frame", endFrame]]) {
|
|
3066
|
+
if (image && (image.width > 256 || image.height > 256)) {
|
|
3067
|
+
throw new Error(`${label}: ${name} is ${image.width}x${image.height}; PixelLab v3 takes up to 256px`);
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
if (endFrame) {
|
|
3071
|
+
const start = startFrame ?? (object.parentFile && existsSync(object.parentFile) ? imageMetadata(readFileSync(object.parentFile)) : null);
|
|
3072
|
+
if (start && (start.width !== endFrame.width || start.height !== endFrame.height)) {
|
|
3073
|
+
throw new Error(
|
|
3074
|
+
`${label}: end frame is ${endFrame.width}x${endFrame.height} but the ${startFrame ? "start frame" : "object's rotation"} is ${start.width}x${start.height}; PixelLab interpolates between frames of one size`
|
|
3075
|
+
);
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
2744
3080
|
/**
|
|
2745
3081
|
* A base is one request that answers with the character id; a state is
|
|
2746
3082
|
* the same with the parent's id; an animation names itself after the
|
|
@@ -2834,6 +3170,78 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2834
3170
|
metadata: { character: { kind: "animation", characterId: parentId, animationName: name, direction: animation.direction, mode: animation.mode, backgroundJobIds: res.background_job_ids } }
|
|
2835
3171
|
};
|
|
2836
3172
|
}
|
|
3173
|
+
/**
|
|
3174
|
+
* Same shape as `submitCharacter`, minus the mode branching (objectPro has
|
|
3175
|
+
* exactly one base-creation path) and minus the client-side "find and
|
|
3176
|
+
* delete the prior take" dance for animation: `/objects/{id}/animations`
|
|
3177
|
+
* has its own `replace_existing` flag, so this always passes it rather
|
|
3178
|
+
* than fetching the object first to check.
|
|
3179
|
+
*/
|
|
3180
|
+
async submitObjectPro(spec, styleImages, context) {
|
|
3181
|
+
const object = spec.objectPro;
|
|
3182
|
+
if (object.kind === "base") {
|
|
3183
|
+
const styleImage = styleImages[0];
|
|
3184
|
+
const res2 = await this.client.createObjectProFlash({
|
|
3185
|
+
description: spec.prompt,
|
|
3186
|
+
directions: object.directions,
|
|
3187
|
+
size: spec.width,
|
|
3188
|
+
view: spec.view,
|
|
3189
|
+
seed: spec.seed,
|
|
3190
|
+
reference: object.reference ? readPose(spec, "reference sprite", object.reference) : void 0,
|
|
3191
|
+
styleReference: styleImage ? { base64: styleImage.base64, format: styleImage.format } : void 0,
|
|
3192
|
+
styleReferenceSize: styleImage ? { width: styleImage.width, height: styleImage.height } : void 0,
|
|
3193
|
+
styleTraits: object.styleTraits
|
|
3194
|
+
});
|
|
3195
|
+
return { jobId: res2.object_id, metadata: { objectPro: { kind: "base", objectId: res2.object_id, directions: object.directions, backgroundJobId: res2.background_job_id } } };
|
|
3196
|
+
}
|
|
3197
|
+
const parentId = context?.parentObjectId;
|
|
3198
|
+
if (!parentId) {
|
|
3199
|
+
throw new Error(
|
|
3200
|
+
`${spec.styleId}/${spec.assetId} is a ${object.kind} of ${object.parentAssetId}, which has no PixelLab object id in the lockfile yet; generate the parent first`
|
|
3201
|
+
);
|
|
3202
|
+
}
|
|
3203
|
+
if (object.kind === "state") {
|
|
3204
|
+
const res2 = await this.client.createObjectProState({
|
|
3205
|
+
objectId: parentId,
|
|
3206
|
+
editDescription: spec.prompt,
|
|
3207
|
+
stateName: spec.assetId.slice(0, 50),
|
|
3208
|
+
seed: spec.seed
|
|
3209
|
+
});
|
|
3210
|
+
return { jobId: res2.object_id, metadata: { objectPro: { kind: "state", objectId: res2.object_id, parentObjectId: parentId, directions: object.directions, backgroundJobId: res2.background_job_id } } };
|
|
3211
|
+
}
|
|
3212
|
+
const animation = object.animation;
|
|
3213
|
+
const name = objectAnimationName(spec);
|
|
3214
|
+
const res = await this.client.animateObject({
|
|
3215
|
+
objectId: parentId,
|
|
3216
|
+
directions: object.directions,
|
|
3217
|
+
direction: animation.direction,
|
|
3218
|
+
animationDescription: spec.prompt.trim() || void 0,
|
|
3219
|
+
displayName: name,
|
|
3220
|
+
mode: animation.mode === "pro" ? "pro" : "v3",
|
|
3221
|
+
frameCount: animation.frames,
|
|
3222
|
+
keepFirstFrame: animation.keepFirstFrame,
|
|
3223
|
+
startFrame: animation.startFrame ? readPose(spec, "start frame", animation.startFrame) : void 0,
|
|
3224
|
+
endFrame: animation.endFrame ? readPose(spec, "end frame", animation.endFrame) : void 0,
|
|
3225
|
+
enhancePrompt: animation.enhancePrompt,
|
|
3226
|
+
replaceExisting: true
|
|
3227
|
+
});
|
|
3228
|
+
const submission = res.submissions.find((s) => s.direction === animation.direction) ?? res.submissions[0];
|
|
3229
|
+
const job = { objectId: parentId, name, direction: animation.direction, jobId: submission?.background_job_id ?? null };
|
|
3230
|
+
return {
|
|
3231
|
+
jobId: encodeObjectAnimationJob(job),
|
|
3232
|
+
metadata: {
|
|
3233
|
+
objectPro: {
|
|
3234
|
+
kind: "animation",
|
|
3235
|
+
objectId: parentId,
|
|
3236
|
+
animationName: name,
|
|
3237
|
+
direction: animation.direction,
|
|
3238
|
+
mode: animation.mode,
|
|
3239
|
+
animationGroupId: res.animation_group_id,
|
|
3240
|
+
animationId: submission?.animation_id ?? null
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
};
|
|
3244
|
+
}
|
|
2837
3245
|
async pollCharacter(jobId, context) {
|
|
2838
3246
|
const animation = decodeAnimationJob(jobId);
|
|
2839
3247
|
if (animation) return this.pollCharacterAnimation(animation, context);
|
|
@@ -2917,10 +3325,93 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2917
3325
|
if (found) return review(found.frames, found.animationId, found.groupId, null);
|
|
2918
3326
|
return cleanedUp ? { status: "failed", error: `animation job is gone upstream and no "${job.name}" ${job.direction} animation exists on the character` } : { status: "processing" };
|
|
2919
3327
|
}
|
|
3328
|
+
async pollObjectPro(jobId, context) {
|
|
3329
|
+
const animation = decodeObjectAnimationJob(jobId);
|
|
3330
|
+
if (animation) return this.pollObjectProAnimation(animation, context);
|
|
3331
|
+
const object = await this.client.getObject(jobId);
|
|
3332
|
+
if (object.status === "failed") return { status: "failed", error: "object generation failed upstream" };
|
|
3333
|
+
if (object.status !== "completed" || !object.rotation_urls) return { status: "processing" };
|
|
3334
|
+
const sources = objectRotationSources(object);
|
|
3335
|
+
if (!sources.length) return { status: "failed", error: "object completed with no rotation images" };
|
|
3336
|
+
const backgroundJobId = context?.metadata?.objectPro?.backgroundJobId;
|
|
3337
|
+
return {
|
|
3338
|
+
status: "ready",
|
|
3339
|
+
objectId: object.id,
|
|
3340
|
+
sourceUrl: sources[0].url,
|
|
3341
|
+
sources,
|
|
3342
|
+
metadata: {
|
|
3343
|
+
objectPro: {
|
|
3344
|
+
objectId: object.id,
|
|
3345
|
+
groupId: object.group_id ?? null,
|
|
3346
|
+
name: object.name,
|
|
3347
|
+
stateName: object.state_name ?? null,
|
|
3348
|
+
directions: object.directions,
|
|
3349
|
+
size: object.size
|
|
3350
|
+
}
|
|
3351
|
+
},
|
|
3352
|
+
billed: await this.billedForJob(backgroundJobId)
|
|
3353
|
+
};
|
|
3354
|
+
}
|
|
3355
|
+
/**
|
|
3356
|
+
* Same resilience shape as `pollCharacterAnimation`: the background job is
|
|
3357
|
+
* the authoritative record while it lasts, the object's own `animations`
|
|
3358
|
+
* list (matched by the `pixelkiln:` display name this asset submitted, or
|
|
3359
|
+
* a recorded animation id) is the fallback once PixelLab cleans the job
|
|
3360
|
+
* up. Never declares failure from absence alone -- a `rate_limited`
|
|
3361
|
+
* `DirectionSubmission` has no background job id at all, so this keeps
|
|
3362
|
+
* reporting "processing" rather than guessing a job that never existed
|
|
3363
|
+
* has failed.
|
|
3364
|
+
*/
|
|
3365
|
+
async pollObjectProAnimation(job, context) {
|
|
3366
|
+
const fps = context?.spec?.objectPro?.animation?.fps ?? 8;
|
|
3367
|
+
const review = (frames, animationId, groupId, billed) => ({
|
|
3368
|
+
status: "review-set",
|
|
3369
|
+
objectId: `${job.objectId}#${groupId ?? animationId ?? job.name}`,
|
|
3370
|
+
frameUrls: frames,
|
|
3371
|
+
sources: frames.map((url, index) => ({ url, role: `frame-${String(index).padStart(2, "0")}` })),
|
|
3372
|
+
fps,
|
|
3373
|
+
metadata: {
|
|
3374
|
+
frameSet: { fps, count: frames.length },
|
|
3375
|
+
objectPro: {
|
|
3376
|
+
kind: "animation",
|
|
3377
|
+
objectId: job.objectId,
|
|
3378
|
+
animationId,
|
|
3379
|
+
animationGroupId: groupId,
|
|
3380
|
+
animationName: job.name,
|
|
3381
|
+
direction: job.direction
|
|
3382
|
+
}
|
|
3383
|
+
},
|
|
3384
|
+
billed
|
|
3385
|
+
});
|
|
3386
|
+
if (job.jobId) {
|
|
3387
|
+
const status = await this.client.getBackgroundJob(job.jobId).catch((err) => {
|
|
3388
|
+
if (err instanceof PixelLabError && err.status === 404) return null;
|
|
3389
|
+
throw err;
|
|
3390
|
+
});
|
|
3391
|
+
if (status) {
|
|
3392
|
+
if (status.status === "failed") return { status: "failed", error: "animation generation failed upstream" };
|
|
3393
|
+
if (status.status !== "completed") return { status: "processing" };
|
|
3394
|
+
const done = status.last_response ?? {};
|
|
3395
|
+
const frames = done.storage_urls?.frames;
|
|
3396
|
+
const animationId = typeof done.animation_id === "string" ? done.animation_id : null;
|
|
3397
|
+
if (Array.isArray(frames) && frames.length && frames.every((f) => typeof f === "string")) {
|
|
3398
|
+
const object2 = await this.client.getObject(job.objectId);
|
|
3399
|
+
const group = findObjectAnimation(object2, job.name, job.direction, animationId);
|
|
3400
|
+
return review(frames, animationId, group?.groupId ?? null, billedFromUsage(status.usage));
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
const recorded = context?.metadata?.objectPro?.animationId ?? null;
|
|
3405
|
+
const object = await this.client.getObject(job.objectId);
|
|
3406
|
+
const found = findObjectAnimation(object, job.name, job.direction, recorded);
|
|
3407
|
+
if (found) return review(found.frames, found.animationId, found.groupId, null);
|
|
3408
|
+
return { status: "processing" };
|
|
3409
|
+
}
|
|
2920
3410
|
async submit(spec, styleImages, context) {
|
|
2921
3411
|
this.validate(spec, styleImages);
|
|
2922
3412
|
if (spec.revision) return this.submitRevision(spec);
|
|
2923
3413
|
if (spec.generator === "character") return this.submitCharacter(spec, styleImages, context);
|
|
3414
|
+
if (spec.generator === "objectPro") return this.submitObjectPro(spec, styleImages, context);
|
|
2924
3415
|
if (spec.generator === "pixflux") {
|
|
2925
3416
|
const swatch = spec.palette.length ? paletteSwatch(spec.palette).toString("base64") : void 0;
|
|
2926
3417
|
const { png } = await this.client.createImagePixflux({
|
|
@@ -2981,6 +3472,20 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
2981
3472
|
});
|
|
2982
3473
|
return { jobId: res2.tileset_id, metadata: { backgroundJobId: res2.background_job_id } };
|
|
2983
3474
|
}
|
|
3475
|
+
if (spec.generator === "isometricTile") {
|
|
3476
|
+
const res2 = await this.client.createIsometricTile({
|
|
3477
|
+
description: spec.prompt,
|
|
3478
|
+
imageWidth: spec.width,
|
|
3479
|
+
imageHeight: spec.height,
|
|
3480
|
+
tileSize: spec.isometricTileSize,
|
|
3481
|
+
tileShape: spec.isometricTileShape,
|
|
3482
|
+
outline: spec.outline,
|
|
3483
|
+
shading: spec.shading,
|
|
3484
|
+
detail: spec.detail,
|
|
3485
|
+
seed: spec.seed
|
|
3486
|
+
});
|
|
3487
|
+
return { jobId: res2.tile_id, metadata: { backgroundJobId: res2.background_job_id } };
|
|
3488
|
+
}
|
|
2984
3489
|
if (spec.generator === "imagePro") {
|
|
2985
3490
|
const res2 = await this.client.createImagePro({
|
|
2986
3491
|
description: spec.prompt,
|
|
@@ -3097,8 +3602,10 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3097
3602
|
if (generator === "map") return this.pollMap(jobId, context);
|
|
3098
3603
|
if (generator === "tiles") return this.pollTiles(jobId, Boolean(context?.tileFeature), context);
|
|
3099
3604
|
if (generator === "terrain") return this.pollTerrain(jobId, context);
|
|
3605
|
+
if (generator === "isometricTile") return this.pollIsometricTile(jobId, context);
|
|
3100
3606
|
if (generator === "imagePro") return this.pollImagePro(jobId);
|
|
3101
3607
|
if (generator === "character") return this.pollCharacter(jobId, context);
|
|
3608
|
+
if (generator === "objectPro") return this.pollObjectPro(jobId, context);
|
|
3102
3609
|
const backgroundJobId = context?.metadata?.backgroundJobId;
|
|
3103
3610
|
const obj = await this.client.getObject(jobId);
|
|
3104
3611
|
if (obj.status === "review") {
|
|
@@ -3307,6 +3814,31 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
3307
3814
|
throw err;
|
|
3308
3815
|
}
|
|
3309
3816
|
}
|
|
3817
|
+
/**
|
|
3818
|
+
* `/create-isometric-tile` reports progress the same way `/create-tileset`
|
|
3819
|
+
* does (423 while drawing, 200 once finished, no `status` field to poll),
|
|
3820
|
+
* and its image also comes back embedded as base64 rather than a
|
|
3821
|
+
* `storage_urls` link. One call is one tile with nothing to review, so
|
|
3822
|
+
* this goes straight to "ready" like a connectable `tiles`/`terrain` set.
|
|
3823
|
+
*/
|
|
3824
|
+
async pollIsometricTile(tileId, context) {
|
|
3825
|
+
try {
|
|
3826
|
+
const { image, usage } = await this.client.getIsometricTile(tileId);
|
|
3827
|
+
const backgroundJobId = context?.metadata?.backgroundJobId;
|
|
3828
|
+
const billed = billedFromUsage(usage) ?? await this.billedForJob(backgroundJobId);
|
|
3829
|
+
const file = path.join(_PixelLabProvider.cacheDir(), `${tileId}.png`);
|
|
3830
|
+
writeFileSync(file, Buffer.from(image.base64, "base64"));
|
|
3831
|
+
return {
|
|
3832
|
+
status: "ready",
|
|
3833
|
+
objectId: tileId,
|
|
3834
|
+
sourceUrl: `file://${file}`,
|
|
3835
|
+
billed
|
|
3836
|
+
};
|
|
3837
|
+
} catch (err) {
|
|
3838
|
+
if (err instanceof PixelLabError && err.status === 423) return { status: "processing" };
|
|
3839
|
+
throw err;
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3310
3842
|
async selectCandidate(jobId, index, commonTag, generator) {
|
|
3311
3843
|
if (generator === "tiles") {
|
|
3312
3844
|
const set = await this.client.getTilesPro(jobId);
|
|
@@ -3423,9 +3955,9 @@ function remoteCharacter(character) {
|
|
|
3423
3955
|
animationCount: character.animation_count
|
|
3424
3956
|
};
|
|
3425
3957
|
}
|
|
3426
|
-
function requirePixelLabOption(name, value, allowed) {
|
|
3958
|
+
function requirePixelLabOption(generator, name, value, allowed) {
|
|
3427
3959
|
if (value != null && !allowed.includes(value)) {
|
|
3428
|
-
throw new Error(`PixelLab
|
|
3960
|
+
throw new Error(`PixelLab ${generator} ${name} must be one of: ${allowed.join(", ")}`);
|
|
3429
3961
|
}
|
|
3430
3962
|
}
|
|
3431
3963
|
function tilesInIndexOrder(urls) {
|
|
@@ -3491,6 +4023,28 @@ function findAnimation(character, name, direction, animationId) {
|
|
|
3491
4023
|
}
|
|
3492
4024
|
return null;
|
|
3493
4025
|
}
|
|
4026
|
+
function objectRotationSources(object) {
|
|
4027
|
+
const order = object.directions === 1 ? ["south"] : CHARACTER_DIRECTIONS_8;
|
|
4028
|
+
const sources = [];
|
|
4029
|
+
for (const direction of order) {
|
|
4030
|
+
const url = object.rotation_urls?.[direction];
|
|
4031
|
+
if (url) sources.push({ url, role: direction });
|
|
4032
|
+
}
|
|
4033
|
+
return sources;
|
|
4034
|
+
}
|
|
4035
|
+
function findObjectAnimation(object, name, direction, animationId) {
|
|
4036
|
+
for (const group of object.animations) {
|
|
4037
|
+
const match = group.directions.find((d) => d.direction === direction);
|
|
4038
|
+
const frames = match ? match.storage_urls.frames : void 0;
|
|
4039
|
+
if (!match || !Array.isArray(frames) || !frames.length || !frames.every((f) => typeof f === "string")) continue;
|
|
4040
|
+
const byName = group.display_name === name;
|
|
4041
|
+
const byId = animationId !== null && frames.some((url) => url.includes(`/animations/${animationId}/`));
|
|
4042
|
+
if (!byName && !byId) continue;
|
|
4043
|
+
const fromUrl = /\/animations\/([^/]+)\//.exec(frames[0])?.[1] ?? null;
|
|
4044
|
+
return { frames, groupId: group.animation_group_id, animationId: animationId ?? fromUrl };
|
|
4045
|
+
}
|
|
4046
|
+
return null;
|
|
4047
|
+
}
|
|
3494
4048
|
|
|
3495
4049
|
// src/providers/retrodiffusion.ts
|
|
3496
4050
|
var DEFAULT_BASE_URL = "https://api.retrodiffusion.ai/v1";
|
|
@@ -6044,14 +6598,22 @@ async function resolveSpecs(loaded, filter) {
|
|
|
6044
6598
|
size = style.terrainTileSize ?? 16;
|
|
6045
6599
|
width = size;
|
|
6046
6600
|
height = size;
|
|
6601
|
+
} else if (generator === "isometricTile") {
|
|
6602
|
+
size = asset.size ?? style.size ?? 32;
|
|
6603
|
+
width = size;
|
|
6604
|
+
height = size;
|
|
6605
|
+
} else if (generator === "objectPro") {
|
|
6606
|
+
size = asset.size ?? style.size ?? 64;
|
|
6607
|
+
width = size;
|
|
6608
|
+
height = size;
|
|
6047
6609
|
} else {
|
|
6048
6610
|
width = asset.width ?? style.size ?? 64;
|
|
6049
6611
|
height = asset.height ?? style.size ?? 64;
|
|
6050
6612
|
size = Math.max(width, height);
|
|
6051
6613
|
}
|
|
6052
|
-
if ((asset.state || asset.animation) && generator !== "character") {
|
|
6614
|
+
if ((asset.state || asset.animation) && generator !== "character" && generator !== "objectPro") {
|
|
6053
6615
|
throw new Error(
|
|
6054
|
-
`assets.${assetId}: ${asset.state ? "state" : "animation"} needs a character style; "${styleId}" generates ${generator}`
|
|
6616
|
+
`assets.${assetId}: ${asset.state ? "state" : "animation"} needs a character or objectPro style; "${styleId}" generates ${generator}`
|
|
6055
6617
|
);
|
|
6056
6618
|
}
|
|
6057
6619
|
const characterKind = asset.animation ? "animation" : asset.state ? "state" : "base";
|
|
@@ -6072,7 +6634,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
6072
6634
|
terrainUpperDescription = wrap(parsed.upper);
|
|
6073
6635
|
terrainTransitionDescription = parsed.transition ? wrap(parsed.transition) : void 0;
|
|
6074
6636
|
prompt = subject.trim();
|
|
6075
|
-
} else if (generator === "character" && characterKind !== "base") {
|
|
6637
|
+
} else if ((generator === "character" || generator === "objectPro") && characterKind !== "base") {
|
|
6076
6638
|
prompt = subject.trim();
|
|
6077
6639
|
} else {
|
|
6078
6640
|
prompt = [style.promptPrefix, subject, style.promptSuffix].map((p) => p.trim()).filter(Boolean).join(", ");
|
|
@@ -6137,8 +6699,11 @@ async function resolveSpecs(loaded, filter) {
|
|
|
6137
6699
|
terrainRaggedness: generator === "terrain" ? style.terrainRaggedness : void 0,
|
|
6138
6700
|
terrainTransitionSize: generator === "terrain" ? style.terrainTransitionSize : void 0,
|
|
6139
6701
|
terrainView: generator === "terrain" ? style.terrainView : void 0,
|
|
6702
|
+
isometricTileSize: generator === "isometricTile" ? style.isometricTileSize : void 0,
|
|
6703
|
+
isometricTileShape: generator === "isometricTile" ? style.isometricTileShape : void 0,
|
|
6140
6704
|
...generator === "character" ? { character: await resolveCharacterShape(asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
6141
|
-
|
|
6705
|
+
...generator === "objectPro" ? { objectPro: await resolveObjectProShape(assetId, asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
6706
|
+
cost: generator === "tiles" ? tilesCost(tileSize, tileVariations) : generator === "terrain" ? tilesCost(size, terrainTiles) : generator === "isometricTile" ? isometricTileCost() : generationCost(width, height, generator),
|
|
6142
6707
|
costUnit: "generations",
|
|
6143
6708
|
candidates: generator === "tiles" ? tileVariations : generator === "terrain" ? terrainTiles : generator === "1dir" || generator === "imagePro" ? candidateCount(size) : 1
|
|
6144
6709
|
};
|
|
@@ -6203,6 +6768,18 @@ async function resolveSpecs(loaded, filter) {
|
|
|
6203
6768
|
} else if (sourceSpec.character) {
|
|
6204
6769
|
resolved.character = { ...sourceSpec.character };
|
|
6205
6770
|
}
|
|
6771
|
+
if (sourceSpec.objectPro?.kind === "animation") {
|
|
6772
|
+
const facing = sourceSpec.objectPro.animation.direction;
|
|
6773
|
+
const mirrored = MIRRORED_DIRECTION[facing];
|
|
6774
|
+
if (mirrored === facing) {
|
|
6775
|
+
throw new Error(
|
|
6776
|
+
`assets.${assetId}: mirroring ${asset.mirror} gives another ${facing}-facing loop; mirror a loop facing east, west, or a diagonal`
|
|
6777
|
+
);
|
|
6778
|
+
}
|
|
6779
|
+
resolved.objectPro = { ...sourceSpec.objectPro, animation: { ...sourceSpec.objectPro.animation, direction: mirrored } };
|
|
6780
|
+
} else if (sourceSpec.objectPro) {
|
|
6781
|
+
resolved.objectPro = { ...sourceSpec.objectPro };
|
|
6782
|
+
}
|
|
6206
6783
|
resolved.mirror = { sourceAssetId: asset.mirror, sourceSpec };
|
|
6207
6784
|
resolved.generator = sourceSpec.generator;
|
|
6208
6785
|
resolved.width = sourceSpec.width;
|
|
@@ -6244,6 +6821,23 @@ async function resolveSpecs(loaded, filter) {
|
|
|
6244
6821
|
directions: resolved.character.kind === "base" ? resolved.character.directions : parentSpec.character?.directions ?? resolved.character.directions
|
|
6245
6822
|
};
|
|
6246
6823
|
}
|
|
6824
|
+
if (resolved.objectPro && characterParent) {
|
|
6825
|
+
const parentSpec = await finalize(characterParent);
|
|
6826
|
+
if (!parentSpec.objectPro) {
|
|
6827
|
+
throw new Error(`assets.${assetId}: ${characterParent} is not an objectPro asset`);
|
|
6828
|
+
}
|
|
6829
|
+
const parentDirections = parentSpec.objectPro.directions;
|
|
6830
|
+
const parentFile = memberPath(parentSpec.outFile, "south", 0, parentDirections, MediaType.PNG);
|
|
6831
|
+
resolved.objectPro = {
|
|
6832
|
+
...resolved.objectPro,
|
|
6833
|
+
parentAssetId: characterParent,
|
|
6834
|
+
parentSpec,
|
|
6835
|
+
parentFile,
|
|
6836
|
+
parentSha256: existsSync4(parentFile) ? await sha256File(parentFile) : null,
|
|
6837
|
+
// A state and its animations keep the base's own rotation count.
|
|
6838
|
+
directions: resolved.objectPro.kind === "base" ? resolved.objectPro.directions : parentSpec.objectPro.directions
|
|
6839
|
+
};
|
|
6840
|
+
}
|
|
6247
6841
|
if (asset.revision) {
|
|
6248
6842
|
if (!activeProvider.supportsRevision?.(asset.revision.mode)) {
|
|
6249
6843
|
throw new Error(
|
|
@@ -6429,6 +7023,60 @@ async function resolveCharacterShape(asset, style, kind, files) {
|
|
|
6429
7023
|
}
|
|
6430
7024
|
return shape;
|
|
6431
7025
|
}
|
|
7026
|
+
async function resolveObjectProShape(assetId, asset, style, kind, files) {
|
|
7027
|
+
const directions = style.objectDirections ?? 8;
|
|
7028
|
+
const shape = {
|
|
7029
|
+
kind,
|
|
7030
|
+
directions,
|
|
7031
|
+
...style.styleTraits ? { styleTraits: style.styleTraits } : {}
|
|
7032
|
+
};
|
|
7033
|
+
if (asset.reference) {
|
|
7034
|
+
const south = typeof asset.reference === "string" ? asset.reference : asset.reference.south;
|
|
7035
|
+
if (south) {
|
|
7036
|
+
const image = await files.load(south, "Reference sprite");
|
|
7037
|
+
shape.reference = { path: path5.resolve(files.root, south), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
7038
|
+
}
|
|
7039
|
+
}
|
|
7040
|
+
if (asset.state) {
|
|
7041
|
+
shape.state = {
|
|
7042
|
+
paletteFromReference: asset.state.paletteFromReference,
|
|
7043
|
+
...asset.state.canvas ? { canvas: asset.state.canvas } : {}
|
|
7044
|
+
};
|
|
7045
|
+
}
|
|
7046
|
+
if (asset.animation) {
|
|
7047
|
+
for (const [field, value] of Object.entries({
|
|
7048
|
+
template: asset.animation.template,
|
|
7049
|
+
subject: asset.animation.subject,
|
|
7050
|
+
outline: asset.animation.outline,
|
|
7051
|
+
shading: asset.animation.shading,
|
|
7052
|
+
detail: asset.animation.detail
|
|
7053
|
+
})) {
|
|
7054
|
+
if (value !== void 0) {
|
|
7055
|
+
throw new Error(
|
|
7056
|
+
`assets.${assetId}: objectPro has no skeleton/template concept, so animation.${field} does not apply; describe the motion in the asset's own prompt instead`
|
|
7057
|
+
);
|
|
7058
|
+
}
|
|
7059
|
+
}
|
|
7060
|
+
if (asset.animation.mode === "template") {
|
|
7061
|
+
throw new Error(`assets.${assetId}: objectPro animations have no "template" mode; use "v3" or "pro"`);
|
|
7062
|
+
}
|
|
7063
|
+
const pose = async (rel, what) => {
|
|
7064
|
+
const image = await files.load(rel, what);
|
|
7065
|
+
return { path: path5.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
|
|
7066
|
+
};
|
|
7067
|
+
shape.animation = {
|
|
7068
|
+
mode: asset.animation.mode ?? "v3",
|
|
7069
|
+
direction: asset.animation.direction,
|
|
7070
|
+
frames: asset.animation.frames ?? 8,
|
|
7071
|
+
fps: asset.animation.fps,
|
|
7072
|
+
keepFirstFrame: asset.animation.keepFirstFrame,
|
|
7073
|
+
...asset.animation.startFrame ? { startFrame: await pose(asset.animation.startFrame, "Animation start frame") } : {},
|
|
7074
|
+
...asset.animation.endFrame ? { endFrame: await pose(asset.animation.endFrame, "Animation end frame") } : {},
|
|
7075
|
+
...asset.animation.enhancePrompt !== void 0 ? { enhancePrompt: asset.animation.enhancePrompt } : {}
|
|
7076
|
+
};
|
|
7077
|
+
}
|
|
7078
|
+
return shape;
|
|
7079
|
+
}
|
|
6432
7080
|
function characterDirections(directions) {
|
|
6433
7081
|
return directions === 4 ? CHARACTER_DIRECTIONS_4 : CHARACTER_DIRECTIONS_8;
|
|
6434
7082
|
}
|
|
@@ -7905,6 +8553,7 @@ async function inspectRevisionReadiness(spec, lock) {
|
|
|
7905
8553
|
if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
|
|
7906
8554
|
if (spec.character?.parentSpec) return inspectCharacterParent(spec, lock, /* @__PURE__ */ new Set());
|
|
7907
8555
|
if (spec.character?.styleAnchor) return inspectStyleAnchor(spec, lock, /* @__PURE__ */ new Set());
|
|
8556
|
+
if (spec.objectPro?.parentSpec) return inspectObjectProParent(spec, lock, /* @__PURE__ */ new Set());
|
|
7908
8557
|
if (!spec.revision) return null;
|
|
7909
8558
|
return inspectRevision(spec, lock, /* @__PURE__ */ new Set());
|
|
7910
8559
|
}
|
|
@@ -7946,6 +8595,24 @@ async function inspectCharacterParent(spec, lock, seen) {
|
|
|
7946
8595
|
}
|
|
7947
8596
|
return { ready: true, reason: `${label} is current` };
|
|
7948
8597
|
}
|
|
8598
|
+
async function inspectObjectProParent(spec, lock, seen) {
|
|
8599
|
+
const object = spec.objectPro;
|
|
8600
|
+
const parentSpec = object.parentSpec;
|
|
8601
|
+
const label = `${object.kind} parent ${spec.styleId}/${object.parentAssetId}`;
|
|
8602
|
+
const dependency = await inspectParent(parentSpec, lock, seen);
|
|
8603
|
+
if (!dependency.ready) return { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
8604
|
+
if (!object.parentSha256 || !object.parentFile || !existsSync7(object.parentFile)) {
|
|
8605
|
+
return { ready: false, reason: `${label} has no generated south-facing file yet` };
|
|
8606
|
+
}
|
|
8607
|
+
if (await sha256File(object.parentFile) !== object.parentSha256) {
|
|
8608
|
+
return { ready: false, reason: `${label} changed after the manifest was resolved` };
|
|
8609
|
+
}
|
|
8610
|
+
const parentEntry = lock.entries[lockKey(parentSpec.styleId, parentSpec.assetId)];
|
|
8611
|
+
if (!parentEntry?.objectId) {
|
|
8612
|
+
return { ready: false, reason: `${label} has no PixelLab object id recorded` };
|
|
8613
|
+
}
|
|
8614
|
+
return { ready: true, reason: `${label} is current` };
|
|
8615
|
+
}
|
|
7949
8616
|
async function inspectRevision(spec, lock, seen) {
|
|
7950
8617
|
const revision = spec.revision;
|
|
7951
8618
|
if (!revision) return { ready: true, reason: "asset has no revision inputs" };
|
|
@@ -7997,6 +8664,10 @@ async function inspectParent(spec, lock, seen) {
|
|
|
7997
8664
|
const inputs = await inspectStyleAnchor(spec, lock, nextSeen);
|
|
7998
8665
|
if (!inputs.ready) return inputs;
|
|
7999
8666
|
}
|
|
8667
|
+
if (spec.objectPro?.parentSpec) {
|
|
8668
|
+
const inputs = await inspectObjectProParent(spec, lock, nextSeen);
|
|
8669
|
+
if (!inputs.ready) return inputs;
|
|
8670
|
+
}
|
|
8000
8671
|
if (spec.quality) {
|
|
8001
8672
|
const quality = await inspectQualityProfile(spec, lock);
|
|
8002
8673
|
return quality?.state === "approved" ? { ready: true, reason: "approved quality output is current" } : {
|
|
@@ -8041,7 +8712,8 @@ function dependencies(spec) {
|
|
|
8041
8712
|
spec.mirror?.sourceSpec,
|
|
8042
8713
|
spec.revision?.sourceSpec,
|
|
8043
8714
|
spec.character?.parentSpec,
|
|
8044
|
-
spec.character?.styleAnchor?.spec
|
|
8715
|
+
spec.character?.styleAnchor?.spec,
|
|
8716
|
+
spec.objectPro?.parentSpec
|
|
8045
8717
|
].filter((parent) => Boolean(parent));
|
|
8046
8718
|
}
|
|
8047
8719
|
function resumeCommandForStatus(status) {
|
|
@@ -10702,7 +11374,8 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
10702
11374
|
lastSubmitAt = Date.now();
|
|
10703
11375
|
try {
|
|
10704
11376
|
const refs = styleImages.get(spec.styleId) ?? [];
|
|
10705
|
-
const
|
|
11377
|
+
const parentSpec = spec.character?.parentSpec ?? spec.objectPro?.parentSpec;
|
|
11378
|
+
const parentEntry = parentSpec ? lock.entries[lockKey(parentSpec.styleId, parentSpec.assetId)] : void 0;
|
|
10706
11379
|
const anchor = spec.character?.styleAnchor;
|
|
10707
11380
|
const anchorEntry = anchor ? lock.entries[lockKey(anchor.spec.styleId, anchor.spec.assetId)] : void 0;
|
|
10708
11381
|
const replacedMetadata = previousEntry?.providerMetadata?.[provider.id];
|
|
@@ -14936,6 +15609,7 @@ export {
|
|
|
14936
15609
|
isFrameSetEntry,
|
|
14937
15610
|
isMemberSetEntry,
|
|
14938
15611
|
isSensitiveSourceUrl,
|
|
15612
|
+
isometricTileCost,
|
|
14939
15613
|
listBundledRecipes,
|
|
14940
15614
|
loadClaims,
|
|
14941
15615
|
loadLock,
|