spine-rigc 2.53.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 +266 -18
- package/package.json +1 -1
- package/src/assertions/bodies/a23.ts +254 -5
- package/src/check.ts +12 -0
- package/src/cli/core_commands.ts +168 -8
- package/src/cli/shared.ts +20 -1
- package/src/core/raw.ts +29 -6
- package/src/environment.ts +193 -0
- package/src/passthrough.ts +575 -0
- package/src/render.ts +15 -1
- package/src/render_core.ts +10 -6
- package/src/render_shared.ts +37 -2
- package/src/timelines.ts +130 -23
- package/src/types.ts +4 -3
package/src/render_shared.ts
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
import { spinePosingFor, type SpineSkeletonData } from './spine_side.ts';
|
|
39
39
|
import { walkTimelines } from './timelines.ts';
|
|
40
40
|
import { firstNonFinite, type PosedVertices, type WorldTransform } from './nonfinite.ts';
|
|
41
|
+
import { environmentAt, type EnvironmentChange, type EnvironmentStep } from './environment.ts';
|
|
41
42
|
|
|
42
43
|
// ---------------------------------------------------------------------------
|
|
43
44
|
// the Spine side, through the seam (issue #1052)
|
|
@@ -211,6 +212,16 @@ export interface FramesSidecar {
|
|
|
211
212
|
slots?: string[];
|
|
212
213
|
/** The slots these frames leave out, when `render --hide` named them — see `slots`. */
|
|
213
214
|
hidden?: string[];
|
|
215
|
+
/**
|
|
216
|
+
* The schedule the physics answered, when `render --environment` gave one
|
|
217
|
+
* (issue #1416) — its steps as read, each `{ t, wind?, gravity? }`.
|
|
218
|
+
*
|
|
219
|
+
* ⭐ Absent on a render without it, for `skin`'s reason: every frame set
|
|
220
|
+
* written before the field existed says the same. A frame set carrying it is
|
|
221
|
+
* a picture of the rig in a world `check` does not pose, so `check` refuses
|
|
222
|
+
* it as a reference by name.
|
|
223
|
+
*/
|
|
224
|
+
environment?: EnvironmentStep[];
|
|
214
225
|
/** The colour the frames were cleared to, straight RGBA 0..255. */
|
|
215
226
|
background: RGBA;
|
|
216
227
|
viewport: {
|
|
@@ -391,6 +402,19 @@ export interface PoseOptions {
|
|
|
391
402
|
* second render writes.
|
|
392
403
|
*/
|
|
393
404
|
unclipped?: boolean;
|
|
405
|
+
/**
|
|
406
|
+
* The world the physics answers, in time — `render --environment` (issue
|
|
407
|
+
* #1416, `./environment.ts`): each step's change set before the step that
|
|
408
|
+
* poses the first frame at or after its `t`, on the sampler's own frame grid,
|
|
409
|
+
* on whichever poser draws. Absent, nothing is set and every pose is the one
|
|
410
|
+
* it always was.
|
|
411
|
+
*
|
|
412
|
+
* ⭐ **It is part of the shot, so the framing reads it.** Unlike `slots` and
|
|
413
|
+
* `hidden`, which say which pixels are drawn of one pose, an environment
|
|
414
|
+
* changes the pose — a gust can carry a coat past the box a still rig fills —
|
|
415
|
+
* so `framingViewport` keeps it and the box holds what the frames draw.
|
|
416
|
+
*/
|
|
417
|
+
environment?: readonly EnvironmentStep[];
|
|
394
418
|
}
|
|
395
419
|
|
|
396
420
|
/**
|
|
@@ -1025,8 +1049,10 @@ export interface Poser {
|
|
|
1025
1049
|
/**
|
|
1026
1050
|
* Animation `name` under `skin`, not looping: `visit(i, posed)` for every
|
|
1027
1051
|
* `i` from 0 to `count`, the pose at `i/fps`. The caller has checked `name`.
|
|
1052
|
+
* `environment(i)`, when given, is the change to the world the physics
|
|
1053
|
+
* answers set before the step that poses frame `i` (issue #1416).
|
|
1028
1054
|
*/
|
|
1029
|
-
animation(name: string, skin: string | undefined, fps: number, count: number, visit: (index: number, posed: Posed) => void): void;
|
|
1055
|
+
animation(name: string, skin: string | undefined, fps: number, count: number, visit: (index: number, posed: Posed) => void, environment?: (index: number) => EnvironmentChange | undefined): void;
|
|
1030
1056
|
/** The rest table for the (slot, attachment) pairs `shown` holds — see `AttachmentRest`. */
|
|
1031
1057
|
rest(skin: string | undefined, shown: readonly AttachmentPose[][]): AttachmentRest[];
|
|
1032
1058
|
}
|
|
@@ -1091,7 +1117,12 @@ export function sampleAnimation(source: PoseSource, name: string, fps: number, o
|
|
|
1091
1117
|
const count = Math.round(animation.duration * fps);
|
|
1092
1118
|
const draw = drawResolver(poser, opts);
|
|
1093
1119
|
const frames: Frame[] = [];
|
|
1094
|
-
|
|
1120
|
+
const visit = (i: number, posed: Posed): void => {
|
|
1121
|
+
frames.push(frameOf(i, i * step, posed, draw(), opts));
|
|
1122
|
+
};
|
|
1123
|
+
// Without an environment the call is the one it always was, so a poser that takes none is asked nothing new.
|
|
1124
|
+
if (opts?.environment === undefined) poser.animation(name, opts?.skin, fps, count, visit);
|
|
1125
|
+
else poser.animation(name, opts.skin, fps, count, visit, environmentAt(opts.environment, fps));
|
|
1095
1126
|
return frames;
|
|
1096
1127
|
}
|
|
1097
1128
|
|
|
@@ -1411,6 +1442,8 @@ export interface GeometryFile {
|
|
|
1411
1442
|
/** The animation, or `null` for a skeleton with none (its one frame is the setup pose). */
|
|
1412
1443
|
animation: string | null;
|
|
1413
1444
|
skin?: string;
|
|
1445
|
+
/** The schedule the physics answered (`render --environment`, issue #1416) — `frames.json`'s own `environment`; absent without it. */
|
|
1446
|
+
environment?: EnvironmentStep[];
|
|
1414
1447
|
fps: number;
|
|
1415
1448
|
/** The box the PNG frames beside this file were drawn over — `frames.json`'s own `viewport`. */
|
|
1416
1449
|
viewport: FramesSidecar['viewport'];
|
|
@@ -1470,6 +1503,7 @@ export function geometryFileOf(
|
|
|
1470
1503
|
frames: Frame[],
|
|
1471
1504
|
viewport: Viewport,
|
|
1472
1505
|
skin: string | undefined,
|
|
1506
|
+
environment?: EnvironmentStep[],
|
|
1473
1507
|
): GeometryFile {
|
|
1474
1508
|
const geometryFrame = (frame: Frame, where: string): Omit<GeometryFrame, 'index' | 'time'> => {
|
|
1475
1509
|
if (frame.bones === undefined || frame.attachments === undefined) {
|
|
@@ -1493,6 +1527,7 @@ export function geometryFileOf(
|
|
|
1493
1527
|
coordinates: GEOMETRY_COORDINATES,
|
|
1494
1528
|
animation,
|
|
1495
1529
|
...(skin === undefined ? {} : { skin }),
|
|
1530
|
+
...(environment === undefined ? {} : { environment }),
|
|
1496
1531
|
fps,
|
|
1497
1532
|
viewport: sidecarViewport(viewport),
|
|
1498
1533
|
bones: poser.bones.map(({ name, parent }) => ({ name, parent })),
|
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,12 +767,86 @@ 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
|
|
|
776
|
+
/** What `physicsStepStability` reads off one pose of a physics constraint. */
|
|
777
|
+
export interface PhysicsStepStability {
|
|
778
|
+
/** `strength · massInverse · step²`, as the step's own products form it: `strength * (step * massInverse) * step`. */
|
|
779
|
+
readonly k: number;
|
|
780
|
+
/** The per-step velocity decay, `damping ** (60 * step)` — the runtime's own expression. */
|
|
781
|
+
readonly d: number;
|
|
782
|
+
/** `2 · (1 + d)`: for `d` below 1 the step is stable exactly when `k` is below it. */
|
|
783
|
+
readonly bound: number;
|
|
784
|
+
/** The largest magnitude among the step map's two eigenvalues — what one step multiplies an undisturbed offset by, at most. */
|
|
785
|
+
readonly growth: number;
|
|
786
|
+
/** `k < bound` and `d < 1`, both strictly. */
|
|
787
|
+
readonly stable: boolean;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Whether one pose of a physics constraint integrates to rest or runs away
|
|
792
|
+
* (issue #1416, part B) — a joint condition on three of the four values
|
|
793
|
+
* `PHYSICS_POSE_RULES` bounds one at a time, and on the constraint's `step`.
|
|
794
|
+
*
|
|
795
|
+
* 🔑 Every substep of `stepPhysics` (`src/core/constraints_physics.ts`, held to
|
|
796
|
+
* spine-core's `PhysicsConstraint.update` at tolerance 0) is, on each driven
|
|
797
|
+
* component, `v += (force − offset·strength)·m; offset += v·step; v *= d` with
|
|
798
|
+
* `m = step · massInverse` and `d = damping ** (60·step)`. The force is
|
|
799
|
+
* additive — wind and gravity set how far the offset goes, never whether it
|
|
800
|
+
* comes back — so with `u = v·step` the step is the linear map
|
|
801
|
+
* `[offset; u] ← [[1 − k, 1], [−k·d, d]]·[offset; u]`, `k = strength·m·step`:
|
|
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.
|
|
818
|
+
*
|
|
819
|
+
* ⚠️ **Strict.** At `k = 2·(1 + d)` one eigenvalue is exactly −1: the offset
|
|
820
|
+
* flips sign every step and never settles, and at `d = 1` the two coincide and
|
|
821
|
+
* it grows linearly.
|
|
822
|
+
*
|
|
823
|
+
* 📏 [measured, issue #1416's comment of 2026-10-14] on 270 generated rigs —
|
|
824
|
+
* x, y, rotate, scaleX and shearX drives; strength 10/100/1000; mass 0.1/1; fps
|
|
825
|
+
* 5/15/60; damping 0/0.85/1 — 115 of 115 with `k > bound` ran past 10⁶ units
|
|
826
|
+
* under a unit gust along the drive (92 of them in the runtime's default world
|
|
827
|
+
* with nothing set), 145 of 145 with `k < bound` stayed bounded, core and
|
|
828
|
+
* spine-core identical bit for bit. The 10 exactly on it (damping 1, k = 4)
|
|
829
|
+
* stayed under 10⁶ in the 6 s walk, which is why the 6 s classifier cannot
|
|
830
|
+
* stand for this edge: the x, y and scaleX ones ended the walk at their peak,
|
|
831
|
+
* still growing, and rotate and shearX swung their tip the bone's full length.
|
|
832
|
+
* All 270 passed every gate before this, because each value was inside its
|
|
833
|
+
* own row.
|
|
834
|
+
*
|
|
835
|
+
* The products are formed in the runtime's own order (`m = t * massInverse`,
|
|
836
|
+
* `d = damping ** (60 * t)`), so the number printed is the runtime's `k`
|
|
837
|
+
* rather than a re-association of it.
|
|
838
|
+
*/
|
|
839
|
+
export function physicsStepStability(strength: number, massInverse: number, damping: number, step: number): PhysicsStepStability {
|
|
840
|
+
const m = step * massInverse;
|
|
841
|
+
const k = strength * m * step;
|
|
842
|
+
const d = damping ** (60 * step);
|
|
843
|
+
const bound = 2 * (1 + d);
|
|
844
|
+
const trace = 1 - k + d;
|
|
845
|
+
const disc = trace * trace - 4 * d;
|
|
846
|
+
const growth = disc >= 0 ? Math.max(Math.abs(trace + Math.sqrt(disc)), Math.abs(trace - Math.sqrt(disc))) / 2 : Math.sqrt(d);
|
|
847
|
+
return { k, d, bound, growth, stable: k < bound && d < 1 };
|
|
848
|
+
}
|
|
849
|
+
|
|
743
850
|
/**
|
|
744
851
|
* What `A23`'s setup arm says about a pose value its rule refuses: the arm of
|
|
745
852
|
* `outside` that holds for it, or the bound itself where the row has no arms or
|
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
|