spine-rigc 2.54.0 → 2.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/docs/AUTHORING.md +144 -24
- package/package.json +1 -1
- package/src/assertions/bodies/a23.ts +80 -5
- package/src/cli/core_commands.ts +73 -4
- package/src/cli/shared.ts +11 -1
- package/src/passthrough.ts +575 -0
- package/src/timelines.ts +75 -29
- package/src/types.ts +4 -3
package/src/timelines.ts
CHANGED
|
@@ -476,7 +476,7 @@ export interface PhysicsPoseRule {
|
|
|
476
476
|
* the value, and the row's `why` — the key's sentence — opens with the arms a
|
|
477
477
|
* key can still take. Until this field existed `src/types.ts` said of all four
|
|
478
478
|
* rows that "the bounds are the runtime's, not a policy", which is true of two
|
|
479
|
-
* of the
|
|
479
|
+
* of the nine ways out.
|
|
480
480
|
*/
|
|
481
481
|
basis: readonly PhysicsBoundBasis[];
|
|
482
482
|
/** The bound in words, for a message: what the value has to be. */
|
|
@@ -517,7 +517,7 @@ const STRENGTH_OUTSIDE: readonly PhysicsOutsideArm[] = [
|
|
|
517
517
|
];
|
|
518
518
|
|
|
519
519
|
/**
|
|
520
|
-
* The
|
|
520
|
+
* The nine ways out of the four bounds, each with its basis (issue #798). They
|
|
521
521
|
* are named constants rather than literals inside the rows because each row's
|
|
522
522
|
* `why` quotes its own — the key's sentence and `A23`'s setup sentence are then
|
|
523
523
|
* one text about one number, as `STRENGTH_OUTSIDE` already made them for two.
|
|
@@ -531,7 +531,8 @@ const STRENGTH_OUTSIDE: readonly PhysicsOutsideArm[] = [
|
|
|
531
531
|
* exactly where the rig with no constraint does, on every step; `mix` −0.5 is
|
|
532
532
|
* finite and moves the bone by exactly −1× what +0.5 moves it by; `damping` 2 is
|
|
533
533
|
* finite over the walk (Infinity only at step 1,065); `damping` −0.5 at 45 fps is
|
|
534
|
-
* NaN at step 2. `
|
|
534
|
+
* NaN at step 2. The ninth, `damping` 1 (issue #1422), is finite on every step
|
|
535
|
+
* and its velocity never decays. `T113` re-takes all nine on every run.
|
|
535
536
|
*/
|
|
536
537
|
const MIX_BELOW_0: PhysicsBoundBasis = {
|
|
537
538
|
kind: 'behavioural',
|
|
@@ -584,6 +585,33 @@ const DAMPING_ABOVE_1: PhysicsBoundBasis = {
|
|
|
584
585
|
witness: { value: 2 },
|
|
585
586
|
does: 'above 1 every velocity grows on every step and the offset diverges',
|
|
586
587
|
};
|
|
588
|
+
/**
|
|
589
|
+
* `damping` at exactly 1 (issue #1422) — inside the bound from #794 until then.
|
|
590
|
+
*
|
|
591
|
+
* 🔑 The decay `damping ** (60 * step)` is 1 at every rate, so the step map
|
|
592
|
+
* `physicsStepStability` derives has determinant 1, and for every `k` under the
|
|
593
|
+
* bound its two eigenvalues are a complex pair of modulus √1 = 1: ON the unit
|
|
594
|
+
* circle, where every other damping puts them inside it. Nothing an excitation
|
|
595
|
+
* puts into the offset ever decays, so the rig is marginal at best — and the
|
|
596
|
+
* excitation is not benign. [measured on issue #1422, spine-core and rigc's core
|
|
597
|
+
* identical bit for bit, x drive, inertia 1, fps 30, strength 100, the runtime's
|
|
598
|
+
* default world, 600 s at 60 Hz] one such link held its peak offset at 40.3 for
|
|
599
|
+
* the whole walk; two identical ones on one chain grew it LINEARLY, 6.03e3 at
|
|
600
|
+
* 60 s to 6.09e4 at 600 s; three ran past 10⁶ at 316 s. The parent's undamped
|
|
601
|
+
* oscillation reaches the child through inertia at exactly the child's own
|
|
602
|
+
* frequency. Detuning the second link's strength by 1 % held it flat, which is
|
|
603
|
+
* why the refusal is of the value and not of a pair: equal maps are the edge,
|
|
604
|
+
* and no authored number states how close is too close.
|
|
605
|
+
*/
|
|
606
|
+
const DAMPING_AT_1: PhysicsBoundBasis = {
|
|
607
|
+
kind: 'behavioural',
|
|
608
|
+
when: (v) => v === 1,
|
|
609
|
+
witness: { value: 1 },
|
|
610
|
+
does:
|
|
611
|
+
'at 1 the per-step decay is 1 at every rate, so no velocity ever decays: the step\'s two eigenvalues lie ON the unit ' +
|
|
612
|
+
'circle (modulus √1 = 1) rather than inside it, so nothing a push puts in (wind, gravity, a key, the bone moving) ever ' +
|
|
613
|
+
'dies out, and two such constraints on one chain stepping alike resonate and grow without limit',
|
|
614
|
+
};
|
|
587
615
|
const DAMPING_BELOW_0: PhysicsBoundBasis = {
|
|
588
616
|
kind: 'arithmetic',
|
|
589
617
|
when: (v) => v < 0,
|
|
@@ -626,8 +654,8 @@ const DAMPING_BELOW_0: PhysicsBoundBasis = {
|
|
|
626
654
|
*
|
|
627
655
|
* 🔎 Each row's `basis` says, per way out, whether the runtime's arithmetic
|
|
628
656
|
* fails there or the value runs and rigc refuses what it does (issue #798). Of
|
|
629
|
-
* the
|
|
630
|
-
* and
|
|
657
|
+
* the nine ways out, two are arithmetic — `mass` at 0 and `damping` below 0 —
|
|
658
|
+
* and seven are rigc's call.
|
|
631
659
|
*/
|
|
632
660
|
export const PHYSICS_POSE_RULES: PhysicsPoseRule[] = [
|
|
633
661
|
{
|
|
@@ -697,7 +725,7 @@ export const PHYSICS_POSE_RULES: PhysicsPoseRule[] = [
|
|
|
697
725
|
timeline: 'damping',
|
|
698
726
|
field: 'damping',
|
|
699
727
|
toPose: (v) => v,
|
|
700
|
-
poseOk: (v) => v >= 0 && v
|
|
728
|
+
poseOk: (v) => v >= 0 && v < 1,
|
|
701
729
|
keyOk: null,
|
|
702
730
|
inertAtSetup: false,
|
|
703
731
|
outside: null,
|
|
@@ -705,22 +733,27 @@ export const PHYSICS_POSE_RULES: PhysicsPoseRule[] = [
|
|
|
705
733
|
// above 1 is a run-away, finite until the velocity overflows (step 1,065
|
|
706
734
|
// for a setup of 2 at 60 fps), so it is behavioural by the line
|
|
707
735
|
// `PhysicsBoundBasis` draws — the card that asked for the bases (#798)
|
|
708
|
-
// counted it arithmetic, and the walk says otherwise.
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
//
|
|
714
|
-
//
|
|
715
|
-
//
|
|
716
|
-
//
|
|
717
|
-
//
|
|
718
|
-
//
|
|
719
|
-
//
|
|
736
|
+
// counted it arithmetic, and the walk says otherwise. 1 itself is
|
|
737
|
+
// behavioural too: finite on every step, and never decaying.
|
|
738
|
+
basis: [DAMPING_ABOVE_1, DAMPING_AT_1, DAMPING_BELOW_0],
|
|
739
|
+
states: 'inside [0, 1)',
|
|
740
|
+
statesKeyed: 'inside [0, 1)',
|
|
741
|
+
// 🔑 The interval is HALF-OPEN (issue #1422): 0 is in and 1 is out. `0 ** x`
|
|
742
|
+
// is 0 for every positive exponent, so 0 follows the bone with no overshoot
|
|
743
|
+
// at every `fps` — a rig somebody can mean. 1 was inside from #794 to #1422,
|
|
744
|
+
// on the argument that `1 ** x` is 1 and finite at every rate, a jiggle held
|
|
745
|
+
// rather than a fault. Finite it is — [measured] through spine-core on the
|
|
746
|
+
// generated physics fixture, 120 steps from `Physics.reset` at 60, 45 and
|
|
747
|
+
// 30 fps, every pose value finite and the velocity never 0 — and that is why
|
|
748
|
+
// its arm is behavioural. What the argument missed is that a step whose
|
|
749
|
+
// decay is 1 has its eigenvalues on the unit circle, so nothing that excites
|
|
750
|
+
// it ever dies out, and a chain of such links resonates: `DAMPING_AT_1` has
|
|
751
|
+
// the measurement. ⚠️ 1 is also 4.2's own parser default for an omitted
|
|
720
752
|
// `damping` (`SkeletonJson.js:242` in 4.2.120,
|
|
721
753
|
// `getValue(constraintMap, "damping", 1)`; 4.3.13 reads 0.85 at `:308`), so a
|
|
722
|
-
// rig migrated from 4.2 that keys "the default" writes 1 and
|
|
723
|
-
//
|
|
754
|
+
// rig migrated from 4.2 that keys "the default" writes 1 and is refused; at
|
|
755
|
+
// #1422 none of the public recipes, the gallery, the twelve example exports,
|
|
756
|
+
// the trailer rig or the parts recipes states it, at rest or keyed.
|
|
724
757
|
//
|
|
725
758
|
// ⚠️ The frame-rate half is why the bound cannot be checked by playing a rig
|
|
726
759
|
// at 60 fps (issue #748): `step` is `1 / fps`, the constraint's own rate, so
|
|
@@ -734,9 +767,9 @@ export const PHYSICS_POSE_RULES: PhysicsPoseRule[] = [
|
|
|
734
767
|
"every rate — finite on every step until the velocity overflows, so refusing it is rigc's call rather than the " +
|
|
735
768
|
"runtime's. Below 0 the result depends on `fps`: where `60 / fps` is a " +
|
|
736
769
|
'whole number a negative base stays finite — at 60 fps it is the velocity\'s sign flipped each step, which can ' +
|
|
737
|
-
`look like a jiggle settling — and ${DAMPING_BELOW_0.expression} (\`(-0.5) ** (60 / 45)\`), so a rig tried only at 60 fps never shows the failure.
|
|
738
|
-
|
|
739
|
-
'
|
|
770
|
+
`look like a jiggle settling — and ${DAMPING_BELOW_0.expression} (\`(-0.5) ** (60 / 45)\`), so a rig tried only at 60 fps never shows the failure. ` +
|
|
771
|
+
`And ${DAMPING_AT_1.does} — finite on every step as well, so that refusal is rigc's call too. 0 is inside: ` +
|
|
772
|
+
'at 0 every velocity is zeroed on every step, so the offset follows the bone with no overshoot',
|
|
740
773
|
},
|
|
741
774
|
];
|
|
742
775
|
|
|
@@ -746,11 +779,11 @@ export interface PhysicsStepStability {
|
|
|
746
779
|
readonly k: number;
|
|
747
780
|
/** The per-step velocity decay, `damping ** (60 * step)` — the runtime's own expression. */
|
|
748
781
|
readonly d: number;
|
|
749
|
-
/** `2 · (1 + d)`: the step is stable exactly when `k` is below it. */
|
|
782
|
+
/** `2 · (1 + d)`: for `d` below 1 the step is stable exactly when `k` is below it. */
|
|
750
783
|
readonly bound: number;
|
|
751
784
|
/** The largest magnitude among the step map's two eigenvalues — what one step multiplies an undisturbed offset by, at most. */
|
|
752
785
|
readonly growth: number;
|
|
753
|
-
/** `k < bound`, strictly. */
|
|
786
|
+
/** `k < bound` and `d < 1`, both strictly. */
|
|
754
787
|
readonly stable: boolean;
|
|
755
788
|
}
|
|
756
789
|
|
|
@@ -766,9 +799,22 @@ export interface PhysicsStepStability {
|
|
|
766
799
|
* additive — wind and gravity set how far the offset goes, never whether it
|
|
767
800
|
* comes back — so with `u = v·step` the step is the linear map
|
|
768
801
|
* `[offset; u] ← [[1 − k, 1], [−k·d, d]]·[offset; u]`, `k = strength·m·step`:
|
|
769
|
-
* trace `1 − k + d`, determinant `d`. For `d` in `[0, 1
|
|
770
|
-
*
|
|
771
|
-
*
|
|
802
|
+
* trace `1 − k + d`, determinant `d`. For `d` in `[0, 1)` both eigenvalues lie
|
|
803
|
+
* inside the unit circle exactly when `0 < k < 2·(1 + d)`. The lower end is the
|
|
804
|
+
* strength row's; this is the upper.
|
|
805
|
+
*
|
|
806
|
+
* ⚠️ **Not at `d = 1`** — this comment said `[0, 1]` until issue #1422, and it
|
|
807
|
+
* was false at that end. The determinant is `d`, so at `d = 1` the product of the
|
|
808
|
+
* two eigenvalues is 1, and for every `0 < k < 4` they are a complex pair of
|
|
809
|
+
* modulus `√d` = 1: on the circle, not inside it. One such link never decays
|
|
810
|
+
* (marginal), and two on one chain stepping alike resonate and grow without
|
|
811
|
+
* limit — measured on #1422, and the reason the damping row now ends below 1
|
|
812
|
+
* (`DAMPING_AT_1`). `d` is 1 for a `damping` of 1 at every rate, and — in the
|
|
813
|
+
* runtime's own doubles — for a `damping` a hair under 1 at a high enough rate:
|
|
814
|
+
* [measured] the largest double below 1 gives `d` exactly 1 from fps 121 on,
|
|
815
|
+
* while the largest float32 below 1, which is all a key can hold, needs about
|
|
816
|
+
* 6.4·10¹⁰. The runtime then multiplies every velocity by exactly 1, so `stable`
|
|
817
|
+
* requires `d < 1` as well, and A23 names that case by the decay it reached.
|
|
772
818
|
*
|
|
773
819
|
* ⚠️ **Strict.** At `k = 2·(1 + d)` one eigenvalue is exactly −1: the offset
|
|
774
820
|
* flips sign every step and never settles, and at `d = 1` the two coincide and
|
|
@@ -798,7 +844,7 @@ export function physicsStepStability(strength: number, massInverse: number, damp
|
|
|
798
844
|
const trace = 1 - k + d;
|
|
799
845
|
const disc = trace * trace - 4 * d;
|
|
800
846
|
const growth = disc >= 0 ? Math.max(Math.abs(trace + Math.sqrt(disc)), Math.abs(trace - Math.sqrt(disc))) / 2 : Math.sqrt(d);
|
|
801
|
-
return { k, d, bound, growth, stable: k < bound };
|
|
847
|
+
return { k, d, bound, growth, stable: k < bound && d < 1 };
|
|
802
848
|
}
|
|
803
849
|
|
|
804
850
|
/**
|
package/src/types.ts
CHANGED
|
@@ -352,14 +352,15 @@ export type BoneProperty =
|
|
|
352
352
|
* export; `PHYSICS_TRACKS` in `compile.ts` carries the argument.
|
|
353
353
|
*
|
|
354
354
|
* 🔒 **Four of them have a compile-time range** (issue #610): `mass` must be
|
|
355
|
-
* `> 0`, `damping` inside the
|
|
356
|
-
* more. The bounds are `PHYSICS_POSE_RULES` in `src/timelines.ts`, and each row's
|
|
355
|
+
* `> 0`, `damping` inside the half-open `[0, 1)` (issue #1422; closed from #794),
|
|
356
|
+
* and `mix` and `strength` `0` or more. The bounds are `PHYSICS_POSE_RULES` in `src/timelines.ts`, and each row's
|
|
357
357
|
* `basis` says, per way out, whose they are (issue #798). Two are the
|
|
358
358
|
* runtime's arithmetic: a `mass` of 0 is an infinite `massInverse` and NaN from
|
|
359
359
|
* the first step, and a `damping` below 0 is a negative base under a fractional
|
|
360
|
-
* exponent at any `fps` where `60 / fps` is not whole. The other
|
|
360
|
+
* exponent at any `fps` where `60 / fps` is not whole. The other seven are rigc's
|
|
361
361
|
* call — the rig runs, finitely, and runs wrongly: `mass` below 0 and
|
|
362
362
|
* `strength` below 0 run away, `damping` above 1 diverges until it overflows,
|
|
363
|
+
* `damping` at 1 never decays and resonates on a chain,
|
|
363
364
|
* `mix` below 0 is the jiggle inverted, and a setup `mix` or `strength` of 0 is
|
|
364
365
|
* a constraint muted or pulled back by nothing. `inertia`, `wind`, `gravity`
|
|
365
366
|
* and the top of `mix` are bounded nowhere, because the runtime documents
|