spine-rigc 2.57.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/docs/AUTHORING.md +607 -263
- package/package.json +1 -1
- package/skills/rigc/SKILL.md +3 -1
- 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/shared.ts +2 -2
- package/src/compile.ts +333 -333
- 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/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/deformgen.ts
CHANGED
|
@@ -377,13 +377,13 @@ export function evaluateDeformTransform(
|
|
|
377
377
|
soft: DeformSoftMask | null = null,
|
|
378
378
|
): DeformTransformReport {
|
|
379
379
|
if (transform === null || typeof transform !== 'object' || Array.isArray(transform)) {
|
|
380
|
-
throw new CompileError(`${where}: "transform" is ${JSON.stringify(transform)}; it is an object with a "kind"
|
|
380
|
+
throw new CompileError(`${where}: "transform" is ${JSON.stringify(transform)}; it is an object with a "kind"`, 'CE_DEFORM_TRANSFORM_OBJECT');
|
|
381
381
|
}
|
|
382
382
|
const kind = (transform as { kind?: unknown }).kind;
|
|
383
383
|
if (typeof kind !== 'string' || !(DEFORM_TRANSFORM_KINDS as readonly string[]).includes(kind)) {
|
|
384
384
|
throw new CompileError(
|
|
385
385
|
`${where}: "transform" has kind ${JSON.stringify(kind)}; this spec evaluates ${DEFORM_TRANSFORM_KINDS.join(', ')}. ` +
|
|
386
|
-
'A model that is none of those is still authorable as a "vertices" run.',
|
|
386
|
+
'A model that is none of those is still authorable as a "vertices" run.', 'CE_DEFORM_TRANSFORM_KIND',
|
|
387
387
|
);
|
|
388
388
|
}
|
|
389
389
|
const count = setup.length / 2;
|
|
@@ -432,14 +432,14 @@ export function evaluateDeformTransform(
|
|
|
432
432
|
throw new CompileError(
|
|
433
433
|
`${where}: transform ${kind} states both "depth": true and a radius ${JSON.stringify(t.radius)}. They are ` +
|
|
434
434
|
'two answers to how far forward each vertex sits — a cylinder derives it, a map states it — and a key ' +
|
|
435
|
-
'carrying both leaves a reader unable to say which one the output came from. Drop one.',
|
|
435
|
+
'carrying both leaves a reader unable to say which one the output came from. Drop one.', 'CE_DEFORM_DEPTH_RADIUS_CONFLICT',
|
|
436
436
|
);
|
|
437
437
|
}
|
|
438
438
|
if (fromDepth && depth === null) {
|
|
439
439
|
throw new CompileError(
|
|
440
440
|
`${where}: transform ${kind} says "depth": true and this attachment has no depth map. The map is named on ` +
|
|
441
441
|
'the attachment\'s generator, as `"depth": { "image": …, "near": …, "zScale": … }`, because it describes ' +
|
|
442
|
-
'the art rather than this key. Name it there, or give this key a radius.',
|
|
442
|
+
'the art rather than this key. Name it there, or give this key a radius.', 'CE_DEFORM_DEPTH_MAP_REQUIRED',
|
|
443
443
|
);
|
|
444
444
|
}
|
|
445
445
|
if (fromDepth && depth !== null && depth.length !== count) {
|
|
@@ -447,12 +447,12 @@ export function evaluateDeformTransform(
|
|
|
447
447
|
// emitted; stated because a silent mismatch here would turn into a turn
|
|
448
448
|
// evaluated against another vertex's depth.
|
|
449
449
|
throw new CompileError(
|
|
450
|
-
`${where}: transform ${kind} has ${depth.length} sampled depths for ${count} vertices`,
|
|
450
|
+
`${where}: transform ${kind} has ${depth.length} sampled depths for ${count} vertices`, 'CE_DEFORM_DEPTH_SAMPLE_COUNT',
|
|
451
451
|
);
|
|
452
452
|
}
|
|
453
453
|
const radius = fromDepth ? 0 : num(t.radius, 'radius', where);
|
|
454
454
|
if (!fromDepth && radius <= 0) {
|
|
455
|
-
throw new CompileError(`${where}: transform ${kind} has radius ${radius}; it is the radius of the cylinder the part is painted on, so a positive number
|
|
455
|
+
throw new CompileError(`${where}: transform ${kind} has radius ${radius}; it is the radius of the cylinder the part is painted on, so a positive number`, 'CE_DEFORM_RADIUS_POSITIVE');
|
|
456
456
|
}
|
|
457
457
|
const rad = (degrees * Math.PI) / 180;
|
|
458
458
|
const cosMinus1 = Math.cos(rad) - 1;
|
|
@@ -473,7 +473,7 @@ export function evaluateDeformTransform(
|
|
|
473
473
|
`${where}: transform ${kind} has radius ${radius}, and vertex ${v} sits at ${kind === 'yaw' ? 'x' : 'y'}=` +
|
|
474
474
|
`${setup[2 * v + along]}, which is ${round(Math.abs(u) - radius)} past it (about=${about}). The cylinder has no ` +
|
|
475
475
|
'surface there, so its depth would be 0 and the projection would be a different model at that vertex. ' +
|
|
476
|
-
'Raise the radius to where the part actually sits, or move the vertex.',
|
|
476
|
+
'Raise the radius to where the part actually sits, or move the vertex.', 'CE_DEFORM_VERTEX_OUTSIDE_CYLINDER',
|
|
477
477
|
);
|
|
478
478
|
}
|
|
479
479
|
// A depth map needs no such check: it states a surface everywhere it
|
|
@@ -533,7 +533,7 @@ export function evaluateDeformTransform(
|
|
|
533
533
|
`${where}: transform affine has scale [${scale[0]}, ${scale[1]}], whose determinant is ${round(det)}. ` +
|
|
534
534
|
'At or below zero the map mirrors or collapses the geometry and EVERY triangle reverses its winding, ' +
|
|
535
535
|
'which is the fold A39_DEFORM_KEEPS_TRIANGLE_WINDING refuses. A positive determinant is what makes this ' +
|
|
536
|
-
'kind incapable of folding a mesh.',
|
|
536
|
+
'kind incapable of folding a mesh.', 'CE_DEFORM_AFFINE_DETERMINANT',
|
|
537
537
|
);
|
|
538
538
|
}
|
|
539
539
|
for (let v = 0; v < count; v++) {
|
|
@@ -563,7 +563,7 @@ export function evaluateDeformTransform(
|
|
|
563
563
|
const wavelength = num(w.wavelength, 'wavelength', where);
|
|
564
564
|
const phase = w.phase === undefined ? 0 : num(w.phase, 'phase', where);
|
|
565
565
|
if (wavelength === 0) {
|
|
566
|
-
throw new CompileError(`${where}: transform wave has wavelength 0; one period cannot be zero long
|
|
566
|
+
throw new CompileError(`${where}: transform wave has wavelength 0; one period cannot be zero long`, 'CE_DEFORM_WAVELENGTH_ZERO');
|
|
567
567
|
}
|
|
568
568
|
const [along, axis] = axes(w.along, w.axis, 'wave', where);
|
|
569
569
|
const phaseRad = (phase * Math.PI) / 180;
|
|
@@ -609,11 +609,11 @@ export function evaluateDeformTransform(
|
|
|
609
609
|
throw new CompileError(
|
|
610
610
|
`${where}: transform bend has power ${power}; it is a whole number ≥ 1 (1 is an affine shear, 2 a cantilever ` +
|
|
611
611
|
'that is flat at "from"). A fractional power is not evaluated because it has no value on the side of "from" ' +
|
|
612
|
-
'the part does not reach.',
|
|
612
|
+
'the part does not reach.', 'CE_DEFORM_BEND_POWER_WHOLE',
|
|
613
613
|
);
|
|
614
614
|
}
|
|
615
615
|
if (to === from) {
|
|
616
|
-
throw new CompileError(`${where}: transform bend has from ${from} and to ${to}; they are the two ends of the bend, so they differ
|
|
616
|
+
throw new CompileError(`${where}: transform bend has from ${from} and to ${to}; they are the two ends of the bend, so they differ`, 'CE_DEFORM_BEND_ENDS_EQUAL');
|
|
617
617
|
}
|
|
618
618
|
const [along, axis] = axes(b.along, b.axis, 'bend', where);
|
|
619
619
|
const span = to - from;
|
|
@@ -679,7 +679,7 @@ export function evaluateDeformTransform(
|
|
|
679
679
|
'which quantises to 0 on the 1e-6 grid a model is evaluated on. So the key states a deformation and ' +
|
|
680
680
|
`emits the identity, and nothing downstream can tell it apart from a key that meant the setup pose. ` +
|
|
681
681
|
`${sampledTo}. A key that MEANS the identity states it in its own parameters (${identitySpelling}) or carries ` +
|
|
682
|
-
'no run at all.',
|
|
682
|
+
'no run at all.', 'CE_DEFORM_TRANSFORM_QUANTISED_IDENTITY',
|
|
683
683
|
);
|
|
684
684
|
}
|
|
685
685
|
|
|
@@ -712,7 +712,7 @@ export function evaluateDeformTransform(
|
|
|
712
712
|
throw new CompileError(
|
|
713
713
|
`${where}: transform ${kind} states ${stated} and moves ${plain.filter((d, i) => i % 2 === 0 && (d !== 0 || plain[i + 1] !== 0)).length} ` +
|
|
714
714
|
`of ${count} vertices, and envelope ${weighed.stated} weighs every one of them 0 — so the key states a ` +
|
|
715
|
-
`deformation and emits the identity. ${weighed.reach}`,
|
|
715
|
+
`deformation and emits the identity. ${weighed.reach}`, 'CE_DEFORM_ENVELOPE_ZERO_WEIGHT',
|
|
716
716
|
);
|
|
717
717
|
}
|
|
718
718
|
if (count > 0 && !identity && moving && offsets.every((d, i) => d === plain[i])) {
|
|
@@ -720,7 +720,7 @@ export function evaluateDeformTransform(
|
|
|
720
720
|
`${where}: transform ${kind} states envelope ${weighed.stated}, and it changes no emitted number: every vertex ` +
|
|
721
721
|
`the model moves takes weight 1 (${weighed.full} of ${count} vertices at 1, ${weighed.zero} at 0), so the run ` +
|
|
722
722
|
`is the un-enveloped one and the envelope is a field a reader would credit for an output it did not shape. ` +
|
|
723
|
-
`${weighed.reach}`,
|
|
723
|
+
`${weighed.reach}`, 'CE_DEFORM_ENVELOPE_NO_EFFECT',
|
|
724
724
|
);
|
|
725
725
|
}
|
|
726
726
|
stated = `${stated} envelope(${weighed.stated})`;
|
|
@@ -781,7 +781,7 @@ export function evaluateDeformEnvelope(
|
|
|
781
781
|
if (envelope === null || typeof envelope !== 'object' || Array.isArray(envelope)) {
|
|
782
782
|
throw new CompileError(
|
|
783
783
|
`${where}: transform field "envelope" is ${JSON.stringify(envelope)}; it is an object — a ramp ` +
|
|
784
|
-
'`{ "axis", "from", "to", "shape" }` or a mask `{ "mask" }`',
|
|
784
|
+
'`{ "axis", "from", "to", "shape" }` or a mask `{ "mask" }`', 'CE_DEFORM_ENVELOPE_OBJECT',
|
|
785
785
|
);
|
|
786
786
|
}
|
|
787
787
|
const e = envelope as DeformEnvelope;
|
|
@@ -791,13 +791,13 @@ export function evaluateDeformEnvelope(
|
|
|
791
791
|
throw new CompileError(
|
|
792
792
|
`${where}: transform field "envelope" states "mask" ${JSON.stringify(e.mask)} beside the ramp's ` +
|
|
793
793
|
`${rampStated.map((f) => `"${f}"`).join(', ')}. They are two answers to one question — what weight each vertex ` +
|
|
794
|
-
'takes — and a key carrying both leaves a reader unable to say which one the output came from. Drop one.',
|
|
794
|
+
'takes — and a key carrying both leaves a reader unable to say which one the output came from. Drop one.', 'CE_DEFORM_ENVELOPE_MASK_RAMP_CONFLICT',
|
|
795
795
|
);
|
|
796
796
|
}
|
|
797
797
|
if (e.mask === undefined && rampStated.length === 0) {
|
|
798
798
|
throw new CompileError(
|
|
799
799
|
`${where}: transform field "envelope" is ${JSON.stringify(envelope)}, which states no weight at all. It is a ramp ` +
|
|
800
|
-
'`{ "axis", "from", "to", "shape" }` or a mask `{ "mask" }`; a key that weighs every vertex 1 carries no envelope.',
|
|
800
|
+
'`{ "axis", "from", "to", "shape" }` or a mask `{ "mask" }`; a key that weighs every vertex 1 carries no envelope.', 'CE_DEFORM_ENVELOPE_WEIGHT_UNSTATED',
|
|
801
801
|
);
|
|
802
802
|
}
|
|
803
803
|
|
|
@@ -812,21 +812,21 @@ export function evaluateDeformEnvelope(
|
|
|
812
812
|
`${where}: transform field "envelope.mask" is ${JSON.stringify(mask)}, and this attachment has no "soft" block, ` +
|
|
813
813
|
'so the mask names nothing. An envelope\'s mask is the attachment\'s own soft mask (AUTHORING §3.4.6), the ' +
|
|
814
814
|
'one its generator sampled at every vertex when the mesh was built; give the generator a "soft" block, or ' +
|
|
815
|
-
'state a ramp instead.',
|
|
815
|
+
'state a ramp instead.', 'CE_DEFORM_ENVELOPE_SOFT_REQUIRED',
|
|
816
816
|
);
|
|
817
817
|
}
|
|
818
818
|
if (soft.mask !== mask) {
|
|
819
819
|
throw new CompileError(
|
|
820
820
|
`${where}: transform field "envelope.mask" is ${JSON.stringify(mask)}, and this attachment's soft mask is ` +
|
|
821
821
|
`${JSON.stringify(soft.mask)}, so the mask names nothing it was built with. An envelope reads the mask the ` +
|
|
822
|
-
'generator sampled at every vertex, and only that one.',
|
|
822
|
+
'generator sampled at every vertex, and only that one.', 'CE_DEFORM_ENVELOPE_MASK_MISMATCH',
|
|
823
823
|
);
|
|
824
824
|
}
|
|
825
825
|
if (soft.levels.length !== count) {
|
|
826
826
|
// Unreachable while the sampler walks the same vertex list the mesh
|
|
827
827
|
// emitted, as for a depth map; stated because a silent mismatch would
|
|
828
828
|
// weigh one vertex by another's level.
|
|
829
|
-
throw new CompileError(`${where}: the soft mask "${mask}" has ${soft.levels.length} sampled levels for ${count} vertices
|
|
829
|
+
throw new CompileError(`${where}: the soft mask "${mask}" has ${soft.levels.length} sampled levels for ${count} vertices`, 'CE_DEFORM_SOFT_SAMPLE_COUNT');
|
|
830
830
|
}
|
|
831
831
|
weights = soft.levels.map((level) => round(level));
|
|
832
832
|
stated = `mask=${mask}`;
|
|
@@ -842,7 +842,7 @@ export function evaluateDeformEnvelope(
|
|
|
842
842
|
throw new CompileError(
|
|
843
843
|
`${where}: transform field "envelope.shape" is ${JSON.stringify(e.shape)}; it is one of ` +
|
|
844
844
|
`${DEFORM_ENVELOPE_SHAPES.map((name) => JSON.stringify(name)).join(', ')} — how the weight rises from 0 at ` +
|
|
845
|
-
'"from" to 1 at "to": "linear" is u, "smooth" is u²(3 − 2u)',
|
|
845
|
+
'"from" to 1 at "to": "linear" is u, "smooth" is u²(3 − 2u)', 'CE_DEFORM_ENVELOPE_SHAPE',
|
|
846
846
|
);
|
|
847
847
|
}
|
|
848
848
|
const length = hypot2(axis[0], axis[1]);
|
|
@@ -855,13 +855,13 @@ export function evaluateDeformEnvelope(
|
|
|
855
855
|
throw new CompileError(
|
|
856
856
|
`${where}: transform field "envelope.axis" is [${axis[0]}, ${axis[1]}], whose length is ${round(length)}; it is a ` +
|
|
857
857
|
'unit direction, length 1 on the model\'s 1e-6 grid. A longer or shorter one would scale every position the ' +
|
|
858
|
-
'ramp compares with "from" and "to" — divide it by its length, and state "from" and "to" along the unit one.',
|
|
858
|
+
'ramp compares with "from" and "to" — divide it by its length, and state "from" and "to" along the unit one.', 'CE_DEFORM_ENVELOPE_AXIS_UNIT',
|
|
859
859
|
);
|
|
860
860
|
}
|
|
861
861
|
if (from === to) {
|
|
862
862
|
throw new CompileError(
|
|
863
863
|
`${where}: transform fields "envelope.from" and "envelope.to" are both ${from}; they are where the weight is 0 ` +
|
|
864
|
-
'and where it is 1, so they differ — a ramp of no length is a step, and a step is the edge a model exists not to leave.',
|
|
864
|
+
'and where it is 1, so they differ — a ramp of no length is a step, and a step is the edge a model exists not to leave.', 'CE_DEFORM_ENVELOPE_ENDS_EQUAL',
|
|
865
865
|
);
|
|
866
866
|
}
|
|
867
867
|
const span = to - from;
|
|
@@ -913,7 +913,7 @@ export function checkEnvelopeWeights(weights: readonly number[], field: string,
|
|
|
913
913
|
if (!(w >= 0 && w <= 1)) {
|
|
914
914
|
throw new CompileError(
|
|
915
915
|
`${where}: transform field "${field}" weighs vertex ${v} at ${w}; an envelope's weight is in [0, 1] — 0 leaves ` +
|
|
916
|
-
'the vertex where it is and 1 moves it the whole way the model says, and nothing past either end is a weight.',
|
|
916
|
+
'the vertex where it is and 1 moves it the whole way the model says, and nothing past either end is a weight.', 'CE_DEFORM_ENVELOPE_WEIGHT_RANGE',
|
|
917
917
|
);
|
|
918
918
|
}
|
|
919
919
|
}
|
|
@@ -922,7 +922,7 @@ export function checkEnvelopeWeights(weights: readonly number[], field: string,
|
|
|
922
922
|
/** One required finite number, refused by field name rather than compiled as a NaN. */
|
|
923
923
|
function num(value: unknown, field: string, where: string): number {
|
|
924
924
|
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
925
|
-
throw new CompileError(`${where}: transform field "${field}" is ${JSON.stringify(value)}; it is a finite number the spec has to state
|
|
925
|
+
throw new CompileError(`${where}: transform field "${field}" is ${JSON.stringify(value)}; it is a finite number the spec has to state`, 'CE_DEFORM_FIELD_NUMBER_FINITE');
|
|
926
926
|
}
|
|
927
927
|
return value;
|
|
928
928
|
}
|
|
@@ -930,7 +930,7 @@ function num(value: unknown, field: string, where: string): number {
|
|
|
930
930
|
/** One required `[a, b]`. */
|
|
931
931
|
function pair(value: unknown, field: string, where: string): [number, number] {
|
|
932
932
|
if (!Array.isArray(value) || value.length !== 2) {
|
|
933
|
-
throw new CompileError(`${where}: transform field "${field}" is ${JSON.stringify(value)}; it is a pair, [x, y]
|
|
933
|
+
throw new CompileError(`${where}: transform field "${field}" is ${JSON.stringify(value)}; it is a pair, [x, y]`, 'CE_DEFORM_FIELD_PAIR');
|
|
934
934
|
}
|
|
935
935
|
return [num(value[0], `${field}[0]`, where), num(value[1], `${field}[1]`, where)];
|
|
936
936
|
}
|
|
@@ -948,14 +948,14 @@ function axes(along: unknown, axis: unknown, kind: string, where: string): [0 |
|
|
|
948
948
|
['axis', axis],
|
|
949
949
|
] as const) {
|
|
950
950
|
if (value !== 'x' && value !== 'y') {
|
|
951
|
-
throw new CompileError(`${where}: transform ${kind} has ${name}=${JSON.stringify(value)}; it is "x" or "y"
|
|
951
|
+
throw new CompileError(`${where}: transform ${kind} has ${name}=${JSON.stringify(value)}; it is "x" or "y"`, 'CE_DEFORM_COORDINATE_NAME');
|
|
952
952
|
}
|
|
953
953
|
}
|
|
954
954
|
if (along === axis) {
|
|
955
955
|
throw new CompileError(
|
|
956
956
|
`${where}: transform ${kind} reads ${String(along)} and displaces ${String(axis)} — the same coordinate, which is a ` +
|
|
957
957
|
'stretch rather than a bend or a wave. An affine scale states that, and states the determinant that proves it ' +
|
|
958
|
-
'keeps every triangle\'s winding.',
|
|
958
|
+
'keeps every triangle\'s winding.', 'CE_DEFORM_SAME_READ_DISPLACE_AXIS',
|
|
959
959
|
);
|
|
960
960
|
}
|
|
961
961
|
return [along === 'x' ? 0 : 1, axis === 'x' ? 0 : 1];
|
package/src/emit_spine.ts
CHANGED
|
@@ -137,7 +137,7 @@ export function boneIndexOf(bones: readonly ModelBone[]): (bone: string) => numb
|
|
|
137
137
|
const index = new Map(bones.map((bone, i) => [bone.name, i] as const));
|
|
138
138
|
return (bone) => {
|
|
139
139
|
const at = index.get(bone);
|
|
140
|
-
if (at === undefined) throw new CompileError(`internal: a weighted vertex binds bone "${bone}", which is not in the model's bone list
|
|
140
|
+
if (at === undefined) throw new CompileError(`internal: a weighted vertex binds bone "${bone}", which is not in the model's bone list`, 'CE_EMIT_INTERNAL_WEIGHT_BONE_UNKNOWN');
|
|
141
141
|
return at;
|
|
142
142
|
};
|
|
143
143
|
}
|
|
@@ -492,7 +492,7 @@ export function emitConstraint(model: ModelConstraint): SpineConstraint {
|
|
|
492
492
|
const out: SpineConstraint = { name: model.name, type: model.kind };
|
|
493
493
|
const stray = Object.keys(model).filter((key) => key !== 'kind' && key !== 'name' && key !== 'declaredIn' && !order.includes(key));
|
|
494
494
|
if (stray.length > 0) {
|
|
495
|
-
throw new CompileError(`internal: ${model.kind} constraint "${model.name}" carries ${stray.join(', ')}, which the emitter has no place for
|
|
495
|
+
throw new CompileError(`internal: ${model.kind} constraint "${model.name}" carries ${stray.join(', ')}, which the emitter has no place for`, 'CE_EMIT_INTERNAL_CONSTRAINT_FIELDS');
|
|
496
496
|
}
|
|
497
497
|
for (const field of order) {
|
|
498
498
|
const value = model[field];
|
|
@@ -641,11 +641,11 @@ function emitDrawOrderKey(key: ModelKey, slotIndex: ReadonlyMap<string, number>)
|
|
|
641
641
|
const out = emitKey(key);
|
|
642
642
|
if (key.offsets === undefined) return out;
|
|
643
643
|
if (!isDrawOrderMoves(key.offsets)) {
|
|
644
|
-
throw new CompileError(`internal: a draw-order key at t=${key.time} carries offsets that are not slot/offset moves
|
|
644
|
+
throw new CompileError(`internal: a draw-order key at t=${key.time} carries offsets that are not slot/offset moves`, 'CE_EMIT_INTERNAL_DRAW_ORDER_SHAPE');
|
|
645
645
|
}
|
|
646
646
|
const indexOf = (slot: string): number => {
|
|
647
647
|
const at = slotIndex.get(slot);
|
|
648
|
-
if (at === undefined) throw new CompileError(`internal: a draw-order key at t=${key.time} moves slot "${slot}", which is not in the model's slot list
|
|
648
|
+
if (at === undefined) throw new CompileError(`internal: a draw-order key at t=${key.time} moves slot "${slot}", which is not in the model's slot list`, 'CE_EMIT_INTERNAL_DRAW_ORDER_SLOT_UNKNOWN');
|
|
649
649
|
return at;
|
|
650
650
|
};
|
|
651
651
|
out.offsets = key.offsets
|
|
@@ -734,7 +734,7 @@ export function emitAnimations(
|
|
|
734
734
|
const out: Record<string, SpineAnimation> = {};
|
|
735
735
|
for (const name of names) {
|
|
736
736
|
const animation = animations.get(name);
|
|
737
|
-
if (animation === undefined) throw new CompileError(`internal: the animation order named "${name}", which is not in the model
|
|
737
|
+
if (animation === undefined) throw new CompileError(`internal: the animation order named "${name}", which is not in the model`, 'CE_EMIT_INTERNAL_ANIMATION_UNKNOWN');
|
|
738
738
|
out[name] = emitAnimation(animation, slotIndex);
|
|
739
739
|
}
|
|
740
740
|
return out;
|
package/src/errors.ts
CHANGED
|
@@ -17,6 +17,14 @@
|
|
|
17
17
|
import type { DroppedState } from './types.ts';
|
|
18
18
|
|
|
19
19
|
export class CompileError extends Error {
|
|
20
|
+
/** Stable guide row identity; absent on legacy one-argument library errors. */
|
|
21
|
+
declare readonly code?: string;
|
|
22
|
+
|
|
23
|
+
constructor(message?: string, codeOrOptions?: string | ErrorOptions, options?: ErrorOptions) {
|
|
24
|
+
super(message, typeof codeOrOptions === 'string' ? options : codeOrOptions);
|
|
25
|
+
if (typeof codeOrOptions === 'string') this.code = codeOrOptions;
|
|
26
|
+
}
|
|
27
|
+
|
|
20
28
|
/**
|
|
21
29
|
* States the manifest listed whose art was not found, recorded before this
|
|
22
30
|
* refusal was raised (issue #671).
|
package/src/guide.ts
CHANGED
|
@@ -54,17 +54,12 @@ export function guidePointer(rule: string): string {
|
|
|
54
54
|
return ` (see ${GUIDE_BIN} guide ${rule})`;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
/**
|
|
58
|
-
* The section a compile error points at (HQ's ruling on rig-c/parts#343): no compile error names a rule — 0 of
|
|
59
|
-
* the 463 throw sites carry a code, and §5.1's tables are keyed on the message — and §5.1 has no #### a thrower
|
|
60
|
-
* could name, so every one points at the section its row is in. When §5.1 grows #### families a thrower can
|
|
61
|
-
* name, this is where `§5.1.<k>` goes.
|
|
62
|
-
*/
|
|
57
|
+
/** Legacy library callers may construct an uncoded error; their pointer remains the compile section. */
|
|
63
58
|
export const COMPILE_ERROR_SECTION = '§5.1';
|
|
64
59
|
|
|
65
|
-
/**
|
|
66
|
-
export function compileErrorLine(message: string): string {
|
|
67
|
-
return `rigc compile error: ${message}${guidePointer(COMPILE_ERROR_SECTION)}`;
|
|
60
|
+
/** The original prefix and message, followed by this site's stable code (or the legacy section). */
|
|
61
|
+
export function compileErrorLine(message: string, code?: string): string {
|
|
62
|
+
return `rigc compile error: ${message}${guidePointer(code ?? COMPILE_ERROR_SECTION)}`;
|
|
68
63
|
}
|
|
69
64
|
|
|
70
65
|
/** A refusal naming its rule, unindented, as `guide` prints its own: `FAIL <RULE>: <detail> (see rigc guide <RULE>)`. */
|
package/src/keys.ts
CHANGED
|
@@ -112,7 +112,7 @@ export function refuseUnknownKeys(
|
|
|
112
112
|
`${where}: ${what} has ${strays.length === 1 ? 'a key' : `${strays.length} keys`} this compiler does not read: ` +
|
|
113
113
|
`${named.join(', ')}. Nothing reads such a key, so it would be dropped from the emitted skeleton in silence — ` +
|
|
114
114
|
'fix the spelling or remove it. ' +
|
|
115
|
-
(known.length === 0 ? 'This shape has no fields at all.' : `Known here: ${[...known].sort().join(', ')}.`),
|
|
115
|
+
(known.length === 0 ? 'This shape has no fields at all.' : `Known here: ${[...known].sort().join(', ')}.`), 'CE_KEYS_UNKNOWN_FIELD',
|
|
116
116
|
);
|
|
117
117
|
}
|
|
118
118
|
|
|
@@ -182,7 +182,7 @@ export function refuseNumbersTheFileCannotCarry(
|
|
|
182
182
|
throw new CompileError(
|
|
183
183
|
`${where}: ${place(path)} is ${String(node)}; a number in this file is finite at float32 precision, at most ` +
|
|
184
184
|
`±${String(FLOAT32_MAX)}, because the skeleton is written as float32 — past that it would be emitted as ` +
|
|
185
|
-
`Infinity, which JSON writes as null, a value the file never stated.${why}`,
|
|
185
|
+
`Infinity, which JSON writes as null, a value the file never stated.${why}`, 'CE_KEYS_NUMBER_FLOAT32_FINITE',
|
|
186
186
|
);
|
|
187
187
|
}
|
|
188
188
|
if (Array.isArray(node)) {
|
|
@@ -409,7 +409,7 @@ export function refuseValuesOfTheWrongType(
|
|
|
409
409
|
const wrong = wrongTypeAt(visit.node[key], type);
|
|
410
410
|
if (wrong === null) continue;
|
|
411
411
|
throw new CompileError(
|
|
412
|
-
`${where}: ${visit.name([key, ...wrong.tail])} is ${typeFound(wrong.value)}; ${wrong.required} is required`,
|
|
412
|
+
`${where}: ${visit.name([key, ...wrong.tail])} is ${typeFound(wrong.value)}; ${wrong.required} is required`, 'CE_KEYS_FIELD_VALUE_TYPE',
|
|
413
413
|
);
|
|
414
414
|
}
|
|
415
415
|
}
|
|
@@ -479,7 +479,7 @@ export function refuseValuesOutsideTheirSet(
|
|
|
479
479
|
if (typeof value === 'string' && rule.set.includes(value)) continue;
|
|
480
480
|
throw new CompileError(
|
|
481
481
|
`${where}: ${visit.name([key])} is ${JSON.stringify(value) ?? String(value)}; one of ` +
|
|
482
|
-
`${rule.set.map((name) => JSON.stringify(name)).join(', ')} — ${rule.readAs}`,
|
|
482
|
+
`${rule.set.map((name) => JSON.stringify(name)).join(', ')} — ${rule.readAs}`, 'CE_KEYS_ENUM_VALUE',
|
|
483
483
|
);
|
|
484
484
|
}
|
|
485
485
|
}
|
package/src/model.ts
CHANGED
|
@@ -733,7 +733,7 @@ function ordered(record: object, keys: readonly string[], where: string, value:
|
|
|
733
733
|
const own = record as Record<string, unknown>;
|
|
734
734
|
for (const key of Object.keys(own)) {
|
|
735
735
|
if (!keys.includes(key)) {
|
|
736
|
-
throw new CompileError(`internal: the model document has no place for field "${key}" of ${where}; it writes [${keys.join(', ')}]
|
|
736
|
+
throw new CompileError(`internal: the model document has no place for field "${key}" of ${where}; it writes [${keys.join(', ')}]`, 'CE_MODEL_INTERNAL_FIELD_UNREPRESENTABLE');
|
|
737
737
|
}
|
|
738
738
|
}
|
|
739
739
|
const out: { [key: string]: DocValue } = {};
|
|
@@ -752,13 +752,13 @@ function ordered(record: object, keys: readonly string[], where: string, value:
|
|
|
752
752
|
function plain(value: unknown, where: string): DocValue {
|
|
753
753
|
if (value === null || typeof value === 'string' || typeof value === 'boolean') return value;
|
|
754
754
|
if (typeof value === 'number') {
|
|
755
|
-
if (!Number.isFinite(value)) throw new CompileError(`internal: the model document cannot carry ${value} at ${where}; JSON writes it as null
|
|
756
|
-
if (Object.is(value, -0)) throw new CompileError(`internal: the model document cannot carry -0 at ${where}; JSON writes it as 0
|
|
755
|
+
if (!Number.isFinite(value)) throw new CompileError(`internal: the model document cannot carry ${value} at ${where}; JSON writes it as null`, 'CE_MODEL_INTERNAL_NONFINITE_NUMBER');
|
|
756
|
+
if (Object.is(value, -0)) throw new CompileError(`internal: the model document cannot carry -0 at ${where}; JSON writes it as 0`, 'CE_MODEL_INTERNAL_NEGATIVE_ZERO');
|
|
757
757
|
return value;
|
|
758
758
|
}
|
|
759
759
|
if (Array.isArray(value)) {
|
|
760
760
|
return value.map((item, i) => {
|
|
761
|
-
if (item === undefined) throw new CompileError(`internal: the model document cannot carry undefined at ${where}[${i}]; JSON writes it as null
|
|
761
|
+
if (item === undefined) throw new CompileError(`internal: the model document cannot carry undefined at ${where}[${i}]; JSON writes it as null`, 'CE_MODEL_INTERNAL_UNDEFINED_ELEMENT');
|
|
762
762
|
return plain(item, `${where}[${i}]`);
|
|
763
763
|
});
|
|
764
764
|
}
|
|
@@ -767,7 +767,7 @@ function plain(value: unknown, where: string): DocValue {
|
|
|
767
767
|
for (const [key, item] of Object.entries(value)) if (item !== undefined) out[key] = plain(item, `${where}.${key}`);
|
|
768
768
|
return out;
|
|
769
769
|
}
|
|
770
|
-
throw new CompileError(`internal: the model document cannot carry ${Object.prototype.toString.call(value)} at ${where}; it writes plain data only
|
|
770
|
+
throw new CompileError(`internal: the model document cannot carry ${Object.prototype.toString.call(value)} at ${where}; it writes plain data only`, 'CE_MODEL_INTERNAL_NONPLAIN_VALUE');
|
|
771
771
|
}
|
|
772
772
|
|
|
773
773
|
/** A `Map` as the array of its entries in the map's order, each `{ name, … }`. */
|
|
@@ -818,11 +818,11 @@ function verticesOf(vertices: ModelVertices, where: string): DocValue {
|
|
|
818
818
|
|
|
819
819
|
function attachmentOf(entry: SkinTableEntry, where: string): DocValue {
|
|
820
820
|
const fields = ATTACHMENT_FIELDS[entry.kind];
|
|
821
|
-
if (fields === undefined) throw new CompileError(`internal: the model document knows no attachment kind "${String(entry.kind)}" at ${where}
|
|
821
|
+
if (fields === undefined) throw new CompileError(`internal: the model document knows no attachment kind "${String(entry.kind)}" at ${where}`, 'CE_MODEL_INTERNAL_ATTACHMENT_KIND');
|
|
822
822
|
if (entry.kind === 'region' && (entry.atlas === undefined) === (entry.sequence === undefined)) {
|
|
823
823
|
throw new CompileError(
|
|
824
824
|
`internal: the region at ${where} carries ${entry.atlas === undefined ? 'neither an atlas rectangle nor a sequence' : 'both an atlas rectangle and a sequence'}; ` +
|
|
825
|
-
'a region states exactly one of the two, and a sequence holds a rectangle per frame (issue #935)',
|
|
825
|
+
'a region states exactly one of the two, and a sequence holds a rectangle per frame (issue #935)', 'CE_MODEL_INTERNAL_REGION_SOURCE',
|
|
826
826
|
);
|
|
827
827
|
}
|
|
828
828
|
return ordered(entry, fields, where, (key, v) => {
|
|
@@ -840,7 +840,7 @@ function attachmentOf(entry: SkinTableEntry, where: string): DocValue {
|
|
|
840
840
|
/** One rectangle, in `ModelAtlasRect`'s order, every field required. */
|
|
841
841
|
function atlasRectOf(rect: ModelAtlasRect, where: string): DocValue {
|
|
842
842
|
for (const key of ATLAS_RECT_FIELDS) {
|
|
843
|
-
if (rect[key] === undefined) throw new CompileError(`internal: the atlas rectangle at ${where} has no ${key}; it states all of [${ATLAS_RECT_FIELDS.join(', ')}]
|
|
843
|
+
if (rect[key] === undefined) throw new CompileError(`internal: the atlas rectangle at ${where} has no ${key}; it states all of [${ATLAS_RECT_FIELDS.join(', ')}]`, 'CE_MODEL_INTERNAL_ATLAS_FIELD_REQUIRED');
|
|
844
844
|
}
|
|
845
845
|
return ordered(rect, ATLAS_RECT_FIELDS, where);
|
|
846
846
|
}
|
|
@@ -922,7 +922,7 @@ function editorOrderOf(model: CompiledModel): DocValue {
|
|
|
922
922
|
if (skin !== undefined) same(`editorOrder.skins[${i}] "${entry.name}".slots`, entry.slots, Object.keys(skin.attachments));
|
|
923
923
|
}
|
|
924
924
|
same('editorOrder.animations', order.animations, [...model.animations.keys()]);
|
|
925
|
-
if (problems.length > 0) throw new CompileError(`internal: the model's editor order is not its own: ${problems.join('; ')}
|
|
925
|
+
if (problems.length > 0) throw new CompileError(`internal: the model's editor order is not its own: ${problems.join('; ')}`, 'CE_MODEL_INTERNAL_EDITOR_ORDER');
|
|
926
926
|
return {
|
|
927
927
|
skins: order.skins.map((entry, i) => ordered(entry, ['name', 'slots'], `editorOrder.skins[${i}]`)),
|
|
928
928
|
animations: plain(order.animations, 'editorOrder.animations'),
|
|
@@ -1040,7 +1040,7 @@ const MODEL_DOCUMENT_LEFT_OUT: readonly string[] = ['setupWorld'];
|
|
|
1040
1040
|
export function modelDocument(model: CompiledModel, skeletonText: string, atlasText: string): string {
|
|
1041
1041
|
for (const key of Object.keys(model)) {
|
|
1042
1042
|
if (!MODEL_DOCUMENT_FIELDS.includes(key) && !MODEL_DOCUMENT_LEFT_OUT.includes(key)) {
|
|
1043
|
-
throw new CompileError(`internal: the model document has no place for the model's field "${key}"; it writes [${MODEL_DOCUMENT_FIELDS.join(', ')}] and leaves out [${MODEL_DOCUMENT_LEFT_OUT.join(', ')}]
|
|
1043
|
+
throw new CompileError(`internal: the model document has no place for the model's field "${key}"; it writes [${MODEL_DOCUMENT_FIELDS.join(', ')}] and leaves out [${MODEL_DOCUMENT_LEFT_OUT.join(', ')}]`, 'CE_MODEL_INTERNAL_DOCUMENT_FIELD');
|
|
1044
1044
|
}
|
|
1045
1045
|
}
|
|
1046
1046
|
const doc: { [key: string]: DocValue } = {
|
package/src/motion.ts
CHANGED
|
@@ -289,7 +289,7 @@ function describe(v: unknown): string {
|
|
|
289
289
|
* has seen one has seen the format.
|
|
290
290
|
*/
|
|
291
291
|
function refuse(where: string, key: string, is: unknown, hint: string): never {
|
|
292
|
-
throw new CompileError(`${where}: \`${key}\` is ${describe(is)}; ${hint}
|
|
292
|
+
throw new CompileError(`${where}: \`${key}\` is ${describe(is)}; ${hint}`, 'CE_MOTION_FIELD_SHAPE');
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
/**
|
|
@@ -436,7 +436,7 @@ function parsePhysics(raw: unknown, where: string): void {
|
|
|
436
436
|
`${where}: \`${key}\` names a physics constraint "${EVERY_GLOBAL_PHYSICS}", and that name is reserved: a ` +
|
|
437
437
|
`track's \`"physics": "${EVERY_GLOBAL_PHYSICS}"\` is the timeline that names no constraint and drives ` +
|
|
438
438
|
'every one declaring the keyed property global, so a constraint called ' +
|
|
439
|
-
`"${EVERY_GLOBAL_PHYSICS}" could not be keyed by name. Give it another name`,
|
|
439
|
+
`"${EVERY_GLOBAL_PHYSICS}" could not be keyed by name. Give it another name`, 'CE_MOTION_PHYSICS_NAME_RESERVED',
|
|
440
440
|
);
|
|
441
441
|
}
|
|
442
442
|
const spec = needObj(entry, where, key,'a physics constraint is an object naming the bone it drives and the components it drives it in');
|
|
@@ -674,7 +674,7 @@ function parseSequence(raw: unknown, where: string, at: string): void {
|
|
|
674
674
|
: '') +
|
|
675
675
|
'. The runtime advances the frame by `(time - keyTime) / delay`, which is Infinity at 0, and ' +
|
|
676
676
|
'`Infinity | 0` is 0 — so the key shows its first frame for as long as it lasts, which is "hold" spelt ' +
|
|
677
|
-
`as "${mode}". State the seconds per frame, or write "hold"`,
|
|
677
|
+
`as "${mode}". State the seconds per frame, or write "hold"`, 'CE_MOTION_SEQUENCE_ADVANCE_ZERO_DELAY',
|
|
678
678
|
);
|
|
679
679
|
}
|
|
680
680
|
}
|
|
@@ -766,10 +766,10 @@ export function parseMotionSpec(raw: unknown, where: string): MotionSpec {
|
|
|
766
766
|
|
|
767
767
|
function parseMotionSpecInto(raw: unknown, where: string, visits: readonly ShapeVisit[]): MotionSpec {
|
|
768
768
|
if (!isObj(raw)) {
|
|
769
|
-
throw new CompileError(`${where}: a motion spec must be a JSON object, and this file holds ${describe(raw)}
|
|
769
|
+
throw new CompileError(`${where}: a motion spec must be a JSON object, and this file holds ${describe(raw)}`, 'CE_MOTION_SPEC_OBJECT');
|
|
770
770
|
}
|
|
771
771
|
if (raw.spec !== MOTION_SPEC_VERSION) {
|
|
772
|
-
throw new CompileError(`${where}: unknown motion spec version: ${String(raw.spec)}, expected "${MOTION_SPEC_VERSION}"
|
|
772
|
+
throw new CompileError(`${where}: unknown motion spec version: ${String(raw.spec)}, expected "${MOTION_SPEC_VERSION}"`, 'CE_MOTION_SPEC_VERSION');
|
|
773
773
|
}
|
|
774
774
|
// Before the field checks, for the reason `parseRigSpec` puts its own first: a
|
|
775
775
|
// key nothing reads is often the CAUSE of the field that is missing, and
|
|
@@ -793,7 +793,7 @@ function parseMotionSpecInto(raw: unknown, where: string, visits: readonly Shape
|
|
|
793
793
|
|
|
794
794
|
const animations = needObj(raw.animations, where, 'animations', 'it is a table keyed by animation name, `{ "<name>": { "duration": …, "tracks": [...] } }` (write `{}` for a static rig)');
|
|
795
795
|
for (const [name, anim] of Object.entries(animations)) {
|
|
796
|
-
if (name.length === 0) throw new CompileError(`${where}: an animation has an empty name
|
|
796
|
+
if (name.length === 0) throw new CompileError(`${where}: an animation has an empty name`, 'CE_MOTION_ANIMATION_NAME_EMPTY');
|
|
797
797
|
parseAnimation(anim, where, name);
|
|
798
798
|
}
|
|
799
799
|
|