@msb235/blits 0.14.0 → 0.15.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 (187) hide show
  1. package/CHANGELOG.md +198 -0
  2. package/README.md +3 -3
  3. package/dist/ahead.d.ts +26 -0
  4. package/dist/ahead.js +264 -0
  5. package/dist/around.d.ts +11 -0
  6. package/dist/around.js +32 -0
  7. package/dist/bare.js +37 -14
  8. package/dist/blank.d.ts +10 -0
  9. package/dist/blank.js +26 -0
  10. package/dist/blend.d.ts +2 -1
  11. package/dist/blend.js +1 -6
  12. package/dist/bound.d.ts +70 -0
  13. package/dist/bound.js +351 -0
  14. package/dist/chain.d.ts +2 -1
  15. package/dist/chain.js +7 -5
  16. package/dist/changes.d.ts +24 -0
  17. package/dist/changes.js +189 -0
  18. package/dist/channels.d.ts +39 -13
  19. package/dist/channels.js +14 -5
  20. package/dist/color.d.ts +12 -10
  21. package/dist/color.js +25 -25
  22. package/dist/columns.d.ts +2 -1
  23. package/dist/columns.js +62 -38
  24. package/dist/contribution.d.ts +30 -0
  25. package/dist/contribution.js +374 -0
  26. package/dist/copyout.d.ts +10 -0
  27. package/dist/copyout.js +69 -24
  28. package/dist/crowd.d.ts +16 -6
  29. package/dist/crowd.js +20 -11
  30. package/dist/cue.d.ts +8 -1
  31. package/dist/cue.js +39 -13
  32. package/dist/cut.d.ts +8 -0
  33. package/dist/cut.js +4 -0
  34. package/dist/delay.d.ts +3 -0
  35. package/dist/delay.js +7 -0
  36. package/dist/drop.d.ts +9 -0
  37. package/dist/drop.js +84 -0
  38. package/dist/due.d.ts +24 -5
  39. package/dist/due.js +114 -34
  40. package/dist/easing.d.ts +6 -4
  41. package/dist/easing.js +7 -3
  42. package/dist/everyone.d.ts +8 -2
  43. package/dist/everyone.js +33 -3
  44. package/dist/fade.d.ts +2 -1
  45. package/dist/fade.js +2 -1
  46. package/dist/fill.d.ts +3 -2
  47. package/dist/fill.js +52 -20
  48. package/dist/fit.d.ts +44 -26
  49. package/dist/fit.js +10 -9
  50. package/dist/fold.d.ts +4 -37
  51. package/dist/fold.js +5 -513
  52. package/dist/gather.d.ts +12 -7
  53. package/dist/gather.js +179 -44
  54. package/dist/gone.js +2 -2
  55. package/dist/grid.d.ts +47 -0
  56. package/dist/grid.js +247 -0
  57. package/dist/handle.d.ts +2 -0
  58. package/dist/handle.js +14 -8
  59. package/dist/held.d.ts +2 -1
  60. package/dist/held.js +2 -1
  61. package/dist/history.d.ts +6 -5
  62. package/dist/history.js +7 -9
  63. package/dist/hosts.js +9 -15
  64. package/dist/index.d.ts +5 -4
  65. package/dist/keyfill.d.ts +1 -1
  66. package/dist/keyfill.js +5 -3
  67. package/dist/keyrows.d.ts +12 -0
  68. package/dist/keyrows.js +99 -12
  69. package/dist/lane.d.ts +38 -11
  70. package/dist/lane.js +46 -11
  71. package/dist/lanes.d.ts +30 -12
  72. package/dist/lanes.js +56 -36
  73. package/dist/leavings.d.ts +2 -2
  74. package/dist/leavings.js +1 -1
  75. package/dist/lives.d.ts +47 -0
  76. package/dist/lives.js +180 -0
  77. package/dist/meet.d.ts +2 -2
  78. package/dist/meet.js +95 -7
  79. package/dist/mixer.d.ts +56 -37
  80. package/dist/mixer.js +57 -171
  81. package/dist/motion.d.ts +14 -118
  82. package/dist/motion.js +16 -8
  83. package/dist/motions.d.ts +62 -155
  84. package/dist/motions.js +56 -370
  85. package/dist/moved.d.ts +1 -1
  86. package/dist/moved.js +16 -7
  87. package/dist/named.d.ts +6 -2
  88. package/dist/named.js +49 -3
  89. package/dist/numbers.d.ts +7 -0
  90. package/dist/numbers.js +33 -0
  91. package/dist/owed.d.ts +2 -0
  92. package/dist/owed.js +4 -0
  93. package/dist/pack.d.ts +2 -1
  94. package/dist/pack.js +2 -3
  95. package/dist/patch.d.ts +7 -3
  96. package/dist/patch.js +6 -2
  97. package/dist/places.d.ts +53 -0
  98. package/dist/places.js +171 -0
  99. package/dist/plot.d.ts +12 -0
  100. package/dist/plot.js +52 -0
  101. package/dist/project.d.ts +4 -4
  102. package/dist/project.js +9 -212
  103. package/dist/pull.js +22 -9
  104. package/dist/qualify.js +93 -31
  105. package/dist/reading.d.ts +6 -0
  106. package/dist/reading.js +5 -0
  107. package/dist/record.d.ts +5 -1
  108. package/dist/record.js +3 -2
  109. package/dist/relink.d.ts +1 -1
  110. package/dist/renumber.d.ts +10 -0
  111. package/dist/renumber.js +75 -0
  112. package/dist/restored.d.ts +2 -2
  113. package/dist/restored.js +1 -1
  114. package/dist/rests.d.ts +9 -0
  115. package/dist/rests.js +76 -0
  116. package/dist/revive.d.ts +3 -2
  117. package/dist/revive.js +2 -2
  118. package/dist/{locus.d.ts → role.d.ts} +10 -9
  119. package/dist/{locus.js → role.js} +3 -3
  120. package/dist/roles.d.ts +10 -0
  121. package/dist/roles.js +158 -0
  122. package/dist/roster.js +3 -3
  123. package/dist/rotation.d.ts +1 -1
  124. package/dist/rotation.js +1 -1
  125. package/dist/rows.js +28 -20
  126. package/dist/sample.d.ts +2 -2
  127. package/dist/sample.js +33 -7
  128. package/dist/seat.d.ts +17 -0
  129. package/dist/seat.js +25 -0
  130. package/dist/seek.js +5 -5
  131. package/dist/setting.d.ts +27 -0
  132. package/dist/setting.js +55 -0
  133. package/dist/shared.d.ts +1 -1
  134. package/dist/shared.js +1 -1
  135. package/dist/signals.d.ts +64 -32
  136. package/dist/signals.js +20 -18
  137. package/dist/stack.d.ts +5 -5
  138. package/dist/stack.js +5 -5
  139. package/dist/standing.d.ts +2 -1
  140. package/dist/standing.js +3 -2
  141. package/dist/store.d.ts +9 -0
  142. package/dist/store.js +24 -0
  143. package/dist/strays.d.ts +2 -1
  144. package/dist/strays.js +23 -15
  145. package/dist/stretch.d.ts +112 -0
  146. package/dist/stretch.js +9 -0
  147. package/dist/subject.d.ts +142 -0
  148. package/dist/subject.js +1 -0
  149. package/dist/tape.d.ts +9 -5
  150. package/dist/tape.js +5 -1
  151. package/dist/then.d.ts +9 -0
  152. package/dist/then.js +57 -0
  153. package/dist/ticker.d.ts +4 -4
  154. package/dist/trace.d.ts +4 -0
  155. package/dist/trace.js +255 -0
  156. package/dist/transport.d.ts +0 -8
  157. package/dist/transport.js +0 -4
  158. package/dist/turns.d.ts +4 -3
  159. package/dist/turns.js +4 -3
  160. package/dist/types/arc.d.ts +12 -11
  161. package/dist/types/channel.d.ts +12 -8
  162. package/dist/types/history.d.ts +35 -19
  163. package/dist/types/mix.d.ts +237 -163
  164. package/dist/types/motion.d.ts +151 -0
  165. package/dist/types/motion.js +1 -0
  166. package/dist/types/patch.d.ts +67 -34
  167. package/dist/types/place.d.ts +51 -36
  168. package/dist/types/trace.d.ts +51 -0
  169. package/dist/types/trace.js +1 -0
  170. package/dist/types/transport.d.ts +20 -18
  171. package/dist/types/voice.d.ts +144 -73
  172. package/dist/types/watch.d.ts +88 -40
  173. package/dist/types.d.ts +2 -0
  174. package/dist/unreached.d.ts +2 -0
  175. package/dist/unreached.js +7 -0
  176. package/dist/voice.d.ts +10 -143
  177. package/dist/voice.js +37 -82
  178. package/dist/warn.d.ts +2 -0
  179. package/dist/warn.js +1 -1
  180. package/dist/watch.d.ts +2 -2
  181. package/dist/watch.js +5 -3
  182. package/dist/wave.d.ts +4 -3
  183. package/dist/wave.js +3 -2
  184. package/dist/weigh.d.ts +7 -1
  185. package/dist/weigh.js +27 -0
  186. package/docs/schema.html +223 -34
  187. package/package.json +2 -2
package/dist/mixer.js CHANGED
@@ -1,35 +1,43 @@
1
1
  import { cueArc } from './arcs.js';
2
+ import { around } from './around.js';
3
+ import { blanks } from './blank.js';
2
4
  import { blended, blendOf, laneRead, shareKept } from './blend.js';
3
- import { aims, chain, headOf, linkable, relink, unlink } from './chain.js';
5
+ import { aims, chain, linkable, relink } from './chain.js';
4
6
  import { lerpInto, STOCK_BAND, scalarOp } from './channels.js';
5
- import { cue } from './cue.js';
7
+ import { baseFor, contribution, isRest, keyed, read, slopeFor, tick } from './contribution.js';
8
+ import { cue, roleTags } from './cue.js';
6
9
  import { DepartedIndex } from './departed.js';
7
- import { beginFade, leaveVoice, parting } from './fade.js';
8
- import { apply, baseFor, clamp, contribution, fold, foldDetour, folded, foldLoci, foldSharers, foldWith, isRest, keyed, linked, near, owedBy, passes, read, shared, slopeFor, tick, } from './fold.js';
9
- import { GoneIndex, goneIndex } from './gone.js';
10
+ import { drop, forgetIn, recordDrop, releaseIn } from './drop.js';
11
+ import { DueQueue } from './due.js';
12
+ import { beginFade, parting } from './fade.js';
13
+ import { apply, clamp, fold, foldDetour, folded, foldSharers, foldWith, linked, owedBy, passes, shared, } from './fold.js';
14
+ import { GoneIndex } from './gone.js';
10
15
  import { held, shownOf, sinceOf, unreachedOf } from './held.js';
11
- import { releasing } from './history.js';
12
16
  import { book, laneHost } from './hosts.js';
13
17
  import { Lanes } from './lanes.js';
14
- import { foldLocus } from './locus.js';
15
18
  import { listed } from './marks.js';
16
19
  import { nextFrame } from './move.js';
17
20
  import { ownerPatch } from './owner.js';
18
21
  import { Keys } from './paging.js';
22
+ import { moved, place, plot, put } from './plot.js';
19
23
  import { projectAll } from './project.js';
20
24
  import { keep, pull } from './pull.js';
21
25
  import { Steps } from './relink.js';
26
+ import { atRest, restful, rests, restsKey, restsLaned, still } from './rests.js';
22
27
  import { Outs } from './revive.js';
28
+ import { foldRole } from './role.js';
29
+ import { foldRoles } from './roles.js';
23
30
  import { ScoreIndex } from './scored.js';
24
31
  import { seek } from './seek.js';
25
32
  import { Fitting, refit } from './spans.js';
26
- import { Masked, Store } from './store.js';
27
- import { record, unplayable } from './tape.js';
33
+ import { Store } from './store.js';
34
+ import { record } from './tape.js';
28
35
  import { touch } from './touch.js';
36
+ import { traceMix } from './trace.js';
29
37
  import { announce, Transport } from './transport.js';
30
38
  import { none } from './voice.js';
31
39
  import { gave, historyFor, register, unregister } from './watch.js';
32
- import { base, capped, fadeOf, horizonFor, ownedBy, ownerBase, prime, weigh, weighOwned, } from './weigh.js';
40
+ import { base, capped, fadeOf, horizonFor, ownedBy, ownerBase, prime, primeLaned, weigh, weighOwned, } from './weigh.js';
33
41
  /**
34
42
  * What a mix takes where its options say nothing. Not exported from the package: the suite turns
35
43
  * `lanes` off to run every test on the general path, which is what makes each one an equivalence check.
@@ -63,7 +71,7 @@ export class Mixer {
63
71
  hostLog = [];
64
72
  hostThen;
65
73
  /** Each subject's last two poses and when they were probed, for `from: 'current'`. */
66
- pose = new Store();
74
+ lastPoses = new Store();
67
75
  /** The clock this mix plays on, which also holds its tape and the marks announced on it. */
68
76
  transport;
69
77
  /** Its place among the mixes on its transport, in the order they joined. */
@@ -82,7 +90,7 @@ export class Mixer {
82
90
  /** The pose `pull` folds a subject into where it cannot read straight from the lanes. */
83
91
  scratch;
84
92
  /** Live voices by the earliest mix time `moveTo` would change each, a binary heap. */
85
- due = [];
93
+ due = new DueQueue();
86
94
  /** Voices retired since `moveTo` last pruned them, in the order they retired. */
87
95
  retired = [];
88
96
  /** Visit every voice each sync, as before the due queue; for tests that compare the two. */
@@ -151,6 +159,8 @@ export class Mixer {
151
159
  */
152
160
  chains = new Store();
153
161
  version = 0;
162
+ /** The positions `plot` gave, which a voice's `within` reads: see `bound.ts`. */
163
+ places = null;
154
164
  /** What `prime` reads its clock, voice time and pass from: see `Primed`. */
155
165
  primed = new Float64Array(3 /* Primed.SIZE */);
156
166
  steps = new Steps();
@@ -182,8 +192,10 @@ export class Mixer {
182
192
  unshared = false;
183
193
  /** 0 where a fold is a walk of the chain: see `detour` in `everyone.ts`. */
184
194
  detours = 0;
185
- /** How many voices in the list carry a locus; with none, a fold allocates nothing. */
186
- loci = 0;
195
+ /** How many voices in the list carry a role; with none, a fold allocates nothing. */
196
+ roleVoices = 0;
197
+ /** The tags the mix's options declare roles. */
198
+ roleTags;
187
199
  /** What every motion patch it plays asks for its voice's time, holding this mix weakly. */
188
200
  owner = null;
189
201
  /** How many voices in the list are anchored, so a sync with none skips placing them. */
@@ -218,9 +230,9 @@ export class Mixer {
218
230
  lerpsInto;
219
231
  /** By slot, a stock scalar channel's arithmetic, which a fold applies without calling the channel. */
220
232
  scalars;
221
- /** What a fold with a locus in play gathers into, one per depth of folds under way. */
222
- locusScratch = [];
223
- locusDepth = 0;
233
+ /** What a fold with a role in play gathers into, one per depth of folds under way. */
234
+ roleScratch = [];
235
+ roleDepth = 0;
224
236
  slotOf = new Map();
225
237
  lanes;
226
238
  handles = null;
@@ -236,6 +248,7 @@ export class Mixer {
236
248
  keys = null;
237
249
  /** With a history store, the voices it paged out whose handles are still held; null without one. */
238
250
  outs = null;
251
+ pose;
239
252
  constructor(kit, opts,
240
253
  /** The lanes an entry was built with; null in one that leaves them out. */
241
254
  lanesOf) {
@@ -246,6 +259,7 @@ export class Mixer {
246
259
  this.channels = this.names.map((k) => kit[k]);
247
260
  this.bounded = this.channels.flatMap((c, i) => (c.bounds ? [i] : []));
248
261
  this.lerpsInto = this.channels.map((c) => lerpInto(c));
262
+ this.pose = blanks(this.names, this.channels);
249
263
  this.scalars = this.channels.map((c) => scalarOp(c));
250
264
  this.names.forEach((k, i) => {
251
265
  this.slotOf.set(k, i);
@@ -255,6 +269,7 @@ export class Mixer {
255
269
  this.lanes =
256
270
  lanesOf !== null && (opts.lanes ?? defaults.lanes) ? new lanesOf(laneHost(this)) : null;
257
271
  this.name = opts.name;
272
+ this.roleTags = roleTags(opts);
258
273
  const shared = opts.transport;
259
274
  if (shared !== undefined) {
260
275
  if (opts.history !== undefined)
@@ -345,14 +360,14 @@ export class Mixer {
345
360
  return cue(this, spec);
346
361
  }
347
362
  blend(members, by, spec = {}) {
348
- const locus = `blend:${this.nextId}`;
363
+ const role = `blend:${this.nextId}`;
349
364
  const of = blendOf(by, members.length - 1);
350
365
  return members.map((member, i) => {
351
366
  const own = {
352
367
  ...spec,
353
368
  patch: member,
354
369
  weight: by,
355
- locus,
370
+ role,
356
371
  };
357
372
  if ('patch' in member) {
358
373
  own.patch = member.patch;
@@ -421,6 +436,9 @@ export class Mixer {
421
436
  project(at) {
422
437
  return projectAll(this.transport, at).of(this);
423
438
  }
439
+ trace(from, to, opts) {
440
+ return traceMix(this, from, to, opts);
441
+ }
424
442
  announce(name, opts = {}) {
425
443
  this.attached();
426
444
  announce(this.transport, name, opts, this.slot, this.name);
@@ -433,38 +451,6 @@ export class Mixer {
433
451
  marks(from, to) {
434
452
  return listed(this, from, to).map(({ order: _, ...m }) => m);
435
453
  }
436
- atRest(subject) {
437
- this.restStamps ??= new Store();
438
- const head = this.linked(subject);
439
- const laned = this.linkedLaned;
440
- const lanes = this.lanes;
441
- // Every voice on lanes and nothing to clamp: the pose a fold would make is the lanes' values.
442
- if (laned && this.restsLaned()) {
443
- const slot = head.slot;
444
- if (!lanes.owes(slot))
445
- return lanes.rests(slot);
446
- }
447
- // A probe this frame answers for the pose it gave the host.
448
- const since = (this.restStamps.get(subject) ?? Number.NaN) - 2 * this.restsKey();
449
- if (since >= 0)
450
- return since === 1;
451
- this.restScratch ??= {};
452
- const pose = this.foldWith(subject, this.restScratch, head, laned, true);
453
- return this.rests((this.bounded.length === 0 ? pose : this.clamp(pose)));
454
- }
455
- rests(pose) {
456
- for (let i = 0; i < this.names.length; i++) {
457
- const channel = this.channels[i];
458
- const value = pose[this.names[i]];
459
- if (channel.rest === undefined) {
460
- if (value !== undefined)
461
- return false;
462
- }
463
- else if (!near(value, channel.rest))
464
- return false;
465
- }
466
- return true;
467
- }
468
454
  rebase() {
469
455
  this.own('rebase').rebase();
470
456
  }
@@ -486,16 +472,6 @@ export class Mixer {
486
472
  this.asleep = true;
487
473
  return inert;
488
474
  }
489
- restful() {
490
- // Standing still, a voice changes no pose but by its weight signal, or by an anchor or start
491
- // that host time may yet reach.
492
- if (this.pace?.stopped(this.u))
493
- return this.cued.every((v) => v.state === 'done' ||
494
- (v.state !== 'pending' &&
495
- typeof v.spec.weight !== 'function' &&
496
- v.spec.anchor === undefined));
497
- return this.cued.every((v) => this.still(v, this.now));
498
- }
499
475
  get rate() {
500
476
  return this.transport.rate;
501
477
  }
@@ -505,22 +481,6 @@ export class Mixer {
505
481
  ramp(rate, over) {
506
482
  this.own('ramp').ramp(rate, over);
507
483
  }
508
- /** Whether a voice will change no pose from `now` on, short of a change made to it. */
509
- still(voice, now) {
510
- if (voice.state === 'done')
511
- return true;
512
- if (voice.state !== 'frozen' && voice.state !== 'live')
513
- return false;
514
- if (typeof voice.spec.weight === 'function' || voice.parts !== null)
515
- return false;
516
- const fadeIn = this.reducedNow ? 0 : (voice.fade.in ?? 0);
517
- if (fadeIn > 0 && now - this.sinceOf(voice, voice.latest) < fadeIn)
518
- return false;
519
- if (voice.state === 'frozen' || voice.holding !== null)
520
- return true;
521
- const motion = voice.motion;
522
- return motion !== undefined && voice.elapsedAt(now) >= voice.latest && motion.landed();
523
- }
524
484
  close() {
525
485
  unregister(this);
526
486
  }
@@ -531,80 +491,6 @@ export class Mixer {
531
491
  }
532
492
  record(this, 'mute', (stage) => stage.mix(this)?.mute(opts));
533
493
  }
534
- drop(subject) {
535
- this.frame = nextFrame();
536
- const head = headOf(this, subject);
537
- // Nothing that kept this head, such as `pull`'s remembered list, may take it as current again.
538
- if (head !== undefined)
539
- unlink(this, head);
540
- // A number a seek carried over is released even where its chain is not linked since.
541
- const slot = this.chains.get(subject)?.slot ?? -1;
542
- if (slot >= 0 && this.lanes !== null)
543
- this.lanes.release(slot);
544
- // A read ahead's copy reads the live mix's poses, and so hides one where the mix deletes it.
545
- if (this.projecting && !(this.pose instanceof Masked))
546
- this.pose = new Masked(this.pose);
547
- this.pose.delete(subject);
548
- this.restStamps?.delete(subject);
549
- this.chains.delete(subject);
550
- this.stir();
551
- // A record of it is only in a voice over every subject, one naming it, or one gone; a ramp out
552
- // of a voice that does not name it is in `parters`, and a patch's own state in `motions`.
553
- const kept = releasing(this);
554
- for (const voice of this.general)
555
- this.forgetIn(voice, subject, kept);
556
- const named = this.named.get(subject);
557
- if (named !== undefined)
558
- for (const voice of [...named])
559
- this.forgetIn(voice, subject, kept);
560
- const gone = goneIndex(this);
561
- for (const voice of gone.general)
562
- this.forgetIn(voice, subject, kept);
563
- const goneNaming = gone.named.get(subject);
564
- if (goneNaming !== undefined)
565
- for (const voice of goneNaming)
566
- this.forgetIn(voice, subject, kept);
567
- if (this.owners !== null)
568
- for (const voice of this.owners)
569
- this.forgetIn(voice, subject, kept);
570
- for (const voice of this.parters) {
571
- this.forgetIn(voice, subject, kept);
572
- if (voice.parts?.size === 0)
573
- voice.parts = null;
574
- if (voice.parted?.size === 0)
575
- voice.parted = null;
576
- if (voice.parts === null && voice.parted === null)
577
- this.parters.delete(voice);
578
- }
579
- const strays = this.strays.get(subject);
580
- if (strays !== undefined)
581
- for (const motion of strays)
582
- this.releaseIn(motion, subject, kept);
583
- this.strays.delete(subject);
584
- for (const motion of this.strayAll)
585
- this.releaseIn(motion, subject, kept);
586
- this.recordDrop(subject);
587
- }
588
- /** Lets `voice` go of a dropped subject. Methods rather than closures, so a drop makes neither. */
589
- forgetIn(voice, subject, kept) {
590
- leaveVoice(this, voice, subject);
591
- voice.subjects.delete(subject);
592
- if (voice.motion !== undefined)
593
- this.releaseIn(voice.motion, subject, kept);
594
- voice.parts?.delete(subject);
595
- voice.parted?.delete(subject);
596
- voice.blend?.of.reads.delete(subject);
597
- }
598
- // A motion patch's state is the live mix's alone: a read ahead cannot start a subject afresh in it.
599
- releaseIn(motion, subject, kept) {
600
- if (!this.projecting)
601
- motion.release(subject, kept[0], kept[1], kept[2]);
602
- else if (motion.holds(subject))
603
- unplayable('a drop of a subject a motion patch holds');
604
- }
605
- recordDrop(subject) {
606
- record(this, 'drop', (stage) => stage.mix(this)?.drop(subject));
607
- }
608
494
  touch(subject) {
609
495
  this.attached();
610
496
  touch(this, subject);
@@ -641,22 +527,6 @@ export class Mixer {
641
527
  restsStirs = -1;
642
528
  /** The pose `atRest` folds into where no probe this frame answers. */
643
529
  restScratch;
644
- restsLaned() {
645
- return this.lanes.whole && this.bounded.length === 0;
646
- }
647
- restsKey() {
648
- if (this.frame !== this.restsFrame ||
649
- this.version !== this.restsVersion ||
650
- this.relinks !== this.restsRelinks ||
651
- this.stirs !== this.restsStirs) {
652
- this.restsEpoch++;
653
- this.restsFrame = this.frame;
654
- this.restsVersion = this.version;
655
- this.restsRelinks = this.relinks;
656
- this.restsStirs = this.stirs;
657
- }
658
- return this.restsEpoch;
659
- }
660
530
  }
661
531
  // The probe's hot path is methods, installed here from the modules that own them: as functions
662
532
  // called directly they made probes and fills up to 9% slower (bench/ab.sh on teitou, 2026-10-05).
@@ -666,7 +536,7 @@ const methods = {
666
536
  linked,
667
537
  foldWith,
668
538
  owedBy,
669
- foldLoci,
539
+ foldRoles,
670
540
  foldDetour,
671
541
  foldSharers,
672
542
  shared,
@@ -681,6 +551,7 @@ const methods = {
681
551
  baseFor,
682
552
  slopeFor,
683
553
  prime,
554
+ primeLaned,
684
555
  capped,
685
556
  horizonFor,
686
557
  weigh,
@@ -702,7 +573,22 @@ const methods = {
702
573
  sinceOf,
703
574
  shownOf,
704
575
  keep,
705
- foldLocus,
576
+ foldRole,
577
+ atRest,
578
+ rests,
579
+ restful,
580
+ still,
581
+ restsLaned,
582
+ restsKey,
583
+ plot,
584
+ place,
585
+ moved,
586
+ put,
587
+ around,
588
+ drop,
589
+ forgetIn,
590
+ releaseIn,
591
+ recordDrop,
706
592
  };
707
593
  for (const [name, value] of Object.entries(methods))
708
594
  Object.defineProperty(Mixer.prototype, name, { value, writable: true, configurable: true });
@@ -724,7 +610,7 @@ export function made(kit, opts, lanes) {
724
610
  return m;
725
611
  }
726
612
  /**
727
- * The one that ships: every form on the CPU, probed per subject on demand.
613
+ * The engine that ships, running every form on the CPU. It probes per subject on demand.
728
614
  *
729
615
  * @category engine
730
616
  */
@@ -736,7 +622,7 @@ export const mixer = {
736
622
  },
737
623
  };
738
624
  /**
739
- * Makes a mix over a kit, on the engine the options name or the stock one.
625
+ * Makes a mix over a kit. It runs on the engine the options name, or the stock one.
740
626
  *
741
627
  * @category mix
742
628
  */
package/dist/motion.d.ts CHANGED
@@ -1,69 +1,11 @@
1
1
  import { Motions } from './motions.js';
2
- import type { Easing, Patch } from './types.js';
3
- /**
4
- * A number, or one number per axis.
5
- *
6
- * @category patch
7
- */
8
- export type Value = number | readonly number[];
9
- /**
10
- * One value for every subject, or one per subject.
11
- *
12
- * @category patch
13
- */
14
- export type PerSubject<I, V> = V | ((subject: I) => V);
15
- /**
16
- * Where a subject is and how fast it is moving, at one moment of voice time.
17
- *
18
- * @category patch
19
- */
20
- export interface Motion<V extends Value = number> {
21
- value: V;
22
- /** Units per second. */
23
- velocity: V;
24
- }
25
- /**
26
- * A patch whose motion is a closed form per subject, so it lands in the same place at any frame
27
- * rate, and whose changes mid-flight start from where the subject is and how fast it moves.
28
- *
29
- * @category patch
30
- */
31
- export type Moving<I, O, V extends Value> = Patch<I, O, void> & {
32
- /**
33
- * Where `subject` is and how fast it moves at voice time `at`, default the mix's latest frame,
34
- * counting every change due by then. With no time given, undefined until a frame of its voice
35
- * has met the subject, while the subject is still inside its stagger, and once that voice is gone.
36
- * At any time, undefined for a subject the patch has never met or changed, or that the mix
37
- * dropped.
38
- */
39
- read(subject: I, at?: number): Motion<V> | undefined;
40
- /**
41
- * Sets `subject` moving at `velocity`, units per second, from voice time `at`, default the mix's
42
- * latest frame; for a subject no frame of its voice has met yet, or still inside its stagger, its
43
- * first read, after any change with a time due by then. A subject faded out of the voice comes
44
- * back at the latest frame too, however long it is before it is next read.
45
- */
46
- push(subject: I, velocity: V, at?: number): void;
47
- /**
48
- * Puts `subject` at `value` from voice time `at`, keeping its velocity, with `at` defaulting as
49
- * `push`'s does: a spring or tween heads for its target from there, a glide coasts on from there.
50
- */
51
- set(subject: I, value: V, at?: number): void;
52
- };
2
+ import type { Glide, GlideOptions, Spring, SpringOptions, Tween, TweenOptions, Value } from './types/motion.js';
3
+ export type { Glide, GlideOptions, Motion, MotionOptions, Moving, PerSubject, Spring, SpringOptions, Tween, TweenOptions, Value, } from './types/motion.js';
53
4
  /**
54
5
  * The state behind a `'motion'` patch, for an engine that reads it directly; a patch another copy
55
6
  * of the same release made counts.
56
7
  */
57
8
  export declare function motionOf<I>(p: object): Motions<I> | undefined;
58
- interface Common<I, V extends Value> {
59
- /** Units per second each subject starts moving at. Default 0. */
60
- velocity?: PerSubject<I, V>;
61
- /**
62
- * Within this distance of where it heads, and this slow, a subject lands there exactly, so a mix
63
- * can see it at rest. Default 1e-4; 0 keeps the curve exact.
64
- */
65
- settle?: number;
66
- }
67
9
  /**
68
10
  * A damped spring toward a target, solved in closed form. `to(subject, target)` retargets one
69
11
  * subject mid-flight, and the new stretch starts from where it is and how fast it moves, so there is
@@ -72,28 +14,12 @@ interface Common<I, V extends Value> {
72
14
  *
73
15
  * @category patch
74
16
  */
75
- export declare function spring<I, O, V extends Value = number>(writes: keyof O, opts: Common<I, V> & {
76
- to: PerSubject<I, V>;
77
- /** Where each subject starts. Default: `to`, at rest. */
78
- from?: PerSubject<I, V>;
79
- /** Per second squared. Default 170. */
80
- stiffness?: number;
81
- /** Per second. Default 26, just short of critical at the default stiffness. */
82
- damping?: number;
83
- mass?: number;
84
- }): Moving<I, O, V> & {
85
- /**
86
- * Heads `subject` for `target` from voice time `at`, keeping its velocity: by default the mix's
87
- * latest frame, or for a subject no frame of its voice has met yet, or still inside its stagger,
88
- * its first read, after any change with a time due by then. A subject faded out of the voice
89
- * comes back at the latest frame too, however long it is before it is next read.
90
- */
91
- to(subject: I, target: V, at?: number): void;
92
- };
17
+ export declare function spring<I, O, V extends Value = number>(writes: keyof O, opts: SpringOptions<I, V>): Spring<I, O, V>;
93
18
  /**
94
- * Coasting under friction, a fling: a subject keeps its velocity, which falls by a factor of e every
95
- * `ms`, and comes to rest `velocity · ms / 1000` past where it was released. `push` sets it moving
96
- * again from wherever it is. Closed form, so frame rate does not change where it stops.
19
+ * A patch that coasts each subject to a stop under friction, as after a fling. A subject keeps its
20
+ * velocity, which falls by a factor of e every `ms`, and comes to rest `velocity · ms / 1000` past
21
+ * where it was released. `push` sets it moving again from wherever it is. Closed form, so frame
22
+ * rate does not change where it stops.
97
23
  *
98
24
  * With `bounds`, a coast that would stop past an edge springs back to it from where it crosses, at
99
25
  * the speed it crosses at, on a critically damped spring of `stiffness`; one let go past an edge
@@ -102,44 +28,14 @@ export declare function spring<I, O, V extends Value = number>(writes: keyof O,
102
28
  *
103
29
  * @category patch
104
30
  */
105
- export declare function glide<I, O, V extends Value = number>(writes: keyof O, opts: Common<I, V> & {
106
- /** Where each subject starts. */
107
- from: PerSubject<I, V>;
108
- /** The friction's time constant, ms. Default 325, a touch scroll's. */
109
- ms?: number;
110
- /** The least and greatest value a coast comes to rest at, per axis. Default none. */
111
- bounds?: PerSubject<I, readonly [min: V, max: V]>;
112
- /** The spring back's, per second squared, with `bounds`. Default 170, as a spring's. */
113
- stiffness?: number;
114
- }): Moving<I, O, V> & {
115
- /** `subject`'s bounds, undefined for a glide without them. */
116
- bounds(subject: I): readonly [min: V, max: V] | undefined;
117
- };
31
+ export declare function glide<I, O, V extends Value = number>(writes: keyof O, opts: GlideOptions<I, V>): Glide<I, O, V>;
118
32
  /**
119
- * From one value to another over `ms` along an easing, solved in closed form, so frame rate does not
120
- * change where a subject is. `to(subject, target)` retargets one subject mid-flight: the new stretch
121
- * starts from where it is and takes the full `ms` again, so its velocity jumps where a spring's
122
- * would not. Time is the voice's, so `rate` and `seek` apply, and a voice's `loop` does not repeat
123
- * it. Cue each tween on one voice. Every subject moves on the same number of axes.
33
+ * From one value to another over `ms` along an easing. Solved in closed form, so frame rate does
34
+ * not change where a subject is. `to(subject, target)` retargets one subject mid-flight: the new
35
+ * stretch starts from where it is and takes the full `ms` again, so its velocity jumps where a
36
+ * spring's would not. Time is the voice's, so `rate` and `seek` apply, and a voice's `loop` does
37
+ * not repeat it. Cue each tween on one voice. Every subject moves on the same number of axes.
124
38
  *
125
39
  * @category patch
126
40
  */
127
- export declare function tween<I, O, V extends Value = number>(writes: keyof O, opts: {
128
- /** Where each subject starts. */
129
- from: PerSubject<I, V>;
130
- /** Where each subject ends. */
131
- to: PerSubject<I, V>;
132
- /** How long a stretch takes, ms: asked per subject each time one of its stretches starts. */
133
- ms: PerSubject<I, number>;
134
- /** The curve from `from` to `to`. Default `'ease'`, as CSS's. */
135
- ease?: Easing;
136
- }): Omit<Moving<I, O, V>, 'push'> & {
137
- /**
138
- * Heads `subject` for `target` from voice time `at`, over a full `ms` from where it is: by default
139
- * the mix's latest frame, or for a subject no frame of its voice has met yet, or still inside its
140
- * stagger, its first read, after any change with a time due by then. A subject faded out of the
141
- * voice comes back at the latest frame too, however long it is before it is next read.
142
- */
143
- to(subject: I, target: V, at?: number): void;
144
- };
145
- export {};
41
+ export declare function tween<I, O, V extends Value = number>(writes: keyof O, opts: TweenOptions<I, V>): Tween<I, O, V>;
package/dist/motion.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { BOUND, COAST, CRITICAL, EASED, OVER, UNDER } from './closed.js';
2
2
  import { curve } from './easing.js';
3
3
  import { Motions } from './motions.js';
4
+ import { reading } from './reading.js';
4
5
  import { release } from './shared.js';
6
+ import { sampleThen } from './then.js';
5
7
  const axes = (v) => (typeof v === 'number' ? [v] : [...v]);
6
8
  /** Zero on every axis `x` has: a motion's velocity where its options give none. */
7
9
  const still = (x) => new Array(x.length).fill(0);
@@ -40,6 +42,11 @@ class MotionPatch {
40
42
  }
41
43
  at(_phase, subject, setting) {
42
44
  const state = this.motions;
45
+ const back = reading.back;
46
+ if (back !== null) {
47
+ const flags = sampleThen(state, subject, setting.elapsed, back);
48
+ return { [this.writes[0]]: state.valueOf(flags, state.xs) };
49
+ }
43
50
  const s = state.slot(subject);
44
51
  state.sample(s, setting.elapsed, state.xs, state.vs);
45
52
  return { [this.writes[0]]: state.value(s, state.xs) };
@@ -252,9 +259,10 @@ export function spring(writes, opts) {
252
259
  return new SpringPatch(writes, new Motions(new SpringShape(law, motion, opts)));
253
260
  }
254
261
  /**
255
- * Coasting under friction, a fling: a subject keeps its velocity, which falls by a factor of e every
256
- * `ms`, and comes to rest `velocity · ms / 1000` past where it was released. `push` sets it moving
257
- * again from wherever it is. Closed form, so frame rate does not change where it stops.
262
+ * A patch that coasts each subject to a stop under friction, as after a fling. A subject keeps its
263
+ * velocity, which falls by a factor of e every `ms`, and comes to rest `velocity · ms / 1000` past
264
+ * where it was released. `push` sets it moving again from wherever it is. Closed form, so frame
265
+ * rate does not change where it stops.
258
266
  *
259
267
  * With `bounds`, a coast that would stop past an edge springs back to it from where it crosses, at
260
268
  * the speed it crosses at, on a critically damped spring of `stiffness`; one let go past an edge
@@ -277,11 +285,11 @@ export function glide(writes, opts) {
277
285
  return new GlidePatch(writes, new Motions(shape), opts.bounds);
278
286
  }
279
287
  /**
280
- * From one value to another over `ms` along an easing, solved in closed form, so frame rate does not
281
- * change where a subject is. `to(subject, target)` retargets one subject mid-flight: the new stretch
282
- * starts from where it is and takes the full `ms` again, so its velocity jumps where a spring's
283
- * would not. Time is the voice's, so `rate` and `seek` apply, and a voice's `loop` does not repeat
284
- * it. Cue each tween on one voice. Every subject moves on the same number of axes.
288
+ * From one value to another over `ms` along an easing. Solved in closed form, so frame rate does
289
+ * not change where a subject is. `to(subject, target)` retargets one subject mid-flight: the new
290
+ * stretch starts from where it is and takes the full `ms` again, so its velocity jumps where a
291
+ * spring's would not. Time is the voice's, so `rate` and `seek` apply, and a voice's `loop` does
292
+ * not repeat it. Cue each tween on one voice. Every subject moves on the same number of axes.
285
293
  *
286
294
  * @category patch
287
295
  */