pixelkiln 0.90.0 → 0.91.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 +142 -14
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +144 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +247 -2
- package/dist/index.d.ts +247 -2
- package/dist/index.js +143 -14
- package/dist/index.js.map +1 -1
- package/docs/CHARACTERS.md +38 -8
- package/docs/ENDPOINTS.md +10 -3
- package/docs/MANIFEST.md +1 -1
- package/docs/PIXELLAB.md +46 -0
- package/package.json +1 -1
- package/schema/manifest.schema.json +4 -0
- package/skills/pixelkiln/references/pixellab-roadmap.md +10 -13
- package/skills/pixelkiln/references/pixellab.md +5 -2
package/dist/index.js
CHANGED
|
@@ -310,6 +310,15 @@ var CharacterAnimationSchema = z.object({
|
|
|
310
310
|
* `src/loop-directions.ts`.
|
|
311
311
|
*/
|
|
312
312
|
directions: z.array(CharacterDirectionSchema).min(1).optional(),
|
|
313
|
+
/**
|
|
314
|
+
* A sentence loop (`mode: "skeleton-v3"`, no `template`) of another
|
|
315
|
+
* direction whose motion this one shares: the asset id of the loop that
|
|
316
|
+
* leads. This loop is then submitted after it, into its animation group,
|
|
317
|
+
* so PixelLab draws it from the same 3D motion. Written for you when
|
|
318
|
+
* `directions` expands (the first direction leads); set by hand only when
|
|
319
|
+
* declaring the loops explicitly.
|
|
320
|
+
*/
|
|
321
|
+
motionFrom: z.string().min(1).optional(),
|
|
313
322
|
/**
|
|
314
323
|
* Frames to generate without a template. A v3 loop takes an even 4 to 16;
|
|
315
324
|
* a sentence loop (`mode: "skeleton-v3"` with no `template`) takes 3 to 15.
|
|
@@ -397,6 +406,9 @@ var CharacterAnimationSchema = z.object({
|
|
|
397
406
|
if (!animation.template && animation.mode === "template") {
|
|
398
407
|
context.addIssue({ code: z.ZodIssueCode.custom, message: "template mode needs a template", path: ["template"] });
|
|
399
408
|
}
|
|
409
|
+
if (animation.motionFrom !== void 0 && !sentence) {
|
|
410
|
+
context.addIssue({ code: z.ZodIssueCode.custom, message: "motionFrom applies to a skeleton-v3 loop from a sentence (no template)", path: ["motionFrom"] });
|
|
411
|
+
}
|
|
400
412
|
if (sentence && animation.subject !== void 0) {
|
|
401
413
|
context.addIssue({ code: z.ZodIssueCode.custom, message: "subject does not apply to a skeleton-v3 loop from a sentence; describe the motion in the prompt", path: ["subject"] });
|
|
402
414
|
}
|
|
@@ -1329,6 +1341,7 @@ var LockTerrainConnectSchema = z.record(
|
|
|
1329
1341
|
TerrainSideSchema,
|
|
1330
1342
|
z.object({ sourceAssetId: z.string().min(1), sourceObjectId: z.string().min(1) }).strict()
|
|
1331
1343
|
);
|
|
1344
|
+
var LockMotionShareSchema = z.object({ sourceAssetId: z.string().min(1), animationGroupId: z.string().min(1).nullable() }).strict();
|
|
1332
1345
|
var LockTerrainSettingsSchema = z.object({
|
|
1333
1346
|
terrainMode: z.string().optional(),
|
|
1334
1347
|
terrainShapeStyle: z.string().optional(),
|
|
@@ -1365,6 +1378,8 @@ var LockHistoryEntrySchema = z.object({
|
|
|
1365
1378
|
postprocess: PostprocessSchema.optional(),
|
|
1366
1379
|
/** A connected terrain set's other sets; see LockEntrySchema.terrainConnect. */
|
|
1367
1380
|
terrainConnect: LockTerrainConnectSchema.optional(),
|
|
1381
|
+
/** A sentence loop that shares another direction's motion; see LockEntrySchema.motionShare. */
|
|
1382
|
+
motionShare: LockMotionShareSchema.optional(),
|
|
1368
1383
|
/** A terrain set's settings outside specHash; see LockEntrySchema.terrainSettings. */
|
|
1369
1384
|
terrainSettings: LockTerrainSettingsSchema.optional(),
|
|
1370
1385
|
/** When the replacement was submitted. */
|
|
@@ -1441,6 +1456,16 @@ var LockEntrySchema = z.object({
|
|
|
1441
1456
|
* Absent (not null) otherwise, so other entries do not grow the key.
|
|
1442
1457
|
*/
|
|
1443
1458
|
terrainConnect: LockTerrainConnectSchema.optional(),
|
|
1459
|
+
/**
|
|
1460
|
+
* For a sentence loop of a later direction: the loop whose motion it
|
|
1461
|
+
* shares and the animation group of that loop's generation this was
|
|
1462
|
+
* drawn into when submitted. Tracked the way `terrainConnect` is, not
|
|
1463
|
+
* through `specHash`: the group lives in the lockfile. A regenerated leader
|
|
1464
|
+
* has a new group, so the plan marks this stale. `animationGroupId` is
|
|
1465
|
+
* null when the leader had no group recorded and this was drawn on its own.
|
|
1466
|
+
* Absent otherwise.
|
|
1467
|
+
*/
|
|
1468
|
+
motionShare: LockMotionShareSchema.optional(),
|
|
1444
1469
|
/**
|
|
1445
1470
|
* For a terrain set: the settings it was submitted with that `specHash`
|
|
1446
1471
|
* leaves out (see LockTerrainSettingsSchema). The plan marks the entry
|
|
@@ -1906,6 +1931,8 @@ var CharacterSubmitSchema = z3.object({
|
|
|
1906
1931
|
var AnimateSubmitSchema = z3.object({
|
|
1907
1932
|
background_job_ids: z3.array(z3.string()),
|
|
1908
1933
|
directions: z3.array(z3.string()),
|
|
1934
|
+
/** skeleton-v3 only: the group the directions were drawn into. */
|
|
1935
|
+
animation_group_id: z3.string().nullable().optional(),
|
|
1909
1936
|
status: z3.string().default("processing"),
|
|
1910
1937
|
usage: UsageSchema
|
|
1911
1938
|
}).passthrough();
|
|
@@ -2629,7 +2656,10 @@ var PixelLabClient = class {
|
|
|
2629
2656
|
* the same job list (plus an `animation_group_id`). With no `template` it
|
|
2630
2657
|
* animates `actionDescription` as a sentence, 3 to 15 `frameCount` frames
|
|
2631
2658
|
* written into one 3D motion; measured on a v3 character (8 frames, 64px:
|
|
2632
|
-
* 3 generations, frames back at 96x96).
|
|
2659
|
+
* 3 generations, frames back at 96x96). `animationGroupId` (skeleton-v3
|
|
2660
|
+
* only) adds the directions to an existing animation of the character,
|
|
2661
|
+
* drawn from its motion; the group's own name must be sent again as
|
|
2662
|
+
* `animationName`, or the group appears renamed.
|
|
2633
2663
|
*/
|
|
2634
2664
|
async animateCharacter(args) {
|
|
2635
2665
|
const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
|
|
@@ -2641,6 +2671,7 @@ var PixelLabClient = class {
|
|
|
2641
2671
|
animation_name: args.animationName,
|
|
2642
2672
|
mode: args.mode,
|
|
2643
2673
|
directions: args.directions,
|
|
2674
|
+
...args.mode === "skeleton-v3" && args.animationGroupId ? { animation_group_id: args.animationGroupId } : {},
|
|
2644
2675
|
...args.template ? { template_animation_id: args.template } : {},
|
|
2645
2676
|
...args.actionDescription ? { action_description: args.actionDescription } : {},
|
|
2646
2677
|
...args.description ? { description: args.description } : {},
|
|
@@ -4013,6 +4044,10 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
4013
4044
|
template: spec.character.template,
|
|
4014
4045
|
parent: spec.character.parentAssetId,
|
|
4015
4046
|
parentSha256: spec.character.parentSha256 ?? null,
|
|
4047
|
+
// Which loop leads, not its group: the group is in the lockfile,
|
|
4048
|
+
// and the plan compares it there. Absent for every loop that
|
|
4049
|
+
// shares nothing, so no existing hash moves.
|
|
4050
|
+
motionFrom: spec.character.motionLeader?.assetId,
|
|
4016
4051
|
state: spec.character.state,
|
|
4017
4052
|
// Pose images enter as their hashes, never their paths.
|
|
4018
4053
|
animation: spec.character.animation ? {
|
|
@@ -5057,7 +5092,13 @@ function decodeAnimationJob(jobId) {
|
|
|
5057
5092
|
try {
|
|
5058
5093
|
const value = JSON.parse(Buffer.from(jobId.slice("character-animation:".length), "base64url").toString("utf8"));
|
|
5059
5094
|
if (typeof value?.characterId === "string" && typeof value.name === "string" && typeof value.direction === "string") {
|
|
5060
|
-
return {
|
|
5095
|
+
return {
|
|
5096
|
+
characterId: value.characterId,
|
|
5097
|
+
name: value.name,
|
|
5098
|
+
direction: value.direction,
|
|
5099
|
+
jobIds: Array.isArray(value.jobIds) ? value.jobIds : [],
|
|
5100
|
+
...typeof value.groupId === "string" && value.groupId ? { groupId: value.groupId } : {}
|
|
5101
|
+
};
|
|
5061
5102
|
}
|
|
5062
5103
|
} catch {
|
|
5063
5104
|
}
|
|
@@ -5895,11 +5936,18 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5895
5936
|
};
|
|
5896
5937
|
}
|
|
5897
5938
|
const animation = character.animation;
|
|
5898
|
-
const
|
|
5939
|
+
const ownName = characterAnimationName(spec);
|
|
5940
|
+
const shared = context?.motionGroup;
|
|
5941
|
+
const name = shared?.animationName ?? ownName;
|
|
5899
5942
|
const replaced = context?.replacedMetadata?.character ?? {};
|
|
5900
5943
|
const existing = await this.client.getCharacter(parentId);
|
|
5944
|
+
if (shared && !existing.animations.some((group) => group.animation_group_id === shared.animationGroupId && group.directions.some((d) => d.direction !== animation.direction))) {
|
|
5945
|
+
throw new Error(
|
|
5946
|
+
`${spec.styleId}/${spec.assetId} shares the motion of an animation group (${shared.animationGroupId}) that is no longer on the character; regenerate the leading direction first`
|
|
5947
|
+
);
|
|
5948
|
+
}
|
|
5901
5949
|
for (const group of existing.animations) {
|
|
5902
|
-
const ours = group.animation_group_id && (group.animation_group_id === replaced.animationGroupId || group.display_name === name || replaced.animationId && group.directions.some((d) => d.frames.some((url) => url.includes(`/animations/${replaced.animationId}/`))));
|
|
5950
|
+
const ours = group.animation_group_id && (group.animation_group_id === replaced.animationGroupId || group.display_name === name || group.display_name === ownName || replaced.animationId && group.directions.some((d) => d.frames.some((url) => url.includes(`/animations/${replaced.animationId}/`))));
|
|
5903
5951
|
if (!ours || !group.animation_group_id) continue;
|
|
5904
5952
|
if (group.directions.some((d) => d.direction === animation.direction)) {
|
|
5905
5953
|
await this.client.deleteCharacterAnimations(parentId, { animationGroupId: group.animation_group_id }, animation.direction);
|
|
@@ -5914,6 +5962,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5914
5962
|
frameCount: animation.frames,
|
|
5915
5963
|
keepFirstFrame: animation.keepFirstFrame,
|
|
5916
5964
|
directions: [animation.direction],
|
|
5965
|
+
...shared ? { animationGroupId: shared.animationGroupId } : {},
|
|
5917
5966
|
seed: spec.seed,
|
|
5918
5967
|
textGuidanceScale: character.textGuidanceScale,
|
|
5919
5968
|
isometric: character.isometric,
|
|
@@ -5926,10 +5975,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5926
5975
|
enhancePrompt: animation.enhancePrompt,
|
|
5927
5976
|
paletteSwatchBase64: spec.palette.length ? paletteSwatch(spec.palette).toString("base64") : void 0
|
|
5928
5977
|
});
|
|
5929
|
-
const
|
|
5978
|
+
const groupId = res.animation_group_id ?? shared?.animationGroupId;
|
|
5979
|
+
const job = { characterId: parentId, name, direction: animation.direction, jobIds: res.background_job_ids, ...groupId ? { groupId } : {} };
|
|
5930
5980
|
return {
|
|
5931
5981
|
jobId: encodeAnimationJob(job),
|
|
5932
|
-
metadata: { character: { kind: "animation", characterId: parentId, animationName: name, direction: animation.direction, mode: animation.mode, backgroundJobIds: res.background_job_ids } }
|
|
5982
|
+
metadata: { character: { kind: "animation", characterId: parentId, animationName: name, direction: animation.direction, mode: animation.mode, backgroundJobIds: res.background_job_ids, ...groupId ? { animationGroupId: groupId } : {} } }
|
|
5933
5983
|
};
|
|
5934
5984
|
}
|
|
5935
5985
|
/**
|
|
@@ -6144,13 +6194,13 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6144
6194
|
const animationId = typeof done.animation_id === "string" ? done.animation_id : null;
|
|
6145
6195
|
if (Array.isArray(frames) && frames.length && frames.every((f) => typeof f === "string")) {
|
|
6146
6196
|
const character2 = await this.client.getCharacter(job.characterId);
|
|
6147
|
-
const group = findAnimation(character2, job.name, job.direction, animationId);
|
|
6148
|
-
return review(frames, animationId, group?.groupId ?? null, billedFromUsage(status.usage));
|
|
6197
|
+
const group = findAnimation(character2, job.name, job.direction, animationId, job.groupId);
|
|
6198
|
+
return review(frames, animationId, group?.groupId ?? job.groupId ?? null, billedFromUsage(status.usage));
|
|
6149
6199
|
}
|
|
6150
6200
|
}
|
|
6151
6201
|
const recorded = context?.metadata?.character?.animationId ?? null;
|
|
6152
6202
|
const character = await this.client.getCharacter(job.characterId);
|
|
6153
|
-
const found = findAnimation(character, job.name, job.direction, recorded);
|
|
6203
|
+
const found = findAnimation(character, job.name, job.direction, recorded, job.groupId);
|
|
6154
6204
|
if (found) return review(found.frames, found.animationId, found.groupId, null);
|
|
6155
6205
|
return cleanedUp ? { status: "failed", error: `animation job is gone upstream and no "${job.name}" ${job.direction} animation exists on the character` } : { status: "processing" };
|
|
6156
6206
|
}
|
|
@@ -7584,10 +7634,11 @@ function rotationSources(character) {
|
|
|
7584
7634
|
}
|
|
7585
7635
|
return sources;
|
|
7586
7636
|
}
|
|
7587
|
-
function findAnimation(character, name, direction, animationId) {
|
|
7637
|
+
function findAnimation(character, name, direction, animationId, groupId) {
|
|
7588
7638
|
for (const group of character.animations) {
|
|
7589
7639
|
const match = group.directions.find((d) => d.direction === direction && d.frames.length > 0);
|
|
7590
7640
|
if (!match) continue;
|
|
7641
|
+
if (groupId && group.animation_group_id !== groupId) continue;
|
|
7591
7642
|
const byName = group.display_name === name || group.animation_type === name;
|
|
7592
7643
|
const byId = animationId !== null && match.frames.some((url) => url.includes(`/animations/${animationId}/`));
|
|
7593
7644
|
if (!byName && !byId) continue;
|
|
@@ -9821,8 +9872,16 @@ function expandLoopDirections(raw) {
|
|
|
9821
9872
|
continue;
|
|
9822
9873
|
}
|
|
9823
9874
|
for (const expandedId of ids) claimed.add(expandedId);
|
|
9824
|
-
|
|
9825
|
-
|
|
9875
|
+
const sentence = animation.mode === "skeleton-v3" && !animation.template;
|
|
9876
|
+
for (const [index, loop] of loops.entries()) {
|
|
9877
|
+
expanded[loop.id] = {
|
|
9878
|
+
...asset,
|
|
9879
|
+
animation: {
|
|
9880
|
+
...animation,
|
|
9881
|
+
direction: loop.direction,
|
|
9882
|
+
...sentence && index > 0 ? { motionFrom: loops[0].id } : {}
|
|
9883
|
+
}
|
|
9884
|
+
};
|
|
9826
9885
|
}
|
|
9827
9886
|
for (const mirror of mirrors) {
|
|
9828
9887
|
const inherited = {};
|
|
@@ -10757,7 +10816,8 @@ async function resolveSpecs(loaded, filter) {
|
|
|
10757
10816
|
`assets.${assetId}: mirroring ${asset.mirror} gives another ${facing}-facing loop; mirror a loop facing east, west, or a diagonal`
|
|
10758
10817
|
);
|
|
10759
10818
|
}
|
|
10760
|
-
|
|
10819
|
+
const { motionLeader: _motionLeader, ...flipped } = sourceSpec.character;
|
|
10820
|
+
resolved.character = { ...flipped, animation: { ...sourceSpec.character.animation, direction: mirrored } };
|
|
10761
10821
|
} else if (sourceSpec.character) {
|
|
10762
10822
|
resolved.character = { ...sourceSpec.character };
|
|
10763
10823
|
}
|
|
@@ -10814,6 +10874,26 @@ async function resolveSpecs(loaded, filter) {
|
|
|
10814
10874
|
directions: resolved.character.kind === "base" ? resolved.character.directions : parentSpec.character?.directions ?? resolved.character.directions
|
|
10815
10875
|
};
|
|
10816
10876
|
}
|
|
10877
|
+
if (resolved.character?.animation && asset.animation?.motionFrom) {
|
|
10878
|
+
const leaderId = asset.animation.motionFrom;
|
|
10879
|
+
const where = `assets.${assetId}.animation.motionFrom`;
|
|
10880
|
+
if (leaderId === assetId) throw new Error(`${where}: an asset cannot share its own motion`);
|
|
10881
|
+
const leader = await finalize(leaderId);
|
|
10882
|
+
const led = leader.character?.animation;
|
|
10883
|
+
if (leader.character?.kind !== "animation" || leader.mirror || !led || led.mode !== "skeleton-v3" || led.template) {
|
|
10884
|
+
throw new Error(`${where}: ${leaderId} is not a skeleton-v3 loop from a sentence`);
|
|
10885
|
+
}
|
|
10886
|
+
if (leader.character.parentAssetId !== resolved.character.parentAssetId) {
|
|
10887
|
+
throw new Error(`${where}: ${leaderId} animates ${leader.character.parentAssetId}, not ${resolved.character.parentAssetId}`);
|
|
10888
|
+
}
|
|
10889
|
+
if (leader.character.motionLeader) {
|
|
10890
|
+
throw new Error(`${where}: ${leaderId} itself shares ${leader.character.motionLeader.assetId}'s motion; point at the loop that leads`);
|
|
10891
|
+
}
|
|
10892
|
+
if (led.direction === resolved.character.animation.direction) {
|
|
10893
|
+
throw new Error(`${where}: ${leaderId} is drawn in the same direction; sharing a motion needs another direction`);
|
|
10894
|
+
}
|
|
10895
|
+
resolved.character = { ...resolved.character, motionLeader: { assetId: leaderId, spec: leader } };
|
|
10896
|
+
}
|
|
10817
10897
|
if (resolved.objectPro && characterParent) {
|
|
10818
10898
|
const parentSpec = await finalize(characterParent);
|
|
10819
10899
|
if (!parentSpec.objectPro) {
|
|
@@ -12808,7 +12888,10 @@ import path14 from "path";
|
|
|
12808
12888
|
async function inspectRevisionReadiness(spec, lock) {
|
|
12809
12889
|
if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
|
|
12810
12890
|
if (spec.character?.kind === "outfit") return inspectOutfit(spec, lock, /* @__PURE__ */ new Set());
|
|
12811
|
-
if (spec.character?.parentSpec)
|
|
12891
|
+
if (spec.character?.parentSpec) {
|
|
12892
|
+
const parent = await inspectCharacterParent(spec, lock, /* @__PURE__ */ new Set());
|
|
12893
|
+
return parent.ready && spec.character.motionLeader ? inspectMotionLeader(spec, lock, /* @__PURE__ */ new Set()) : parent;
|
|
12894
|
+
}
|
|
12812
12895
|
if (spec.character?.styleAnchor) return inspectStyleAnchor(spec, lock, /* @__PURE__ */ new Set());
|
|
12813
12896
|
if (spec.objectPro?.parentSpec) return inspectObjectProParent(spec, lock, /* @__PURE__ */ new Set());
|
|
12814
12897
|
if (spec.scene) return inspectScene(spec);
|
|
@@ -12843,6 +12926,17 @@ async function inspectTerrainConnect(spec, lock, seen) {
|
|
|
12843
12926
|
}
|
|
12844
12927
|
return { ready: true, reason: "connected sets are current" };
|
|
12845
12928
|
}
|
|
12929
|
+
function recordedAnimationGroup(entry) {
|
|
12930
|
+
const character = entry?.providerMetadata?.[entry.provider]?.character;
|
|
12931
|
+
const text = (value) => typeof value === "string" && value ? value : null;
|
|
12932
|
+
return { animationGroupId: text(character?.animationGroupId), animationName: text(character?.animationName) };
|
|
12933
|
+
}
|
|
12934
|
+
async function inspectMotionLeader(spec, lock, seen) {
|
|
12935
|
+
const leader = spec.character.motionLeader;
|
|
12936
|
+
const label = `motion leader ${spec.styleId}/${leader.assetId}`;
|
|
12937
|
+
const dependency = await inspectParent(leader.spec, lock, seen);
|
|
12938
|
+
return dependency.ready ? { ready: true, reason: `${label} is current` } : { ready: false, reason: `${label} is not ready: ${dependency.reason}` };
|
|
12939
|
+
}
|
|
12846
12940
|
async function inspectScene(spec) {
|
|
12847
12941
|
const scene = spec.scene;
|
|
12848
12942
|
const files = [{ label: "scene image", file: scene.file, sha256: scene.sha256 }];
|
|
@@ -13001,6 +13095,10 @@ async function inspectParent(spec, lock, seen) {
|
|
|
13001
13095
|
const inputs = await inspectCharacterParent(spec, lock, nextSeen);
|
|
13002
13096
|
if (!inputs.ready) return inputs;
|
|
13003
13097
|
}
|
|
13098
|
+
if (spec.character?.motionLeader) {
|
|
13099
|
+
const inputs = await inspectMotionLeader(spec, lock, nextSeen);
|
|
13100
|
+
if (!inputs.ready) return inputs;
|
|
13101
|
+
}
|
|
13004
13102
|
if (spec.character?.styleAnchor) {
|
|
13005
13103
|
const inputs = await inspectStyleAnchor(spec, lock, nextSeen);
|
|
13006
13104
|
if (!inputs.ready) return inputs;
|
|
@@ -13057,6 +13155,7 @@ function dependencies(spec) {
|
|
|
13057
13155
|
spec.mirror?.sourceSpec,
|
|
13058
13156
|
spec.revision?.sourceSpec,
|
|
13059
13157
|
spec.character?.parentSpec,
|
|
13158
|
+
spec.character?.motionLeader?.spec,
|
|
13060
13159
|
spec.character?.styleAnchor?.spec,
|
|
13061
13160
|
spec.objectPro?.parentSpec,
|
|
13062
13161
|
spec.terrainConnect?.lower?.spec,
|
|
@@ -13073,6 +13172,12 @@ function changedTerrainConnection(spec, entry, lock) {
|
|
|
13073
13172
|
}
|
|
13074
13173
|
return null;
|
|
13075
13174
|
}
|
|
13175
|
+
function changedMotionLeader(spec, entry, lock) {
|
|
13176
|
+
const leader = spec.character?.motionLeader;
|
|
13177
|
+
if (!leader) return null;
|
|
13178
|
+
const current = recordedAnimationGroup(lock.entries[lockKey(leader.spec.styleId, leader.spec.assetId)]).animationGroupId;
|
|
13179
|
+
return (entry.motionShare?.animationGroupId ?? null) === current ? null : leader.assetId;
|
|
13180
|
+
}
|
|
13076
13181
|
function changedTerrainSettings(spec, entry) {
|
|
13077
13182
|
const recorded = entry.terrainSettings;
|
|
13078
13183
|
const current = terrainSettings(spec);
|
|
@@ -13114,6 +13219,7 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
13114
13219
|
let state;
|
|
13115
13220
|
let reason;
|
|
13116
13221
|
const changedConnection = entry ? changedTerrainConnection(spec, entry, lock) : null;
|
|
13222
|
+
const changedMotion = entry ? changedMotionLeader(spec, entry, lock) : null;
|
|
13117
13223
|
const changedSettings = entry ? changedTerrainSettings(spec, entry) : null;
|
|
13118
13224
|
const revision = await inspectRevisionReadiness(spec, lock);
|
|
13119
13225
|
if (revision && !revision.ready) {
|
|
@@ -13153,6 +13259,9 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
13153
13259
|
} else if (changedConnection) {
|
|
13154
13260
|
state = "stale";
|
|
13155
13261
|
reason = `connected set ${changedConnection} was regenerated; draw this set from its new terrain`;
|
|
13262
|
+
} else if (changedMotion) {
|
|
13263
|
+
state = "stale";
|
|
13264
|
+
reason = `shares motion with ${changedMotion}, which was regenerated; draw this direction into its new motion`;
|
|
13156
13265
|
} else if (changedSettings) {
|
|
13157
13266
|
state = "stale";
|
|
13158
13267
|
reason = changedSettings;
|
|
@@ -13216,6 +13325,14 @@ async function buildPlan(specs, lock, opts = {}) {
|
|
|
13216
13325
|
}
|
|
13217
13326
|
}
|
|
13218
13327
|
}
|
|
13328
|
+
for (const item of items) {
|
|
13329
|
+
const leader = item.spec.character?.motionLeader;
|
|
13330
|
+
if (!leader || !isActionable(item)) continue;
|
|
13331
|
+
const leaderEntry = lock.entries[lockKey(leader.spec.styleId, leader.spec.assetId)];
|
|
13332
|
+
if (leaderEntry?.status === "downloaded" && !recordedAnimationGroup(leaderEntry).animationGroupId) {
|
|
13333
|
+
item.reason += `; ${leader.assetId} has no animation group recorded, so this is drawn on its own and its motion will differ`;
|
|
13334
|
+
}
|
|
13335
|
+
}
|
|
13219
13336
|
const actionable = items.filter(isActionable);
|
|
13220
13337
|
const groupsByProvider = /* @__PURE__ */ new Map();
|
|
13221
13338
|
for (const item of actionable) {
|
|
@@ -14625,6 +14742,7 @@ function retireGeneration(entry, retiredAt) {
|
|
|
14625
14742
|
provider: entry.provider,
|
|
14626
14743
|
postprocess: entry.postprocess,
|
|
14627
14744
|
...entry.terrainConnect ? { terrainConnect: entry.terrainConnect } : {},
|
|
14745
|
+
...entry.motionShare ? { motionShare: entry.motionShare } : {},
|
|
14628
14746
|
...entry.terrainSettings ? { terrainSettings: entry.terrainSettings } : {},
|
|
14629
14747
|
retiredAt
|
|
14630
14748
|
};
|
|
@@ -14720,6 +14838,8 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
14720
14838
|
// A connected set comes back with the connection it was drawn from, so
|
|
14721
14839
|
// the plan compares the restored generation, not the one it replaced.
|
|
14722
14840
|
terrainConnect: generation.terrainConnect,
|
|
14841
|
+
// Likewise a loop that shared another direction's motion.
|
|
14842
|
+
motionShare: generation.motionShare,
|
|
14723
14843
|
// Likewise its settings; a generation from before they were recorded
|
|
14724
14844
|
// comes back with none and is taken as current.
|
|
14725
14845
|
terrainSettings: generation.terrainSettings,
|
|
@@ -15759,6 +15879,12 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
15759
15879
|
};
|
|
15760
15880
|
terrainConnect[side] = { sourceAssetId: link.assetId, sourceObjectId: objectId };
|
|
15761
15881
|
}
|
|
15882
|
+
const motionLeader = spec.character?.motionLeader;
|
|
15883
|
+
const leaderGroup = motionLeader ? recordedAnimationGroup(lock.entries[lockKey(motionLeader.spec.styleId, motionLeader.spec.assetId)]) : void 0;
|
|
15884
|
+
const motionGroup = leaderGroup?.animationGroupId && leaderGroup.animationName ? { animationGroupId: leaderGroup.animationGroupId, animationName: leaderGroup.animationName } : void 0;
|
|
15885
|
+
if (motionLeader && !motionGroup) {
|
|
15886
|
+
log(` ${key}: ${motionLeader.assetId} has no animation group recorded; drawing this direction on its own`);
|
|
15887
|
+
}
|
|
15762
15888
|
upsert(lock, key, {
|
|
15763
15889
|
styleId: spec.styleId,
|
|
15764
15890
|
assetId: spec.assetId,
|
|
@@ -15800,6 +15926,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
15800
15926
|
referenceSha256: spec.character.outfit.reference.sha256
|
|
15801
15927
|
} : null,
|
|
15802
15928
|
terrainConnect: spec.terrainConnect ? terrainConnect : void 0,
|
|
15929
|
+
motionShare: motionLeader ? { sourceAssetId: motionLeader.assetId, animationGroupId: motionGroup?.animationGroupId ?? null } : void 0,
|
|
15803
15930
|
// The terrain knobs specHash leaves out, so the plan can tell a
|
|
15804
15931
|
// changed one; undefined (no key) for every other generator.
|
|
15805
15932
|
terrainSettings: terrainSettings(spec),
|
|
@@ -15850,6 +15977,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
15850
15977
|
...anchorEntry?.objectId ? { styleObjectId: anchorEntry.objectId } : {},
|
|
15851
15978
|
...parentOutputs ? { parentOutputs } : {},
|
|
15852
15979
|
...spec.terrainConnect ? { terrainLinks } : {},
|
|
15980
|
+
...motionGroup ? { motionGroup } : {},
|
|
15853
15981
|
...replacedMetadata ? { replacedMetadata } : {},
|
|
15854
15982
|
...lastGenerationMetadata ? { lastGenerationMetadata } : {},
|
|
15855
15983
|
...previousMetadata ? { previousMetadata } : {},
|
|
@@ -21041,6 +21169,7 @@ export {
|
|
|
21041
21169
|
readVfxConversation,
|
|
21042
21170
|
readVfxRecipe,
|
|
21043
21171
|
recipeDigest,
|
|
21172
|
+
recordedAnimationGroup,
|
|
21044
21173
|
referencedHashes,
|
|
21045
21174
|
refineAsset,
|
|
21046
21175
|
refineFrameSet,
|