@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
@@ -65,11 +65,22 @@ export declare function unsight<I, O>(voice: Voice<I, O>, slot: number): void;
65
65
  * positions take their own records now, since each held the one.
66
66
  */
67
67
  export declare function unshare<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, kept?: boolean, keeper?: I): void;
68
- /**
69
- * Counts what makes a mix's fold more than a walk of each chain: a role, a voice sharing one
70
- * record, a voice that stopped sharing this frame. Called wherever one of the three changes, so
71
- * `foldWith` asks one number.
72
- */
73
- export declare function detour<I, O>(mix: Mixer<I, O>): void;
74
68
  /** Gives a record just made for a subject a sharing voice had seen what the voice kept for it. */
75
69
  export declare function inherit<I, O>(voice: Voice<I, O>, held: Subject<unknown>, slot: number): void;
70
+ /** What the general path calls on a sharing voice, reached through `Lanes`, so a lean bundle leaves it out. */
71
+ export declare const share: {
72
+ shares: typeof shares;
73
+ outgrow: typeof outgrow;
74
+ everyoneOf: typeof everyoneOf;
75
+ sighted: typeof sighted;
76
+ sight: typeof sight;
77
+ weightAt: typeof weightAt;
78
+ weighAt: typeof weighAt;
79
+ open: typeof open;
80
+ shut: typeof shut;
81
+ restamp: typeof restamp;
82
+ unsight: typeof unsight;
83
+ unshare: typeof unshare;
84
+ inherit: typeof inherit;
85
+ };
86
+ export type Share = typeof share;
package/dist/everyone.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { headOf } from './chain.js';
2
2
  import { numericOf } from './channels.js';
3
3
  import { record } from './record.js';
4
+ import { detour } from './roster.js';
4
5
  /**
5
6
  * Whether a voice can keep one record for every subject, decided when it is cued. A record holds
6
7
  * what a voice knows of one subject; where the voice is over every subject with no stagger, keeps
@@ -263,17 +264,25 @@ export function unshare(mix, voice, kept = false, keeper) {
263
264
  detour(mix);
264
265
  mix.lanes?.refill();
265
266
  }
266
- /**
267
- * Counts what makes a mix's fold more than a walk of each chain: a role, a voice sharing one
268
- * record, a voice that stopped sharing this frame. Called wherever one of the three changes, so
269
- * `foldWith` asks one number.
270
- */
271
- export function detour(mix) {
272
- mix.detours = mix.roleVoices + mix.sharers.length + (mix.unshared ? 1 : 0);
273
- }
274
267
  /** Gives a record just made for a subject a sharing voice had seen what the voice kept for it. */
275
268
  export function inherit(voice, held, slot) {
276
269
  held.weight = weightAt(voice, slot);
277
270
  open(voice, held, slot);
278
271
  unsight(voice, slot);
279
272
  }
273
+ /** What the general path calls on a sharing voice, reached through `Lanes`, so a lean bundle leaves it out. */
274
+ export const share = {
275
+ shares,
276
+ outgrow,
277
+ everyoneOf,
278
+ sighted,
279
+ sight,
280
+ weightAt,
281
+ weighAt,
282
+ open,
283
+ shut,
284
+ restamp,
285
+ unsight,
286
+ unshare,
287
+ inherit,
288
+ };
package/dist/fade.d.ts CHANGED
@@ -3,7 +3,11 @@ import type { Left, Parted, Parting } from './subject.js';
3
3
  import type { Voice } from './voice.js';
4
4
  /** Starts one subject's ramp out of a voice, or takes it out at once for a ramp of 0. */
5
5
  export declare function fadeSubject<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, subject: I, over?: number): void;
6
- /** What a subject's own ramp out of a voice leaves of its weight this frame, 0..1. */
6
+ /**
7
+ * What a subject's own ramp out of a voice leaves of its weight this frame. It also holds the
8
+ * voice's fade in where the ramp began, as a fade out of the whole voice does, so it is a ratio to
9
+ * the fade in the voice's envelope gives.
10
+ */
7
11
  export declare function parting<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, subject: I, now: number): number;
8
12
  /**
9
13
  * Takes a subject out of one voice for good, as of mix time `at`: the voice forgets its record,
@@ -30,7 +34,19 @@ export declare function beginFade<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, op
30
34
  at?: 'rest';
31
35
  deadline?: number;
32
36
  back?: boolean;
37
+ stays?: boolean;
33
38
  }, at?: number): void;
39
+ /**
40
+ * Keeps a voice faded at the end of its passes with its owner, live and quiet, rather than letting
41
+ * it leave; the owner, where it holds no child still playing, leaves at `at`, or stays quiet in
42
+ * turn where an owner holds it.
43
+ */
44
+ export declare function stay<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, at: number): void;
45
+ /**
46
+ * Lets an owner holding no child still playing leave at `at`; under history one an owner holds
47
+ * stays with it instead, quiet while its children are.
48
+ */
49
+ export declare function release<I, O>(mix: Mixer<I, O>, owner: Voice<I, O>, at: number): void;
34
50
  /**
35
51
  * Turns a fading voice around: it climbs back from where its fade out had got to, up the same
36
52
  * curve, at the whole curve per `over` ms. A fade waiting for rest is taken back at once. A voice
@@ -44,5 +60,10 @@ export declare function beginRise<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, op
44
60
  * exactly there; at or behind now, the fade began then and is partway.
45
61
  */
46
62
  export declare function fadeAt<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, at: number, over: number | undefined): void;
63
+ /**
64
+ * Brings back a voice kept quiet past its passes, its clock back in them by mix time `now`: it
65
+ * climbs from nothing over the fade it left over, from when its clock reached them.
66
+ */
67
+ export declare function rouse<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, now: number): void;
47
68
  /** Removes a voice, recording that it left at `at`, default now. */
48
69
  export declare function retire<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>, at?: number): void;
package/dist/fade.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { changed, headOf, unlink } from './chain.js';
2
- import { envelope } from './clock.js';
2
+ import { envelope, fadeInAt } from './clock.js';
3
3
  import { schedule } from './due.js';
4
- import { unshare, unsight } from './everyone.js';
5
4
  import { leave, noted, releasing } from './history.js';
6
5
  import { markOf } from './marks.js';
7
- import { orphan, ownerReading } from './owner.js';
6
+ import { startedAt } from './origin.js';
7
+ import { orphan, ownerReading, plays } from './owner.js';
8
8
  import { startOf } from './place.js';
9
9
  import { isRested } from './record.js';
10
10
  import { scoreTouched } from './scored.js';
@@ -14,7 +14,7 @@ import { unreach } from './unreached.js';
14
14
  export function fadeSubject(mix, voice, subject, over) {
15
15
  if (voice.state === 'done' || voice.parted?.has(subject) || voice.parts?.has(subject))
16
16
  return;
17
- unshare(mix, voice);
17
+ mix.share?.unshare(mix, voice);
18
18
  mix.stir();
19
19
  const ms = mix.reduced ? 0 : (over ?? voice.fade.out ?? 0);
20
20
  const at = Number.isNaN(mix.now) ? startOf(voice) : mix.now;
@@ -28,12 +28,26 @@ export function fadeSubject(mix, voice, subject, over) {
28
28
  schedule(mix, voice);
29
29
  mix.lanes?.refill();
30
30
  }
31
- /** What a subject's own ramp out of a voice leaves of its weight this frame, 0..1. */
31
+ /**
32
+ * What a subject's own ramp out of a voice leaves of its weight this frame. It also holds the
33
+ * voice's fade in where the ramp began, as a fade out of the whole voice does, so it is a ratio to
34
+ * the fade in the voice's envelope gives.
35
+ */
32
36
  export function parting(voice, subject, now) {
33
37
  const r = voice.parts?.get(subject);
34
38
  if (r === undefined)
35
39
  return 1;
36
- return envelope(0, { at: r.at, over: r.over, rest: false }, voice.ease, this.reducedNow, now, 0);
40
+ const reduced = this.reducedNow;
41
+ const w = envelope(0, { at: r.at, over: r.over, rest: false }, voice.ease, reduced, now, 0);
42
+ const fadeIn = voice.fade.in ?? 0;
43
+ const since = voice.subjects.get(subject)?.shown;
44
+ if (!(fadeIn > 0) || reduced || since === undefined || !(r.at < now))
45
+ return w;
46
+ const out = voice.out;
47
+ const climbing = fadeInAt(fadeIn, out, voice.ease, now, since, voice.stalls);
48
+ if (!(climbing > 0))
49
+ return w;
50
+ return w * (fadeInAt(fadeIn, out, voice.ease, now, since, voice.stalls, r.at) / climbing);
37
51
  }
38
52
  /**
39
53
  * Takes a subject out of one voice for good, as of mix time `at`: the voice forgets its record,
@@ -104,7 +118,7 @@ function forgetIn(mix, voice, subject) {
104
118
  if (head !== undefined && head.slot >= 0) {
105
119
  unreach(voice, head.slot, false);
106
120
  // What the voice kept for the subject while it shared one record, which `weightOf` reads.
107
- unsight(voice, head.slot);
121
+ mix.share?.unsight(voice, head.slot);
108
122
  }
109
123
  const held = voice.subjects.get(subject);
110
124
  if (held !== undefined) {
@@ -135,7 +149,7 @@ export function beginFade(mix, voice, opts, at) {
135
149
  // A fade at rest marks each subject's record as it comes to rest, at its next read: one already
136
150
  // read this frame is read again, as after `touch`.
137
151
  if (opts.at === 'rest') {
138
- unshare(mix, voice);
152
+ mix.share?.unshare(mix, voice);
139
153
  voice.seeks++;
140
154
  }
141
155
  voice.state = 'fading';
@@ -148,14 +162,49 @@ export function beginFade(mix, voice, opts, at) {
148
162
  };
149
163
  if (opts.back)
150
164
  voice.out.back = true;
165
+ if (opts.stays)
166
+ voice.out.stays = true;
151
167
  noted(mix, voice);
152
168
  // A fade at rest, from a voice's start, or of a voice yet to start takes it off its lane.
153
169
  if (opts.at === 'rest' || opts.back || waiting)
154
170
  mix.lanes?.invalidate();
155
171
  else
156
172
  mix.lanes?.refill();
157
- if (over === 0 && opts.at !== 'rest')
158
- retire(mix, voice, voice.out.at);
173
+ if (over === 0 && opts.at !== 'rest') {
174
+ if (opts.stays)
175
+ stay(mix, voice, voice.out.at);
176
+ else
177
+ retire(mix, voice, voice.out.at);
178
+ }
179
+ }
180
+ /**
181
+ * Keeps a voice faded at the end of its passes with its owner, live and quiet, rather than letting
182
+ * it leave; the owner, where it holds no child still playing, leaves at `at`, or stays quiet in
183
+ * turn where an owner holds it.
184
+ */
185
+ export function stay(mix, voice, at) {
186
+ voice.state = 'live';
187
+ mix.lanes?.refill();
188
+ const owner = voice.owner;
189
+ if (owner.state === 'done')
190
+ return;
191
+ const now = Number.isNaN(mix.now) ? Number.NEGATIVE_INFINITY : mix.now;
192
+ if (plays(owner, now) || lingers(owner, at)) {
193
+ if (owner.fitting !== null && !voice.skipped)
194
+ refit(mix, owner, at);
195
+ }
196
+ else
197
+ release(mix, owner, at);
198
+ }
199
+ /**
200
+ * Lets an owner holding no child still playing leave at `at`; under history one an owner holds
201
+ * stays with it instead, quiet while its children are.
202
+ */
203
+ export function release(mix, owner, at) {
204
+ if (owner.owner !== null && owner.state === 'live' && mix.opts.history !== undefined)
205
+ beginFade(mix, owner, { over: 0, stays: true }, at);
206
+ else
207
+ retire(mix, owner, at);
159
208
  }
160
209
  /**
161
210
  * Turns a fading voice around: it climbs back from where its fade out had got to, up the same
@@ -184,7 +233,12 @@ export function beginRise(mix, voice, opts) {
184
233
  voice.out = null;
185
234
  voice.outAt = Number.POSITIVE_INFINITY;
186
235
  voice.state = waiting ? 'pending' : 'live';
236
+ if (waiting)
237
+ voice.begunSeq = Number.POSITIVE_INFINITY;
187
238
  voice.back = over > 0 && from < 1 ? { from, at, over } : null;
239
+ // Its fade in stood still from where the fade out began, and climbs on from where it stood.
240
+ if (!out.rest && out.at < at)
241
+ voice.stalls = [...(voice.stalls ?? []), out.at, at];
188
242
  if (waiting)
189
243
  changed(mix, voice);
190
244
  noted(mix, voice);
@@ -202,7 +256,7 @@ export function fadeAt(mix, voice, at, over) {
202
256
  return;
203
257
  const now = Number.isNaN(mix.now) ? startOf(voice) : mix.now;
204
258
  if (at <= now) {
205
- beginFade(mix, voice, { over }, Math.max(startOf(voice), at));
259
+ beginFade(mix, voice, { over }, Math.max(startedAt(voice), at));
206
260
  mix.stir();
207
261
  return;
208
262
  }
@@ -211,6 +265,21 @@ export function fadeAt(mix, voice, at, over) {
211
265
  voice.outSet = true;
212
266
  noted(mix, voice);
213
267
  }
268
+ /**
269
+ * Brings back a voice kept quiet past its passes, its clock back in them by mix time `now`: it
270
+ * climbs from nothing over the fade it left over, from when its clock reached them.
271
+ */
272
+ export function rouse(mix, voice, now) {
273
+ const out = voice.out;
274
+ const edge = out.back === true ? voice.soonest : voice.span + voice.latest;
275
+ const reached = voice.reaches(edge);
276
+ const at = Math.max(out.at + out.over, reached <= now ? reached : now);
277
+ const over = mix.reduced ? 0 : out.over;
278
+ voice.out = null;
279
+ voice.back = over > 0 ? { from: 0, at, over } : null;
280
+ noted(mix, voice);
281
+ mix.lanes?.refill();
282
+ }
214
283
  /** Whether a motion patch still asks this voice of this mix for its time. */
215
284
  function asks(mix, motion, voice) {
216
285
  return motion.owner === mix.owner && motion.ownerId === voice.id;
@@ -248,7 +317,7 @@ export function retire(mix, voice, at) {
248
317
  if (voice.owner !== null) {
249
318
  const span = voice.owner;
250
319
  leaving(mix, voice);
251
- const owner = orphan(voice);
320
+ const owner = orphan(voice, now);
252
321
  if (owner !== null && !lingers(owner, voice.doneAt))
253
322
  retire(mix, owner, voice.doneAt);
254
323
  // A child the fit skipped had no time in it, so the rest of the fit stands: fitting again
package/dist/fill.js CHANGED
@@ -472,7 +472,8 @@ export function call(voice, chans, rec, slot) {
472
472
  rec.delta = null;
473
473
  rec.probed = this.now;
474
474
  rec.seeks = voice.seeks;
475
- return false;
475
+ // A role member is one at weight 0 too, unasked, and holds its role's place as on the general path.
476
+ return voice.spec.role !== undefined;
476
477
  }
477
478
  const kept = reading.kept;
478
479
  host.ready(voice, subject, rec);
package/dist/fit.js CHANGED
@@ -50,6 +50,14 @@ function least(lo, hi, ok) {
50
50
  return lo;
51
51
  if (!ok(hi))
52
52
  return hi;
53
+ // An unbounded `hi`, as an `Infinity` hint gives, is brought to a finite one before halving.
54
+ if (hi === Number.POSITIVE_INFINITY) {
55
+ hi = Math.max(lo, 1) * 2;
56
+ while (!ok(hi)) {
57
+ lo = hi;
58
+ hi *= 2;
59
+ }
60
+ }
53
61
  for (let i = 0; i < 60; i++) {
54
62
  const mid = (lo + hi) / 2;
55
63
  if (ok(mid))
package/dist/fold.js CHANGED
@@ -1,10 +1,7 @@
1
1
  import { foldNumber } from './channels.js';
2
2
  import { copy as copyValue, first } from './clone.js';
3
- import { open as openRecord, shut as shutRecord, weighAt } from './everyone.js';
4
3
  // V8 reads a local const as a constant, where it reads an import from its module on every call.
5
4
  const copy = copyValue;
6
- const open = openRecord;
7
- const shut = shutRecord;
8
5
  /** A record's band state, made when a rest-less channel first asks. */
9
6
  function bandsFor(held, n) {
10
7
  const bands = new Uint8Array(n);
@@ -107,9 +104,9 @@ export function shared(voice, slot, rec, q) {
107
104
  // A projection numbers no subject, and keeps nothing by subject.
108
105
  if (slot < 0)
109
106
  return q;
110
- const cap = this.lanes.cap;
111
- weighAt(voice, slot, rec.weight, cap);
112
- shut(voice, rec, slot, cap);
107
+ const lanes = this.lanes;
108
+ this.share.weighAt(voice, slot, rec.weight, lanes.cap);
109
+ this.share.shut(voice, rec, slot, lanes.cap);
113
110
  return q;
114
111
  }
115
112
  const sharers = this.sharers;
@@ -265,7 +262,7 @@ export function foldSharers(pose, subject, head, q, before, laned, dry, except,
265
262
  continue;
266
263
  if (voice.id === except || voice.state === 'done')
267
264
  continue;
268
- open(voice, rec, head.slot);
265
+ this.share.open(voice, rec, head.slot);
269
266
  const delta = this.read(voice, subject, now, dry, rec);
270
267
  if (owed >= 0 && voice.laned)
271
268
  this.lanes.paid(voice.id, owed, rec.weight);
package/dist/gone.js CHANGED
@@ -77,6 +77,7 @@ export function goneIndex(mix) {
77
77
  /** Hands voices let go of from `gone` to the departed index, noting the latest that left. */
78
78
  function forgot(mix, out) {
79
79
  for (const v of out) {
80
+ mix.parters.delete(v);
80
81
  mix.departed.add(mix, v);
81
82
  if (v.doneAt > mix.transport.forgotTo)
82
83
  mix.transport.forgotTo = v.doneAt;
package/dist/handle.d.ts CHANGED
@@ -11,7 +11,7 @@ export interface HandleHost<I, O> {
11
11
  * The voice's clock jumped: for an owner, so did every clock it holds. With `rebuild`, their
12
12
  * state is made again for where they now are. How sure that leaves it.
13
13
  */
14
- sought(voice: Voice<I, O>, rebuild: boolean): Doubt;
14
+ sought(voice: Voice<I, O>, rebuild: boolean, ahead: boolean): Doubt;
15
15
  fade(voice: Voice<I, O>, opts: FadeOptions<I> | undefined): void;
16
16
  rise(voice: Voice<I, O>, opts: {
17
17
  over?: number;
package/dist/handle.js CHANGED
@@ -115,14 +115,16 @@ export class VoiceHandle {
115
115
  return 'exact';
116
116
  voice.written = true;
117
117
  // A pending voice's clock is anchored at its start, so it starts there on time.
118
+ let ahead = false;
118
119
  if (voice.state !== 'pending') {
119
120
  const u = this.#host.nowFor(voice);
120
121
  leave(voice, u, true);
121
122
  voice.rebase(u);
123
+ ahead = elapsed > voice.anchorElapsed;
122
124
  }
123
125
  voice.anchorElapsed = elapsed;
124
126
  voice.seeks++;
125
- const doubt = this.#host.sought(voice, opts?.state !== 'keep');
127
+ const doubt = this.#host.sought(voice, opts?.state !== 'keep', ahead);
126
128
  this.#host.changed(voice);
127
129
  if (this.#host.taping())
128
130
  this.#host.record(this, 'seek', (h) => h.seek(elapsed, opts));
package/dist/held.d.ts CHANGED
@@ -22,7 +22,8 @@ export declare function sinceOf<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, del
22
22
  /**
23
23
  * The mix time a subject's fade in counts from: `since`, but no earlier than its voice starts,
24
24
  * where a voice sought ahead of its start has a subject's delay run out before it; and from when
25
- * the voice first showed, for one freezing before.
25
+ * the voice first showed, for one freezing before. Its start is where its owners' clocks first
26
+ * read it, as `since` is, not where they would by their controls now.
26
27
  */
27
28
  export declare function shownOf<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, since: number): number;
28
29
  /** Lists a record whose origin is still ahead of `now`, for a later change to the clock to move. */
package/dist/held.js CHANGED
@@ -2,9 +2,7 @@ import { changed } from './chain.js';
2
2
  import { stepOf } from './clock.js';
3
3
  import { delayOf } from './delay.js';
4
4
  import { schedule } from './due.js';
5
- import { everyoneOf, inherit, sighted } from './everyone.js';
6
- import { originOf } from './origin.js';
7
- import { startOf } from './place.js';
5
+ import { originOf, startedAt } from './origin.js';
8
6
  import { record } from './record.js';
9
7
  import { unreach, unreached } from './unreached.js';
10
8
  /** The record a voice gives every subject it does not reach. */
@@ -24,7 +22,7 @@ export function unreachedOf(voice, now) {
24
22
  */
25
23
  export function held(voice, subject, slot, guess = null) {
26
24
  if (voice.sharing) {
27
- const all = everyoneOf(this, voice);
25
+ const all = this.share.everyoneOf(this, voice);
28
26
  if (all !== null)
29
27
  return all;
30
28
  }
@@ -67,8 +65,8 @@ export function held(voice, subject, slot, guess = null) {
67
65
  voice.subjects.set(subject, held);
68
66
  waits(voice, held, now);
69
67
  // A subject a voice saw while it shared one record was counted then.
70
- if (sighted(voice, slot))
71
- inherit(voice, held, slot);
68
+ if (voice.sighted !== null && this.share?.sighted(voice, slot))
69
+ this.share.inherit(voice, held, slot);
72
70
  else
73
71
  voice.seen++;
74
72
  if (delay > voice.latest) {
@@ -119,13 +117,21 @@ export function sinceOf(voice, delay) {
119
117
  /**
120
118
  * The mix time a subject's fade in counts from: `since`, but no earlier than its voice starts,
121
119
  * where a voice sought ahead of its start has a subject's delay run out before it; and from when
122
- * the voice first showed, for one freezing before.
120
+ * the voice first showed, for one freezing before. Its start is where its owners' clocks first
121
+ * read it, as `since` is, not where they would by their controls now.
123
122
  */
124
123
  export function shownOf(voice, since) {
125
- const start = startOf(voice);
124
+ const start = startedAt(voice);
126
125
  const from = since < start ? start : since;
127
126
  return voice.freezesBefore && voice.opened < from ? voice.opened : from;
128
127
  }
128
+ /**
129
+ * Whether a listed record stays listed at `now`: its origin is ahead, or it is a motion voice's,
130
+ * unread since its origin, which `still` asks after since the motion has no run for it yet.
131
+ */
132
+ function stillEarly(voice, held, now) {
133
+ return !(held.since <= now) || (voice.motion !== undefined && !(held.probed >= held.since));
134
+ }
129
135
  /** Lists a record whose origin is still ahead of `now`, for a later change to the clock to move. */
130
136
  export function waits(voice, held, now) {
131
137
  if (held.since <= now)
@@ -136,7 +142,7 @@ export function waits(voice, held, now) {
136
142
  if (early.length >= 64 && (early.length & (early.length - 1)) === 0) {
137
143
  let kept = 0;
138
144
  for (const h of early)
139
- if (!(h.since <= now))
145
+ if (stillEarly(voice, h, now))
140
146
  early[kept++] = h;
141
147
  early.length = kept;
142
148
  }
@@ -148,6 +154,7 @@ export function waits(voice, held, now) {
148
154
  */
149
155
  export function begin(mix, voice, now) {
150
156
  voice.state = 'live';
157
+ voice.begunSeq = mix.transport.seq;
151
158
  if (voice.anchorElapsed !== 0) {
152
159
  voice.seeks++;
153
160
  voice.rebuilds++;
@@ -179,7 +186,7 @@ export function reorigin(mix, voice, now) {
179
186
  let kept = 0;
180
187
  for (const held of early) {
181
188
  origin(mix, voice, held);
182
- if (!(held.since <= now))
189
+ if (stillEarly(voice, held, now))
183
190
  early[kept++] = held;
184
191
  }
185
192
  early.length = kept;
package/dist/hosts.d.ts CHANGED
@@ -4,6 +4,13 @@ import type { Booker, BookOptions, Handle } from './types.js';
4
4
  import type { Voice } from './voice.js';
5
5
  export declare function book<I, O>(mix: Mixer<I, O>, opts: BookOptions<unknown, I>): Booker;
6
6
  export declare function laneHost<I, O>(mix: Mixer<I, O>): LaneHost<I, O>;
7
+ /**
8
+ * Whether a laned voice belongs in a crowd: one naming a single subject, which shares nothing
9
+ * across subjects for a lane to save. A motion voice writes one channel; a keys or fn voice may
10
+ * write several, and joins the crowd of voices writing those same channels. A motion going round
11
+ * an `ink` color's hue stays out, since a crowd solves its stretches straight across each axis.
12
+ */
13
+ export declare function crowdable<I, O>(v: Voice<I, O>): boolean;
7
14
  /**
8
15
  * Whether a voice's patch and spec can run on a lane, its channels aside. A voice whose patch has
9
16
  * kept state on a record through `setting.keep` is stateful from then on, and leaves its lane;
package/dist/hosts.js CHANGED
@@ -3,9 +3,7 @@ import { Book } from './book.js';
3
3
  import { headOf } from './chain.js';
4
4
  import { numericOf } from './channels.js';
5
5
  import { stepOf } from './clock.js';
6
- import { crowdable } from './crowd.js';
7
6
  import { schedule } from './due.js';
8
- import { unshare, unsight, weightAt } from './everyone.js';
9
7
  import { beginFade, beginRise, fadeAt, fadeSubject } from './fade.js';
10
8
  import { goneIndex } from './gone.js';
11
9
  import { VoiceHandle } from './handle.js';
@@ -67,7 +65,7 @@ export function laneHost(mix) {
67
65
  for (const voice of mix.general) {
68
66
  unreach(voice, slot, false);
69
67
  if (voice.sighted !== null)
70
- unsight(voice, slot);
68
+ mix.share?.unsight(voice, slot);
71
69
  }
72
70
  for (const voice of goneIndex(mix).general)
73
71
  unreach(voice, slot, false);
@@ -110,6 +108,19 @@ export function laneHost(mix) {
110
108
  kept: (voice) => stateful(mix, voice),
111
109
  };
112
110
  }
111
+ /**
112
+ * Whether a laned voice belongs in a crowd: one naming a single subject, which shares nothing
113
+ * across subjects for a lane to save. A motion voice writes one channel; a keys or fn voice may
114
+ * write several, and joins the crowd of voices writing those same channels. A motion going round
115
+ * an `ink` color's hue stays out, since a crowd solves its stretches straight across each axis.
116
+ */
117
+ export function crowdable(v) {
118
+ return (v.named !== null &&
119
+ v.named.size === 1 &&
120
+ v.spec.role === undefined &&
121
+ (v.slots.length === 1 || v.motion === undefined) &&
122
+ v.motion?.round !== true);
123
+ }
113
124
  /**
114
125
  * Whether a voice's patch and spec can run on a lane, its channels aside. A voice whose patch has
115
126
  * kept state on a record through `setting.keep` is stateful from then on, and leaves its lane;
@@ -181,7 +192,7 @@ export function stateful(mix, voice) {
181
192
  voice.keeping = true;
182
193
  // The call that kept state was for the subject last sent to, whose record the state is on.
183
194
  if (voice.sharing)
184
- unshare(mix, voice, true, mix.sending.subject);
195
+ mix.share?.unshare(mix, voice, true, mix.sending.subject);
185
196
  mix.lanes?.invalidate();
186
197
  }
187
198
  export function handle(mix, voice) {
@@ -205,7 +216,11 @@ function handleHost(mix) {
205
216
  mix.lanes?.refill();
206
217
  },
207
218
  // A delta read before an owner's seek, or a hit booked, is not taken as standing after it.
208
- sought: (voice, rebuild) => {
219
+ sought: (voice, rebuild, ahead) => {
220
+ if (ahead && voice.holding !== null && !mix.projecting) {
221
+ mix.leapt ??= new Set();
222
+ mix.leapt.add(voice);
223
+ }
209
224
  if (rebuild)
210
225
  voice.rebuilds++;
211
226
  let doubt = seekDoubt(mix, voice, rebuild);
@@ -248,7 +263,9 @@ function handleHost(mix) {
248
263
  ? undefined
249
264
  : voice.subjects.get(subject);
250
265
  // A voice that stopped sharing makes a subject's record at its next read, from what it kept.
251
- return held !== undefined ? held.weight : weightAt(voice, slot);
266
+ if (held !== undefined)
267
+ return held.weight;
268
+ return mix.share === null ? 0 : mix.share.weightAt(voice, slot);
252
269
  },
253
270
  taping: () => taping(mix),
254
271
  record: (h, label, again) => record(mix, label, (stage) => {
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export type { LastOptions, NumberOptions, NumberTuple, Vec } from './channels.js';
2
2
  export { kit, last, max, mul, sum, vec } from './channels.js';
3
3
  export type { ColorOptions } from './color.js';
4
- export { color, css, mixHex, oklab, toHex } from './color.js';
4
+ export { color, css, ink, mixHex, oklab, toHex } from './color.js';
5
5
  export type { Curve } from './easing.js';
6
6
  export { curve } from './easing.js';
7
7
  export type { Claim, Fit, FitPlan, Layout, Order, OverrunOptions, SpanClaim, Strength, } from './fit.js';
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { kit, last, max, mul, sum, vec } from './channels.js';
2
- export { color, css, mixHex, oklab, toHex } from './color.js';
2
+ export { color, css, ink, mixHex, oklab, toHex } from './color.js';
3
3
  export { curve } from './easing.js';
4
4
  export { conclude, condense, lax, layout, overrun, pipe, plain, shed, } from './fit.js';
5
5
  export { mix, mixer } from './mixer.js';
package/dist/lane.d.ts CHANGED
@@ -185,6 +185,8 @@ export declare class Lane<I, O> implements Positions<I, O> {
185
185
  * checked against every lane up to some epoch has met this one if this one's is no later.
186
186
  */
187
187
  epoch: number;
188
+ /** The lanes' probe count when it was made: a probe no later read its subject elsewhere. */
189
+ made: number;
188
190
  /**
189
191
  * Per position, `STRIDE` numbers side by side, so one subject's are one read from memory: its
190
192
  * delay, its `since`, its weight at the last fill, its weight at the last fill a probe read, a
package/dist/lane.js CHANGED
@@ -37,6 +37,8 @@ export class Lane {
37
37
  * checked against every lane up to some epoch has met this one if this one's is no later.
38
38
  */
39
39
  epoch = 0;
40
+ /** The lanes' probe count when it was made: a probe no later read its subject elsewhere. */
41
+ made = 0;
40
42
  /**
41
43
  * Per position, `STRIDE` numbers side by side, so one subject's are one read from memory: its
42
44
  * delay, its `since`, its weight at the last fill, its weight at the last fill a probe read, a
package/dist/lanes.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Column } from './columns.js';
1
+ import { flush, pullRun, writeLater } from './columns.js';
2
2
  import { type Copier, copy } from './copyout.js';
3
3
  import { type Crowd, row } from './crowd.js';
4
4
  import { call, foldKeys, one, parting, readKeyed, reset, run, runEveryone, signalled, subjectAt } from './fill.js';
@@ -7,6 +7,8 @@ import { type Lane, type Laned, type Role } from './lane.js';
7
7
  import { absent, Numbers } from './numbers.js';
8
8
  import { Owed } from './owed.js';
9
9
  import { Placed } from './placed.js';
10
+ import type { Column } from './pull.js';
11
+ import { renumber } from './renumber.js';
10
12
  import { type Seat } from './seat.js';
11
13
  import type { Watcher } from './stretch.js';
12
14
  import type { Subject } from './subject.js';
@@ -64,6 +66,29 @@ export interface LaneHost<I, O> {
64
66
  */
65
67
  export declare class Lanes<I, O> implements Watcher {
66
68
  readonly host: LaneHost<I, O>;
69
+ /** What the general path calls on a sharing voice, given to a mix by its engine so a lean bundle leaves it out. */
70
+ static readonly share: {
71
+ shares: typeof import("./everyone.js").shares;
72
+ outgrow: typeof import("./everyone.js").outgrow;
73
+ everyoneOf: typeof import("./everyone.js").everyoneOf;
74
+ sighted: typeof import("./everyone.js").sighted;
75
+ sight: typeof import("./everyone.js").sight;
76
+ weightAt: typeof import("./everyone.js").weightAt;
77
+ weighAt: typeof import("./everyone.js").weighAt;
78
+ open: typeof import("./everyone.js").open;
79
+ shut: typeof import("./everyone.js").shut;
80
+ restamp: typeof import("./everyone.js").restamp;
81
+ unsight: typeof import("./everyone.js").unsight;
82
+ unshare: typeof import("./everyone.js").unshare;
83
+ inherit: typeof import("./everyone.js").inherit;
84
+ };
85
+ /** What `pull` calls on lanes alone. */
86
+ readonly pulls: {
87
+ pullRun: typeof pullRun;
88
+ writeLater: typeof writeLater;
89
+ flush: typeof flush;
90
+ renumber: typeof renumber;
91
+ };
67
92
  readonly numbers: Numbers<I>;
68
93
  lanes: Lane<I, O>[];
69
94
  /** The lanes over every subject that have started playing, by epoch. */