@weasel-js/animator 1.10.1 → 1.11.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.
@@ -0,0 +1,1625 @@
1
+ import { isPlainObject, createEmitter } from '@weasel-js/registry';
2
+ import { keys } from '@msb235/blits';
3
+ import { resolveEasing } from '@weasel-js/geom';
4
+
5
+ // src/engine/axes.ts
6
+ function fieldByField(keys) {
7
+ const [a, b, c, e] = keys;
8
+ switch (keys.length) {
9
+ case 1:
10
+ return {
11
+ add: (v, d) => ({ [a]: v[a] + d[0] }),
12
+ write: (v, o, at) => {
13
+ o[at] = v[a];
14
+ }
15
+ };
16
+ case 2:
17
+ return {
18
+ add: (v, d) => ({ [a]: v[a] + d[0], [b]: v[b] + d[1] }),
19
+ write: (v, o, at) => {
20
+ o[at] = v[a];
21
+ o[at + 1] = v[b];
22
+ }
23
+ };
24
+ case 3:
25
+ return {
26
+ add: (v, d) => ({ [a]: v[a] + d[0], [b]: v[b] + d[1], [c]: v[c] + d[2] }),
27
+ write: (v, o, at) => {
28
+ o[at] = v[a];
29
+ o[at + 1] = v[b];
30
+ o[at + 2] = v[c];
31
+ }
32
+ };
33
+ case 4:
34
+ return {
35
+ add: (v, d) => ({ [a]: v[a] + d[0], [b]: v[b] + d[1], [c]: v[c] + d[2], [e]: v[e] + d[3] }),
36
+ write: (v, o, at) => {
37
+ o[at] = v[a];
38
+ o[at + 1] = v[b];
39
+ o[at + 2] = v[c];
40
+ o[at + 3] = v[e];
41
+ }
42
+ };
43
+ default:
44
+ return null;
45
+ }
46
+ }
47
+ function axesOf(sample) {
48
+ if (typeof sample === "number") {
49
+ return {
50
+ count: 1,
51
+ shape: "number",
52
+ to: (v) => [v],
53
+ from: (a, at = 0) => a[at],
54
+ add: (v, delta) => v + delta[0],
55
+ write: (v, out, at) => {
56
+ out[at] = v;
57
+ }
58
+ };
59
+ }
60
+ if (Array.isArray(sample)) {
61
+ if (!sample.every((x) => typeof x === "number")) return null;
62
+ return {
63
+ count: sample.length,
64
+ shape: `array:${sample.length}`,
65
+ to: (v) => v.slice(),
66
+ from: (a, at = 0) => {
67
+ const out = new Array(sample.length);
68
+ for (let i = 0; i < out.length; i++) out[i] = a[at + i];
69
+ return out;
70
+ },
71
+ add: (v, delta) => {
72
+ const out = new Array(sample.length);
73
+ for (let i = 0; i < out.length; i++) out[i] = v[i] + delta[i];
74
+ return out;
75
+ },
76
+ write: (v, out, at) => {
77
+ for (let i = 0; i < sample.length; i++) out[at + i] = v[i];
78
+ }
79
+ };
80
+ }
81
+ if (!isPlainObject(sample)) return null;
82
+ const keys = Object.keys(sample).sort();
83
+ if (keys.length === 0 || !keys.every((k) => typeof sample[k] === "number")) return null;
84
+ return {
85
+ count: keys.length,
86
+ shape: `object:${JSON.stringify(keys)}`,
87
+ names: keys,
88
+ to: (v) => keys.map((k) => v[k]),
89
+ from: (a, at = 0) => {
90
+ const out = {};
91
+ for (let i = 0; i < keys.length; i++) out[keys[i]] = a[at + i];
92
+ return out;
93
+ },
94
+ ...fieldByField(keys) ?? {
95
+ add: (v, delta) => {
96
+ const out = {};
97
+ for (let i = 0; i < keys.length; i++) out[keys[i]] = v[keys[i]] + delta[i];
98
+ return out;
99
+ },
100
+ write: (v, out, at) => {
101
+ for (let i = 0; i < keys.length; i++) out[at + i] = v[keys[i]];
102
+ }
103
+ }
104
+ };
105
+ }
106
+
107
+ // src/axisNames.ts
108
+ var LISTED = 8;
109
+ function axisIndexes(axes, wanted, what = "animator: `axes`") {
110
+ const { count, names } = axes;
111
+ const out = [];
112
+ for (const w of wanted) {
113
+ if (typeof w === "number") {
114
+ if (!Number.isInteger(w) || w < 0 || w >= count) throw new Error(`${what} names axis ${w} of a target with ${count}`);
115
+ out.push(w);
116
+ continue;
117
+ }
118
+ if (!names) throw new Error(`${what} names "${w}" on a target whose axes have no names`);
119
+ const before = out.length;
120
+ const under = `${w}.`;
121
+ for (let i = 0; i < names.length; i++) {
122
+ if (names[i] === w || names[i].startsWith(under)) out.push(i);
123
+ }
124
+ if (out.length === before) {
125
+ const some = names.slice(0, LISTED).map((n) => `"${n}"`).join(", ");
126
+ throw new Error(`${what} names "${w}", and the target's axes are ${some}${names.length > LISTED ? ", \u2026" : ""}`);
127
+ }
128
+ }
129
+ return out;
130
+ }
131
+
132
+ // src/target.ts
133
+ var states = /* @__PURE__ */ new WeakMap();
134
+ var scratch = new Float64Array(16);
135
+ var showing = (share2, cover, kept) => 1 - share2 * (1 - (1 - cover) * kept);
136
+ function addMoves(s, out, at, cover, base) {
137
+ const n = s.axes.count;
138
+ const { behind } = s;
139
+ for (let j = 0; j < behind.length; j++) {
140
+ const b = behind[j];
141
+ const { slot, adjust, kept, share: share2 } = b;
142
+ if (slot) {
143
+ for (let i = 0; i < n; i++) {
144
+ const m = slot.cols[slot.at + i] + (adjust ? adjust[i] : 0);
145
+ out[at + i] = out[at + i] + (cover || kept ? m * showing(share2[i], cover ? cover[base + i] : 0, kept ? kept[i] : 1) : m);
146
+ }
147
+ } else if (cover || kept) {
148
+ const own = b.own();
149
+ for (let i = 0; i < n; i++) {
150
+ out[at + i] = out[at + i] - own[i] * (1 - showing(share2[i], cover ? cover[base + i] : 0, kept ? kept[i] : 1));
151
+ }
152
+ }
153
+ }
154
+ }
155
+ function addMoving(s, out, at) {
156
+ const n = s.axes.count;
157
+ const { slot, adjust, beside } = s;
158
+ for (let i = 0; i < n; i++) {
159
+ out[at + i] = out[at + i] + (slot ? slot.cols[slot.at + i] : 0) + adjust[i] + (beside ? beside[i] : 0);
160
+ }
161
+ const h = s.holds;
162
+ if (s.behind.length > 0 && (s.row || h || s.dimmed)) addMoves(s, out, at, h ? h.store.cover : null, h ? h.row * n : 0);
163
+ }
164
+ function committedInto(s, out, at) {
165
+ if (s.write) s.write(out, at);
166
+ else if (s.axes.write) s.axes.write(s.get(), out, at);
167
+ else {
168
+ const a = s.axes.to(s.get());
169
+ for (let i = 0; i < a.length; i++) out[at + i] = a[i];
170
+ }
171
+ }
172
+ function heldAxes(s, committed) {
173
+ const a = s.axes.to(committed);
174
+ const h = s.holds;
175
+ if (!h) return a;
176
+ const at = h.row * a.length;
177
+ const { store } = h;
178
+ for (let i = 0; i < a.length; i++) {
179
+ if (a[i] === store.at[at + i]) continue;
180
+ store.refold(h.row);
181
+ break;
182
+ }
183
+ for (let i = 0; i < a.length; i++) a[i] = a[i] + store.delta[at + i];
184
+ return a;
185
+ }
186
+ function readTarget(s) {
187
+ const committed = s.get();
188
+ if (s.live === 0) return committed;
189
+ const { axes } = s;
190
+ if (!axes.add || s.holds || s.dimmed) {
191
+ const a = heldAxes(s, committed);
192
+ addMoving(s, a, 0);
193
+ return axes.from(a);
194
+ }
195
+ const n = axes.count;
196
+ if (scratch.length < n) scratch = new Float64Array(n);
197
+ const d = scratch;
198
+ d.fill(0, 0, n);
199
+ addMoving(s, d, 0);
200
+ return axes.add(committed, d);
201
+ }
202
+ function readTargetInto(s, out, at) {
203
+ const committed = s.get();
204
+ const { axes } = s;
205
+ const moving = s.live > 0;
206
+ if (axes.write && !(moving && s.holds)) axes.write(committed, out, at);
207
+ else {
208
+ const a = moving ? heldAxes(s, committed) : axes.to(committed);
209
+ for (let i = 0; i < a.length; i++) out[at + i] = a[i];
210
+ }
211
+ if (moving) addMoving(s, out, at);
212
+ }
213
+ function isTarget(x) {
214
+ return typeof x === "object" && x !== null && states.has(x);
215
+ }
216
+ function stateOf(target) {
217
+ const s = states.get(target);
218
+ if (!s) throw new Error("animator: `target` was not made by createTarget");
219
+ return s;
220
+ }
221
+ function createTarget(opts) {
222
+ const axes = opts.axes ?? axesOf(opts.get());
223
+ if (!axes) {
224
+ throw new Error("createTarget: `axes` is required for a value that is not a number, an array of numbers, or an object of numeric fields");
225
+ }
226
+ const s = {
227
+ get: opts.get,
228
+ set: opts.set,
229
+ show: opts.show,
230
+ publish: opts.publish,
231
+ axes,
232
+ write: null,
233
+ owner: null,
234
+ discard: false,
235
+ held: 0,
236
+ holds: null,
237
+ behind: [],
238
+ dimmed: false,
239
+ beside: null,
240
+ parts: /* @__PURE__ */ new Set(),
241
+ row: null,
242
+ live: 0,
243
+ slot: null,
244
+ adjust: new Array(axes.count).fill(0),
245
+ stale: false
246
+ };
247
+ const target = {
248
+ read: () => readTarget(s),
249
+ readInto: (out, at = 0) => readTargetInto(s, out, at),
250
+ axisCount: axes.count,
251
+ axes,
252
+ axisIndexes: (wanted) => axisIndexes(axes, wanted),
253
+ committed: opts.get,
254
+ commit: opts.set,
255
+ get active() {
256
+ return s.live > 0;
257
+ },
258
+ stop(o) {
259
+ const owner = s.owner;
260
+ if (!owner) return;
261
+ s.discard = o?.commit === false;
262
+ try {
263
+ owner.stop(s);
264
+ } finally {
265
+ s.discard = false;
266
+ }
267
+ }
268
+ };
269
+ states.set(target, s);
270
+ return target;
271
+ }
272
+
273
+ // src/engine/offsets.ts
274
+ var TAU = Math.PI * 2;
275
+ function axesFor(axes, value, what) {
276
+ const a = axes.to(value);
277
+ if (a.length !== axes.count || !a.every((x) => typeof x === "number" && !Number.isNaN(x))) {
278
+ throw new Error(`animator: \`${what}\` is not the target's shape`);
279
+ }
280
+ return a;
281
+ }
282
+ function shortWay(x) {
283
+ x %= TAU;
284
+ if (x > Math.PI) return x - TAU;
285
+ if (x <= -Math.PI) return x + TAU;
286
+ return x;
287
+ }
288
+ function travel(axes, a, b) {
289
+ const d = a.map((x, i) => x - b[i]);
290
+ for (const i of axes.angles ?? []) d[i] = shortWay(d[i]);
291
+ return d;
292
+ }
293
+ var plus = (a, b) => a.map((x, i) => x + b[i]);
294
+ var minus = (a, b) => a.map((x, i) => x - b[i]);
295
+ var norm = (a) => Math.hypot(...a);
296
+ function bump(s, delta, sign) {
297
+ for (let i = 0; i < delta.length; i++) s.adjust[i] = s.adjust[i] + sign * delta[i];
298
+ stale(s);
299
+ s.row?.table.mark(s.row.index);
300
+ }
301
+ function bumpBehind(s, b, delta, sign) {
302
+ const adjust = b.adjust ??= new Array(delta.length).fill(0);
303
+ for (let i = 0; i < delta.length; i++) adjust[i] = adjust[i] + sign * delta[i];
304
+ stale(s);
305
+ s.row?.table.mark(s.row.index);
306
+ }
307
+ function seat(s, holder, slot) {
308
+ const was = holder.slot;
309
+ if (was && was !== slot && was.refs === 0) {
310
+ const n = s.axes.count;
311
+ const adjust = holder.adjust ??= new Array(n).fill(0);
312
+ for (let i = 0; i < n; i++) adjust[i] = adjust[i] + was.cols[was.at + i];
313
+ stale(s);
314
+ }
315
+ holder.slot = slot;
316
+ }
317
+ function setBeside(s, beside) {
318
+ s.beside = beside;
319
+ s.row?.table.beside(s.row.index, beside);
320
+ }
321
+ function stale(s) {
322
+ if (s.stale || !s.owner) return;
323
+ s.stale = true;
324
+ s.owner.adjusted(s);
325
+ }
326
+ function absorb(s, delta) {
327
+ if (s.discard) return;
328
+ s.set(s.axes.from(plus(s.axes.to(s.get()), delta)));
329
+ }
330
+ function shareBehind(axes, committed, to, start) {
331
+ return travel(axes, committed, to).map((m, i) => start[i] === 0 ? 0 : m / start[i]);
332
+ }
333
+ var times = (a, b) => a.map((x, i) => x * b[i]);
334
+ function goBehind(s, own, left, share2) {
335
+ const b = {
336
+ own,
337
+ left,
338
+ share: share2,
339
+ kept: null,
340
+ slot: null,
341
+ adjust: null,
342
+ move: s.row ? s.row.table.move(s.row.index, share2) : null
343
+ };
344
+ s.behind.push(b);
345
+ return b;
346
+ }
347
+ var runsOn = (s, b) => b.move ? b : s;
348
+ function bumpOn(s, b, delta, sign) {
349
+ if (b.move) bumpBehind(s, b, delta, sign);
350
+ else bump(s, delta, sign);
351
+ }
352
+ function comeOut(s, b, left) {
353
+ const at = s.behind.indexOf(b);
354
+ if (at >= 0) s.behind.splice(at, 1);
355
+ b.move?.leave();
356
+ if (b.kept) {
357
+ s.dimmed = false;
358
+ for (const other of s.behind) if (other.kept) s.dimmed = true;
359
+ }
360
+ return b.kept ? times(left, b.kept) : left.slice();
361
+ }
362
+ function commitOffset(s, own, left, shown) {
363
+ const top = minus(own, left);
364
+ if (s.held === 0) {
365
+ keep(s, plus(shown, top));
366
+ return;
367
+ }
368
+ if (shown.some((x) => x !== 0)) absorb(s, shown);
369
+ if (top.some((x) => x !== 0)) keep(s, top);
370
+ }
371
+ function keep(s, delta) {
372
+ if (s.discard) return;
373
+ if (s.held === 0) absorb(s, delta);
374
+ else setBeside(s, s.beside ? plus(s.beside, delta) : delta.slice());
375
+ s.row?.table.mark(s.row.index);
376
+ }
377
+ function takeUp(s) {
378
+ const committed = s.axes.to(s.get());
379
+ const beside = s.beside;
380
+ if (beside) setBeside(s, null);
381
+ return beside ? plus(committed, beside) : committed;
382
+ }
383
+ function createTargetBook(owner) {
384
+ const adjusted = [];
385
+ const token = { ...owner, adjusted: (s) => {
386
+ adjusted.push(s);
387
+ } };
388
+ const showing2 = /* @__PURE__ */ new Set();
389
+ const rested = /* @__PURE__ */ new Set();
390
+ const publishes = /* @__PURE__ */ new Set();
391
+ const tables2 = /* @__PURE__ */ new Map();
392
+ const stores = /* @__PURE__ */ new Map();
393
+ const shown = (s, value) => {
394
+ s.show(value);
395
+ if (s.publish) publishes.add(s.publish);
396
+ };
397
+ const publish = () => {
398
+ for (const p of publishes) p();
399
+ publishes.clear();
400
+ };
401
+ return {
402
+ attach(s) {
403
+ if (s.owner && s.owner !== token) {
404
+ throw new Error("animator: this target is being moved by another animator");
405
+ }
406
+ s.owner = token;
407
+ s.live += 1;
408
+ if (s.show) showing2.add(s);
409
+ rested.delete(s);
410
+ if (s.row) {
411
+ tables2.set(s.row.table, (tables2.get(s.row.table) ?? 0) + 1);
412
+ s.row.table.mark(s.row.index);
413
+ }
414
+ },
415
+ detach(s) {
416
+ s.live -= 1;
417
+ if (s.row) {
418
+ tables2.set(s.row.table, tables2.get(s.row.table) - 1);
419
+ s.row.table.mark(s.row.index);
420
+ }
421
+ if (s.live > 0) return;
422
+ s.owner = null;
423
+ s.slot = null;
424
+ if (s.beside) setBeside(s, null);
425
+ for (const b of s.behind) b.move?.leave();
426
+ s.behind.length = 0;
427
+ s.dimmed = false;
428
+ s.adjust.fill(0);
429
+ s.stale = false;
430
+ if (s.show && showing2.delete(s)) rested.add(s);
431
+ },
432
+ settle() {
433
+ for (const s of adjusted) {
434
+ if (!s.stale || s.owner !== token) continue;
435
+ s.adjust.fill(0);
436
+ for (const b of s.behind) b.adjust = null;
437
+ s.stale = false;
438
+ }
439
+ adjusted.length = 0;
440
+ for (const store of stores.keys()) store.frame();
441
+ for (const t of tables2.keys()) t.settle();
442
+ },
443
+ holding(store, by) {
444
+ const count = (stores.get(store) ?? 0) + by;
445
+ if (count > 0) stores.set(store, count);
446
+ else stores.delete(store);
447
+ },
448
+ show() {
449
+ for (const s of showing2) shown(s, readTarget(s));
450
+ for (const s of rested) shown(s, s.get());
451
+ rested.clear();
452
+ for (const [t, moving] of tables2) {
453
+ t.fill(owner.columns);
454
+ if (moving === 0) tables2.delete(t);
455
+ }
456
+ publish();
457
+ },
458
+ showNow(s) {
459
+ if (!s.show) return;
460
+ shown(s, readTarget(s));
461
+ publish();
462
+ },
463
+ get resting() {
464
+ return rested.size > 0;
465
+ }
466
+ };
467
+ }
468
+
469
+ // src/engine/holdStore.ts
470
+ var share = (w) => w > 0 ? Math.min(w, 1) : 0;
471
+ function wider(a, length, fill = 0) {
472
+ const b = new a.constructor(length);
473
+ if (fill !== 0) b.fill(fill);
474
+ b.set(a);
475
+ return b;
476
+ }
477
+ function makeLayer(rows, n, pooled) {
478
+ return {
479
+ values: new Float64Array(rows * n),
480
+ weights: new Float64Array(rows),
481
+ has: new Uint8Array(rows),
482
+ covers: null,
483
+ sources: [],
484
+ pooled,
485
+ asked: /* @__PURE__ */ new Set()
486
+ };
487
+ }
488
+ function widenLayer(l, rows, n) {
489
+ l.values = wider(l.values, rows * n);
490
+ l.weights = wider(l.weights, rows);
491
+ l.has = wider(l.has, rows);
492
+ if (l.covers) l.covers = wider(l.covers, rows * n);
493
+ }
494
+ function ask(l, row, n) {
495
+ const src = l.sources[row];
496
+ if (!src) return;
497
+ if (src.weight) l.weights[row] = src.weight();
498
+ if (src.value && share(l.weights[row]) > 0) src.value(l.values, row * n);
499
+ }
500
+ function foldRow(layers, angle, n, row, committed, at, skip, delta, total) {
501
+ const base = row * n;
502
+ const holds = (l, a) => l === skip || !l.has[row] || l.covers && !l.covers[base + a] ? 0 : share(l.weights[row]);
503
+ for (let a = 0; a < n; a++) {
504
+ const c = committed[at + a];
505
+ let weight = 0;
506
+ let sum = 0;
507
+ let sin = 0;
508
+ let cos = 0;
509
+ for (let h = 0; h < layers.length; h++) {
510
+ const l = layers[h];
511
+ const w = holds(l, a);
512
+ if (!(w > 0)) continue;
513
+ const v = l.values[base + a];
514
+ weight += w;
515
+ if (!angle[a]) sum += w * (v - c);
516
+ else {
517
+ sin += w * Math.sin(v);
518
+ cos += w * Math.cos(v);
519
+ }
520
+ }
521
+ if (weight > 0 && angle[a]) {
522
+ const mean = Math.hypot(sin, cos) < 1e-9 ? null : Math.atan2(sin, cos);
523
+ for (let h = 0; h < layers.length; h++) {
524
+ const l = layers[h];
525
+ const w = holds(l, a);
526
+ if (!(w > 0)) continue;
527
+ const v = l.values[base + a];
528
+ sum += w * (mean === null ? shortWay(v - c) : shortWay(mean - c) + shortWay(v - mean));
529
+ }
530
+ }
531
+ total[a] = weight;
532
+ delta[a] = weight > 0 ? sum / Math.max(1, weight) : 0;
533
+ }
534
+ }
535
+ function createHoldStore(axes, rows, read, angles) {
536
+ const n = axes;
537
+ const angle = new Uint8Array(n);
538
+ for (const a of angles ?? []) angle[a] = 1;
539
+ let cap = Math.max(rows, 1);
540
+ const layers = [];
541
+ let size = 0;
542
+ const store = {
543
+ delta: new Float64Array(cap * n),
544
+ cover: new Float64Array(cap * n),
545
+ at: new Float64Array(cap * n),
546
+ count: new Int32Array(cap),
547
+ held: new Int32Array(cap),
548
+ /** Each row's place in `held`; -1 for one not held. */
549
+ place: new Int32Array(cap).fill(-1),
550
+ /** The frame each row was last folded on. */
551
+ folded: new Int32Array(cap)
552
+ };
553
+ let pass2 = 0;
554
+ const fold = (row, committed, at, skip, delta, total) => foldRow(layers, angle, n, row, committed, at, skip, delta, total);
555
+ const scratchDelta = new Array(n);
556
+ const scratchTotal = new Array(n);
557
+ const settle = (row) => {
558
+ const base = row * n;
559
+ read(row, store.at, base);
560
+ fold(row, store.at, base, null, scratchDelta, scratchTotal);
561
+ for (let a = 0; a < n; a++) {
562
+ store.delta[base + a] = scratchDelta[a];
563
+ store.cover[base + a] = Math.min(scratchTotal[a], 1);
564
+ }
565
+ };
566
+ const made = (pooled) => {
567
+ const l = makeLayer(cap, n, pooled);
568
+ layers.push(l);
569
+ return l;
570
+ };
571
+ return {
572
+ axes: n,
573
+ get delta() {
574
+ return store.delta;
575
+ },
576
+ get cover() {
577
+ return store.cover;
578
+ },
579
+ get at() {
580
+ return store.at;
581
+ },
582
+ get count() {
583
+ return store.count;
584
+ },
585
+ get held() {
586
+ return store.held;
587
+ },
588
+ get size() {
589
+ return size;
590
+ },
591
+ grow(want) {
592
+ if (want <= cap) return;
593
+ cap = Math.max(want, cap * 2);
594
+ for (const k of ["delta", "cover", "at"]) store[k] = wider(store[k], cap * n);
595
+ for (const k of ["count", "held", "folded"]) store[k] = wider(store[k], cap);
596
+ store.place = wider(store.place, cap, -1);
597
+ for (const l of layers) widenLayer(l, cap, n);
598
+ },
599
+ layer: () => made(false),
600
+ free(row) {
601
+ for (const l of layers) if (l.pooled && !l.has[row]) return l;
602
+ return made(true);
603
+ },
604
+ join(layer, row, sources) {
605
+ const l = layer;
606
+ if (l.has[row]) throw new Error(`holds: row ${row} is held by this layer already`);
607
+ l.has[row] = 1;
608
+ if (sources) {
609
+ l.sources[row] = sources;
610
+ if (!sources.fixed) l.asked.add(row);
611
+ if (sources.covers) (l.covers ??= new Uint8Array(cap * n).fill(1)).set(sources.covers, row * n);
612
+ }
613
+ if (store.count[row]++ > 0) return;
614
+ store.place[row] = size;
615
+ store.held[size++] = row;
616
+ },
617
+ part(layer, row) {
618
+ const l = layer;
619
+ if (!l.has[row]) return;
620
+ l.has[row] = 0;
621
+ l.sources[row] = void 0;
622
+ l.asked.delete(row);
623
+ l.covers?.fill(1, row * n, (row + 1) * n);
624
+ if (--store.count[row] > 0) return;
625
+ const at = store.place[row];
626
+ const last = store.held[--size];
627
+ store.held[at] = last;
628
+ store.place[last] = at;
629
+ store.place[row] = -1;
630
+ store.delta.fill(0, row * n, (row + 1) * n);
631
+ store.cover.fill(0, row * n, (row + 1) * n);
632
+ },
633
+ drop(layer) {
634
+ const i = layers.indexOf(layer);
635
+ if (i >= 0) layers.splice(i, 1);
636
+ },
637
+ frame() {
638
+ for (const l of layers) for (const row of l.asked) ask(l, row, n);
639
+ pass2 += 1;
640
+ for (const l of layers) {
641
+ for (const row of l.asked) {
642
+ if (store.folded[row] === pass2) continue;
643
+ store.folded[row] = pass2;
644
+ settle(row);
645
+ }
646
+ }
647
+ },
648
+ refold(row) {
649
+ for (const l of layers) if (l.has[row]) ask(l, row, n);
650
+ settle(row);
651
+ },
652
+ fold,
653
+ valueOf: (layer, row) => Array.from(layer.values.subarray(row * n, (row + 1) * n))
654
+ };
655
+ }
656
+
657
+ // src/tableMoves.ts
658
+ function createTableMoves(n) {
659
+ const group = {};
660
+ let cap = 16;
661
+ let rowOf = new Int32Array(cap);
662
+ let share2 = new Float64Array(cap * n);
663
+ let kept = new Float64Array(cap * n);
664
+ const free = [];
665
+ let high = 0;
666
+ const besides = /* @__PURE__ */ new Map();
667
+ let dimmed = 0;
668
+ let dims = new Uint8Array(cap);
669
+ const grow = () => {
670
+ cap *= 2;
671
+ const r = new Int32Array(cap);
672
+ r.set(rowOf);
673
+ rowOf = r;
674
+ const s = new Float64Array(cap * n);
675
+ s.set(share2);
676
+ share2 = s;
677
+ const k = new Float64Array(cap * n);
678
+ k.set(kept);
679
+ kept = k;
680
+ const d = new Uint8Array(cap);
681
+ d.set(dims);
682
+ dims = d;
683
+ };
684
+ return {
685
+ group,
686
+ move(row, of) {
687
+ const id = free.pop() ?? high++;
688
+ if (id >= cap) grow();
689
+ rowOf[id] = row;
690
+ share2.set(of, id * n);
691
+ kept.fill(1, id * n, (id + 1) * n);
692
+ let gone = false;
693
+ return {
694
+ place: { group, tag: id },
695
+ kept: (k) => {
696
+ kept.set(k, id * n);
697
+ if (!dims[id]) {
698
+ dims[id] = 1;
699
+ dimmed += 1;
700
+ }
701
+ },
702
+ leave() {
703
+ if (gone) return;
704
+ gone = true;
705
+ if (dims[id]) {
706
+ dims[id] = 0;
707
+ dimmed -= 1;
708
+ }
709
+ free.push(id);
710
+ }
711
+ };
712
+ },
713
+ beside(row, value) {
714
+ if (value) besides.set(row, value);
715
+ else besides.delete(row);
716
+ },
717
+ add(shown, committed, delta, cover, columns, stamp, pass2) {
718
+ if (columns) {
719
+ const { cols, tags, count } = columns;
720
+ const whole = !cover && dimmed === 0;
721
+ for (let j = 0; j < count; j++) {
722
+ const id = tags[j];
723
+ const row = rowOf[id];
724
+ const at = row * n;
725
+ const fresh = stamp[row] !== pass2;
726
+ stamp[row] = pass2;
727
+ if (whole && fresh && !delta) {
728
+ for (let k = 0; k < n; k++) shown[at + k] = committed[at + k] + cols[j * n + k];
729
+ continue;
730
+ }
731
+ for (let k = 0; k < n; k++) {
732
+ const f = whole ? 1 : showing(share2[id * n + k], cover ? cover[at + k] : 0, kept[id * n + k]);
733
+ const from = fresh ? committed[at + k] + (delta ? delta[at + k] : 0) : shown[at + k];
734
+ shown[at + k] = from + cols[j * n + k] * f;
735
+ }
736
+ }
737
+ }
738
+ for (const [row, value] of besides) {
739
+ for (let k = 0; k < n; k++) shown[row * n + k] = shown[row * n + k] + value[k];
740
+ }
741
+ }
742
+ };
743
+ }
744
+
745
+ // src/targetTable.ts
746
+ function put(into, n, row, value) {
747
+ if (value.length !== n) throw new Error(`target table: a row is ${n} numbers, and this value is ${value.length}`);
748
+ for (let k = 0; k < n; k++) into[row * n + k] = value[k];
749
+ }
750
+ function rowTarget(n, row, rows, committed, commit) {
751
+ const target = createTarget({
752
+ get: () => {
753
+ const from = committed();
754
+ const value = new Array(n);
755
+ for (let k = 0; k < n; k++) value[k] = from[row * n + k];
756
+ return value;
757
+ },
758
+ set: (value) => {
759
+ committed();
760
+ commit(row, value);
761
+ }
762
+ });
763
+ const state = stateOf(target);
764
+ state.row = { table: rows, index: row };
765
+ state.write = (out, at) => {
766
+ const from = committed();
767
+ for (let k = 0; k < n; k++) out[at + k] = from[row * n + k];
768
+ };
769
+ return target;
770
+ }
771
+ function pass(shown, committed, n, holds, moves, columns, moved, stamp, at) {
772
+ const h = holds && holds.size > 0 ? holds : null;
773
+ const delta = h ? h.delta : null;
774
+ if (h) {
775
+ const { held, size } = h;
776
+ for (let j = 0; j < size; j++) {
777
+ const row = held[j];
778
+ stamp[row] = at;
779
+ for (let k = 0; k < n; k++) shown[row * n + k] = committed[row * n + k] + delta[row * n + k];
780
+ }
781
+ }
782
+ if (columns) {
783
+ const { cols, tags, count } = columns;
784
+ for (let j = 0; j < count; j++) {
785
+ const row = tags[j];
786
+ const base = row * n;
787
+ stamp[row] = at;
788
+ if (delta) for (let k = 0; k < n; k++) shown[base + k] = committed[base + k] + cols[j * n + k] + delta[base + k];
789
+ else for (let k = 0; k < n; k++) shown[base + k] = committed[base + k] + cols[j * n + k];
790
+ }
791
+ }
792
+ moves.add(shown, committed, delta, h ? h.cover : null, moved, stamp, at);
793
+ }
794
+ function inUse(high, free) {
795
+ const gone = new Set(free);
796
+ return Array.from({ length: high }, (_, r) => r).filter((r) => !gone.has(r));
797
+ }
798
+ var tables = /* @__PURE__ */ new WeakMap();
799
+ function rowTableOf(table) {
800
+ const rows = tables.get(table);
801
+ if (!rows) throw new Error("animator: `table` was not made by createTargetTable");
802
+ return rows;
803
+ }
804
+ function createTargetTable(opts) {
805
+ const n = opts.axes;
806
+ const Shown = opts.shown ?? Float64Array;
807
+ if (!Number.isInteger(n) || n < 1) throw new Error("createTargetTable: `axes` is a whole number of at least 1");
808
+ let committed = new Float64Array(Math.max(opts.capacity ?? 64, 1) * n);
809
+ let shown = new Shown(committed.length);
810
+ let high = 0;
811
+ let size = 0;
812
+ const free = [];
813
+ const targets = [];
814
+ const states2 = [];
815
+ const removals = [];
816
+ const marks = /* @__PURE__ */ new Set();
817
+ let holds = null;
818
+ const moves = createTableMoves(n);
819
+ let stamp = new Int32Array(committed.length / n);
820
+ let passes = 0;
821
+ let last = () => null;
822
+ const flush = () => {
823
+ for (const row of marks) {
824
+ const s = states2[row];
825
+ if (s) readTargetInto(s, shown, row * n);
826
+ }
827
+ };
828
+ const commit = (row, value) => {
829
+ put(committed, n, row, value);
830
+ const s = states2[row];
831
+ if (s && s.live > 0) marks.add(row);
832
+ else put(shown, n, row, value);
833
+ opts.set?.(row);
834
+ };
835
+ const rows = {
836
+ mark: (row) => {
837
+ marks.add(row);
838
+ },
839
+ settle() {
840
+ flush();
841
+ marks.clear();
842
+ },
843
+ fill(columns) {
844
+ last = columns;
845
+ pass(shown, committed, n, holds, moves, columns(rows), columns(moves.group), stamp, ++passes);
846
+ flush();
847
+ opts.show?.();
848
+ },
849
+ move: moves.move,
850
+ beside: moves.beside,
851
+ refill() {
852
+ const { show } = opts;
853
+ opts.show = void 0;
854
+ try {
855
+ rows.fill(last);
856
+ } finally {
857
+ opts.show = show;
858
+ }
859
+ },
860
+ rows: () => inUse(high, free),
861
+ holds: () => holds ??= createHoldStore(n, committed.length / n, (row, out, at) => {
862
+ for (let k = 0; k < n; k++) out[at + k] = committed[row * n + k];
863
+ })
864
+ };
865
+ const table = {
866
+ axes: n,
867
+ get size() {
868
+ return size;
869
+ },
870
+ get committed() {
871
+ return committed;
872
+ },
873
+ get shown() {
874
+ flush();
875
+ return shown;
876
+ },
877
+ add(value) {
878
+ const row = free.pop() ?? high++;
879
+ if ((row + 1) * n > committed.length) {
880
+ const grown = new Float64Array(Math.max(committed.length * 2, (row + 1) * n));
881
+ grown.set(committed);
882
+ committed = grown;
883
+ const wider2 = new Shown(grown.length);
884
+ wider2.set(shown);
885
+ shown = wider2;
886
+ holds?.grow(grown.length / n);
887
+ stamp = Int32Array.from({ length: grown.length / n }, (_, r) => stamp[r] ?? 0);
888
+ }
889
+ put(committed, n, row, value);
890
+ put(shown, n, row, value);
891
+ size += 1;
892
+ return row;
893
+ },
894
+ remove(row) {
895
+ targets[row]?.stop();
896
+ targets[row] = void 0;
897
+ removals[row] = (removals[row] ?? 0) + 1;
898
+ states2[row] = void 0;
899
+ marks.delete(row);
900
+ free.push(row);
901
+ size -= 1;
902
+ },
903
+ set: commit,
904
+ target(row) {
905
+ const held = targets[row];
906
+ if (held) return held;
907
+ const made = removals[row] ??= 0;
908
+ const mine = () => {
909
+ if (removals[row] !== made) throw new Error(`target table: row ${row} was removed, and this is its old target`);
910
+ };
911
+ const target = rowTarget(n, row, rows, () => {
912
+ mine();
913
+ return committed;
914
+ }, commit);
915
+ targets[row] = target;
916
+ states2[row] = stateOf(target);
917
+ return target;
918
+ }
919
+ };
920
+ tables.set(table, rows);
921
+ return table;
922
+ }
923
+
924
+ // src/colorRegistry.ts
925
+ var ColorOverrideRegistry = class {
926
+ map = /* @__PURE__ */ new Map();
927
+ slots = /* @__PURE__ */ new Map();
928
+ tables = /* @__PURE__ */ new Map();
929
+ _version = 0;
930
+ set(id, channel, override) {
931
+ let entry = this.map.get(id);
932
+ if (!entry) {
933
+ entry = {};
934
+ this.map.set(id, entry);
935
+ }
936
+ entry[channel] = override;
937
+ this._version++;
938
+ }
939
+ /** Drops `id`'s override on `channel`, and the colors a finished or canceled animation on its
940
+ * {@link target} left painted. An animation still running goes on. */
941
+ clear(id, channel) {
942
+ this.release(id, channel);
943
+ const entry = this.map.get(id);
944
+ if (!entry) return;
945
+ if (!(channel in entry)) return;
946
+ delete entry[channel];
947
+ if (!entry.fill && !entry.stroke) this.map.delete(id);
948
+ this._version++;
949
+ }
950
+ clearAll() {
951
+ for (const [id, node] of this.slots) {
952
+ for (const channel of Object.keys(node)) this.release(id, channel);
953
+ }
954
+ if (this.map.size === 0) return;
955
+ this.map.clear();
956
+ this._version++;
957
+ }
958
+ /** What `set` stored for `id`'s `channel`; failing that, the colors its {@link target} paints. */
959
+ get(id, channel) {
960
+ const override = this.map.get(id)?.[channel];
961
+ if (override !== void 0) return override;
962
+ const slot = this.slots.get(id)?.[channel];
963
+ return slot?.painted ? shownOf(slot) : void 0;
964
+ }
965
+ /** Whether anything overrides `id`, on either channel. */
966
+ has(id) {
967
+ return this.map.has(id) || this.slots.has(id);
968
+ }
969
+ /**
970
+ * The {@link Target} for `id`'s colors on `channel`: `length` numbers, flat RGBA per anchor.
971
+ * Every caller for one node and channel gets the same one, so their animations add. The
972
+ * registry holds its committed colors and nothing writes the scene.
973
+ *
974
+ * What it paints: with no override from `set`, its colors in place of the painter's own; over
975
+ * one, only what its animations have left to travel, added on top. Ask again for each
976
+ * animation rather than keeping it: {@link release} and {@link clear} drop a resting one, and
977
+ * the target it had throws from then on.
978
+ */
979
+ target(id, channel, length) {
980
+ if (!(length > 0) || length % 4 !== 0) {
981
+ throw new Error(`vertex colors: length ${length} must be a positive multiple of 4`);
982
+ }
983
+ let node = this.slots.get(id);
984
+ const held = node?.[channel];
985
+ if (held?.length === length) return held.table.target(held.row);
986
+ if (held) {
987
+ if (held.table.target(held.row).active) {
988
+ throw new Error(`vertex colors: ${id}'s ${channel} is moving as ${held.length} numbers, not ${length}`);
989
+ }
990
+ this.drop(held);
991
+ }
992
+ const at = this.tableOf(length);
993
+ const row = at.table.add(new Float64Array(length));
994
+ const slot = { table: at.table, row, length, painted: false };
995
+ at.rows[row] = slot;
996
+ if (!node) {
997
+ node = {};
998
+ this.slots.set(id, node);
999
+ }
1000
+ node[channel] = slot;
1001
+ return at.table.target(row);
1002
+ }
1003
+ tableOf(length) {
1004
+ const held = this.tables.get(length);
1005
+ if (held) return held;
1006
+ const rows = [];
1007
+ const table = createTargetTable({
1008
+ axes: length,
1009
+ set: (row) => {
1010
+ const slot = rows[row];
1011
+ if (slot) slot.painted = true;
1012
+ this._version++;
1013
+ },
1014
+ show: () => {
1015
+ this._version++;
1016
+ }
1017
+ });
1018
+ const made = { table, rows };
1019
+ this.tables.set(length, made);
1020
+ return made;
1021
+ }
1022
+ drop(slot) {
1023
+ const at = this.tables.get(slot.length);
1024
+ at.rows[slot.row] = void 0;
1025
+ slot.table.remove(slot.row);
1026
+ if (slot.table.size === 0) this.tables.delete(slot.length);
1027
+ }
1028
+ /** Drops the colors `id`'s {@link target} on `channel` left painted, unless an animation is
1029
+ * still moving it. An override from `set` stays. */
1030
+ release(id, channel) {
1031
+ const node = this.slots.get(id);
1032
+ const slot = node?.[channel];
1033
+ if (!node || !slot || slot.table.target(slot.row).active) return;
1034
+ delete node[channel];
1035
+ if (!node.fill && !node.stroke) this.slots.delete(id);
1036
+ this.drop(slot);
1037
+ if (slot.painted) this._version++;
1038
+ }
1039
+ /**
1040
+ * The colors to paint on `id`'s `channel`, given the colors the painter would
1041
+ * otherwise paint and the frame's timestamp. A function override needs that
1042
+ * `base` and must return an array of its length; failing either, `base`
1043
+ * stands. A {@link target} in motion then adds what it has left to travel.
1044
+ */
1045
+ resolve(id, channel, base, tMs) {
1046
+ const override = this.map.get(id)?.[channel];
1047
+ const slot = this.slots.get(id)?.[channel];
1048
+ if (override === void 0) return slot?.painted ? shownOf(slot) : base;
1049
+ const under = typeof override === "function" ? this.apply(override, base, tMs) : override;
1050
+ if (!slot?.painted || under?.length !== slot.length || !slot.table.target(slot.row).active) return under;
1051
+ const { shown, committed } = slot.table;
1052
+ const at = slot.row * slot.length;
1053
+ return under.map((x, i) => x + shown[at + i] - committed[at + i]);
1054
+ }
1055
+ apply(override, base, tMs) {
1056
+ if (base === void 0) return void 0;
1057
+ const result = override(base, tMs);
1058
+ return result.length === base.length ? result : base;
1059
+ }
1060
+ version() {
1061
+ return this._version;
1062
+ }
1063
+ };
1064
+ function shownOf(slot) {
1065
+ const { shown } = slot.table;
1066
+ const at = slot.row * slot.length;
1067
+ const out = new Array(slot.length);
1068
+ for (let i = 0; i < out.length; i++) out[i] = shown[at + i];
1069
+ return out;
1070
+ }
1071
+
1072
+ // src/timeline/targetStops.ts
1073
+ function targetStops(track) {
1074
+ const { axes } = stateOf(track.target);
1075
+ const stops = [];
1076
+ for (const k of track.keys) {
1077
+ const row = axes.to(k.value);
1078
+ if (row.length !== axes.count || !row.every((x) => typeof x === "number" && !Number.isNaN(x))) {
1079
+ throw new Error("sampleTrack: a key's value is not its target's shape");
1080
+ }
1081
+ const prev = stops[stops.length - 1];
1082
+ if (prev && axes.angles) {
1083
+ const short = travel(axes, row, prev);
1084
+ for (const i of axes.angles) if (short[i] !== row[i] - prev[i]) row[i] = prev[i] + short[i];
1085
+ }
1086
+ stops.push(row);
1087
+ }
1088
+ return { axes, stops };
1089
+ }
1090
+
1091
+ // src/timeline/sampleTrack.ts
1092
+ var NO_SETTING = {};
1093
+ var BUILT = -1;
1094
+ function build(track, cache) {
1095
+ const { keys: keys$1 } = track;
1096
+ let t0 = Infinity;
1097
+ let t1 = -Infinity;
1098
+ const exact = /* @__PURE__ */ new Map();
1099
+ for (const k of keys$1) {
1100
+ t0 = Math.min(t0, k.t);
1101
+ t1 = Math.max(t1, k.t);
1102
+ exact.set(k.t, k.value);
1103
+ }
1104
+ const span = t1 - t0;
1105
+ const out = { track, keys: keys$1, patch: null, t0, span, exact, fresh: null, first: keys$1[0].value, last: keys$1[keys$1.length - 1].value, read: (v) => v };
1106
+ const { interpolator, interpolate } = track;
1107
+ const onTarget = track.target && !interpolator && !interpolate ? targetStops(track) : null;
1108
+ if (onTarget) {
1109
+ const { axes: axes2, stops } = onTarget;
1110
+ out.fresh = new Map(keys$1.map((k, i) => [k.t, () => axes2.from(stops[i])]));
1111
+ out.first = axes2.from(stops[0]);
1112
+ out.last = axes2.from(stops[stops.length - 1]);
1113
+ }
1114
+ if (span <= 0) return out;
1115
+ const ease = (k) => k.easing ? resolveEasing(k.easing) : void 0;
1116
+ if (onTarget) {
1117
+ const { axes: axes2, stops } = onTarget;
1118
+ out.patch = keys(
1119
+ 1,
1120
+ keys$1.map((k, i) => ({ at: (k.t - t0) / span, delta: { v: stops[i] }, ease: ease(k) }))
1121
+ );
1122
+ out.read = (v) => axes2.from(v);
1123
+ return out;
1124
+ }
1125
+ if (interpolator || interpolate) {
1126
+ const boxes = keys$1.map((k, i) => ({ i, value: k.value }));
1127
+ const lerp = (a, b, u) => {
1128
+ if (interpolator) {
1129
+ let fn = cache?.get(b.i);
1130
+ if (!fn) {
1131
+ fn = interpolator(a.value, b.value);
1132
+ cache?.set(b.i, fn);
1133
+ }
1134
+ return { i: -1, value: fn(u) };
1135
+ }
1136
+ return { i: -1, value: interpolate(a.value, b.value, u) };
1137
+ };
1138
+ out.patch = keys(
1139
+ 1,
1140
+ keys$1.map((k, i) => ({ at: (k.t - t0) / span, delta: { v: boxes[i] }, ease: ease(k) })),
1141
+ { lerpBy: () => lerp }
1142
+ );
1143
+ out.read = (v) => v.value;
1144
+ out.fresh = /* @__PURE__ */ new Map();
1145
+ for (let i = 0; i < keys$1.length - 1; i++) {
1146
+ const at = keys$1[i].t;
1147
+ if (at > t0 && at < t1) out.fresh.set(at, () => lerp(boxes[i], boxes[i + 1], 0).value);
1148
+ }
1149
+ return out;
1150
+ }
1151
+ const axes = axesOf(keys$1[0].value);
1152
+ if (!axes) return out;
1153
+ for (const k of keys$1) {
1154
+ if (axesOf(k.value)?.shape !== axes.shape) {
1155
+ throw new Error(`sampleTrack: every key needs the shape of the first (${axes.shape})`);
1156
+ }
1157
+ }
1158
+ const scalar = axes.shape === "number";
1159
+ out.patch = keys(
1160
+ 1,
1161
+ keys$1.map((k) => ({ at: (k.t - t0) / span, delta: { v: scalar ? k.value : axes.to(k.value) }, ease: ease(k) }))
1162
+ );
1163
+ if (!scalar) out.read = (v) => axes.from(v);
1164
+ return out;
1165
+ }
1166
+ function sampleTrack(track, t, segmentCache) {
1167
+ if (track.keys.length === 0) return void 0;
1168
+ const cache = segmentCache;
1169
+ let b = cache?.get(BUILT);
1170
+ if (!b || b.track !== track || b.keys !== track.keys) {
1171
+ cache?.clear();
1172
+ b = build(track, segmentCache);
1173
+ cache?.set(BUILT, b);
1174
+ }
1175
+ if (Number.isNaN(t)) return b.first;
1176
+ const fresh = b.fresh?.get(t);
1177
+ if (fresh) return fresh();
1178
+ const hit = b.exact.get(t);
1179
+ if (hit !== void 0 || b.exact.has(t)) return hit;
1180
+ if (!b.patch) {
1181
+ if (t < b.t0) return b.first;
1182
+ if (t >= b.t0 + b.span) return b.last;
1183
+ throw new Error("sampleTrack: interpolate or interpolator is required for non-numeric keyframe values");
1184
+ }
1185
+ return b.read(b.patch.at((t - b.t0) / b.span, 0, NO_SETTING).v);
1186
+ }
1187
+
1188
+ // src/timeline/booking.ts
1189
+ var RESYNC_MS = 50;
1190
+ var SLEW = 0.05;
1191
+ var RETIME_MS = 1;
1192
+ function firstAfter(events, t) {
1193
+ let lo = 0;
1194
+ let hi = events.length;
1195
+ while (lo < hi) {
1196
+ const mid = lo + hi >> 1;
1197
+ if (events[mid].t > t) hi = mid;
1198
+ else lo = mid + 1;
1199
+ }
1200
+ return lo;
1201
+ }
1202
+ function firstAtOrAfter(events, t) {
1203
+ let i = firstAfter(events, t);
1204
+ while (i > 0 && events[i - 1].t === t) i -= 1;
1205
+ return i;
1206
+ }
1207
+ var isAfter = (a, b) => a.lap > b.lap || a.lap === b.lap && a.r > b.r;
1208
+ function createBooker(opts, host) {
1209
+ const lookahead = opts.lookahead ?? 100;
1210
+ const maxLate = opts.maxLate ?? Infinity;
1211
+ const cursors = /* @__PURE__ */ new Map();
1212
+ let start = { lap: 0, r: -Infinity };
1213
+ let bookings = [];
1214
+ let est = null;
1215
+ let mapVirtual = 0;
1216
+ let lastScale = 0;
1217
+ const playheadCursor = () => ({ lap: host.lap(), r: host.playhead() });
1218
+ const unwrapped = (b) => (b.lap - host.lap()) * host.duration() + b.r;
1219
+ const ahead = (b) => isAfter(b, playheadCursor());
1220
+ const stopWhere = (now, pred) => {
1221
+ bookings = bookings.filter((b) => {
1222
+ if (!b.handle || b.when <= now || !ahead(b) || !pred(b)) return true;
1223
+ try {
1224
+ b.handle.stop();
1225
+ } catch (err) {
1226
+ console.error("timeline: booking stop threw", err);
1227
+ }
1228
+ return false;
1229
+ });
1230
+ const at = playheadCursor();
1231
+ for (const [track, c] of cursors) if (isAfter(c, at)) cursors.set(track, at);
1232
+ if (isAfter(start, at)) start = at;
1233
+ };
1234
+ const whenFor = (u, playhead, scale, now) => Math.max(est + (u - playhead) / scale, now);
1235
+ const scanTrack = (track, at, horizon, lastLap, now, scale) => {
1236
+ const lap = host.lap();
1237
+ const duration = host.duration();
1238
+ const playhead = host.playhead();
1239
+ const { events } = track;
1240
+ let c = cursors.get(track) ?? start;
1241
+ for (; ; ) {
1242
+ const lapStart = (c.lap - lap) * duration;
1243
+ if (lapStart > horizon) break;
1244
+ const i = (c.open ? firstAtOrAfter : firstAfter)(events, c.r - at);
1245
+ if (i >= events.length || at + events[i].t > duration) {
1246
+ if (c.lap >= lastLap) break;
1247
+ c = { lap: Math.max(c.lap + 1, lap), r: -Infinity };
1248
+ continue;
1249
+ }
1250
+ const r = at + events[i].t;
1251
+ const u = lapStart + r;
1252
+ if (u > horizon) break;
1253
+ c = { lap: c.lap, r };
1254
+ for (let j = i; j < events.length && events[j].t === events[i].t; j += 1) {
1255
+ const ev = events[j];
1256
+ if (!ev.book || bookings.some((b) => b.event === ev && b.lap === c.lap)) continue;
1257
+ if ((playhead - u) / scale > maxLate) continue;
1258
+ const when = whenFor(u, playhead, scale, now);
1259
+ let handle;
1260
+ try {
1261
+ handle = ev.book(when) ?? void 0;
1262
+ } catch (err) {
1263
+ console.error("timeline: book threw", err);
1264
+ }
1265
+ bookings.push({ event: ev, lap: c.lap, r, when, handle });
1266
+ }
1267
+ }
1268
+ cursors.set(track, c);
1269
+ };
1270
+ const visit = (tracks, at, horizon, lastLap, now, scale) => {
1271
+ for (const track of tracks) {
1272
+ if (track.kind === "event") scanTrack(track, at, horizon, lastLap, now, scale);
1273
+ else if (track.kind === "timeline") {
1274
+ visit(track.timeline.tracks, at + track.at, horizon, lastLap, now, scale);
1275
+ }
1276
+ }
1277
+ };
1278
+ return {
1279
+ tick(virtualNow, scale) {
1280
+ const now = opts.clock.now();
1281
+ bookings = bookings.filter(ahead);
1282
+ if (!(scale > 0)) {
1283
+ if (lastScale > 0) stopWhere(now, () => true);
1284
+ lastScale = 0;
1285
+ est = null;
1286
+ return;
1287
+ }
1288
+ let moved = lastScale > 0 && scale !== lastScale;
1289
+ if (est === null) {
1290
+ est = now;
1291
+ } else {
1292
+ est += (virtualNow - mapVirtual) / scale;
1293
+ const residual = now - est;
1294
+ if (Math.abs(residual) > RESYNC_MS) {
1295
+ est = now;
1296
+ moved = true;
1297
+ } else {
1298
+ est += residual * SLEW;
1299
+ }
1300
+ }
1301
+ mapVirtual = virtualNow;
1302
+ lastScale = scale;
1303
+ const playhead = host.playhead();
1304
+ if (moved) {
1305
+ stopWhere(now, (b) => Math.abs(whenFor(unwrapped(b), playhead, scale, now) - b.when) > RETIME_MS);
1306
+ }
1307
+ const duration = host.duration();
1308
+ const lastLap = host.lap() + (duration > 0 ? host.lapsLeft() : 0);
1309
+ visit(host.tracks, 0, playhead + lookahead * scale, lastLap, now, scale);
1310
+ },
1311
+ retract() {
1312
+ stopWhere(opts.clock.now(), () => true);
1313
+ },
1314
+ seek() {
1315
+ bookings = [];
1316
+ cursors.clear();
1317
+ start = { ...playheadCursor(), open: true };
1318
+ },
1319
+ rearm() {
1320
+ est = null;
1321
+ }
1322
+ };
1323
+ }
1324
+
1325
+ // src/timeline/fireEvents.ts
1326
+ function createEventCursor(fire) {
1327
+ let at = -Infinity;
1328
+ let open = false;
1329
+ return {
1330
+ to(tracks, to) {
1331
+ fire(tracks, at, to, open);
1332
+ at = to;
1333
+ open = false;
1334
+ },
1335
+ place(t, o) {
1336
+ at = t;
1337
+ open = o;
1338
+ }
1339
+ };
1340
+ }
1341
+ function createEventFirer(report, booked) {
1342
+ const path = [];
1343
+ let warnedUnbooked = false;
1344
+ const fire = (tracks, from, to, fromOpen) => {
1345
+ for (let k = 0; k < tracks.length; k += 1) {
1346
+ const track = tracks[k];
1347
+ if (track.kind === "event") {
1348
+ const end = firstAfter(track.events, to);
1349
+ const first = (fromOpen ? firstAtOrAfter : firstAfter)(track.events, from);
1350
+ for (let i = first; i < end; i += 1) {
1351
+ const ev = track.events[i];
1352
+ ev.fire?.(to - ev.t);
1353
+ if (report?.watched) {
1354
+ path.push(k);
1355
+ report.fire(track, path, ev, to - ev.t);
1356
+ path.pop();
1357
+ }
1358
+ if (ev.book && !booked && !warnedUnbooked) {
1359
+ warnedUnbooked = true;
1360
+ console.warn("timeline: an event has `book` but the timeline has no `booking` clock; it never books.");
1361
+ }
1362
+ }
1363
+ } else if (track.kind === "timeline") {
1364
+ path.push(k);
1365
+ fire(track.timeline.tracks, from - track.at, to - track.at, fromOpen);
1366
+ path.pop();
1367
+ }
1368
+ }
1369
+ };
1370
+ return fire;
1371
+ }
1372
+
1373
+ // src/timeline/trackHolds.ts
1374
+ function createTrackSampler(open) {
1375
+ let caches = /* @__PURE__ */ new WeakMap();
1376
+ const cacheFor = (track) => {
1377
+ let c = caches.get(track);
1378
+ if (!c) {
1379
+ c = /* @__PURE__ */ new Map();
1380
+ caches.set(track, c);
1381
+ }
1382
+ return c;
1383
+ };
1384
+ const held = /* @__PURE__ */ new Map();
1385
+ let edit = 0;
1386
+ let pass2 = 0;
1387
+ const openOn = (st, h) => {
1388
+ if (!open) throw new Error("timeline: a track with a `target` needs a timeline made by an animator");
1389
+ const w = st.weight ?? 1;
1390
+ return typeof w === "function" ? open(st.target, () => h.value, w, st.axes) : open(st.target, () => h.value, () => w, st.axes, true);
1391
+ };
1392
+ const place = (st, value, t) => {
1393
+ let h = held.get(st);
1394
+ if (!h) held.set(st, h = { hold: null, value, where: null, t, edit, pass: pass2 });
1395
+ const moved = h.t !== t || h.edit !== edit;
1396
+ h.value = value;
1397
+ h.t = t;
1398
+ h.edit = edit;
1399
+ h.pass = pass2;
1400
+ const { keys } = st;
1401
+ const where = t < keys[0].t ? "before" : t > keys[keys.length - 1].t ? "after" : "inside";
1402
+ if (where === "inside") {
1403
+ if (!h.hold) h.hold = openOn(st, h);
1404
+ else if (moved) h.hold.touch();
1405
+ } else if (where === "before") {
1406
+ h.hold?.leave(false);
1407
+ h.hold = null;
1408
+ } else if (h.where !== "after") {
1409
+ (h.hold ?? openOn(st, h)).leave(true);
1410
+ h.hold = null;
1411
+ }
1412
+ h.where = where;
1413
+ };
1414
+ const visit = (tracks, t) => {
1415
+ for (const track of tracks) {
1416
+ if (track.kind === "sampled") {
1417
+ const st = track;
1418
+ const v = sampleTrack(st, t, cacheFor(st));
1419
+ if (v === void 0) continue;
1420
+ if (st.target) place(st, v, t);
1421
+ st.onTick?.(v);
1422
+ } else if (track.kind === "timeline") {
1423
+ visit(track.timeline.tracks, t - track.at);
1424
+ }
1425
+ }
1426
+ };
1427
+ return {
1428
+ apply(tracks, t) {
1429
+ pass2 += 1;
1430
+ visit(tracks, t);
1431
+ for (const [st, h] of held) {
1432
+ if (h.pass === pass2) continue;
1433
+ h.hold?.leave(true);
1434
+ held.delete(st);
1435
+ }
1436
+ },
1437
+ edited() {
1438
+ caches = /* @__PURE__ */ new WeakMap();
1439
+ edit += 1;
1440
+ },
1441
+ release() {
1442
+ for (const h of held.values()) {
1443
+ if (!h.hold) continue;
1444
+ h.hold.leave(true);
1445
+ h.hold = null;
1446
+ h.where = null;
1447
+ }
1448
+ }
1449
+ };
1450
+ }
1451
+
1452
+ // src/timeline/createTimeline.ts
1453
+ function trackEnd(track) {
1454
+ switch (track.kind) {
1455
+ case "sampled":
1456
+ return track.keys.length ? track.keys[track.keys.length - 1].t : 0;
1457
+ case "event":
1458
+ return track.events.length ? track.events[track.events.length - 1].t : 0;
1459
+ case "timeline":
1460
+ return track.at + tracksEnd(track.timeline.tracks, track.timeline.duration);
1461
+ }
1462
+ }
1463
+ function tracksEnd(tracks, explicit) {
1464
+ if (explicit != null) return explicit;
1465
+ let max = 0;
1466
+ for (const t of tracks) max = Math.max(max, trackEnd(t));
1467
+ return max;
1468
+ }
1469
+ function crossLaps(playhead, duration, loopsLeft, lap, take) {
1470
+ if (duration <= 0) return;
1471
+ const laps = playhead >= duration ? Math.min(Math.floor(playhead / duration), loopsLeft) : playhead < 0 ? -Math.min(Math.ceil(-playhead / duration), lap) : 0;
1472
+ if (laps !== 0) take(laps);
1473
+ }
1474
+ function createTimeline(register, id, opts, report, hold) {
1475
+ let duration = tracksEnd(opts.tracks, opts.duration);
1476
+ let offset = 0;
1477
+ let lastVirtual = 0;
1478
+ let playhead = 0;
1479
+ let held = false;
1480
+ let done = false;
1481
+ let live = true;
1482
+ let canceled = false;
1483
+ let wantPaused = opts.autoplay === false;
1484
+ let wantScale = 1;
1485
+ const edited = createEmitter();
1486
+ const playback = createEmitter();
1487
+ const playbackChanged = playback.emit;
1488
+ const loopOpt = opts.loop ?? false;
1489
+ let loopsLeft = loopOpt === true ? Infinity : loopOpt === false ? 0 : loopOpt;
1490
+ let lap = 0;
1491
+ const booker = opts.booking ? createBooker(opts.booking, {
1492
+ tracks: opts.tracks,
1493
+ duration: () => duration,
1494
+ lapsLeft: () => loopsLeft,
1495
+ lap: () => lap,
1496
+ playhead: () => playhead
1497
+ }) : null;
1498
+ const sampler = createTrackSampler(hold);
1499
+ const events = createEventCursor(createEventFirer(report, booker !== null));
1500
+ const onWrap = (laps) => {
1501
+ if (!held && laps > 0) events.to(opts.tracks, duration);
1502
+ events.place(laps > 0 ? -Infinity : playhead, false);
1503
+ if (report?.watched) report.lap(lap);
1504
+ };
1505
+ const tick = (virtualNow, scale) => {
1506
+ lastVirtual = virtualNow;
1507
+ const rate = scale ?? (base.isPaused() ? 0 : base.timeScale());
1508
+ held = rate === 0;
1509
+ const parked = held && duration > 0 && playhead === duration && loopsLeft > 0;
1510
+ if (!parked) {
1511
+ playhead = virtualNow + offset;
1512
+ crossLaps(playhead, duration, loopsLeft, lap, (laps) => {
1513
+ loopsLeft -= laps;
1514
+ offset -= laps * duration;
1515
+ playhead -= laps * duration;
1516
+ lap += laps;
1517
+ onWrap(laps);
1518
+ });
1519
+ }
1520
+ const finished = playhead >= duration && !parked;
1521
+ const rewound = rate < 0 && playhead <= 0;
1522
+ playhead = Math.min(Math.max(playhead, 0), duration);
1523
+ sampler.apply(opts.tracks, playhead);
1524
+ if (!held) events.to(opts.tracks, playhead);
1525
+ booker?.tick(virtualNow, rate);
1526
+ if ((finished || rewound) && !done) {
1527
+ done = true;
1528
+ opts.onDone?.();
1529
+ playbackChanged();
1530
+ }
1531
+ if ((rewound ? playhead > 0 : playhead < duration) || parked) return false;
1532
+ live = false;
1533
+ sampler.release();
1534
+ return true;
1535
+ };
1536
+ const onCancel = () => {
1537
+ const first = !canceled;
1538
+ canceled = true;
1539
+ live = false;
1540
+ booker?.retract();
1541
+ sampler.release();
1542
+ if (first) playbackChanged();
1543
+ };
1544
+ const seed = { id, cancelKey: opts.cancelKey, tick, onCancel, kind: "timeline", label: opts.label, progress: () => duration > 0 ? playhead / duration : 1 };
1545
+ const base = register(seed);
1546
+ if (opts.autoplay === false) base.pause();
1547
+ const rearm = () => {
1548
+ if (canceled || playhead >= duration) return;
1549
+ done = false;
1550
+ if (live) return;
1551
+ booker?.rearm();
1552
+ lastVirtual = 0;
1553
+ offset = playhead;
1554
+ live = true;
1555
+ register({ ...seed, keepExisting: true });
1556
+ if (wantPaused) base.pause();
1557
+ base.setTimeScale(wantScale);
1558
+ };
1559
+ const sampleNow = () => {
1560
+ if (live && playhead >= 0 && playhead < duration) sampler.apply(opts.tracks, playhead);
1561
+ };
1562
+ return {
1563
+ ...base,
1564
+ // Not just `onCancel`: cancelling a finished timeline never reaches the
1565
+ // animator, and must still keep a later seek from reviving it.
1566
+ cancel() {
1567
+ onCancel();
1568
+ base.cancel();
1569
+ },
1570
+ // Playback intent is tracked here, not read back from the entry: a revived
1571
+ // one is a fresh registration, and defaults to running at scale 1.
1572
+ pause() {
1573
+ wantPaused = true;
1574
+ base.pause();
1575
+ booker?.retract();
1576
+ playbackChanged();
1577
+ },
1578
+ resume() {
1579
+ wantPaused = false;
1580
+ base.resume();
1581
+ playbackChanged();
1582
+ },
1583
+ setTimeScale(s) {
1584
+ wantScale = s;
1585
+ base.setTimeScale(s);
1586
+ playbackChanged();
1587
+ },
1588
+ timeScale: () => live ? base.timeScale() : wantScale,
1589
+ isPaused: () => live ? base.isPaused() : wantPaused,
1590
+ seek(t) {
1591
+ booker?.retract();
1592
+ offset = t - lastVirtual;
1593
+ playhead = t;
1594
+ events.place(t, true);
1595
+ booker?.seek();
1596
+ rearm();
1597
+ sampleNow();
1598
+ playbackChanged();
1599
+ },
1600
+ time: () => playhead,
1601
+ duration: () => duration,
1602
+ tracks: () => opts.tracks,
1603
+ setLoop(loop) {
1604
+ loopsLeft = loop === true ? Infinity : loop === false ? 0 : loop;
1605
+ booker?.retract();
1606
+ playbackChanged();
1607
+ },
1608
+ loop: () => loopsLeft === Infinity ? true : loopsLeft === 0 ? false : loopsLeft,
1609
+ edit(fn) {
1610
+ fn();
1611
+ sampler.edited();
1612
+ duration = tracksEnd(opts.tracks, opts.duration);
1613
+ booker?.retract();
1614
+ rearm();
1615
+ sampleNow();
1616
+ edited.emit();
1617
+ },
1618
+ subscribe: edited.subscribe,
1619
+ subscribePlayback: playback.subscribe
1620
+ };
1621
+ }
1622
+
1623
+ export { ColorOverrideRegistry, absorb, axesFor, axesOf, axisIndexes, bump, bumpOn, comeOut, commitOffset, committedInto, createHoldStore, createTarget, createTargetBook, createTargetTable, createTimeline, goBehind, isTarget, minus, norm, plus, rowTableOf, runsOn, sampleTrack, seat, setBeside, shareBehind, shortWay, stateOf, takeUp, times, travel };
1624
+ //# sourceMappingURL=chunk-H7WENFPC.js.map
1625
+ //# sourceMappingURL=chunk-H7WENFPC.js.map