rig-c 2.38.0 → 2.39.1
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/NOTICE.md +1 -1
- package/README.md +43 -42
- package/docs/AUTHORING.md +25 -25
- package/docs/FACE.md +14 -14
- package/docs/INGEST.md +4 -4
- package/docs/MOTION.md +2 -2
- package/docs/PROMPTING.md +4 -4
- package/docs/RIGGING.md +33 -33
- package/package.json +4 -4
- package/skills/rigc/SKILL.md +8 -8
- package/skills/rigc-face/SKILL.md +6 -6
- package/skills/rigc-ingest/SKILL.md +5 -5
- package/skills/rigc-motion/SKILL.md +6 -6
- package/skills/rigc-rigging/SKILL.md +6 -6
- package/src/ballot.ts +1 -1
- package/src/bonedist.ts +1 -1
- package/src/cli/shared.ts +1 -1
- package/src/cli/spine_commands.ts +1 -1
- package/src/core/tracks.ts +223 -39
- package/src/deformmeasure.ts +3 -3
- package/src/depth.ts +3 -3
- package/src/diff.ts +1 -1
- package/src/generation.ts +1 -1
- package/src/preview.ts +2 -2
- package/src/trackgen.ts +1 -1
|
@@ -9,7 +9,7 @@ compatibility: Requires Bun 1.2 or later and the npm package rig-c.
|
|
|
9
9
|
|
|
10
10
|
Load this when the request is a **skeleton rather than a movement**: loose part
|
|
11
11
|
PNGs in, a bone hierarchy out. Every rule below is owned by
|
|
12
|
-
[RIGGING.md](https://github.com/
|
|
12
|
+
[RIGGING.md](https://github.com/rig-c/core/blob/main/docs/RIGGING.md); this file says when to open it and what it
|
|
13
13
|
will not do for you.
|
|
14
14
|
|
|
15
15
|
## Non-negotiables
|
|
@@ -30,15 +30,15 @@ are named in RIGGING §11 — neither as a pass bar.
|
|
|
30
30
|
|
|
31
31
|
## Read, in this order
|
|
32
32
|
|
|
33
|
-
1. [AUTHORING.md](https://github.com/
|
|
33
|
+
1. [AUTHORING.md](https://github.com/rig-c/core/blob/main/docs/AUTHORING.md) — the rig spec field by field (§3),
|
|
34
34
|
the failure map (§5–§6), reading a pose out of a picture (§8.1, §11, §12),
|
|
35
35
|
and the editor's own conventions (§10).
|
|
36
|
-
2. [RIGGING.md](https://github.com/
|
|
36
|
+
2. [RIGGING.md](https://github.com/rig-c/core/blob/main/docs/RIGGING.md) — the structure itself: what identifies a
|
|
37
37
|
pivot and what moving one costs (§2–§3), gauges (§4), what a chain can reach
|
|
38
38
|
(§6), and the instruments that see structure (§11).
|
|
39
|
-
3. Then [MOTION.md](https://github.com/
|
|
40
|
-
[FACE.md](https://github.com/
|
|
41
|
-
[INGEST.md](https://github.com/
|
|
39
|
+
3. Then [MOTION.md](https://github.com/rig-c/core/blob/main/docs/MOTION.md) once the skeleton exists;
|
|
40
|
+
[FACE.md](https://github.com/rig-c/core/blob/main/docs/FACE.md) if the figure is a head;
|
|
41
|
+
[INGEST.md](https://github.com/rig-c/core/blob/main/docs/INGEST.md) if the skeleton was handed to you already
|
|
42
42
|
compiled.
|
|
43
43
|
|
|
44
44
|
Every guide linked here is in the installed package at `node_modules/rig-c/docs/`,
|
package/src/ballot.ts
CHANGED
|
@@ -616,7 +616,7 @@ export function buildBallot(input: BallotInput): { html: string; manifest: Ballo
|
|
|
616
616
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
617
617
|
<title>rigc ballot — ${escapeHtml(manifest.ballot)}</title>
|
|
618
618
|
<!--
|
|
619
|
-
Generated by rigc ${escapeHtml(input.version)} — https://github.com/
|
|
619
|
+
Generated by rigc ${escapeHtml(input.version)} — https://github.com/rig-c/core
|
|
620
620
|
|
|
621
621
|
Every candidate's skeleton, atlas and atlas pages are embedded in this file as
|
|
622
622
|
data URIs, so it plays on its own with no server and no sibling files.
|
package/src/bonedist.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* a rotation of 300° are one key each. Stage 3 is the measure for that, and
|
|
10
10
|
* until this file it did not exist: *"None of it exists. Do not report a
|
|
11
11
|
* per-frame figure until it does"*
|
|
12
|
-
* ([issue #8](https://github.com/
|
|
12
|
+
* ([issue #8](https://github.com/rig-c/core/issues/8)).
|
|
13
13
|
*
|
|
14
14
|
* `rigc check` is the other neighbour and it is **not** this. It measures a
|
|
15
15
|
* candidate against **pictures** — the rendered reference frames — which is
|
package/src/cli/shared.ts
CHANGED
|
@@ -334,7 +334,7 @@ function ceilingPair(axis: { positive: FoldLimit | null; negative: FoldLimit | n
|
|
|
334
334
|
* above the minimum. A ceiling set by one bad texel has 99 % of the mesh
|
|
335
335
|
* surviving to the form's angle while the reported number collapses — 64.58°
|
|
336
336
|
* against 6.08° for one texel of 160,000, with the percentile unmoved at 64.80°
|
|
337
|
-
* in both ([#412](https://github.com/
|
|
337
|
+
* in both ([#412](https://github.com/rig-c/core/issues/412),
|
|
338
338
|
* `bench/studies/2026-09-05-noise` §6).
|
|
339
339
|
*
|
|
340
340
|
* Three spellings, three different claims, for the reason `ceilingPair` prints
|
|
@@ -28,7 +28,7 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
28
28
|
function repositoryUrl(): string {
|
|
29
29
|
const repo = readPackageMeta()?.repository;
|
|
30
30
|
const url = typeof repo === 'string' ? repo : repo?.url;
|
|
31
|
-
return (url ?? 'https://github.com/
|
|
31
|
+
return (url ?? 'https://github.com/rig-c/core').replace(/^git\+/, '').replace(/\.git$/, '');
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
// ---------------------------------------------------------------------------
|
package/src/core/tracks.ts
CHANGED
|
@@ -88,14 +88,65 @@
|
|
|
88
88
|
* compounds without it. The state carries nothing from one step to the next
|
|
89
89
|
* but its entries, so a write lasts one pose, as on the single track.
|
|
90
90
|
*
|
|
91
|
+
* ## Alpha and additive entries (issue #1344)
|
|
92
|
+
*
|
|
93
|
+
* An entry carries spine-core's `TrackEntry.alpha` (`MixOptions.alpha`, 1 when
|
|
94
|
+
* absent, changed while it plays by `setAlpha`) and `TrackEntry.additive`
|
|
95
|
+
* (`MixOptions.additive`). 4.3.13 has no `MixBlend` on an entry — `additive` is
|
|
96
|
+
* a boolean fixed before the next apply — so the option carries that name and
|
|
97
|
+
* that type. What the runtime does with them, ported and measured (`CO52`):
|
|
98
|
+
*
|
|
99
|
+
* - **Alpha is a weight, multiplied in**: the current entry applies at
|
|
100
|
+
* `alpha · mix`; one mixing out at `from.alpha · fromMix · (1 − mix)`, its
|
|
101
|
+
* held timelines at that over `1 − mix · to.alpha`. Track 0 below alpha 1
|
|
102
|
+
* leaves the setup-pose shortcut and mixes as a higher track does.
|
|
103
|
+
* - **Events ignore alpha**: the current entry fires its events at any alpha,
|
|
104
|
+
* 0 included; an entry mixing out fires none (`eventThreshold` 0, above).
|
|
105
|
+
* - **Attachments ignore alpha**: `alphaAttachmentThreshold` is 0, so the
|
|
106
|
+
* current entry keeps its attachments at any alpha, 0 included;
|
|
107
|
+
* `mixAttachmentThreshold` is 0, so an entry mixing out keeps none. The draw
|
|
108
|
+
* order stays refused, so `mixDrawOrderThreshold` has nothing to act on.
|
|
109
|
+
* - **Additive adds what the runtime marks additive** (`Timeline.additive`):
|
|
110
|
+
* every bone timeline keying numbers and the deform. On a property a lower
|
|
111
|
+
* track or an older entry keyed, the keyed value (a rotation's, a
|
|
112
|
+
* translation's, a shear's) is added times alpha — `current + value · alpha`
|
|
113
|
+
* — a scale's offset from the setup scale is added, and a deform's offset
|
|
114
|
+
* from the setup vertices (weighted: the offsets themselves). From the setup
|
|
115
|
+
* pose it is the same as replacing. A rotation is added as it stands, with
|
|
116
|
+
* no shortest-angle wrap and no remembered direction (`shortestRotation` is
|
|
117
|
+
* `additive || …`). Colour, attachment, inherit and event timelines do not
|
|
118
|
+
* read `add`: under an additive entry they apply as under a replacing one.
|
|
119
|
+
* Track 0 at alpha 1 with no mix adds nothing — the runtime passes `add`
|
|
120
|
+
* false on that shortcut.
|
|
121
|
+
* - **An additive entry is never held**, and does not hold what it mixes in
|
|
122
|
+
* over: its additive timelines take their mode with no `HOLD` bit, and a
|
|
123
|
+
* timeline of an entry it replaces is not held for it. Mixing out, an
|
|
124
|
+
* additive entry adds at its faded alpha, so its offset fades to nothing.
|
|
125
|
+
*
|
|
126
|
+
* `CO52`'s oracle resets bones and slots (`setupPose()`) before every apply,
|
|
127
|
+
* not the bones alone as `CO51`'s does: on its blend probe a two-colour key at
|
|
128
|
+
* a partial alpha over track 0's `rgba` mixes the dark colour from what the
|
|
129
|
+
* previous frame left, which carries in a runtime whose slots are not reset —
|
|
130
|
+
* measured off with the bones-only reset, equal with the full one. The state
|
|
131
|
+
* poses every frame from the setup pose, slots included, as it always has.
|
|
132
|
+
*
|
|
133
|
+
* Each rejected reading is planted (`TracksPlant`) and read red by `CO52`:
|
|
134
|
+
* alpha inverted, alpha ignored, additive as replace, an additive rotation
|
|
135
|
+
* through the remembered direction, an additive entry's attachments as a
|
|
136
|
+
* plain timeline, the hold blind to the entry mixing in, the holds as if no
|
|
137
|
+
* entry were additive, and an alpha threshold of one half on attachments.
|
|
138
|
+
*
|
|
91
139
|
* ## What is left out, by name
|
|
92
140
|
*
|
|
93
141
|
* An animation is refused when it is set or queued if it keys what the state
|
|
94
142
|
* does not mix: a constraint timeline (ik, transform, path, physics, slider),
|
|
95
143
|
* a sequence timeline, the draw order, or a deform of a path attachment (a
|
|
96
144
|
* path's deformed curve reaches the constraints, which this state poses from
|
|
97
|
-
* their records as they stand).
|
|
98
|
-
* `
|
|
145
|
+
* their records as they stand). `timeScale`, `holdPrevious`, `reverse`,
|
|
146
|
+
* `shortestRotation` on a replacing entry, the thresholds as options, per-pair
|
|
147
|
+
* mix tables and the listeners are not offered. An additive entry is refused
|
|
148
|
+
* nothing more: every timeline the state accepts is defined under it (*Alpha
|
|
149
|
+
* and additive entries* below).
|
|
99
150
|
*
|
|
100
151
|
* ⛔ **A state refused part-way stays refused**, as a live track does: a step
|
|
101
152
|
* is checked before anything moves, and a refusal past that — a construct the
|
|
@@ -117,10 +168,18 @@ export interface TracksOptions {
|
|
|
117
168
|
adjust?: LocalAdjust;
|
|
118
169
|
}
|
|
119
170
|
|
|
120
|
-
/**
|
|
171
|
+
/**
|
|
172
|
+
* How `setAnimation` starts an entry: whether it loops, over how many seconds
|
|
173
|
+
* the entry it replaces mixes out (0 cuts), and — issue #1344 — how strongly
|
|
174
|
+
* it applies and whether it adds (the header's *Alpha and additive entries*).
|
|
175
|
+
*/
|
|
121
176
|
export interface MixOptions {
|
|
122
177
|
loop: boolean;
|
|
123
178
|
mixDuration: number;
|
|
179
|
+
/** `TrackEntry.alpha`: from 0 to 1, multiplied into every timeline's weight; 1 when absent. Changed while the entry plays with `LiveTracks.setAlpha`. */
|
|
180
|
+
alpha?: number;
|
|
181
|
+
/** `TrackEntry.additive`: the entry's bone and deform timelines add their keyed values to what the tracks below posed rather than replacing it; `false` when absent. Fixed when the entry is set or queued, as the runtime fixes it before the next apply. */
|
|
182
|
+
additive?: boolean;
|
|
124
183
|
}
|
|
125
184
|
|
|
126
185
|
/** How `addAnimation` queues an entry: `MixOptions`, and its delay — at or below 0 relative to when the entry before it completes, above 0 seconds after the entry before starts. */
|
|
@@ -136,6 +195,10 @@ export interface TrackEntryView {
|
|
|
136
195
|
/** The animation time the entry applies at now (`getAnimationTime`): the track time wrapped or held at the duration. */
|
|
137
196
|
animationTime: number;
|
|
138
197
|
duration: number;
|
|
198
|
+
/** The entry's alpha as it stands (`MixOptions.alpha`, `LiveTracks.setAlpha`). */
|
|
199
|
+
alpha: number;
|
|
200
|
+
/** Whether the entry adds (`MixOptions.additive`). */
|
|
201
|
+
additive: boolean;
|
|
139
202
|
}
|
|
140
203
|
|
|
141
204
|
/** The live tracks (`openTracks`). */
|
|
@@ -148,6 +211,8 @@ export interface LiveTracks {
|
|
|
148
211
|
addAnimation(track: number, animation: string, options: QueueOptions): void;
|
|
149
212
|
/** Queues a mix out to nothing after the last entry on `track`; `delay` 0 ends the mix when that entry completes. */
|
|
150
213
|
addEmptyAnimation(track: number, mixDuration: number, delay: number): void;
|
|
214
|
+
/** Sets the alpha of `track`'s current entry (`TrackEntry.alpha`), from the next pose on; an entry it mixes from keeps its own. A track playing nothing is refused. */
|
|
215
|
+
setAlpha(track: number, alpha: number): void;
|
|
151
216
|
/** Pose 0, the reset (the header's *What a step is*): once, before the first `step`. */
|
|
152
217
|
start(): RawPose;
|
|
153
218
|
/** The next pose: every entry moved by `dt`, a finite time at or above 0. */
|
|
@@ -174,6 +239,22 @@ export interface TracksPlant extends TimelinePlant {
|
|
|
174
239
|
queueDelayNow?: boolean;
|
|
175
240
|
/** An entry mixing out fires its events while the mix is below one half — the reading the issue's brief stated, which the runtime's `eventThreshold` of 0 contradicts. */
|
|
176
241
|
eventsUntilHalfMix?: boolean;
|
|
242
|
+
/** `CO52`'s, issue #1344: every entry's alpha read as `1 − alpha`. */
|
|
243
|
+
alphaInverted?: boolean;
|
|
244
|
+
/** Every entry's alpha read as 1 — the option ignored. */
|
|
245
|
+
alphaIgnored?: boolean;
|
|
246
|
+
/** An additive entry applied, and its modes computed, as a replacing one. */
|
|
247
|
+
additiveAsReplace?: boolean;
|
|
248
|
+
/** An additive entry's rotations mixed through the remembered direction (`applyRotateTimeline`) as a replacing entry's are, rather than added through the timeline itself. */
|
|
249
|
+
additiveRotateRemembered?: boolean;
|
|
250
|
+
/** An additive entry's attachment timelines applied as a plain timeline (`AttachmentTimeline.apply`, no attachment state) rather than under the threshold rule (`applyAttachmentTimeline`). */
|
|
251
|
+
additiveAttachmentsDirect?: boolean;
|
|
252
|
+
/** The hold alpha computed as if the entry mixing in were at alpha 1 (`keep = 1 − mix`, not `1 − mix·to.alpha`). */
|
|
253
|
+
holdIgnoresEntryAlpha?: boolean;
|
|
254
|
+
/** The holds computed as if no entry were additive: an additive entry's own timelines held, and a timeline held under an additive entry mixing in. */
|
|
255
|
+
additiveHeld?: boolean;
|
|
256
|
+
/** An entry applied below alpha one half keeps none of its attachments — a threshold the runtime's `alphaAttachmentThreshold` of 0 does not have. */
|
|
257
|
+
attachmentsBelowHalfAlpha?: boolean;
|
|
177
258
|
}
|
|
178
259
|
|
|
179
260
|
// ---------------------------------------------------------------------------
|
|
@@ -206,6 +287,11 @@ interface MixAnimation {
|
|
|
206
287
|
/** The empty animation (`AnimationState.emptyAnimation`): no timeline, duration 0 — one object, so setting it on an entry never applied replaces that entry. */
|
|
207
288
|
const EMPTY: MixAnimation = { name: null, duration: 0, timelines: [], ids: new Set() };
|
|
208
289
|
|
|
290
|
+
/** `Timeline.additive`: the timelines the runtime adds under an additive entry — every bone timeline that keys numbers, and the deform. Colour, attachment, inherit and event timelines do not read `add`. */
|
|
291
|
+
function addable(tl: MixTimeline): boolean {
|
|
292
|
+
return tl.type === 'deform' || (tl.type === 'bone' && tl.kind !== 'inherit');
|
|
293
|
+
}
|
|
294
|
+
|
|
209
295
|
/** The properties a bone timeline keys, spine-core's `propertyIds` by bone name. */
|
|
210
296
|
const BONE_PROPERTIES: Record<BoneTimelineKind, readonly string[]> = {
|
|
211
297
|
rotate: ['rotate'], translate: ['x', 'y'], translatex: ['x'], translatey: ['y'], scale: ['scaleX', 'scaleY'], scalex: ['scaleX'], scaley: ['scaleY'], shear: ['shearX', 'shearY'], shearx: ['shearX'], sheary: ['shearY'], inherit: ['inherit'],
|
|
@@ -289,6 +375,10 @@ interface Entry {
|
|
|
289
375
|
mixTime: number;
|
|
290
376
|
mixDuration: number;
|
|
291
377
|
totalAlpha: number;
|
|
378
|
+
/** `TrackEntry.alpha`. */
|
|
379
|
+
alpha: number;
|
|
380
|
+
/** `TrackEntry.additive`. */
|
|
381
|
+
additive: boolean;
|
|
292
382
|
keepHold: boolean;
|
|
293
383
|
timelineMode: number[];
|
|
294
384
|
timelineHoldMix: Array<Entry | null | undefined>;
|
|
@@ -296,12 +386,12 @@ interface Entry {
|
|
|
296
386
|
timelinesRotation: number[];
|
|
297
387
|
}
|
|
298
388
|
|
|
299
|
-
/** A new entry (`AnimationState.trackEntry`): every field the runtime's default; the mix duration 0, `AnimationStateData`'s default mix. */
|
|
389
|
+
/** A new entry (`AnimationState.trackEntry`): every field the runtime's default; the mix duration 0, `AnimationStateData`'s default mix, alpha 1, not additive. */
|
|
300
390
|
function newEntry(anim: MixAnimation, loop: boolean): Entry {
|
|
301
391
|
return {
|
|
302
392
|
anim, loop, previous: null, next: null, mixingFrom: null, mixingTo: null,
|
|
303
393
|
delay: 0, trackTime: 0, trackLast: -1, nextTrackLast: -1, trackEnd: Number.MAX_VALUE,
|
|
304
|
-
animationLast: -1, nextAnimationLast: -1, mixTime: 0, mixDuration: 0, totalAlpha: 0, keepHold: false,
|
|
394
|
+
animationLast: -1, nextAnimationLast: -1, mixTime: 0, mixDuration: 0, totalAlpha: 0, alpha: 1, additive: false, keepHold: false,
|
|
305
395
|
timelineMode: [], timelineHoldMix: [], timelinesRotation: [],
|
|
306
396
|
};
|
|
307
397
|
}
|
|
@@ -535,6 +625,22 @@ class Mixer {
|
|
|
535
625
|
return this.plant.mixReversed === true ? 1 - m : m;
|
|
536
626
|
}
|
|
537
627
|
|
|
628
|
+
/** `TrackEntry.alpha` — inverted or ignored under the plants. */
|
|
629
|
+
alphaOf(e: Entry): number {
|
|
630
|
+
if (this.plant.alphaIgnored === true) return 1;
|
|
631
|
+
return this.plant.alphaInverted === true ? 1 - e.alpha : e.alpha;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/** `TrackEntry.additive` — never under the plant that applies an additive entry as a replacing one. */
|
|
635
|
+
additiveOf(e: Entry): boolean {
|
|
636
|
+
return e.additive && this.plant.additiveAsReplace !== true;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/** Whether `computeHold` reads an entry as additive — never under the plant that holds as if none were. */
|
|
640
|
+
private holdsAdditive(e: Entry | null): boolean {
|
|
641
|
+
return e !== null && this.additiveOf(e) && this.plant.additiveHeld !== true;
|
|
642
|
+
}
|
|
643
|
+
|
|
538
644
|
update(delta: number): void {
|
|
539
645
|
const tracks = this.tracks;
|
|
540
646
|
for (let i = 0, n = tracks.length; i < n; i++) {
|
|
@@ -612,7 +718,7 @@ class Mixer {
|
|
|
612
718
|
const current = tracks[i];
|
|
613
719
|
if (current === null || current.delay > 0) continue;
|
|
614
720
|
// Apply mixing from entries first.
|
|
615
|
-
let alpha =
|
|
721
|
+
let alpha = this.alphaOf(current);
|
|
616
722
|
if (current.mixingFrom !== null) alpha *= this.applyMixingFrom(current, pose, i);
|
|
617
723
|
else if (current.trackTime >= current.trackEnd && current.next === null) alpha = 0;
|
|
618
724
|
// Apply the current entry.
|
|
@@ -621,20 +727,27 @@ class Mixer {
|
|
|
621
727
|
const timelines = current.anim.timelines;
|
|
622
728
|
const count = timelines.length;
|
|
623
729
|
if (i === 0 && alpha === 1) {
|
|
730
|
+
// Track 0 at full weight poses from the setup pose and adds nothing, additive or not — the runtime passes `add` false here.
|
|
624
731
|
for (const tl of timelines) {
|
|
625
732
|
if (tl.type === 'attachment') this.applyAttachment(pose, tl, animationTime, SETUP, true);
|
|
626
|
-
else this.applyTimeline(pose, tl, animationLast, animationTime, true, 1, SETUP, false);
|
|
733
|
+
else this.applyTimeline(pose, tl, animationLast, animationTime, true, 1, SETUP, false, false);
|
|
627
734
|
}
|
|
628
735
|
} else {
|
|
629
736
|
const modes = current.timelineMode;
|
|
630
|
-
|
|
631
|
-
const
|
|
737
|
+
// `alphaAttachmentThreshold` is 0: an entry keeps its attachments at any alpha, 0 included.
|
|
738
|
+
const retain = this.plant.attachmentsBelowHalfAlpha === true ? alpha >= 0.5 : alpha >= 0;
|
|
739
|
+
const add = this.additiveOf(current);
|
|
740
|
+
// `shortestRotation = add || …`: an additive entry's rotations go through the timeline itself, never the remembered direction.
|
|
741
|
+
const shortest = add && this.plant.additiveRotateRemembered !== true;
|
|
742
|
+
const firstFrame = !shortest && current.timelinesRotation.length !== count << 1;
|
|
632
743
|
if (firstFrame) current.timelinesRotation.length = count << 1;
|
|
633
744
|
timelines.forEach((tl, ii) => {
|
|
634
745
|
const from = this.fromOf(modes[ii] & MODE, i);
|
|
635
|
-
if (tl.type === 'bone' && tl.kind === 'rotate') this.applyRotate(pose, tl, animationTime, alpha, from, current.timelinesRotation, ii << 1, firstFrame);
|
|
636
|
-
else if (tl.type === 'attachment')
|
|
637
|
-
|
|
746
|
+
if (!shortest && tl.type === 'bone' && tl.kind === 'rotate') this.applyRotate(pose, tl, animationTime, alpha, from, current.timelinesRotation, ii << 1, firstFrame);
|
|
747
|
+
else if (tl.type === 'attachment') {
|
|
748
|
+
if (add && this.plant.additiveAttachmentsDirect === true) this.applyAttachmentDirect(pose, tl, animationTime, from, false);
|
|
749
|
+
else this.applyAttachment(pose, tl, animationTime, from, retain);
|
|
750
|
+
} else this.applyTimeline(pose, tl, animationLast, animationTime, true, alpha, from, false, add);
|
|
638
751
|
});
|
|
639
752
|
}
|
|
640
753
|
if (i === 0) base = animationTime;
|
|
@@ -657,15 +770,17 @@ class Mixer {
|
|
|
657
770
|
const from = to.mixingFrom as Entry;
|
|
658
771
|
const fromMix = from.mixingFrom !== null ? this.applyMixingFrom(from, pose, track) : 1;
|
|
659
772
|
const mix = this.mixOf(to);
|
|
660
|
-
const a =
|
|
661
|
-
const keep = 1 - mix * 1;
|
|
773
|
+
const a = this.alphaOf(from) * fromMix;
|
|
774
|
+
const keep = 1 - mix * (this.plant.holdIgnoresEntryAlpha === true ? 1 : this.alphaOf(to));
|
|
662
775
|
const alphaMix = a * (1 - mix);
|
|
663
776
|
const alphaHold = keep > 0 ? alphaMix / keep : a;
|
|
664
777
|
const timelines = from.anim.timelines;
|
|
665
778
|
const count = timelines.length;
|
|
666
779
|
const modes = from.timelineMode;
|
|
667
780
|
const holdMix = from.timelineHoldMix;
|
|
668
|
-
const
|
|
781
|
+
const add = this.additiveOf(from);
|
|
782
|
+
const shortest = add && this.plant.additiveRotateRemembered !== true;
|
|
783
|
+
const firstFrame = !shortest && from.timelinesRotation.length !== count << 1;
|
|
669
784
|
if (firstFrame) from.timelinesRotation.length = count << 1;
|
|
670
785
|
const animationLast = from.animationLast;
|
|
671
786
|
const animationTime = animationTimeOf(from);
|
|
@@ -680,10 +795,15 @@ class Mixer {
|
|
|
680
795
|
alpha = h === null || h === undefined ? alphaHold : alphaHold * (1 - this.mixOf(h));
|
|
681
796
|
} else alpha = alphaMix;
|
|
682
797
|
from.totalAlpha += alpha;
|
|
683
|
-
if (tl.type === 'bone' && tl.kind === 'rotate') this.applyRotate(pose, tl, animationTime, alpha, mixFrom, from.timelinesRotation, i << 1, firstFrame);
|
|
684
|
-
else if (tl.type === 'attachment')
|
|
798
|
+
if (!shortest && tl.type === 'bone' && tl.kind === 'rotate') this.applyRotate(pose, tl, animationTime, alpha, mixFrom, from.timelinesRotation, i << 1, firstFrame);
|
|
799
|
+
else if (tl.type === 'attachment') {
|
|
800
|
+
// `mixAttachmentThreshold` is 0: an entry mixing out never keeps an attachment, whatever its alpha.
|
|
801
|
+
if (add && this.plant.additiveAttachmentsDirect === true) this.applyAttachmentDirect(pose, tl, animationTime, mixFrom, true);
|
|
802
|
+
else this.applyAttachment(pose, tl, animationTime, mixFrom, false);
|
|
803
|
+
}
|
|
685
804
|
// An entry mixing out fires no event: `eventThreshold` is 0 and its events are applied only while the mix is below it.
|
|
686
|
-
|
|
805
|
+
// An additive one mixing out adds at the faded alpha — its additive timelines are never held (`computeHold`).
|
|
806
|
+
else this.applyTimeline(pose, tl, animationLast, animationTime, fire, alpha, mixFrom, true, add);
|
|
687
807
|
});
|
|
688
808
|
from.nextAnimationLast = animationTime;
|
|
689
809
|
from.nextTrackLast = from.trackTime;
|
|
@@ -712,16 +832,23 @@ class Mixer {
|
|
|
712
832
|
const holdMix = entry.timelineHoldMix;
|
|
713
833
|
holdMix.length = 0;
|
|
714
834
|
const to = entry.mixingTo;
|
|
835
|
+
const add = this.holdsAdditive(entry);
|
|
836
|
+
const toAdds = this.holdsAdditive(to);
|
|
715
837
|
timelines.forEach((tl, i) => {
|
|
716
838
|
const from = this.propertyFrom(track, tl.ids);
|
|
839
|
+
// An additive entry's additive timelines are never held, and the entry's keepHold does not reach them.
|
|
840
|
+
if (add && addable(tl)) {
|
|
841
|
+
modes[i] = from;
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
717
844
|
let mode: number;
|
|
718
|
-
// Hold if the next entry will overwrite this property.
|
|
719
|
-
if (to === null || tl.instant || !hasAny(to.anim, tl.ids)) mode = from;
|
|
845
|
+
// Hold if the next entry will overwrite this property — an additive one adding to it does not.
|
|
846
|
+
if (to === null || tl.instant || (toAdds && addable(tl)) || !hasAny(to.anim, tl.ids)) mode = from;
|
|
720
847
|
else {
|
|
721
848
|
mode = from | HOLD;
|
|
722
849
|
// Find the next entry that doesn't overwrite this property. Its mix fades out the hold, instead of it ending abruptly.
|
|
723
850
|
for (let next = to.mixingTo; next !== null; next = next.mixingTo) {
|
|
724
|
-
if (!hasAny(next.anim, tl.ids)) {
|
|
851
|
+
if ((this.holdsAdditive(next) && addable(tl)) || !hasAny(next.anim, tl.ids)) {
|
|
725
852
|
if (next.mixDuration > 0) holdMix[i] = next;
|
|
726
853
|
break;
|
|
727
854
|
}
|
|
@@ -761,12 +888,12 @@ class Mixer {
|
|
|
761
888
|
return keys[index].values.map((_v, c) => channel(keys, index, c, t));
|
|
762
889
|
}
|
|
763
890
|
|
|
764
|
-
/** `Timeline.apply` for every timeline but an attachment's and a rotation mixed below full weight. */
|
|
765
|
-
private applyTimeline(pose: Pose, tl: MixTimeline, lastTime: number, time: number, fire: boolean, alpha: number, from: number, out: boolean): void {
|
|
891
|
+
/** `Timeline.apply` for every timeline but an attachment's and a rotation mixed below full weight; `add` is the entry's, read only by the timelines the runtime adds (`addable`). */
|
|
892
|
+
private applyTimeline(pose: Pose, tl: MixTimeline, lastTime: number, time: number, fire: boolean, alpha: number, from: number, out: boolean, add: boolean): void {
|
|
766
893
|
switch (tl.type) {
|
|
767
|
-
case 'bone': return this.applyBone(pose, tl, time, alpha, from, out);
|
|
894
|
+
case 'bone': return this.applyBone(pose, tl, time, alpha, from, out, add);
|
|
768
895
|
case 'colour': return this.applyColour(pose, tl, time, alpha, from);
|
|
769
|
-
case 'deform': return this.applyDeform(pose, tl, time, alpha, from);
|
|
896
|
+
case 'deform': return this.applyDeform(pose, tl, time, alpha, from, add);
|
|
770
897
|
case 'events':
|
|
771
898
|
if (fire) pose.events.push(...eventsFired(tl.keys, lastTime, time, rawNumber, time < lastTime));
|
|
772
899
|
return;
|
|
@@ -774,7 +901,7 @@ class Mixer {
|
|
|
774
901
|
}
|
|
775
902
|
}
|
|
776
903
|
|
|
777
|
-
private applyBone(pose: Pose, tl: Extract<MixTimeline, { type: 'bone' }>, time: number, alpha: number, from: number, out: boolean): void {
|
|
904
|
+
private applyBone(pose: Pose, tl: Extract<MixTimeline, { type: 'bone' }>, time: number, alpha: number, from: number, out: boolean, add: boolean): void {
|
|
778
905
|
const bone = pose.bone(tl.bone);
|
|
779
906
|
if (bone === null) return;
|
|
780
907
|
const setup = this.doc.bones[tl.bone];
|
|
@@ -797,7 +924,8 @@ class Mixer {
|
|
|
797
924
|
const relative = (field: 'x' | 'y' | 'rotation' | 'shearX' | 'shearY', i: number): void => {
|
|
798
925
|
const s = setup[field] ?? 0;
|
|
799
926
|
const c = bone[field] ?? 0;
|
|
800
|
-
|
|
927
|
+
// `getRelativeValue`: from the setup pose the same either way; else the keyed value added, or the value over the setup mixed in.
|
|
928
|
+
bone[field] = v === null ? beforeFirstKey(from, alpha, c, s) : from === SETUP ? s + v[i] * alpha : c + (add ? v[i] : v[i] + s - c) * alpha;
|
|
801
929
|
};
|
|
802
930
|
const pair = (fx: 'x' | 'shearX', fy: 'y' | 'shearY'): void => {
|
|
803
931
|
const sx = setup[fx] ?? 0;
|
|
@@ -815,6 +943,9 @@ class Mixer {
|
|
|
815
943
|
if (from === SETUP) {
|
|
816
944
|
bone[fx] = sx + v[0] * alpha;
|
|
817
945
|
bone[fy] = sy + v[1] * alpha;
|
|
946
|
+
} else if (add) {
|
|
947
|
+
bone[fx] = (bone[fx] ?? 0) + v[0] * alpha;
|
|
948
|
+
bone[fy] = (bone[fy] ?? 0) + v[1] * alpha;
|
|
818
949
|
} else {
|
|
819
950
|
bone[fx] = (bone[fx] ?? 0) + (sx + v[0] - (bone[fx] ?? 0)) * alpha;
|
|
820
951
|
bone[fy] = (bone[fy] ?? 0) + (sy + v[1] - (bone[fy] ?? 0)) * alpha;
|
|
@@ -823,7 +954,7 @@ class Mixer {
|
|
|
823
954
|
const scale = (field: 'scaleX' | 'scaleY', i: number): void => {
|
|
824
955
|
const s = setup[field] ?? 1;
|
|
825
956
|
const c = bone[field] ?? 1;
|
|
826
|
-
bone[field] = v === null ? beforeFirstKey(from, alpha, c, s) : scaleValue(v[i] * s, alpha, from, out, c, s);
|
|
957
|
+
bone[field] = v === null ? beforeFirstKey(from, alpha, c, s) : scaleValue(v[i] * s, alpha, from, out, add, c, s);
|
|
827
958
|
};
|
|
828
959
|
switch (tl.kind) {
|
|
829
960
|
case 'rotate': relative('rotation', 0); break;
|
|
@@ -845,8 +976,8 @@ class Mixer {
|
|
|
845
976
|
bone.scaleY = (bone.scaleY ?? 1) + (sy - (bone.scaleY ?? 1)) * alpha;
|
|
846
977
|
}
|
|
847
978
|
} else {
|
|
848
|
-
bone.scaleX = scaleValue(v[0] * (setup.scaleX ?? 1), alpha, from, out, bone.scaleX ?? 1, setup.scaleX ?? 1);
|
|
849
|
-
bone.scaleY = scaleValue(v[1] * (setup.scaleY ?? 1), alpha, from, out, bone.scaleY ?? 1, setup.scaleY ?? 1);
|
|
979
|
+
bone.scaleX = scaleValue(v[0] * (setup.scaleX ?? 1), alpha, from, out, add, bone.scaleX ?? 1, setup.scaleX ?? 1);
|
|
980
|
+
bone.scaleY = scaleValue(v[1] * (setup.scaleY ?? 1), alpha, from, out, add, bone.scaleY ?? 1, setup.scaleY ?? 1);
|
|
850
981
|
}
|
|
851
982
|
break;
|
|
852
983
|
case 'scalex': scale('scaleX', 0); break;
|
|
@@ -858,7 +989,7 @@ class Mixer {
|
|
|
858
989
|
private applyRotate(pose: Pose, tl: Extract<MixTimeline, { type: 'bone' }>, time: number, alpha: number, from: number, rotations: number[], i: number, firstFrame: boolean): void {
|
|
859
990
|
if (firstFrame) rotations[i] = 0;
|
|
860
991
|
if (alpha === 1) {
|
|
861
|
-
this.applyBone(pose, tl, time, 1, from, false);
|
|
992
|
+
this.applyBone(pose, tl, time, 1, from, false, false);
|
|
862
993
|
return;
|
|
863
994
|
}
|
|
864
995
|
const bone = pose.bone(tl.bone);
|
|
@@ -934,6 +1065,15 @@ class Mixer {
|
|
|
934
1065
|
else if (!setup) s.attachmentState = ATTACH_SETUP;
|
|
935
1066
|
}
|
|
936
1067
|
|
|
1068
|
+
/** `AttachmentTimeline.apply` — the plain timeline, writing no attachment state: only `additiveAttachmentsDirect` reaches it, the reading `CO52` rejects. */
|
|
1069
|
+
private applyAttachmentDirect(pose: Pose, tl: Extract<MixTimeline, { type: 'attachment' }>, time: number, from: number, out: boolean): void {
|
|
1070
|
+
if (!pose.slotLive(tl.slot)) return;
|
|
1071
|
+
const index = (this.plant.search ?? keyIndexAt)(tl.keys, time);
|
|
1072
|
+
if (out || index < 0) {
|
|
1073
|
+
if (from !== CURRENT) pose.setAttachment(tl.slot, this.doc.slots[tl.slot].setup);
|
|
1074
|
+
} else pose.setAttachment(tl.slot, tl.keys[index].name ?? null);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
937
1077
|
private applyColour(pose: Pose, tl: Extract<MixTimeline, { type: 'colour' }>, time: number, alpha: number, from: number): void {
|
|
938
1078
|
if (!pose.slotLive(tl.slot)) return;
|
|
939
1079
|
const slot = this.doc.slots[tl.slot];
|
|
@@ -1030,7 +1170,7 @@ class Mixer {
|
|
|
1030
1170
|
}
|
|
1031
1171
|
|
|
1032
1172
|
/** `DeformTimeline.apply`: every live slot showing the record the timeline keys, its deform array mixed. */
|
|
1033
|
-
private applyDeform(pose: Pose, tl: Extract<MixTimeline, { type: 'deform' }>, time: number, alpha: number, from: number): void {
|
|
1173
|
+
private applyDeform(pose: Pose, tl: Extract<MixTimeline, { type: 'deform' }>, time: number, alpha: number, from: number, add: boolean): void {
|
|
1034
1174
|
const keys = tl.timeline.deform;
|
|
1035
1175
|
if (keys === null) return;
|
|
1036
1176
|
pose.slots.forEach((s, k) => {
|
|
@@ -1061,13 +1201,27 @@ class Mixer {
|
|
|
1061
1201
|
}
|
|
1062
1202
|
return;
|
|
1063
1203
|
}
|
|
1064
|
-
// `applyToSlot
|
|
1065
|
-
|
|
1066
|
-
|
|
1204
|
+
// `applyToSlot`: added only over a deform already there — over none it is the setup pose's branch, additive or not.
|
|
1205
|
+
const fromSetup = mixFrom === SETUP;
|
|
1206
|
+
if (alpha === 1) {
|
|
1207
|
+
if (add && !fromSetup) {
|
|
1208
|
+
const current = s.deform as number[];
|
|
1209
|
+
if (!vertices.weighted) {
|
|
1210
|
+
const setup = setupArray(vertices);
|
|
1211
|
+
s.deform = current.map((c, i) => c + (target[i] - setup[i]));
|
|
1212
|
+
} else s.deform = current.map((c, i) => c + target[i]);
|
|
1213
|
+
} else s.deform = [...target];
|
|
1214
|
+
} else if (fromSetup) {
|
|
1067
1215
|
if (!vertices.weighted) {
|
|
1068
1216
|
const setup = setupArray(vertices);
|
|
1069
1217
|
s.deform = target.map((v, i) => setup[i] + (v - setup[i]) * alpha);
|
|
1070
1218
|
} else s.deform = target.map((v) => v * alpha);
|
|
1219
|
+
} else if (add) {
|
|
1220
|
+
const current = s.deform as number[];
|
|
1221
|
+
if (!vertices.weighted) {
|
|
1222
|
+
const setup = setupArray(vertices);
|
|
1223
|
+
s.deform = current.map((c, i) => c + (target[i] - setup[i]) * alpha);
|
|
1224
|
+
} else s.deform = current.map((c, i) => c + target[i] * alpha);
|
|
1071
1225
|
} else {
|
|
1072
1226
|
const current = s.deform as number[];
|
|
1073
1227
|
s.deform = current.map((c, i) => c + (target[i] - c) * alpha);
|
|
@@ -1083,10 +1237,11 @@ function beforeFirstKey(from: number, alpha: number, current: number, setup: num
|
|
|
1083
1237
|
return current;
|
|
1084
1238
|
}
|
|
1085
1239
|
|
|
1086
|
-
/** `CurveTimeline1.getScaleValue` past the first key, `value` the curve times the setup scale,
|
|
1087
|
-
function scaleValue(value: number, alpha: number, from: number, out: boolean, current: number, setup: number): number {
|
|
1088
|
-
if (alpha === 1) return value;
|
|
1240
|
+
/** `CurveTimeline1.getScaleValue` past the first key, `value` the curve times the setup scale; added, its offset from the setup scale is added to the base. */
|
|
1241
|
+
function scaleValue(value: number, alpha: number, from: number, out: boolean, add: boolean, current: number, setup: number): number {
|
|
1242
|
+
if (alpha === 1 && !add) return value;
|
|
1089
1243
|
let base = from === SETUP ? setup : current;
|
|
1244
|
+
if (add) return base + (value - setup) * alpha;
|
|
1090
1245
|
if (out) return base + (Math.abs(value) * Math.sign(base) - base) * alpha;
|
|
1091
1246
|
base = Math.abs(base) * Math.sign(value);
|
|
1092
1247
|
return base + (value - base) * alpha;
|
|
@@ -1117,6 +1272,20 @@ function loopOf(options: MixOptions | undefined, call: string): boolean {
|
|
|
1117
1272
|
return options.loop;
|
|
1118
1273
|
}
|
|
1119
1274
|
|
|
1275
|
+
/** An alpha: a finite number from 0 to 1, or the call refused naming it — the runtime clamps nothing and defines no weight outside it. */
|
|
1276
|
+
function alphaValue(v: unknown, what: string, call: string): number {
|
|
1277
|
+
if (typeof v !== 'number' || !Number.isFinite(v) || v < 0 || v > 1) throw new CoreInputError(`the live tracks' ${call} ${what} is ${typeof v === 'number' ? String(v) : JSON.stringify(v)}, not a number from 0 to 1`);
|
|
1278
|
+
return v;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
/** `options.alpha` and `options.additive`: 1 and `false` when absent, else refused by name where they are not a weight and a boolean. */
|
|
1282
|
+
function blendOf(options: MixOptions, call: string): { alpha: number; additive: boolean } {
|
|
1283
|
+
const alpha = options.alpha === undefined ? 1 : alphaValue(options.alpha, 'options.alpha', call);
|
|
1284
|
+
const additive: unknown = options.additive;
|
|
1285
|
+
if (additive !== undefined && typeof additive !== 'boolean') throw new CoreInputError(`the live tracks' ${call} options.additive is ${JSON.stringify(additive)}, not true or false — an entry adds or it does not`);
|
|
1286
|
+
return { alpha, additive: additive === true };
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1120
1289
|
function delayOf(v: number, call: string): number {
|
|
1121
1290
|
if (typeof v !== 'number' || !Number.isFinite(v)) throw new CoreInputError(`the live tracks' ${call} delay is ${String(v)}, not a finite number`);
|
|
1122
1291
|
return v;
|
|
@@ -1163,8 +1332,11 @@ class Tracks implements LiveTracks {
|
|
|
1163
1332
|
const i = trackIndex(track, 'setAnimation');
|
|
1164
1333
|
const loop = loopOf(options, 'setAnimation');
|
|
1165
1334
|
const mix = finiteAtOrAbove0(options.mixDuration, 'mixDuration', 'setAnimation');
|
|
1335
|
+
const blend = blendOf(options, 'setAnimation');
|
|
1166
1336
|
const entry = this.mixer.setAnimation(i, this.mixer.animation(animation), loop);
|
|
1167
1337
|
entry.mixDuration = mix;
|
|
1338
|
+
entry.alpha = blend.alpha;
|
|
1339
|
+
entry.additive = blend.additive;
|
|
1168
1340
|
}
|
|
1169
1341
|
|
|
1170
1342
|
setEmptyAnimation(track: number, mixDuration: number): void {
|
|
@@ -1182,8 +1354,11 @@ class Tracks implements LiveTracks {
|
|
|
1182
1354
|
const loop = loopOf(options, 'addAnimation');
|
|
1183
1355
|
const mix = finiteAtOrAbove0(options.mixDuration, 'mixDuration', 'addAnimation');
|
|
1184
1356
|
const delay = delayOf(options.delay, 'addAnimation');
|
|
1357
|
+
const blend = blendOf(options, 'addAnimation');
|
|
1185
1358
|
const entry = this.mixer.addAnimation(i, this.mixer.animation(animation), loop, delay);
|
|
1186
1359
|
this.mixer.setMixDuration(entry, mix, delay);
|
|
1360
|
+
entry.alpha = blend.alpha;
|
|
1361
|
+
entry.additive = blend.additive;
|
|
1187
1362
|
}
|
|
1188
1363
|
|
|
1189
1364
|
addEmptyAnimation(track: number, mixDuration: number, delay: number): void {
|
|
@@ -1197,10 +1372,19 @@ class Tracks implements LiveTracks {
|
|
|
1197
1372
|
entry.trackEnd = mix;
|
|
1198
1373
|
}
|
|
1199
1374
|
|
|
1375
|
+
setAlpha(track: number, alpha: number): void {
|
|
1376
|
+
this.live('setAlpha');
|
|
1377
|
+
const i = trackIndex(track, 'setAlpha');
|
|
1378
|
+
const a = alphaValue(alpha, 'alpha', 'setAlpha');
|
|
1379
|
+
const e = this.mixer.tracks[i] ?? null;
|
|
1380
|
+
if (e === null) throw new CoreInputError(`the live tracks' setAlpha names track ${i}, which plays nothing — there is no entry to weigh`);
|
|
1381
|
+
e.alpha = a;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1200
1384
|
current(track: number): TrackEntryView | null {
|
|
1201
1385
|
const e = this.mixer.tracks[trackIndex(track, 'current')] ?? null;
|
|
1202
1386
|
if (e === null) return null;
|
|
1203
|
-
return { animation: e.anim.name, loop: e.loop, trackTime: e.trackTime, animationTime: animationTimeOf(e), duration: e.anim.duration };
|
|
1387
|
+
return { animation: e.anim.name, loop: e.loop, trackTime: e.trackTime, animationTime: animationTimeOf(e), duration: e.anim.duration, alpha: e.alpha, additive: e.additive };
|
|
1204
1388
|
}
|
|
1205
1389
|
|
|
1206
1390
|
start(): RawPose {
|
package/src/deformmeasure.ts
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* ⭐ **One survey, two consumers.** `A39_DEFORM_KEEPS_TRIANGLE_WINDING` reads the
|
|
5
5
|
* reversals out of it and refuses a build; `explain`'s `DEFORM` block prints the
|
|
6
6
|
* whole of it and refuses nothing. That split is issue
|
|
7
|
-
* [#296](https://github.com/
|
|
8
|
-
* [#314](https://github.com/
|
|
9
|
-
* [#316](https://github.com/
|
|
7
|
+
* [#296](https://github.com/rig-c/core/issues/296)'s two halves —
|
|
8
|
+
* [#314](https://github.com/rig-c/core/pull/314) landed the assertion and
|
|
9
|
+
* [#316](https://github.com/rig-c/core/issues/316) the report — and the reason
|
|
10
10
|
* they share this file rather than each posing the skeleton themselves is that
|
|
11
11
|
* the report's `winding 32 of 32 kept` and A39's `8 of 32 reverse` are **the same
|
|
12
12
|
* count**. Two derivations of one number drift, and the one that drifts silently
|
package/src/depth.ts
CHANGED
|
@@ -454,14 +454,14 @@ export function foldPrecedes(
|
|
|
454
454
|
* minimum alone cannot answer the question an author actually has: `degrees` is
|
|
455
455
|
* the same number whether a whole band of the mesh reaches the limit together
|
|
456
456
|
* or one triangle does, and those are a form and a bad texel respectively
|
|
457
|
-
* ([#412](https://github.com/
|
|
457
|
+
* ([#412](https://github.com/rig-c/core/issues/412),
|
|
458
458
|
* `bench/studies/2026-09-05-noise` §6).
|
|
459
459
|
*
|
|
460
460
|
* ⚠️ And a band is not sufficient evidence of a form either, which is what
|
|
461
461
|
* `stepShare` is here for: an OUTLINE is a band, so a mesh whose ceiling is set
|
|
462
462
|
* by the occlusion edge of a cut-out reads `p1/degrees` near 1 and a `depthStep`
|
|
463
463
|
* of most of the sheet's range — both of the older figures reading *healthy* on
|
|
464
|
-
* the same measurement ([#448](https://github.com/
|
|
464
|
+
* the same measurement ([#448](https://github.com/rig-c/core/issues/448)).
|
|
465
465
|
*
|
|
466
466
|
* ⛔ Neither figure changes `degrees`, and neither is a threshold. rigc does not
|
|
467
467
|
* have the authority to guess its input away, so nothing here filters,
|
|
@@ -499,7 +499,7 @@ export interface FoldLimit {
|
|
|
499
499
|
* triangle that folds first.
|
|
500
500
|
*
|
|
501
501
|
* ⭐ The figure that tells a form from a cliff, and it is the one reading the
|
|
502
|
-
* other two cannot give ([#448](https://github.com/
|
|
502
|
+
* other two cannot give ([#448](https://github.com/rig-c/core/issues/448)).
|
|
503
503
|
* A form has a slope, so refining the lattice halves the step and halves this
|
|
504
504
|
* with it while the angle converges. A **discontinuity has no slope**: the
|
|
505
505
|
* step stays the whole range however fine the lattice gets, this figure pins
|
package/src/diff.ts
CHANGED
|
@@ -362,7 +362,7 @@ function atPlaces(n: number): number {
|
|
|
362
362
|
* it, so `note` states both rates and which side is the bigger one. A histogram
|
|
363
363
|
* intersection over the same quantity reads 421/1339 whether the candidate has
|
|
364
364
|
* a third of the reference's keys or three times them — that ambiguity is what
|
|
365
|
-
* [issue #20](https://github.com/
|
|
365
|
+
* [issue #20](https://github.com/rig-c/core/issues/20) turned on, and a
|
|
366
366
|
* figure that repeated it would be the second copy of the same defect.
|
|
367
367
|
*/
|
|
368
368
|
function rateAgreement(id: string, what: string, unit: string, a: number, b: number, convention: string): DiffMeasure {
|
package/src/generation.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* **silently**: 4.3 takes constraints from the top-level `constraints` array
|
|
8
8
|
* alone, so a 4.0–4.2 file parses clean with none of them — measured across
|
|
9
9
|
* 1,302 shipped skeletons that loaded 0 of 8,672 constraints
|
|
10
|
-
* ([#706](https://github.com/
|
|
10
|
+
* ([#706](https://github.com/rig-c/core/issues/706) row 1). rigc is where
|
|
11
11
|
* that knowledge lives for the tools around it (#706 *Ownership*), and living
|
|
12
12
|
* in one place means one reader of `skeleton.spine`:
|
|
13
13
|
* `A16_SKELETON_VERSION_4_3` asks this function for its verdict, and `ingest`
|