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/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 eight ways out.
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 eight ways out of the four bounds, each with its basis (issue #798). They
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. `T113` re-takes all eight on every run.
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 eight ways out, two are arithmetic — `mass` at 0 and `damping` below 0 —
630
- * and six are rigc's call.
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 <= 1,
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
- basis: [DAMPING_ABOVE_1, DAMPING_BELOW_0],
710
- states: 'inside [0, 1]',
711
- statesKeyed: 'inside [0, 1]',
712
- // 🔑 The interval is CLOSED (issue #794). `1 ** x` is 1 and `0 ** x` is 0
713
- // for every positive exponent, so both ends are finite at every `fps` and
714
- // both are rigs somebody can mean: 1 holds the jiggle, 0 follows the bone
715
- // with no overshoot. [measured] through spine-core on the generated physics
716
- // fixture, 120 steps from `Physics.reset` at 60, 45 and 30 fps under a
717
- // displacing animation, as a setup value and as a key: every pose value
718
- // finite, the velocity never 0 at 1 and 0 after every step at 0. And 1 is
719
- // not only a choice: it is 4.2's own parser default for an omitted
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 the editor
723
- // exports it as it is. The open interval this replaced refused that rig.
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. The two ends are ` +
738
- 'values the runtime plays at every rate: at 1 the velocity never decays, so the jiggle holds for as long as it ' +
739
- 'runs, and at 0 every velocity is zeroed on every step, so the offset follows the bone with no overshoot',
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]` (the damping row's own
770
- * bound) both eigenvalues lie inside the unit circle exactly when
771
- * `0 < k < 2·(1 + d)`. The lower end is the strength row's; this is the upper.
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 closed `[0, 1]`, and `mix` and `strength` `0` or
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 six are rigc's
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