spine-rigc 2.56.0 → 2.58.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/README.md +3 -1
- package/docs/AUTHORING.md +709 -264
- package/package.json +1 -1
- package/skills/rigc/SKILL.md +7 -2
- package/skills/rigc-face/SKILL.md +3 -1
- package/skills/rigc-ingest/SKILL.md +3 -1
- package/skills/rigc-motion/SKILL.md +3 -1
- package/skills/rigc-rigging/SKILL.md +3 -1
- package/src/atlas.ts +10 -10
- package/src/cli/core_commands.ts +52 -0
- package/src/cli/shared.ts +119 -3
- package/src/compile.ts +356 -334
- package/src/deformgen.ts +28 -28
- package/src/emit_spine.ts +5 -5
- package/src/errors.ts +8 -0
- package/src/guide.ts +4 -9
- package/src/keys.ts +4 -4
- package/src/meshsplit.ts +683 -0
- package/src/model.ts +10 -10
- package/src/motion.ts +6 -6
- package/src/rig.ts +76 -76
- package/src/stop.ts +30 -30
- package/src/trackgen.ts +11 -11
package/src/rig.ts
CHANGED
|
@@ -1315,7 +1315,7 @@ export function splitRigSkin(skin: RigSkin, where: string): RigSkinParts {
|
|
|
1315
1315
|
physics: [],
|
|
1316
1316
|
slider: [],
|
|
1317
1317
|
});
|
|
1318
|
-
if (!isObj(skin)) throw new CompileError(`${where}: a skin must be an object
|
|
1318
|
+
if (!isObj(skin)) throw new CompileError(`${where}: a skin must be an object`, 'CE_RIG_SKIN_OBJECT');
|
|
1319
1319
|
const known = new Set<string>(RIG_SKIN_KEYS);
|
|
1320
1320
|
const keys = Object.keys(skin);
|
|
1321
1321
|
if (!keys.some((key) => known.has(key))) {
|
|
@@ -1323,7 +1323,7 @@ export function splitRigSkin(skin: RigSkin, where: string): RigSkinParts {
|
|
|
1323
1323
|
}
|
|
1324
1324
|
const entry = skin as RigSkinEntry;
|
|
1325
1325
|
if (entry.attachments !== undefined && !isObj(entry.attachments)) {
|
|
1326
|
-
throw new CompileError(`${where}: "attachments" is \`slotName -> placeholderName -> attachment\`, not ${JSON.stringify(entry.attachments)}
|
|
1326
|
+
throw new CompileError(`${where}: "attachments" is \`slotName -> placeholderName -> attachment\`, not ${JSON.stringify(entry.attachments)}`, 'CE_RIG_SKIN_ATTACHMENTS_SHAPE');
|
|
1327
1327
|
}
|
|
1328
1328
|
for (const key of keys) {
|
|
1329
1329
|
if (known.has(key)) continue;
|
|
@@ -1331,15 +1331,15 @@ export function splitRigSkin(skin: RigSkin, where: string): RigSkinParts {
|
|
|
1331
1331
|
`${where}: uses the long form (it declares ${keys.filter((k) => known.has(k)).map((k) => `"${k}"`).join(', ')}) ` +
|
|
1332
1332
|
`and also has a key "${key}". In that form every key is one of ${RIG_SKIN_KEYS.join(', ')} — so "${key}" reads ` +
|
|
1333
1333
|
'as neither a member list nor a slot, and a slot left outside the block is an attachment that vanishes. ' +
|
|
1334
|
-
'Move it inside "attachments".',
|
|
1334
|
+
'Move it inside "attachments".', 'CE_RIG_SKIN_LONG_FORM_STRAY_SLOT',
|
|
1335
1335
|
);
|
|
1336
1336
|
}
|
|
1337
1337
|
const nameList = (value: unknown, field: string): string[] => {
|
|
1338
1338
|
if (value === undefined) return [];
|
|
1339
|
-
if (!Array.isArray(value)) throw new CompileError(`${where}: "${field}" must be an array of names, not ${JSON.stringify(value)}
|
|
1339
|
+
if (!Array.isArray(value)) throw new CompileError(`${where}: "${field}" must be an array of names, not ${JSON.stringify(value)}`, 'CE_RIG_SKIN_MEMBERS_ARRAY');
|
|
1340
1340
|
return value.map((name, i) => {
|
|
1341
1341
|
if (typeof name !== 'string' || name.length === 0) {
|
|
1342
|
-
throw new CompileError(`${where}: ${field}[${i}] is ${JSON.stringify(name)}; a skin lists bones and constraints BY NAME
|
|
1342
|
+
throw new CompileError(`${where}: ${field}[${i}] is ${JSON.stringify(name)}; a skin lists bones and constraints BY NAME`, 'CE_RIG_SKIN_MEMBER_NAME');
|
|
1343
1343
|
}
|
|
1344
1344
|
return name;
|
|
1345
1345
|
});
|
|
@@ -2373,7 +2373,7 @@ function checkRigSequence(att: Record<string, unknown>, who: string, where: stri
|
|
|
2373
2373
|
if (!isObj(seq)) {
|
|
2374
2374
|
throw new CompileError(
|
|
2375
2375
|
`${where}: ${at} is ${JSON.stringify(seq) ?? String(seq)}, and a sequence is an object: ` +
|
|
2376
|
-
'`{ "count": <frames>, "start"?: <first number>, "digits"?: <zero padding>, "setup"?: <setup frame> }`',
|
|
2376
|
+
'`{ "count": <frames>, "start"?: <first number>, "digits"?: <zero padding>, "setup"?: <setup frame> }`', 'CE_RIG_SEQUENCE_OBJECT',
|
|
2377
2377
|
);
|
|
2378
2378
|
}
|
|
2379
2379
|
const whole = (field: string, min: number): void => {
|
|
@@ -2388,7 +2388,7 @@ function checkRigSequence(att: Record<string, unknown>, who: string, where: stri
|
|
|
2388
2388
|
`${where}: ${at}.${field} is ${JSON.stringify(value) ?? String(value)}; it is a whole number` +
|
|
2389
2389
|
(min > 0 ? ` of at least ${min}` : min === 0 ? ' of at least 0' : '') +
|
|
2390
2390
|
' — `Sequence.getPath` writes `start + i` into the region name digit for digit, so a fraction names a ' +
|
|
2391
|
-
'region like `stem1.5` and a non-number names none',
|
|
2391
|
+
'region like `stem1.5` and a non-number names none', 'CE_RIG_SEQUENCE_INTEGER_FIELD',
|
|
2392
2392
|
);
|
|
2393
2393
|
}
|
|
2394
2394
|
};
|
|
@@ -2396,7 +2396,7 @@ function checkRigSequence(att: Record<string, unknown>, who: string, where: stri
|
|
|
2396
2396
|
throw new CompileError(
|
|
2397
2397
|
`${where}: ${at} states no "count". The parser reads \`getValue(map, "count", 0)\` ` +
|
|
2398
2398
|
'(`SkeletonJson.js:644`), so an omitted count is a series of NO frames: the attachment loads holding no ' +
|
|
2399
|
-
'region and draws nothing, without an error. State how many frames the series has.',
|
|
2399
|
+
'region and draws nothing, without an error. State how many frames the series has.', 'CE_RIG_SEQUENCE_COUNT_REQUIRED',
|
|
2400
2400
|
);
|
|
2401
2401
|
}
|
|
2402
2402
|
whole('count', 1);
|
|
@@ -2408,7 +2408,7 @@ function checkRigSequence(att: Record<string, unknown>, who: string, where: stri
|
|
|
2408
2408
|
throw new CompileError(
|
|
2409
2409
|
`${where}: ${at}.setup is ${seq.setup}, and a ${count}-frame series has frames 0 to ${count - 1}. ` +
|
|
2410
2410
|
'`Sequence.resolveIndex` clamps an index at or past the end to the LAST frame (measured: `setup: 7` on ' +
|
|
2411
|
-
'four frames showed frame 4), so this would show a frame the spec does not name. `setup` is 0-based.',
|
|
2411
|
+
'four frames showed frame 4), so this would show a frame the spec does not name. `setup` is 0-based.', 'CE_RIG_SEQUENCE_SETUP_RANGE',
|
|
2412
2412
|
);
|
|
2413
2413
|
}
|
|
2414
2414
|
for (const [field, why] of [
|
|
@@ -2418,7 +2418,7 @@ function checkRigSequence(att: Record<string, unknown>, who: string, where: stri
|
|
|
2418
2418
|
'the spec says — author the geometry, which every frame shares'],
|
|
2419
2419
|
] as const) {
|
|
2420
2420
|
if (att[field] !== undefined) {
|
|
2421
|
-
throw new CompileError(`${where}: ${who} states "${field}" beside "sequence"; ${why}. Remove "${field}"
|
|
2421
|
+
throw new CompileError(`${where}: ${who} states "${field}" beside "sequence"; ${why}. Remove "${field}".`, 'CE_RIG_SEQUENCE_CONFLICTING_FIELD');
|
|
2422
2422
|
}
|
|
2423
2423
|
}
|
|
2424
2424
|
}
|
|
@@ -2543,7 +2543,7 @@ function checkRigSpecKeys(raw: Record<string, unknown>, where: string): Array<{
|
|
|
2543
2543
|
`regions, and only the ${SEQUENCE_ATTACHMENT_TYPES.length} kinds that draw a region carry one — ` +
|
|
2544
2544
|
`${SEQUENCE_ATTACHMENT_TYPES.join(', ')} (\`readAttachment\` calls \`readSequence\` in exactly those ` +
|
|
2545
2545
|
`branches, \`SkeletonJson.js:530\` and \`:561\`); on a ${type} the parser never reads the key, so the ` +
|
|
2546
|
-
'series would be dropped in silence. Remove it, or put it on a region or a mesh.',
|
|
2546
|
+
'series would be dropped in silence. Remove it, or put it on a region or a mesh.', 'CE_RIG_SEQUENCE_ATTACHMENT_KIND',
|
|
2547
2547
|
);
|
|
2548
2548
|
}
|
|
2549
2549
|
const here = [...base, slot, placeholder];
|
|
@@ -2571,7 +2571,7 @@ function checkRigSpecKeys(raw: Record<string, unknown>, where: string): Array<{
|
|
|
2571
2571
|
const stated = gen.kind === undefined ? 'absent' : (JSON.stringify(gen.kind) ?? String(gen.kind));
|
|
2572
2572
|
throw new CompileError(
|
|
2573
2573
|
`${where}: ${who} generator.kind is ${stated}; one of ${RIG_GENERATOR_KINDS.map((k) => JSON.stringify(k)).join(', ')} — ` +
|
|
2574
|
-
'the kind names the builder, and each builder reads its own keys, so nothing about the mesh can be checked or built without one',
|
|
2574
|
+
'the kind names the builder, and each builder reads its own keys, so nothing about the mesh can be checked or built without one', 'CE_RIG_GENERATOR_KIND',
|
|
2575
2575
|
);
|
|
2576
2576
|
}
|
|
2577
2577
|
at(gen, genShape, `${who} generator (${String(gen.kind)})`, [...here, 'generator']);
|
|
@@ -2641,9 +2641,9 @@ function rigPlace(raw: Record<string, unknown>): (path: ReadonlyArray<string | n
|
|
|
2641
2641
|
* consumer.
|
|
2642
2642
|
*/
|
|
2643
2643
|
export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
2644
|
-
if (!isObj(raw)) throw new CompileError(`${where}: a rig spec must be a JSON object
|
|
2644
|
+
if (!isObj(raw)) throw new CompileError(`${where}: a rig spec must be a JSON object`, 'CE_RIG_SPEC_OBJECT');
|
|
2645
2645
|
if (raw.spec !== RIG_SPEC_VERSION) {
|
|
2646
|
-
throw new CompileError(`${where}: unknown rig spec version ${JSON.stringify(raw.spec)}, expected "${RIG_SPEC_VERSION}"
|
|
2646
|
+
throw new CompileError(`${where}: unknown rig spec version ${JSON.stringify(raw.spec)}, expected "${RIG_SPEC_VERSION}"`, 'CE_RIG_SPEC_VERSION');
|
|
2647
2647
|
}
|
|
2648
2648
|
// Before anything resolves by name AND before the required keys are asked
|
|
2649
2649
|
// for, because a key nothing reads is very often the CAUSE of the name — or
|
|
@@ -2688,13 +2688,13 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
2688
2688
|
refuseNumbersTheFileCannotCarry(raw, where, place);
|
|
2689
2689
|
|
|
2690
2690
|
if (typeof raw.name !== 'string' || raw.name.length === 0) {
|
|
2691
|
-
throw new CompileError(`${where}: a rig spec needs a "name" — a motion spec names it to pick this rig
|
|
2691
|
+
throw new CompileError(`${where}: a rig spec needs a "name" — a motion spec names it to pick this rig`, 'CE_RIG_NAME_REQUIRED');
|
|
2692
2692
|
}
|
|
2693
2693
|
if (!Array.isArray(raw.bones) || raw.bones.length === 0) {
|
|
2694
|
-
throw new CompileError(`${where}: a rig spec needs a non-empty "bones" array
|
|
2694
|
+
throw new CompileError(`${where}: a rig spec needs a non-empty "bones" array`, 'CE_RIG_BONES_ARRAY_REQUIRED');
|
|
2695
2695
|
}
|
|
2696
2696
|
if (!Array.isArray(raw.slots)) {
|
|
2697
|
-
throw new CompileError(`${where}: a rig spec needs a "slots" array (it may be empty; its ORDER is the draw order)
|
|
2697
|
+
throw new CompileError(`${where}: a rig spec needs a "slots" array (it may be empty; its ORDER is the draw order)`, 'CE_RIG_SLOTS_ARRAY_REQUIRED');
|
|
2698
2698
|
}
|
|
2699
2699
|
|
|
2700
2700
|
// `stops` first, ahead of every name this function resolves: a chain's next
|
|
@@ -2711,7 +2711,7 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
2711
2711
|
if (header.audio !== undefined && header.audio !== null && typeof header.audio !== 'string') {
|
|
2712
2712
|
throw new CompileError(
|
|
2713
2713
|
`${where}: "skeleton" states audio ${JSON.stringify(header.audio)}; it is a path from the skeleton file to ` +
|
|
2714
|
-
'its audio folder, or null for none — a string or null',
|
|
2714
|
+
'its audio folder, or null for none — a string or null', 'CE_RIG_SKELETON_AUDIO_TYPE',
|
|
2715
2715
|
);
|
|
2716
2716
|
}
|
|
2717
2717
|
const noWidth = header.width === null;
|
|
@@ -2722,7 +2722,7 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
2722
2722
|
throw new CompileError(
|
|
2723
2723
|
`${where}: "skeleton" states ${absent}: null and a ${stated} of ` +
|
|
2724
2724
|
`${JSON.stringify(noWidth ? header.height : header.width)}. A stage has both extents or neither: ` +
|
|
2725
|
-
'write both as null for "this skeleton declares no stage", or give both a number',
|
|
2725
|
+
'write both as null for "this skeleton declares no stage", or give both a number', 'CE_RIG_STAGE_EXTENTS_PAIR',
|
|
2726
2726
|
);
|
|
2727
2727
|
}
|
|
2728
2728
|
if (noWidth && noHeight && (header.x !== undefined || header.y !== undefined)) {
|
|
@@ -2730,7 +2730,7 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
2730
2730
|
throw new CompileError(
|
|
2731
2731
|
`${where}: "skeleton" declares no stage (width: null, height: null) and still states ${origin.join(' and ')}. ` +
|
|
2732
2732
|
`${origin.length === 1 ? 'That is an origin' : 'Those are an origin'} for a box that is not there: ` +
|
|
2733
|
-
'drop them, or state a width and a height',
|
|
2733
|
+
'drop them, or state a width and a height', 'CE_RIG_ABSENT_STAGE_ORIGIN',
|
|
2734
2734
|
);
|
|
2735
2735
|
}
|
|
2736
2736
|
// The stage's box (issue #1168): both names stated, and a stage to draw it from.
|
|
@@ -2741,14 +2741,14 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
2741
2741
|
throw new CompileError(
|
|
2742
2742
|
`${where}: skeleton.stageBox states no ${missing.map((key) => `"${key}"`).join(' and ')}. It names the slot the ` +
|
|
2743
2743
|
'stage box goes in and the attachment name it carries — `{ "slot": "stage", "attachment": "stage" }` — ' +
|
|
2744
|
-
'and both are required: rigc writes the box\'s numbers from the stage and names nothing on its own',
|
|
2744
|
+
'and both are required: rigc writes the box\'s numbers from the stage and names nothing on its own', 'CE_RIG_STAGE_BOX_NAMES_REQUIRED',
|
|
2745
2745
|
);
|
|
2746
2746
|
}
|
|
2747
2747
|
if (noWidth && noHeight) {
|
|
2748
2748
|
throw new CompileError(
|
|
2749
2749
|
`${where}: skeleton.stageBox asks for the stage as a bounding box in slot "${box.slot}", and "skeleton" ` +
|
|
2750
2750
|
'declares no stage (width: null, height: null) — a box around nothing. State a width and a height, or ' +
|
|
2751
|
-
'drop stageBox',
|
|
2751
|
+
'drop stageBox', 'CE_RIG_STAGE_BOX_WITHOUT_STAGE',
|
|
2752
2752
|
);
|
|
2753
2753
|
}
|
|
2754
2754
|
}
|
|
@@ -2757,10 +2757,10 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
2757
2757
|
const seen = new Set<string>();
|
|
2758
2758
|
for (const bone of spec.bones) {
|
|
2759
2759
|
if (!isObj(bone) || typeof bone.name !== 'string' || bone.name.length === 0) {
|
|
2760
|
-
throw new CompileError(`${where}: every bone needs a "name"
|
|
2760
|
+
throw new CompileError(`${where}: every bone needs a "name"`, 'CE_RIG_BONE_NAME_REQUIRED');
|
|
2761
2761
|
}
|
|
2762
2762
|
if (seen.has(bone.name)) {
|
|
2763
|
-
throw new CompileError(`${where}: two bones are called "${bone.name}"; bone names are the join key for slots, meshes and timelines
|
|
2763
|
+
throw new CompileError(`${where}: two bones are called "${bone.name}"; bone names are the join key for slots, meshes and timelines`, 'CE_RIG_BONE_NAME_DUPLICATE');
|
|
2764
2764
|
}
|
|
2765
2765
|
seen.add(bone.name);
|
|
2766
2766
|
if (bone.parent === undefined) continue;
|
|
@@ -2769,12 +2769,12 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
2769
2769
|
// forward reference is not a rigc restriction — it is a bone with no parent
|
|
2770
2770
|
// in the loaded skeleton, which loads as a second root.
|
|
2771
2771
|
throw new CompileError(
|
|
2772
|
-
`${where}: bone "${bone.name}" names parent ${JSON.stringify(bone.parent)}, which is not declared before it`,
|
|
2772
|
+
`${where}: bone "${bone.name}" names parent ${JSON.stringify(bone.parent)}, which is not declared before it`, 'CE_RIG_BONE_PARENT_ORDER',
|
|
2773
2773
|
);
|
|
2774
2774
|
}
|
|
2775
2775
|
if (bone.inherit !== undefined && resolveBoneInherit(bone.inherit) === undefined) {
|
|
2776
2776
|
throw new CompileError(
|
|
2777
|
-
`${where}: bone "${bone.name}" has inherit ${JSON.stringify(bone.inherit)}; ${BONE_INHERIT_KNOWN}`,
|
|
2777
|
+
`${where}: bone "${bone.name}" has inherit ${JSON.stringify(bone.inherit)}; ${BONE_INHERIT_KNOWN}`, 'CE_RIG_BONE_INHERIT_MODE',
|
|
2778
2778
|
);
|
|
2779
2779
|
}
|
|
2780
2780
|
const from = bone.from;
|
|
@@ -2782,19 +2782,19 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
2782
2782
|
const sources = ['anchor', 'slotWindow', 'meshCenter'].filter((k) => from[k as keyof RigBoneFrom] !== undefined);
|
|
2783
2783
|
if (sources.length > 1) {
|
|
2784
2784
|
throw new CompileError(
|
|
2785
|
-
`${where}: bone "${bone.name}" takes its position from more than one source (${sources.join(', ')}); name exactly one`,
|
|
2785
|
+
`${where}: bone "${bone.name}" takes its position from more than one source (${sources.join(', ')}); name exactly one`, 'CE_RIG_BONE_POSITION_SOURCES',
|
|
2786
2786
|
);
|
|
2787
2787
|
}
|
|
2788
2788
|
if ((bone.x !== undefined || bone.y !== undefined) && sources.length === 1) {
|
|
2789
2789
|
throw new CompileError(
|
|
2790
|
-
`${where}: bone "${bone.name}" declares both a literal x/y and from.${sources[0]}; the two would disagree the first time the art moved`,
|
|
2790
|
+
`${where}: bone "${bone.name}" declares both a literal x/y and from.${sources[0]}; the two would disagree the first time the art moved`, 'CE_RIG_BONE_POSITION_CONFLICT',
|
|
2791
2791
|
);
|
|
2792
2792
|
}
|
|
2793
2793
|
if (from.rotation !== undefined && bone.rotation !== undefined) {
|
|
2794
|
-
throw new CompileError(`${where}: bone "${bone.name}" declares both a literal rotation and from.rotation
|
|
2794
|
+
throw new CompileError(`${where}: bone "${bone.name}" declares both a literal rotation and from.rotation`, 'CE_RIG_BONE_ROTATION_CONFLICT');
|
|
2795
2795
|
}
|
|
2796
2796
|
if (from.rotation === 'anchor' && from.anchor === undefined) {
|
|
2797
|
-
throw new CompileError(`${where}: bone "${bone.name}" wants its rotation from an anchor but names no from.anchor
|
|
2797
|
+
throw new CompileError(`${where}: bone "${bone.name}" wants its rotation from an anchor but names no from.anchor`, 'CE_RIG_BONE_ROTATION_ANCHOR_REQUIRED');
|
|
2798
2798
|
}
|
|
2799
2799
|
}
|
|
2800
2800
|
}
|
|
@@ -2802,13 +2802,13 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
2802
2802
|
const slotNames = new Set<string>();
|
|
2803
2803
|
for (const slot of spec.slots) {
|
|
2804
2804
|
if (!isObj(slot) || typeof slot.name !== 'string' || slot.name.length === 0) {
|
|
2805
|
-
throw new CompileError(`${where}: every slot needs a "name"
|
|
2805
|
+
throw new CompileError(`${where}: every slot needs a "name"`, 'CE_RIG_SLOT_NAME_REQUIRED');
|
|
2806
2806
|
}
|
|
2807
|
-
if (slotNames.has(slot.name)) throw new CompileError(`${where}: two slots are called "${slot.name}"
|
|
2807
|
+
if (slotNames.has(slot.name)) throw new CompileError(`${where}: two slots are called "${slot.name}"`, 'CE_RIG_SLOT_NAME_DUPLICATE');
|
|
2808
2808
|
slotNames.add(slot.name);
|
|
2809
2809
|
if (typeof slot.bone !== 'string' || !seen.has(slot.bone)) {
|
|
2810
2810
|
throw new CompileError(
|
|
2811
|
-
`${where}: slot "${slot.name}" names bone ${JSON.stringify(slot.bone)}, which this rig does not declare`,
|
|
2811
|
+
`${where}: slot "${slot.name}" names bone ${JSON.stringify(slot.bone)}, which this rig does not declare`, 'CE_RIG_SLOT_BONE_UNKNOWN',
|
|
2812
2812
|
);
|
|
2813
2813
|
}
|
|
2814
2814
|
// Issue #946: this used to compare case-insensitively, so `ADDITIVE` built
|
|
@@ -2818,7 +2818,7 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
2818
2818
|
throw new CompileError(
|
|
2819
2819
|
`${where}: slot "${slot.name}" has blend ${JSON.stringify(slot.blend)}; known: ${RIG_SLOT_BLEND.join(', ')} ` +
|
|
2820
2820
|
"(only the first letter's case is free — the parser's enumValue uppercases that one character and nothing " +
|
|
2821
|
-
'else, and an unresolved name becomes undefined without an error)',
|
|
2821
|
+
'else, and an unresolved name becomes undefined without an error)', 'CE_RIG_SLOT_BLEND_MODE',
|
|
2822
2822
|
);
|
|
2823
2823
|
}
|
|
2824
2824
|
}
|
|
@@ -2833,29 +2833,29 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
2833
2833
|
if (mayFold !== undefined) {
|
|
2834
2834
|
if (!Array.isArray(mayFold)) {
|
|
2835
2835
|
throw new CompileError(
|
|
2836
|
-
`${where}: invariants.deformMayFold is ${JSON.stringify(mayFold)}, expected an array of { "slot": …, "why": … }`,
|
|
2836
|
+
`${where}: invariants.deformMayFold is ${JSON.stringify(mayFold)}, expected an array of { "slot": …, "why": … }`, 'CE_RIG_DEFORM_FOLD_EXEMPTIONS_SHAPE',
|
|
2837
2837
|
);
|
|
2838
2838
|
}
|
|
2839
2839
|
const declared = new Set<string>();
|
|
2840
2840
|
for (const entry of mayFold) {
|
|
2841
2841
|
if (!isObj(entry) || typeof entry.slot !== 'string' || entry.slot.length === 0) {
|
|
2842
|
-
throw new CompileError(`${where}: every invariants.deformMayFold entry needs a "slot"
|
|
2842
|
+
throw new CompileError(`${where}: every invariants.deformMayFold entry needs a "slot"`, 'CE_RIG_DEFORM_FOLD_SLOT_REQUIRED');
|
|
2843
2843
|
}
|
|
2844
2844
|
if (!slotNames.has(entry.slot)) {
|
|
2845
2845
|
throw new CompileError(
|
|
2846
2846
|
`${where}: invariants.deformMayFold exempts slot "${entry.slot}", which this rig does not declare — ` +
|
|
2847
|
-
'a name that resolves to nothing exempts nothing, and reads like the exemption worked',
|
|
2847
|
+
'a name that resolves to nothing exempts nothing, and reads like the exemption worked', 'CE_RIG_DEFORM_FOLD_SLOT_UNKNOWN',
|
|
2848
2848
|
);
|
|
2849
2849
|
}
|
|
2850
2850
|
if (declared.has(entry.slot)) {
|
|
2851
|
-
throw new CompileError(`${where}: invariants.deformMayFold names slot "${entry.slot}" twice
|
|
2851
|
+
throw new CompileError(`${where}: invariants.deformMayFold names slot "${entry.slot}" twice`, 'CE_RIG_DEFORM_FOLD_SLOT_DUPLICATE');
|
|
2852
2852
|
}
|
|
2853
2853
|
declared.add(entry.slot);
|
|
2854
2854
|
if (typeof entry.why !== 'string' || entry.why.trim().length === 0) {
|
|
2855
2855
|
throw new CompileError(
|
|
2856
2856
|
`${where}: invariants.deformMayFold entry for slot "${entry.slot}" needs a "why" — this field switches ` +
|
|
2857
2857
|
'A39_DEFORM_KEEPS_TRIANGLE_WINDING off for that slot, and an exemption nobody can date or justify is ' +
|
|
2858
|
-
'how a defect ships as a decision',
|
|
2858
|
+
'how a defect ships as a decision', 'CE_RIG_DEFORM_FOLD_REASON_REQUIRED',
|
|
2859
2859
|
);
|
|
2860
2860
|
}
|
|
2861
2861
|
}
|
|
@@ -2877,14 +2877,14 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
2877
2877
|
const constraintKinds = new Map<string, string[]>();
|
|
2878
2878
|
for (const constraint of spec.constraints ?? []) {
|
|
2879
2879
|
if (!isObj(constraint) || typeof constraint.name !== 'string' || constraint.name.length === 0) {
|
|
2880
|
-
throw new CompileError(`${where}: every constraint needs a "name"
|
|
2880
|
+
throw new CompileError(`${where}: every constraint needs a "name"`, 'CE_RIG_CONSTRAINT_NAME_REQUIRED');
|
|
2881
2881
|
}
|
|
2882
2882
|
const declared = { type: String(constraint.type), name: constraint.name, skinRequired: constraint.skin === true };
|
|
2883
2883
|
if (constraintFacts.has(constraintAt(declared.type, declared.name))) {
|
|
2884
2884
|
throw new CompileError(
|
|
2885
2885
|
`${where}: two ${declared.type} constraints are called "${declared.name}" — a constraint resolves by name ` +
|
|
2886
2886
|
'AND type (`SkeletonData.findConstraint`), so names are unique PER KIND: an ik and a transform constraint ' +
|
|
2887
|
-
'may share one, two of a kind may not',
|
|
2887
|
+
'may share one, two of a kind may not', 'CE_RIG_CONSTRAINT_NAME_DUPLICATE',
|
|
2888
2888
|
);
|
|
2889
2889
|
}
|
|
2890
2890
|
constraintsDeclared.push(declared);
|
|
@@ -2902,7 +2902,7 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
2902
2902
|
for (let at = spec.bones.find((b) => b.name === ikBones[1])?.parent; at !== undefined; at = spec.bones.find((b) => b.name === at)?.parent) secondAncestors.push(at);
|
|
2903
2903
|
}
|
|
2904
2904
|
const fault = ikShapeFault(constraint.name, ikBones, secondAncestors);
|
|
2905
|
-
if (fault !== null) throw new CompileError(`${where}: ${fault}
|
|
2905
|
+
if (fault !== null) throw new CompileError(`${where}: ${fault}`, 'CE_RIG_IK_BONES_SHAPE');
|
|
2906
2906
|
}
|
|
2907
2907
|
}
|
|
2908
2908
|
|
|
@@ -2915,12 +2915,12 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
2915
2915
|
if (consumerDriven !== undefined) {
|
|
2916
2916
|
const shape = 'an array of { "constraint": …, "type": "ik" | "transform", "why": … }';
|
|
2917
2917
|
if (!Array.isArray(consumerDriven)) {
|
|
2918
|
-
throw new CompileError(`${where}: invariants.consumerDrivenMix is ${JSON.stringify(consumerDriven)}, expected ${shape}
|
|
2918
|
+
throw new CompileError(`${where}: invariants.consumerDrivenMix is ${JSON.stringify(consumerDriven)}, expected ${shape}`, 'CE_RIG_CONSUMER_MIX_EXEMPTIONS_SHAPE');
|
|
2919
2919
|
}
|
|
2920
2920
|
const named = new Set<string>();
|
|
2921
2921
|
for (const entry of consumerDriven) {
|
|
2922
2922
|
if (!isObj(entry) || typeof entry.constraint !== 'string' || entry.constraint.length === 0) {
|
|
2923
|
-
throw new CompileError(`${where}: every invariants.consumerDrivenMix entry needs a "constraint" — ${shape}
|
|
2923
|
+
throw new CompileError(`${where}: every invariants.consumerDrivenMix entry needs a "constraint" — ${shape}`, 'CE_RIG_CONSUMER_MIX_CONSTRAINT_REQUIRED');
|
|
2924
2924
|
}
|
|
2925
2925
|
if (entry.type !== 'ik' && entry.type !== 'transform') {
|
|
2926
2926
|
const declaredAs = constraintKinds.get(entry.constraint) ?? [];
|
|
@@ -2929,7 +2929,7 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
2929
2929
|
'only "ik" and "transform" read this declaration (A47_IK_CONSTRAINT_NOT_MUTED_THROUGHOUT, ' +
|
|
2930
2930
|
'A48_TRANSFORM_CONSTRAINT_NOT_MUTED_THROUGHOUT). A path, physics or slider constraint resting muted is ' +
|
|
2931
2931
|
'A36, A23 or A37, which read no declaration, so the entry would exempt nothing' +
|
|
2932
|
-
(declaredAs.length ? ` — the rig declares "${entry.constraint}" as ${declaredAs.map((t) => `${t === 'ik' ? 'an' : 'a'} ${t}`).join(' and ')} constraint` : ''),
|
|
2932
|
+
(declaredAs.length ? ` — the rig declares "${entry.constraint}" as ${declaredAs.map((t) => `${t === 'ik' ? 'an' : 'a'} ${t}`).join(' and ')} constraint` : ''), 'CE_RIG_CONSUMER_MIX_KIND',
|
|
2933
2933
|
);
|
|
2934
2934
|
}
|
|
2935
2935
|
const key = constraintAt(entry.type, entry.constraint);
|
|
@@ -2940,16 +2940,16 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
2940
2940
|
(declaredAs.length
|
|
2941
2941
|
? ` — "${entry.constraint}" is declared as ${declaredAs.map((t) => `${t === 'ik' ? 'an' : 'a'} ${t}`).join(' and ')} constraint, and a constraint resolves by name AND type`
|
|
2942
2942
|
: '') +
|
|
2943
|
-
'. A name that resolves to nothing exempts nothing, and reads like the exemption worked',
|
|
2943
|
+
'. A name that resolves to nothing exempts nothing, and reads like the exemption worked', 'CE_RIG_CONSUMER_MIX_CONSTRAINT_UNKNOWN',
|
|
2944
2944
|
);
|
|
2945
2945
|
}
|
|
2946
|
-
if (named.has(key)) throw new CompileError(`${where}: invariants.consumerDrivenMix names ${key} twice
|
|
2946
|
+
if (named.has(key)) throw new CompileError(`${where}: invariants.consumerDrivenMix names ${key} twice`, 'CE_RIG_CONSUMER_MIX_CONSTRAINT_DUPLICATE');
|
|
2947
2947
|
named.add(key);
|
|
2948
2948
|
if (typeof entry.why !== 'string' || entry.why.trim().length === 0) {
|
|
2949
2949
|
throw new CompileError(
|
|
2950
2950
|
`${where}: invariants.consumerDrivenMix entry for ${key} needs a "why" — this field switches ` +
|
|
2951
2951
|
`${entry.type === 'ik' ? 'A47_IK_CONSTRAINT_NOT_MUTED_THROUGHOUT' : 'A48_TRANSFORM_CONSTRAINT_NOT_MUTED_THROUGHOUT'} ` +
|
|
2952
|
-
'off for that constraint, and an exemption nobody can date or justify is how a defect ships as a decision',
|
|
2952
|
+
'off for that constraint, and an exemption nobody can date or justify is how a defect ships as a decision', 'CE_RIG_CONSUMER_MIX_REASON_REQUIRED',
|
|
2953
2953
|
);
|
|
2954
2954
|
}
|
|
2955
2955
|
}
|
|
@@ -2967,13 +2967,13 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
2967
2967
|
if (idleDrives !== undefined) {
|
|
2968
2968
|
const shape = '{ "why": "<why this idle deforms meshes on purpose>" }';
|
|
2969
2969
|
if (!isObj(idleDrives)) {
|
|
2970
|
-
throw new CompileError(`${where}: invariants.idleDrivesMeshes is ${JSON.stringify(idleDrives)}, expected ${shape}
|
|
2970
|
+
throw new CompileError(`${where}: invariants.idleDrivesMeshes is ${JSON.stringify(idleDrives)}, expected ${shape}`, 'CE_RIG_IDLE_MESH_EXEMPTION_SHAPE');
|
|
2971
2971
|
}
|
|
2972
2972
|
if (typeof idleDrives.why !== 'string' || idleDrives.why.trim().length === 0) {
|
|
2973
2973
|
throw new CompileError(
|
|
2974
2974
|
`${where}: invariants.idleDrivesMeshes needs a "why" (a non-blank string), got ` +
|
|
2975
2975
|
`${idleDrives.why === undefined ? 'none' : JSON.stringify(idleDrives.why)} — expected ${shape}. This field switches ` +
|
|
2976
|
-
'A15_IDLE_NO_MESH_BONE_KEYS off, and an exemption nobody can date or justify is how a defect ships as a decision',
|
|
2976
|
+
'A15_IDLE_NO_MESH_BONE_KEYS off, and an exemption nobody can date or justify is how a defect ships as a decision', 'CE_RIG_IDLE_MESH_REASON_REQUIRED',
|
|
2977
2977
|
);
|
|
2978
2978
|
}
|
|
2979
2979
|
}
|
|
@@ -2989,34 +2989,34 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
2989
2989
|
const axes = spec.invariants?.axes;
|
|
2990
2990
|
if (axes !== undefined) {
|
|
2991
2991
|
const shape = 'an array of { "name": …, "through": [<bone>, <bone>], "bones": [<bone>, …], "tolerance": <degrees> }';
|
|
2992
|
-
if (!Array.isArray(axes)) throw new CompileError(`${where}: invariants.axes is ${JSON.stringify(axes)}, expected ${shape}
|
|
2992
|
+
if (!Array.isArray(axes)) throw new CompileError(`${where}: invariants.axes is ${JSON.stringify(axes)}, expected ${shape}`, 'CE_RIG_AXES_SHAPE');
|
|
2993
2993
|
const named = new Set<string>();
|
|
2994
2994
|
for (const [i, entry] of axes.entries()) {
|
|
2995
2995
|
const field = `invariants.axes[${i}]`;
|
|
2996
2996
|
if (!isObj(entry) || typeof entry.name !== 'string' || entry.name.length === 0) {
|
|
2997
|
-
throw new CompileError(`${where}: ${field} needs a "name" — ${shape}
|
|
2997
|
+
throw new CompileError(`${where}: ${field} needs a "name" — ${shape}`, 'CE_RIG_AXIS_NAME_REQUIRED');
|
|
2998
2998
|
}
|
|
2999
2999
|
const axis = `${field} "${entry.name}"`;
|
|
3000
|
-
if (named.has(entry.name)) throw new CompileError(`${where}: ${field}: invariants.axes names "${entry.name}" twice — a failure names its entry, and two entries of one name could not be told apart
|
|
3000
|
+
if (named.has(entry.name)) throw new CompileError(`${where}: ${field}: invariants.axes names "${entry.name}" twice — a failure names its entry, and two entries of one name could not be told apart`, 'CE_RIG_AXIS_NAME_DUPLICATE');
|
|
3001
3001
|
named.add(entry.name);
|
|
3002
3002
|
const through: unknown = entry.through;
|
|
3003
3003
|
if (!Array.isArray(through) || through.length !== 2 || !through.every((b): b is string => typeof b === 'string')) {
|
|
3004
|
-
throw new CompileError(`${where}: ${axis}: "through" is ${JSON.stringify(through)}, expected two bone names — the line is drawn through their setup-pose world origins
|
|
3004
|
+
throw new CompileError(`${where}: ${axis}: "through" is ${JSON.stringify(through)}, expected two bone names — the line is drawn through their setup-pose world origins`, 'CE_RIG_AXIS_THROUGH_PAIR');
|
|
3005
3005
|
}
|
|
3006
3006
|
for (const [k, bone] of through.entries()) {
|
|
3007
|
-
if (!seen.has(bone)) throw new CompileError(`${where}: ${axis}: through[${k}] names bone "${bone}", which this rig does not declare — a line through nothing holds nothing
|
|
3007
|
+
if (!seen.has(bone)) throw new CompileError(`${where}: ${axis}: through[${k}] names bone "${bone}", which this rig does not declare — a line through nothing holds nothing`, 'CE_RIG_AXIS_THROUGH_BONE_UNKNOWN');
|
|
3008
3008
|
}
|
|
3009
3009
|
if (through[0] === through[1]) {
|
|
3010
|
-
throw new CompileError(`${where}: ${axis}: "through" names bone "${through[0]}" twice — one bone's origin is a point, and a point draws no line
|
|
3010
|
+
throw new CompileError(`${where}: ${axis}: "through" names bone "${through[0]}" twice — one bone's origin is a point, and a point draws no line`, 'CE_RIG_AXIS_THROUGH_BONE_DUPLICATE');
|
|
3011
3011
|
}
|
|
3012
3012
|
const bones: unknown = entry.bones;
|
|
3013
3013
|
if (!Array.isArray(bones) || bones.length === 0 || !bones.every((b): b is string => typeof b === 'string')) {
|
|
3014
|
-
throw new CompileError(`${where}: ${axis}: "bones" is ${JSON.stringify(bones)}, expected a non-empty array of bone names — the bones held to the line
|
|
3014
|
+
throw new CompileError(`${where}: ${axis}: "bones" is ${JSON.stringify(bones)}, expected a non-empty array of bone names — the bones held to the line`, 'CE_RIG_AXIS_BONES_ARRAY');
|
|
3015
3015
|
}
|
|
3016
3016
|
const held = new Set<string>();
|
|
3017
3017
|
for (const [k, bone] of bones.entries()) {
|
|
3018
|
-
if (!seen.has(bone)) throw new CompileError(`${where}: ${axis}: bones[${k}] names bone "${bone}", which this rig does not declare — a name that resolves to nothing is held to nothing, and reads like it was
|
|
3019
|
-
if (held.has(bone)) throw new CompileError(`${where}: ${axis}: "bones" names bone "${bone}" twice
|
|
3018
|
+
if (!seen.has(bone)) throw new CompileError(`${where}: ${axis}: bones[${k}] names bone "${bone}", which this rig does not declare — a name that resolves to nothing is held to nothing, and reads like it was`, 'CE_RIG_AXIS_HELD_BONE_UNKNOWN');
|
|
3019
|
+
if (held.has(bone)) throw new CompileError(`${where}: ${axis}: "bones" names bone "${bone}" twice`, 'CE_RIG_AXIS_HELD_BONE_DUPLICATE');
|
|
3020
3020
|
held.add(bone);
|
|
3021
3021
|
}
|
|
3022
3022
|
const tolerance: unknown = entry.tolerance;
|
|
@@ -3024,7 +3024,7 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
3024
3024
|
throw new CompileError(
|
|
3025
3025
|
`${where}: ${axis}: "tolerance" is ${tolerance === undefined ? 'absent' : JSON.stringify(tolerance)}; it must be a number of degrees above 0 and below 90, ` +
|
|
3026
3026
|
'and there is no default — two lines meet at 0° to 90°, so 90 or more admits every orientation and can never fail, ' +
|
|
3027
|
-
'and 0 refuses a bone rotated exactly onto the line whenever the posed arithmetic rounds',
|
|
3027
|
+
'and 0 refuses a bone rotated exactly onto the line whenever the posed arithmetic rounds', 'CE_RIG_AXIS_TOLERANCE_RANGE',
|
|
3028
3028
|
);
|
|
3029
3029
|
}
|
|
3030
3030
|
}
|
|
@@ -3067,7 +3067,7 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
3067
3067
|
const skinBoneUse = new Set<string>();
|
|
3068
3068
|
const skinConstraintUse = new Set<string>();
|
|
3069
3069
|
if (spec.skins !== undefined) {
|
|
3070
|
-
if (!isObj(spec.skins)) throw new CompileError(`${where}: "skins" is an object keyed by skin name
|
|
3070
|
+
if (!isObj(spec.skins)) throw new CompileError(`${where}: "skins" is an object keyed by skin name`, 'CE_RIG_SKINS_TABLE');
|
|
3071
3071
|
const collision = RIG_SKIN_KEYS.find((key) => slotNames.has(key));
|
|
3072
3072
|
if (collision !== undefined) {
|
|
3073
3073
|
// The long form is recognised by these keys, so a slot of one of those
|
|
@@ -3076,7 +3076,7 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
3076
3076
|
throw new CompileError(
|
|
3077
3077
|
`${where}: a slot is called "${collision}", which is one of the keys that tell a skin's long form ` +
|
|
3078
3078
|
'(`{ "attachments": {…}, "bones": [...] }`) from its short one (`slotName -> placeholder -> attachment`): ' +
|
|
3079
|
-
`${RIG_SKIN_KEYS.join(', ')}. Rename the slot.`,
|
|
3079
|
+
`${RIG_SKIN_KEYS.join(', ')}. Rename the slot.`, 'CE_RIG_SLOT_SKIN_KEY_COLLISION',
|
|
3080
3080
|
);
|
|
3081
3081
|
}
|
|
3082
3082
|
for (const [skinName, skin] of Object.entries(spec.skins)) {
|
|
@@ -3084,7 +3084,7 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
3084
3084
|
const parts = splitRigSkin(skin, at);
|
|
3085
3085
|
for (const bone of parts.bones) {
|
|
3086
3086
|
if (!seen.has(bone)) {
|
|
3087
|
-
throw new CompileError(`${at} activates bone "${bone}", which this rig does not declare
|
|
3087
|
+
throw new CompileError(`${at} activates bone "${bone}", which this rig does not declare`, 'CE_RIG_SKIN_BONE_UNKNOWN');
|
|
3088
3088
|
}
|
|
3089
3089
|
skinBoneUse.add(bone);
|
|
3090
3090
|
const declared = spec.bones.find((b) => b.name === bone);
|
|
@@ -3092,7 +3092,7 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
3092
3092
|
throw new CompileError(
|
|
3093
3093
|
`${at} activates bone "${bone}", but that bone does not declare \`"skin": true\`. ` +
|
|
3094
3094
|
'Skeleton.updateCache starts a bone active unless it is skinRequired, so this list changes nothing — ' +
|
|
3095
|
-
'the bone poses under every skin.',
|
|
3095
|
+
'the bone poses under every skin.', 'CE_RIG_SKIN_BONE_NOT_REQUIRED',
|
|
3096
3096
|
);
|
|
3097
3097
|
}
|
|
3098
3098
|
}
|
|
@@ -3105,20 +3105,20 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
3105
3105
|
// two different misses and say so.
|
|
3106
3106
|
const kinds = constraintKinds.get(name) ?? [];
|
|
3107
3107
|
if (kinds.length === 0) {
|
|
3108
|
-
throw new CompileError(`${at} activates ${type} constraint "${name}", which this rig does not declare
|
|
3108
|
+
throw new CompileError(`${at} activates ${type} constraint "${name}", which this rig does not declare`, 'CE_RIG_SKIN_CONSTRAINT_UNKNOWN');
|
|
3109
3109
|
}
|
|
3110
3110
|
// `findConstraint(name, IkConstraintData)` resolves by name AND type,
|
|
3111
3111
|
// and the parser throws on the miss.
|
|
3112
3112
|
throw new CompileError(
|
|
3113
3113
|
`${at} lists "${name}" under "${type}", but the rig declares it as a "${kinds.join('", "')}" constraint — ` +
|
|
3114
|
-
'a skin looks its constraints up by name AND type, so this one is a miss and the loader throws',
|
|
3114
|
+
'a skin looks its constraints up by name AND type, so this one is a miss and the loader throws', 'CE_RIG_SKIN_CONSTRAINT_KIND',
|
|
3115
3115
|
);
|
|
3116
3116
|
}
|
|
3117
3117
|
skinConstraintUse.add(constraintAt(type, name));
|
|
3118
3118
|
if (!facts.skinRequired) {
|
|
3119
3119
|
throw new CompileError(
|
|
3120
3120
|
`${at} activates ${type} constraint "${name}", but that constraint does not declare \`"skin": true\`. ` +
|
|
3121
|
-
'A constraint is active unless it is skinRequired, so this list changes nothing.',
|
|
3121
|
+
'A constraint is active unless it is skinRequired, so this list changes nothing.', 'CE_RIG_SKIN_CONSTRAINT_NOT_REQUIRED',
|
|
3122
3122
|
);
|
|
3123
3123
|
}
|
|
3124
3124
|
}
|
|
@@ -3139,7 +3139,7 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
3139
3139
|
if (bone.skin === true && !activated.has(bone.name)) {
|
|
3140
3140
|
throw new CompileError(
|
|
3141
3141
|
`${where}: bone "${bone.name}" declares \`"skin": true\` but no skin activates it, so it is never active — ` +
|
|
3142
|
-
'list it in the skin it belongs to, or drop the flag',
|
|
3142
|
+
'list it in the skin it belongs to, or drop the flag', 'CE_RIG_BONE_SKIN_NEVER_ACTIVE',
|
|
3143
3143
|
);
|
|
3144
3144
|
}
|
|
3145
3145
|
}
|
|
@@ -3147,7 +3147,7 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
3147
3147
|
if (skinRequired && !skinConstraintUse.has(constraintAt(type, name))) {
|
|
3148
3148
|
throw new CompileError(
|
|
3149
3149
|
`${where}: ${type} constraint "${name}" declares \`"skin": true\` but no skin activates it, so it never runs — ` +
|
|
3150
|
-
`list it in that skin's "${type}" array, or drop the flag`,
|
|
3150
|
+
`list it in that skin's "${type}" array, or drop the flag`, 'CE_RIG_CONSTRAINT_SKIN_NEVER_ACTIVE',
|
|
3151
3151
|
);
|
|
3152
3152
|
}
|
|
3153
3153
|
}
|
|
@@ -3155,26 +3155,26 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
3155
3155
|
if (raw.events !== undefined) {
|
|
3156
3156
|
if (!isObj(raw.events)) {
|
|
3157
3157
|
throw new CompileError(
|
|
3158
|
-
`${where}: "events" is an object keyed by event name (\`{ "footstep": {} }\`), not an array — the format's own shape`,
|
|
3158
|
+
`${where}: "events" is an object keyed by event name (\`{ "footstep": {} }\`), not an array — the format's own shape`, 'CE_RIG_EVENTS_TABLE',
|
|
3159
3159
|
);
|
|
3160
3160
|
}
|
|
3161
3161
|
for (const [name, def] of Object.entries(raw.events)) {
|
|
3162
|
-
if (name.length === 0) throw new CompileError(`${where}: an event has an empty name
|
|
3162
|
+
if (name.length === 0) throw new CompileError(`${where}: an event has an empty name`, 'CE_RIG_EVENT_NAME_EMPTY');
|
|
3163
3163
|
if (!isObj(def)) {
|
|
3164
|
-
throw new CompileError(`${where}: event "${name}" must be an object of payload defaults (use {} for none)
|
|
3164
|
+
throw new CompileError(`${where}: event "${name}" must be an object of payload defaults (use {} for none)`, 'CE_RIG_EVENT_DEFAULTS_OBJECT');
|
|
3165
3165
|
}
|
|
3166
3166
|
for (const field of ['int', 'float', 'volume', 'balance'] as const) {
|
|
3167
3167
|
const v = def[field];
|
|
3168
3168
|
if (v !== undefined && (typeof v !== 'number' || !Number.isFinite(v))) {
|
|
3169
|
-
throw new CompileError(`${where}: event "${name}" has ${field} ${JSON.stringify(v)}, which is not a finite number
|
|
3169
|
+
throw new CompileError(`${where}: event "${name}" has ${field} ${JSON.stringify(v)}, which is not a finite number`, 'CE_RIG_EVENT_NUMBER_FINITE');
|
|
3170
3170
|
}
|
|
3171
3171
|
}
|
|
3172
3172
|
if (def.int !== undefined && !Number.isInteger(def.int)) {
|
|
3173
|
-
throw new CompileError(`${where}: event "${name}" has int ${JSON.stringify(def.int)}; the payload is an integer
|
|
3173
|
+
throw new CompileError(`${where}: event "${name}" has int ${JSON.stringify(def.int)}; the payload is an integer`, 'CE_RIG_EVENT_INT_WHOLE');
|
|
3174
3174
|
}
|
|
3175
3175
|
for (const field of ['string', 'audio'] as const) {
|
|
3176
3176
|
if (def[field] !== undefined && typeof def[field] !== 'string') {
|
|
3177
|
-
throw new CompileError(`${where}: event "${name}" has ${field} ${JSON.stringify(def[field])}, which is not a string
|
|
3177
|
+
throw new CompileError(`${where}: event "${name}" has ${field} ${JSON.stringify(def[field])}, which is not a string`, 'CE_RIG_EVENT_STRING_TYPE');
|
|
3178
3178
|
}
|
|
3179
3179
|
}
|
|
3180
3180
|
// SkeletonJson.ts:478-481 reads these two ONLY inside `if (data.audioPath)`.
|
|
@@ -3183,7 +3183,7 @@ export function parseRigSpec(raw: unknown, where: string): RigSpec {
|
|
|
3183
3183
|
for (const field of ['volume', 'balance'] as const) {
|
|
3184
3184
|
if (def[field] !== undefined && def.audio === undefined) {
|
|
3185
3185
|
throw new CompileError(
|
|
3186
|
-
`${where}: event "${name}" declares ${field} but no "audio"; the parser reads ${field} only when an audio path is set, so it would be dropped in silence`,
|
|
3186
|
+
`${where}: event "${name}" declares ${field} but no "audio"; the parser reads ${field} only when an audio path is set, so it would be dropped in silence`, 'CE_RIG_EVENT_AUDIO_REQUIRED',
|
|
3187
3187
|
);
|
|
3188
3188
|
}
|
|
3189
3189
|
}
|