@msb235/blits 0.15.1 → 0.16.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.
Files changed (83) hide show
  1. package/CHANGELOG.md +184 -0
  2. package/README.md +1 -1
  3. package/dist/ahead.js +11 -13
  4. package/dist/chain.js +2 -3
  5. package/dist/clock.d.ts +11 -1
  6. package/dist/clock.js +28 -9
  7. package/dist/color.d.ts +36 -14
  8. package/dist/color.js +186 -58
  9. package/dist/columns.d.ts +1 -11
  10. package/dist/columns.js +1 -6
  11. package/dist/contribution.js +11 -4
  12. package/dist/crowd.d.ts +0 -6
  13. package/dist/crowd.js +0 -11
  14. package/dist/cue.js +19 -7
  15. package/dist/due.d.ts +18 -0
  16. package/dist/due.js +101 -2
  17. package/dist/everyone.d.ts +17 -6
  18. package/dist/everyone.js +17 -8
  19. package/dist/fade.d.ts +22 -1
  20. package/dist/fade.js +81 -12
  21. package/dist/fill.js +2 -1
  22. package/dist/fit.js +8 -0
  23. package/dist/fold.js +4 -7
  24. package/dist/gone.js +1 -0
  25. package/dist/handle.d.ts +1 -1
  26. package/dist/handle.js +3 -1
  27. package/dist/held.d.ts +2 -1
  28. package/dist/held.js +17 -10
  29. package/dist/hosts.d.ts +7 -0
  30. package/dist/hosts.js +23 -6
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +1 -1
  33. package/dist/lane.d.ts +2 -0
  34. package/dist/lane.js +2 -0
  35. package/dist/lanes.d.ts +26 -1
  36. package/dist/lanes.js +9 -1
  37. package/dist/lch.d.ts +23 -0
  38. package/dist/lch.js +126 -0
  39. package/dist/meet.js +9 -0
  40. package/dist/mixer.d.ts +6 -1
  41. package/dist/mixer.js +5 -0
  42. package/dist/motion.js +6 -6
  43. package/dist/motions.d.ts +4 -1
  44. package/dist/motions.js +16 -2
  45. package/dist/move.js +311 -79
  46. package/dist/origin.d.ts +2 -0
  47. package/dist/origin.js +4 -0
  48. package/dist/owner.d.ts +7 -2
  49. package/dist/owner.js +17 -5
  50. package/dist/paging.d.ts +5 -1
  51. package/dist/paging.js +13 -3
  52. package/dist/patch.js +24 -4
  53. package/dist/place.js +67 -31
  54. package/dist/project.d.ts +5 -2
  55. package/dist/project.js +9 -11
  56. package/dist/pull.d.ts +11 -0
  57. package/dist/pull.js +10 -6
  58. package/dist/qualify.js +7 -2
  59. package/dist/rests.js +9 -1
  60. package/dist/revive.d.ts +11 -1
  61. package/dist/revive.js +7 -0
  62. package/dist/roles.js +6 -5
  63. package/dist/roster.d.ts +6 -0
  64. package/dist/roster.js +8 -1
  65. package/dist/rows.js +4 -1
  66. package/dist/sample.js +2 -1
  67. package/dist/seat.d.ts +5 -1
  68. package/dist/seat.js +5 -1
  69. package/dist/seek.js +6 -16
  70. package/dist/shared.d.ts +1 -1
  71. package/dist/shared.js +1 -1
  72. package/dist/spans.d.ts +13 -4
  73. package/dist/spans.js +125 -32
  74. package/dist/touch.js +1 -2
  75. package/dist/trace.js +7 -2
  76. package/dist/turns.js +2 -1
  77. package/dist/types/transport.d.ts +2 -1
  78. package/dist/types/voice.d.ts +3 -2
  79. package/dist/voice.d.ts +59 -2
  80. package/dist/voice.js +76 -2
  81. package/dist/weigh.js +1 -1
  82. package/docs/schema.html +38 -12
  83. package/package.json +3 -1
package/dist/lanes.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { lit } from './bound.js';
2
+ import { flush, pullRun, writeLater } from './columns.js';
2
3
  import { copy } from './copyout.js';
3
4
  import { row } from './crowd.js';
5
+ import { share } from './everyone.js';
4
6
  import { call, fillAll, foldKeys, one, parting, readKeyed, reset, run, runEveryone, signalled, subjectAt, } from './fill.js';
5
7
  import { fold, foldDelta, foldInto, foldRun, gate, gather, unplain } from './gather.js';
6
8
  import { meet, reach } from './meet.js';
@@ -9,6 +11,7 @@ import { Owed } from './owed.js';
9
11
  import { Placed } from './placed.js';
10
12
  import { requalify, retouch } from './qualify.js';
11
13
  import { reading } from './reading.js';
14
+ import { renumber } from './renumber.js';
12
15
  import { reportedRow, unplace } from './rows.js';
13
16
  import { seatOf } from './seat.js';
14
17
  /**
@@ -18,6 +21,10 @@ import { seatOf } from './seat.js';
18
21
  // biome-ignore lint/suspicious/noUnsafeDeclarationMerging: every member of `Methods` is installed below
19
22
  export class Lanes {
20
23
  host;
24
+ /** What the general path calls on a sharing voice, given to a mix by its engine so a lean bundle leaves it out. */
25
+ static share = share;
26
+ /** What `pull` calls on lanes alone. */
27
+ pulls = { pullRun, writeLater, flush, renumber };
21
28
  numbers;
22
29
  lanes = [];
23
30
  /** The lanes over every subject that have started playing, by epoch. */
@@ -261,7 +268,8 @@ export class Lanes {
261
268
  }
262
269
  reported(lane, slot) {
263
270
  const o = slot * 6 /* Per.SLOT */;
264
- if (!(this.per[o + 3 /* Per.LANE_PROBE */] > this.per[o + 4 /* Per.GENERAL_PROBE */]))
271
+ const probe = this.per[o + 3 /* Per.LANE_PROBE */];
272
+ if (!(probe > this.per[o + 4 /* Per.GENERAL_PROBE */]) || !(probe > lane.made))
265
273
  return undefined;
266
274
  const p = lane.positionOf(slot);
267
275
  if (p < 0)
package/dist/lch.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import type { Curve } from './easing.js';
2
+ /** Below this chroma a color is gray and its hue is noise, so it takes the other end's. */
3
+ export declare const GRAY = 0.0001;
4
+ /** Where `shortWay` leaves the hue a way starts from and how far it turns. */
5
+ export declare const HUES: number[];
6
+ /** The way from hue `ah` to `bh` the short way round into `HUES`, a gray end taking the other's. */
7
+ export declare function shortWay(ah: number, ac: number, bh: number, bc: number): void;
8
+ /**
9
+ * Two premultiplied OKLab colors, `[w·L, w·a, w·b, w]` read from `x` at `i` and `y` at `j`, into
10
+ * `out` as `[L, C, h, w]` each, `y`'s hue the short way round from `x`'s. A color with no coverage
11
+ * has none of its own to keep, so it takes the other end's.
12
+ */
13
+ export declare function lchPair(x: ArrayLike<number>, i: number, y: ArrayLike<number>, j: number, out: Float64Array): void;
14
+ /**
15
+ * `closed` for a stretch of a color traveling round the hue, as `ink` does: its ends and velocity,
16
+ * premultiplied OKLab as the patch keeps them, moved into `[L, C, h, w]`, solved there axis by
17
+ * axis, and the position and
18
+ * velocity moved back into `xo` and `vo`. A spring heads round the hue the short way from where
19
+ * the stretch starts, so a retarget bends the path rather than restarting it, and one that
20
+ * overshoots its target overshoots in hue too. A glide comes to rest `v·τ` along each of the four,
21
+ * so it coasts round the hue rather than off along a tangent.
22
+ */
23
+ export declare function closedLch(law: Float64Array, ease: Curve | undefined, at: number, ms: number, x0: ArrayLike<number>, x: number, v0: ArrayLike<number>, v: number, to: ArrayLike<number>, g: number, t: number, xo: Float64Array, vo: Float64Array): boolean;
package/dist/lch.js ADDED
@@ -0,0 +1,126 @@
1
+ import { COAST, closed } from './closed.js';
2
+ /** Below this chroma a color is gray and its hue is noise, so it takes the other end's. */
3
+ export const GRAY = 1e-4;
4
+ /** Where `shortWay` leaves the hue a way starts from and how far it turns. */
5
+ export const HUES = [0, 0];
6
+ /** The way from hue `ah` to `bh` the short way round into `HUES`, a gray end taking the other's. */
7
+ export function shortWay(ah, ac, bh, bc) {
8
+ const from = ac < GRAY ? bh : ah;
9
+ const to = bc < GRAY ? ah : bh;
10
+ let turn = to - from;
11
+ if (turn > Math.PI)
12
+ turn -= 2 * Math.PI;
13
+ else if (turn < -Math.PI)
14
+ turn += 2 * Math.PI;
15
+ HUES[0] = from;
16
+ HUES[1] = turn;
17
+ }
18
+ /**
19
+ * Two premultiplied OKLab colors, `[w·L, w·a, w·b, w]` read from `x` at `i` and `y` at `j`, into
20
+ * `out` as `[L, C, h, w]` each, `y`'s hue the short way round from `x`'s. A color with no coverage
21
+ * has none of its own to keep, so it takes the other end's.
22
+ */
23
+ export function lchPair(x, i, y, j, out) {
24
+ const xw = x[i + 3] ?? 0;
25
+ const yw = y[j + 3] ?? 0;
26
+ const ys = yw > 0 ? 1 / yw : 0;
27
+ const yL = (y[j] ?? 0) * ys;
28
+ const yA = (y[j + 1] ?? 0) * ys;
29
+ const yB = (y[j + 2] ?? 0) * ys;
30
+ const xs = xw > 0 ? 1 / xw : 0;
31
+ const xL = xw > 0 ? (x[i] ?? 0) * xs : yL;
32
+ const xA = xw > 0 ? (x[i + 1] ?? 0) * xs : yA;
33
+ const xB = xw > 0 ? (x[i + 2] ?? 0) * xs : yB;
34
+ const toL = yw > 0 ? yL : xL;
35
+ const toA = yw > 0 ? yA : xA;
36
+ const toB = yw > 0 ? yB : xB;
37
+ const xc = Math.sqrt(xA * xA + xB * xB);
38
+ const yc = Math.sqrt(toA * toA + toB * toB);
39
+ shortWay(Math.atan2(xB, xA), xc, Math.atan2(toB, toA), yc);
40
+ out[0] = xL;
41
+ out[1] = xc;
42
+ out[2] = HUES[0];
43
+ out[3] = xw;
44
+ out[4] = toL;
45
+ out[5] = yc;
46
+ out[6] = HUES[0] + HUES[1];
47
+ out[7] = yw;
48
+ }
49
+ const PAIR = new Float64Array(8);
50
+ const RATES = new Float64Array(4);
51
+ const REST = new Float64Array(4);
52
+ const XS = new Float64Array(4);
53
+ const VS = new Float64Array(4);
54
+ /**
55
+ * `closed` for a stretch of a color traveling round the hue, as `ink` does: its ends and velocity,
56
+ * premultiplied OKLab as the patch keeps them, moved into `[L, C, h, w]`, solved there axis by
57
+ * axis, and the position and
58
+ * velocity moved back into `xo` and `vo`. A spring heads round the hue the short way from where
59
+ * the stretch starts, so a retarget bends the path rather than restarting it, and one that
60
+ * overshoots its target overshoots in hue too. A glide comes to rest `v·τ` along each of the four,
61
+ * so it coasts round the hue rather than off along a tangent.
62
+ */
63
+ export function closedLch(law, ease, at, ms, x0, x, v0, v, to, g, t, xo, vo) {
64
+ const p = PAIR;
65
+ lchPair(x0, x, to, g, p);
66
+ const L = p[0];
67
+ const C = p[1];
68
+ const h = p[2];
69
+ const w = p[3];
70
+ // OKLab's rates of change at the start, its coverage's share divided out, turned onto L, C and h.
71
+ const dw = v0[v + 3];
72
+ let dL = 0;
73
+ let dC = 0;
74
+ let dh = 0;
75
+ if (w > 0) {
76
+ const cos = Math.cos(h);
77
+ const sin = Math.sin(h);
78
+ dL = (v0[v] - dw * L) / w;
79
+ const da = (v0[v + 1] - dw * C * cos) / w;
80
+ const db = (v0[v + 2] - dw * C * sin) / w;
81
+ dC = da * cos + db * sin;
82
+ dh = C < GRAY ? 0 : (db * cos - da * sin) / C;
83
+ }
84
+ RATES[0] = dL;
85
+ RATES[1] = dC;
86
+ RATES[2] = dh;
87
+ RATES[3] = dw;
88
+ let goal = p;
89
+ let o = 4;
90
+ if (law[0] === COAST) {
91
+ const tau = law[2];
92
+ for (let i = 0; i < 4; i++)
93
+ REST[i] = p[i] + RATES[i] * tau;
94
+ goal = REST;
95
+ o = 0;
96
+ }
97
+ const still = closed(law, ease, 4, at, ms, p, 0, RATES, 0, goal, o, t, XS, VS);
98
+ if (still && o === 4) {
99
+ for (let i = 0; i < 4; i++) {
100
+ xo[i] = to[g + i];
101
+ vo[i] = 0;
102
+ }
103
+ return true;
104
+ }
105
+ const nL = XS[0];
106
+ const nC = XS[1];
107
+ const nh = XS[2];
108
+ const nw = XS[3];
109
+ const cos = Math.cos(nh);
110
+ const sin = Math.sin(nh);
111
+ const a = nC * cos;
112
+ const b = nC * sin;
113
+ const rL = VS[0];
114
+ const rC = VS[1];
115
+ const rh = VS[2];
116
+ const rw = VS[3];
117
+ xo[0] = nL * nw;
118
+ xo[1] = a * nw;
119
+ xo[2] = b * nw;
120
+ xo[3] = nw;
121
+ vo[0] = rL * nw + nL * rw;
122
+ vo[1] = (rC * cos - nC * rh * sin) * nw + a * rw;
123
+ vo[2] = (rC * sin + nC * rh * cos) * nw + b * rw;
124
+ vo[3] = rw;
125
+ return still;
126
+ }
package/dist/meet.js CHANGED
@@ -86,6 +86,15 @@ export function meet(lanes, slot, subject, head, lane) {
86
86
  }
87
87
  if (!met)
88
88
  return lane;
89
+ // Met newest first; folded, as the general path folds them, in voice order.
90
+ const ids = lanes.met;
91
+ for (let i = 1; i < lanes.metN; i++) {
92
+ const id = ids[i];
93
+ let j = i - 1;
94
+ for (; j >= 0 && ids[j] > id; j--)
95
+ ids[j + 1] = ids[j];
96
+ ids[j + 1] = id;
97
+ }
89
98
  // The fill ran before the subject had these positions. Where every voice it just met folds
90
99
  // after every other laned voice, the general path folds just those onto the lanes' values;
91
100
  // otherwise it reads the general path all frame.
package/dist/mixer.d.ts CHANGED
@@ -4,11 +4,11 @@ import { blended, laneRead, shareKept } from './blend.js';
4
4
  import type { Book } from './book.js';
5
5
  import { aims, chain, linkable, relink } from './chain.js';
6
6
  import { type LerpInto } from './channels.js';
7
- import type { Column } from './columns.js';
8
7
  import { baseFor, contribution, isRest, keyed, read, slopeFor, tick } from './contribution.js';
9
8
  import { DepartedIndex } from './departed.js';
10
9
  import { drop, forgetIn, recordDrop, releaseIn } from './drop.js';
11
10
  import { DueQueue } from './due.js';
11
+ import type { Share } from './everyone.js';
12
12
  import { parting } from './fade.js';
13
13
  import { apply, clamp, fold, foldDetour, folded, foldSharers, foldWith, type Key, linked, owedBy, passes, shared } from './fold.js';
14
14
  import { GoneIndex } from './gone.js';
@@ -20,6 +20,7 @@ import type { Pace } from './pace.js';
20
20
  import { Keys } from './paging.js';
21
21
  import type { Places } from './places.js';
22
22
  import { moved, place, plot, put } from './plot.js';
23
+ import type { Column } from './pull.js';
23
24
  import { keep } from './pull.js';
24
25
  import { Steps } from './relink.js';
25
26
  import { atRest, restful, rests, restsKey, restsLaned, still } from './rests.js';
@@ -105,6 +106,8 @@ export declare class Mixer<I, O> implements Mix<I, O> {
105
106
  retired: Voice<I, O>[];
106
107
  /** Visit every voice each sync, as before the due queue; for tests that compare the two. */
107
108
  walkAll: boolean;
109
+ /** Owners sought forward since the last frame, which passes what each seek went past in order. */
110
+ leapt: Set<Voice<I, O>> | null;
108
111
  /** The last array `pull` read, by position, with each subject's chain head, to skip the lookup. */
109
112
  pulled: I[];
110
113
  pulledHeads: (Subject<unknown> | undefined)[];
@@ -209,6 +212,8 @@ export declare class Mixer<I, O> implements Mix<I, O> {
209
212
  roleDepth: number;
210
213
  readonly slotOf: Map<string, number>;
211
214
  lanes: Lanes<I, O> | null;
215
+ /** The sharing hooks of the lanes this entry was built with, which a projection's copy keeps. */
216
+ share: Share | null;
212
217
  handles: HandleHost<I, O> | null;
213
218
  /** What `book` made, still booking; null while there is none. */
214
219
  bookers: Book<I, O>[] | null;
package/dist/mixer.js CHANGED
@@ -95,6 +95,8 @@ export class Mixer {
95
95
  retired = [];
96
96
  /** Visit every voice each sync, as before the due queue; for tests that compare the two. */
97
97
  walkAll = false;
98
+ /** Owners sought forward since the last frame, which passes what each seek went past in order. */
99
+ leapt = null;
98
100
  /** The last array `pull` read, by position, with each subject's chain head, to skip the lookup. */
99
101
  pulled = [];
100
102
  pulledHeads = [];
@@ -235,6 +237,8 @@ export class Mixer {
235
237
  roleDepth = 0;
236
238
  slotOf = new Map();
237
239
  lanes;
240
+ /** The sharing hooks of the lanes this entry was built with, which a projection's copy keeps. */
241
+ share;
238
242
  handles = null;
239
243
  /** What `book` made, still booking; null while there is none. */
240
244
  bookers = null;
@@ -266,6 +270,7 @@ export class Mixer {
266
270
  });
267
271
  if (lanesOf === null && opts.lanes === true)
268
272
  throw new Error("blits: '@msb235/blits/lean' has no lanes; take mix from '@msb235/blits'");
273
+ this.share = lanesOf?.share ?? null;
269
274
  this.lanes =
270
275
  lanesOf !== null && (opts.lanes ?? defaults.lanes) ? new lanesOf(laneHost(this)) : null;
271
276
  this.name = opts.name;
package/dist/motion.js CHANGED
@@ -241,8 +241,8 @@ export function spring(writes, opts) {
241
241
  const k = opts.stiffness ?? 170;
242
242
  const c = opts.damping ?? 26;
243
243
  const m = opts.mass ?? 1;
244
- if (!(k > 0) || !(m > 0) || !(c >= 0))
245
- throw new RangeError('blits: a spring takes a positive stiffness and mass, and damping of 0 or more');
244
+ if (!(k > 0 && k < Infinity) || !(m > 0 && m < Infinity) || !(c >= 0 && c < Infinity))
245
+ throw new RangeError('blits: a spring takes a finite positive stiffness and mass, and finite damping of 0 or more');
246
246
  const settle = opts.settle ?? 1e-4;
247
247
  const w0 = Math.sqrt(k / m);
248
248
  const zeta = c / (2 * Math.sqrt(k * m));
@@ -273,11 +273,11 @@ export function spring(writes, opts) {
273
273
  */
274
274
  export function glide(writes, opts) {
275
275
  const ms = opts.ms ?? 325;
276
- if (!(ms > 0))
277
- throw new RangeError('blits: a glide takes a positive ms');
276
+ if (!(ms > 0 && ms < Infinity))
277
+ throw new RangeError('blits: a glide takes a finite positive ms');
278
278
  const k = opts.stiffness ?? 170;
279
- if (!(k > 0))
280
- throw new RangeError('blits: a glide takes a positive stiffness');
279
+ if (!(k > 0 && k < Infinity))
280
+ throw new RangeError('blits: a glide takes a finite positive stiffness');
281
281
  const tau = ms / 1000;
282
282
  const settle = opts.settle ?? 1e-4;
283
283
  const bounded = opts.bounds !== undefined;
package/dist/motions.d.ts CHANGED
@@ -64,11 +64,14 @@ export declare class Motions<I> {
64
64
  /** Whether the channel it was cued on holds a number; undefined until it is cued. */
65
65
  private holdsNumber;
66
66
  private channel;
67
+ /** Whether the channel it was cued on travels round the hue, as `ink` does, and its stretches too. */
68
+ round: boolean;
67
69
  /**
68
70
  * Records what the channel the patch is cued on holds, refusing a value of the other kind: at once
69
71
  * where every subject's value is one kind, else on the first sample of a subject whose value is not.
72
+ * `round` for a channel that travels round the hue, as `ink` does.
70
73
  */
71
- cuedOn(channel: string, holdsNumber: boolean): void;
74
+ cuedOn(channel: string, holdsNumber: boolean, round?: boolean): void;
72
75
  frame(subject: I): number;
73
76
  constructor(shape: Shape<I>);
74
77
  /** The patch's number for `subject`, made with its first stretch on first ask. */
package/dist/motions.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { applied, change, commit, cut, dueTo, insert, peek, queue, rewind } from './changes.js';
2
- import { closed } from './closed.js';
2
+ import { BOUND, closed } from './closed.js';
3
+ import { closedLch } from './lch.js';
3
4
  import { made, pack, prune, reclaim, release, restretch, runAt, runThen, unpack, unrelease, } from './lives.js';
4
5
  import { absent, Numbers } from './numbers.js';
5
6
  import { reading } from './reading.js';
@@ -16,6 +17,7 @@ const PENDING = 2 /* RunFlag.PENDING */;
16
17
  const OLDER = 4 /* RunFlag.OLDER */;
17
18
  const LANDED = 8 /* RunFlag.LANDED */;
18
19
  const wrongKind = (channel, holdsNumber) => new Error(`blits: channel ${channel} holds ${holdsNumber ? 'a number' : 'an array'}, and this motion patch gives a subject ${holdsNumber ? 'an array' : 'a number'}`);
20
+ const notOklab = (channel) => new Error(`blits: channel ${channel} goes round the hue, and a motion on it moves oklab() values, four numbers each`);
19
21
  /**
20
22
  * A motion patch's subjects: the stretch each is playing, in one flat array by a number the patch
21
23
  * gives it, with earlier stretches a read back may reach and changes not yet applied beside them.
@@ -68,11 +70,18 @@ export class Motions {
68
70
  /** Whether the channel it was cued on holds a number; undefined until it is cued. */
69
71
  holdsNumber = undefined;
70
72
  channel = '';
73
+ /** Whether the channel it was cued on travels round the hue, as `ink` does, and its stretches too. */
74
+ round = false;
71
75
  /**
72
76
  * Records what the channel the patch is cued on holds, refusing a value of the other kind: at once
73
77
  * where every subject's value is one kind, else on the first sample of a subject whose value is not.
78
+ * `round` for a channel that travels round the hue, as `ink` does.
74
79
  */
75
- cuedOn(channel, holdsNumber) {
80
+ cuedOn(channel, holdsNumber, round = false) {
81
+ if (round && this.shape.law[0] === BOUND)
82
+ throw new Error(`blits: channel ${channel} goes round the hue, and a glide's bounds are a box on its OKLab axes; glide it without bounds`);
83
+ if (round && this.n >= 0 && this.n !== 4)
84
+ throw notOklab(channel);
76
85
  const every = this.shape.scalarEvery();
77
86
  if (every !== undefined && every !== holdsNumber)
78
87
  throw wrongKind(channel, holdsNumber);
@@ -82,6 +91,7 @@ export class Motions {
82
91
  throw wrongKind(channel, holdsNumber);
83
92
  this.holdsNumber = holdsNumber;
84
93
  this.channel = channel;
94
+ this.round = round;
85
95
  }
86
96
  frame(subject) {
87
97
  return this.owner === null ? Number.NaN : this.owner.frame(this.ownerId, subject);
@@ -127,6 +137,8 @@ export class Motions {
127
137
  }
128
138
  /** Sets the axes every subject moves on, at the first. */
129
139
  size(n) {
140
+ if (this.round && n !== 4)
141
+ throw notOklab(this.channel);
130
142
  this.n = n;
131
143
  this.stride = 3 + 3 * n;
132
144
  if (shared.xs.length < n)
@@ -426,6 +438,8 @@ export class Motions {
426
438
  this.evaluate(seg.at, seg.ms, seg.x0, 0, seg.v0, 0, seg.to, 0, t, xo, vo);
427
439
  }
428
440
  evaluate(at, ms, x0, x, v0, v, to, g, t, xo, vo) {
441
+ if (this.round)
442
+ return closedLch(this.runs, this.shape.ease, at, ms, x0, x, v0, v, to, g, t, xo, vo);
429
443
  return closed(this.runs, this.shape.ease, this.n, at, ms, x0, x, v0, v, to, g, t, xo, vo);
430
444
  }
431
445
  }