@msb235/blits 0.7.1 → 0.8.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 (156) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/README.md +22 -4
  3. package/dist/book.js +4 -4
  4. package/dist/chain.d.ts +1 -1
  5. package/dist/chain.js +57 -54
  6. package/dist/channels.d.ts +26 -5
  7. package/dist/channels.js +12 -8
  8. package/dist/clock.d.ts +8 -1
  9. package/dist/clock.js +14 -3
  10. package/dist/clone.d.ts +8 -0
  11. package/dist/clone.js +22 -0
  12. package/dist/closed.d.ts +1 -1
  13. package/dist/closed.js +3 -2
  14. package/dist/color.d.ts +0 -11
  15. package/dist/color.js +4 -24
  16. package/dist/columns.js +0 -1
  17. package/dist/copyout.d.ts +6 -0
  18. package/dist/copyout.js +38 -0
  19. package/dist/crowd.d.ts +7 -5
  20. package/dist/crowd.js +32 -18
  21. package/dist/cue.d.ts +3 -1
  22. package/dist/cue.js +31 -20
  23. package/dist/departed.d.ts +34 -0
  24. package/dist/departed.js +73 -0
  25. package/dist/easing.d.ts +5 -0
  26. package/dist/easing.js +31 -2
  27. package/dist/everyone.d.ts +56 -0
  28. package/dist/everyone.js +221 -0
  29. package/dist/fade.d.ts +8 -1
  30. package/dist/fade.js +34 -6
  31. package/dist/fill.d.ts +22 -13
  32. package/dist/fill.js +104 -41
  33. package/dist/fit.js +2 -1
  34. package/dist/fold.d.ts +37 -0
  35. package/dist/fold.js +216 -31
  36. package/dist/frames.d.ts +21 -0
  37. package/dist/frames.js +59 -0
  38. package/dist/gather.d.ts +15 -9
  39. package/dist/gather.js +60 -25
  40. package/dist/gone.js +10 -0
  41. package/dist/handle.d.ts +16 -5
  42. package/dist/handle.js +56 -6
  43. package/dist/held.d.ts +8 -5
  44. package/dist/held.js +56 -58
  45. package/dist/history.d.ts +17 -13
  46. package/dist/history.js +47 -28
  47. package/dist/hosts.js +55 -8
  48. package/dist/index.d.ts +9 -4
  49. package/dist/index.js +5 -2
  50. package/dist/keyfill.d.ts +7 -0
  51. package/dist/keyfill.js +124 -0
  52. package/dist/keyrows.d.ts +1 -0
  53. package/dist/keyrows.js +1 -1
  54. package/dist/lane.d.ts +32 -2
  55. package/dist/lane.js +5 -0
  56. package/dist/lanes.d.ts +32 -8
  57. package/dist/lanes.js +69 -37
  58. package/dist/leavings.d.ts +26 -0
  59. package/dist/leavings.js +70 -0
  60. package/dist/locus.js +2 -2
  61. package/dist/meet.js +14 -8
  62. package/dist/mixer.d.ts +39 -7
  63. package/dist/mixer.js +50 -10
  64. package/dist/motion.d.ts +2 -0
  65. package/dist/motion.js +4 -1
  66. package/dist/motions.d.ts +22 -15
  67. package/dist/motions.js +40 -34
  68. package/dist/move.js +23 -16
  69. package/dist/moved.d.ts +11 -0
  70. package/dist/moved.js +29 -0
  71. package/dist/origin.d.ts +19 -0
  72. package/dist/origin.js +68 -0
  73. package/dist/owed.d.ts +4 -4
  74. package/dist/owed.js +8 -8
  75. package/dist/owner.d.ts +1 -1
  76. package/dist/owner.js +2 -2
  77. package/dist/pace.d.ts +13 -5
  78. package/dist/pace.js +21 -8
  79. package/dist/pack.d.ts +3 -1
  80. package/dist/pack.js +15 -24
  81. package/dist/paging.d.ts +8 -2
  82. package/dist/paging.js +39 -4
  83. package/dist/patch.d.ts +14 -9
  84. package/dist/patch.js +83 -21
  85. package/dist/place.js +18 -3
  86. package/dist/project.d.ts +12 -1
  87. package/dist/project.js +101 -83
  88. package/dist/pull.d.ts +2 -0
  89. package/dist/pull.js +8 -0
  90. package/dist/qualify.js +10 -2
  91. package/dist/reading.d.ts +5 -0
  92. package/dist/reading.js +5 -0
  93. package/dist/record.d.ts +9 -0
  94. package/dist/record.js +42 -0
  95. package/dist/relink.d.ts +11 -5
  96. package/dist/relink.js +7 -5
  97. package/dist/restored.d.ts +41 -0
  98. package/dist/restored.js +91 -0
  99. package/dist/revive.d.ts +10 -3
  100. package/dist/revive.js +31 -23
  101. package/dist/roster.d.ts +11 -0
  102. package/dist/roster.js +46 -0
  103. package/dist/rotation.d.ts +29 -0
  104. package/dist/rotation.js +91 -0
  105. package/dist/rows.d.ts +5 -5
  106. package/dist/rows.js +5 -5
  107. package/dist/sample.d.ts +3 -2
  108. package/dist/sample.js +32 -15
  109. package/dist/seek.js +97 -114
  110. package/dist/shared.d.ts +6 -0
  111. package/dist/shared.js +15 -0
  112. package/dist/signals.d.ts +24 -3
  113. package/dist/signals.js +50 -11
  114. package/dist/spans.js +7 -0
  115. package/dist/stack.d.ts +11 -0
  116. package/dist/stack.js +47 -0
  117. package/dist/standing.d.ts +10 -0
  118. package/dist/standing.js +79 -0
  119. package/dist/store.d.ts +3 -3
  120. package/dist/store.js +3 -3
  121. package/dist/strays.js +3 -0
  122. package/dist/tape.d.ts +8 -5
  123. package/dist/tape.js +21 -13
  124. package/dist/testing.d.ts +35 -0
  125. package/dist/testing.js +116 -0
  126. package/dist/touch.d.ts +6 -0
  127. package/dist/touch.js +28 -0
  128. package/dist/transport.d.ts +45 -6
  129. package/dist/transport.js +64 -19
  130. package/dist/turns.d.ts +18 -0
  131. package/dist/turns.js +72 -0
  132. package/dist/tweened.js +1 -1
  133. package/dist/types/channel.d.ts +46 -0
  134. package/dist/types/channel.js +1 -0
  135. package/dist/types/history.d.ts +139 -0
  136. package/dist/types/history.js +1 -0
  137. package/dist/types/mix.d.ts +336 -0
  138. package/dist/types/mix.js +1 -0
  139. package/dist/types/patch.d.ts +187 -0
  140. package/dist/types/patch.js +1 -0
  141. package/dist/types/place.d.ts +157 -0
  142. package/dist/types/place.js +1 -0
  143. package/dist/types/transport.d.ts +71 -0
  144. package/dist/types/transport.js +1 -0
  145. package/dist/types/voice.d.ts +323 -0
  146. package/dist/types/voice.js +1 -0
  147. package/dist/types.d.ts +7 -1162
  148. package/dist/unpage.d.ts +2 -2
  149. package/dist/unpage.js +53 -25
  150. package/dist/voice.d.ts +90 -22
  151. package/dist/voice.js +105 -43
  152. package/dist/wave.d.ts +42 -0
  153. package/dist/wave.js +62 -0
  154. package/dist/weigh.js +1 -1
  155. package/docs/schema.html +42 -36
  156. package/package.json +8 -3
package/dist/lanes.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import type { Column } from './columns.js';
2
+ import { copy } from './copyout.js';
2
3
  import { type Crowd, row } from './crowd.js';
3
- import { call, foldKeys, one, parting, readKeyed, reset, run, signalled, subjectAt } from './fill.js';
4
- import { fold, foldDelta, foldInto, foldRun, gate, gather } from './gather.js';
4
+ import { call, foldKeys, one, parting, readKeyed, reset, run, runEveryone, signalled, subjectAt } from './fill.js';
5
+ import { fold, foldDelta, foldInto, foldRun, gate, gather, unplain } from './gather.js';
5
6
  import { type Lane, type Laned, type Locus } from './lane.js';
6
7
  import type { Watcher } from './motions.js';
7
- import { type absent, Numbers } from './numbers.js';
8
+ import { absent, Numbers } from './numbers.js';
8
9
  import { Owed } from './owed.js';
9
10
  import type { Channel } from './types.js';
10
11
  import type { Subject, Voice } from './voice.js';
@@ -61,7 +62,7 @@ export declare class Lanes<I, O> implements Watcher {
61
62
  /** The lanes over every subject that have started playing, by epoch. */
62
63
  dense: Lane<I, O>[];
63
64
  readonly byId: Map<number, Lane<I, O>>;
64
- /** The crowds of single-subject motion voices, one per channel, and which holds each voice. */
65
+ /** The crowds of single-subject voices, one per list of channels, and which holds each voice. */
65
66
  crowds: Crowd<I, O>[];
66
67
  readonly crowdOf: Map<number, Crowd<I, O>>;
67
68
  /** Whether two crowds share a channel, so their rows must interleave in voice order. */
@@ -71,13 +72,20 @@ export declare class Lanes<I, O> implements Watcher {
71
72
  /** The lane and record `one` is folding, whose band state a rest-less channel reads. */
72
73
  folding: Lane<I, O> | null;
73
74
  rec: Subject<unknown> | null;
75
+ /** The numbers a fill's methods hand each other: see `Arg`. */
76
+ readonly arg: Float64Array<ArrayBuffer>;
74
77
  /** While a fill gathers a locus: that locus and the member gathering, where folds go instead. */
75
78
  into: Locus<I, O> | null;
76
79
  intoId: number;
77
80
  readonly lawsByKey: Map<string, Float64Array<ArrayBufferLike>>;
78
81
  lastLaw: Float64Array | null;
79
- /** The lanes the last `meet` gave a position, and whether it placed a crowd row. */
82
+ /**
83
+ * The lanes the last `meet` gave a position, the first `metN` of `met`, and whether it placed a
84
+ * crowd row. Counted, not cut to length: an array emptied by its length takes new room at its
85
+ * next push, about 150 B a subject met (the `swap` row, bench/allocs.mjs, 2026-10-09).
86
+ */
80
87
  readonly met: number[];
88
+ metN: number;
81
89
  metCrowd: boolean;
82
90
  /** By subject number, the laned voices its probes fold on the general path this fill. */
83
91
  readonly owed: Owed;
@@ -137,8 +145,9 @@ export declare class Lanes<I, O> implements Watcher {
137
145
  keeps: boolean;
138
146
  now: number;
139
147
  filledAt: number;
140
- /** `reading.moved` at the last fill, so a retarget or push between two probes refills. */
148
+ /** `reading.moved` and `reading.inputs` at the last fill: see `current`. */
141
149
  moved: number;
150
+ inputs: number;
142
151
  /** The `now` of the latest probe, and the probe count before its first, to tell a probe this frame. */
143
152
  frameAt: number;
144
153
  frameProbes: number;
@@ -191,6 +200,13 @@ export declare class Lanes<I, O> implements Watcher {
191
200
  prepare(slot: number, subject: I, version: number, head: Subject<unknown> | null): boolean;
192
201
  /** Whether this frame's lanes are filled and nothing since asks `begin` to look again. */
193
202
  filled(now: number, version: number): boolean;
203
+ /**
204
+ * Whether the last fill still stands: everything a fill reads that can change between two probes,
205
+ * each stamped by `fillAll`. The clock; the mix's version, which voices coming, going, starting
206
+ * and stopping move; a motion's retargets and pushes; and a `level` set. Whatever else can change
207
+ * between probes keeps its voice off lanes in `fits`.
208
+ */
209
+ current(now: number, version: number): boolean;
194
210
  /**
195
211
  * What a probe does before its subject, once a frame's lanes are settled: qualifies and fills as
196
212
  * needed. Says whether the subject takes the general path or the lanes are filled.
@@ -215,8 +231,6 @@ export declare class Lanes<I, O> implements Watcher {
215
231
  * would write: each number within 1e-9 of its rest.
216
232
  */
217
233
  rests(slot: number): boolean;
218
- /** Writes a subject's laned values into a pose, each array channel into a new array. */
219
- copy(slot: number, pose: Record<string, unknown>): void;
220
234
  forget(slot: number): void;
221
235
  grow(size: number): void;
222
236
  /**
@@ -224,16 +238,25 @@ export declare class Lanes<I, O> implements Watcher {
224
238
  * next fill, and a lane keeps the sample the old one gave.
225
239
  */
226
240
  stretchChanged(id: number, s: number): void;
241
+ /**
242
+ * A voice that kept one record for every subject keeps one each from now on: each of its lane's
243
+ * positions takes the record `of` gives its subject.
244
+ */
245
+ rerecord(id: number, of: (subject: I, slot: number) => Subject<unknown>): void;
246
+ /** A voice's clock moved the `shown` of records it had already placed: copies them again. */
247
+ reshown(id: number): void;
227
248
  /** Something on a voice changed: a crowd copies it again at the next fill. */
228
249
  voiceChanged(id: number): void;
229
250
  }
230
251
  declare const methods: {
252
+ copy: typeof copy;
231
253
  row: typeof row;
232
254
  signalled: typeof signalled;
233
255
  parting: typeof parting;
234
256
  subjectAt: typeof subjectAt;
235
257
  reset: typeof reset;
236
258
  run: typeof run;
259
+ runEveryone: typeof runEveryone;
237
260
  one: typeof one;
238
261
  foldKeys: typeof foldKeys;
239
262
  readKeyed: typeof readKeyed;
@@ -244,6 +267,7 @@ declare const methods: {
244
267
  foldRun: typeof foldRun;
245
268
  foldInto: typeof foldInto;
246
269
  gate: typeof gate;
270
+ unplain: typeof unplain;
247
271
  };
248
272
  type Methods = typeof methods;
249
273
  export interface Lanes<I, O> extends Methods {
package/dist/lanes.js CHANGED
@@ -1,8 +1,9 @@
1
+ import { copy } from './copyout.js';
1
2
  import { row } from './crowd.js';
2
- import { call, fillAll, foldKeys, one, parting, readKeyed, reset, run, signalled, subjectAt, } from './fill.js';
3
- import { fold, foldDelta, foldInto, foldRun, gate, gather } from './gather.js';
3
+ import { call, fillAll, foldKeys, one, parting, readKeyed, reset, run, runEveryone, signalled, subjectAt, } from './fill.js';
4
+ import { fold, foldDelta, foldInto, foldRun, gate, gather, unplain } from './gather.js';
4
5
  import { meet, reach } from './meet.js';
5
- import { Numbers } from './numbers.js';
6
+ import { absent, Numbers } from './numbers.js';
6
7
  import { Owed } from './owed.js';
7
8
  import { requalify, retouch } from './qualify.js';
8
9
  import { reading } from './reading.js';
@@ -19,7 +20,7 @@ export class Lanes {
19
20
  /** The lanes over every subject that have started playing, by epoch. */
20
21
  dense = [];
21
22
  byId = new Map();
22
- /** The crowds of single-subject motion voices, one per channel, and which holds each voice. */
23
+ /** The crowds of single-subject voices, one per list of channels, and which holds each voice. */
23
24
  crowds = [];
24
25
  crowdOf = new Map();
25
26
  /** Whether two crowds share a channel, so their rows must interleave in voice order. */
@@ -29,13 +30,20 @@ export class Lanes {
29
30
  /** The lane and record `one` is folding, whose band state a rest-less channel reads. */
30
31
  folding = null;
31
32
  rec = null;
33
+ /** The numbers a fill's methods hand each other: see `Arg`. */
34
+ arg = new Float64Array(6 /* Arg.SIZE */);
32
35
  /** While a fill gathers a locus: that locus and the member gathering, where folds go instead. */
33
36
  into = null;
34
37
  intoId = 0;
35
38
  lawsByKey = new Map();
36
39
  lastLaw = null;
37
- /** The lanes the last `meet` gave a position, and whether it placed a crowd row. */
40
+ /**
41
+ * The lanes the last `meet` gave a position, the first `metN` of `met`, and whether it placed a
42
+ * crowd row. Counted, not cut to length: an array emptied by its length takes new room at its
43
+ * next push, about 150 B a subject met (the `swap` row, bench/allocs.mjs, 2026-10-09).
44
+ */
38
45
  met = [];
46
+ metN = 0;
39
47
  metCrowd = false;
40
48
  /** By subject number, the laned voices its probes fold on the general path this fill. */
41
49
  owed = new Owed();
@@ -95,8 +103,9 @@ export class Lanes {
95
103
  keeps = false;
96
104
  now = Number.NaN;
97
105
  filledAt = Number.NaN;
98
- /** `reading.moved` at the last fill, so a retarget or push between two probes refills. */
106
+ /** `reading.moved` and `reading.inputs` at the last fill: see `current`. */
99
107
  moved = 0;
108
+ inputs = 0;
100
109
  /** The `now` of the latest probe, and the probe count before its first, to tell a probe this frame. */
101
110
  frameAt = Number.NaN;
102
111
  frameProbes = 0;
@@ -233,13 +242,23 @@ export class Lanes {
233
242
  }
234
243
  /** Whether this frame's lanes are filled and nothing since asks `begin` to look again. */
235
244
  filled(now, version) {
245
+ return (this.frameAt === now &&
246
+ this.qualifiedVersion === version &&
247
+ this.touched.length === 0 &&
248
+ !this.filling &&
249
+ this.current(now, version));
250
+ }
251
+ /**
252
+ * Whether the last fill still stands: everything a fill reads that can change between two probes,
253
+ * each stamped by `fillAll`. The clock; the mix's version, which voices coming, going, starting
254
+ * and stopping move; a motion's retargets and pushes; and a `level` set. Whatever else can change
255
+ * between probes keeps its voice off lanes in `fits`.
256
+ */
257
+ current(now, version) {
236
258
  return (this.filledAt === now &&
237
- this.frameAt === now &&
238
259
  this.filledVersion === version &&
239
- this.qualifiedVersion === version &&
240
260
  this.moved === reading.moved &&
241
- this.touched.length === 0 &&
242
- !this.filling);
261
+ this.inputs === reading.inputs);
243
262
  }
244
263
  /**
245
264
  * What a probe does before its subject, once a frame's lanes are settled: qualifies and fills as
@@ -257,8 +276,9 @@ export class Lanes {
257
276
  }
258
277
  if (this.filling)
259
278
  return 0 /* Begin.GENERAL */;
260
- // The mix's version moves only when a voice over every subject comes, goes, starts or stops
261
- // waiting, each of which touches it, so a version past a qualify that stands is a set of touches.
279
+ // Past a qualify that stands, the mix's version moves when a voice over every subject comes,
280
+ // goes, starts or stops waiting, each of which touches it, or stops sharing one record, which
281
+ // moves no voice on or off a lane: so with voices touched it is a set of touches.
262
282
  if (this.qualifiedVersion !== version) {
263
283
  if (!Number.isNaN(this.qualifiedVersion) && this.touched.length > 0 && retouch(this))
264
284
  this.qualifiedVersion = version;
@@ -269,7 +289,7 @@ export class Lanes {
269
289
  requalify(this, version);
270
290
  if (this.laned.length === 0)
271
291
  return 0 /* Begin.GENERAL */;
272
- if (this.filledAt !== now || this.filledVersion !== version || this.moved !== reading.moved) {
292
+ if (!this.current(now, version)) {
273
293
  fillAll(this, now, version);
274
294
  // A patch call in that fill made kept state, which took its voice off its lane: fill without it.
275
295
  if (this.qualifiedVersion !== version) {
@@ -349,30 +369,6 @@ export class Lanes {
349
369
  }
350
370
  return true;
351
371
  }
352
- /** Writes a subject's laned values into a pose, each array channel into a new array. */
353
- copy(slot, pose) {
354
- const laned = this.laned;
355
- for (let c = 0; c < laned.length; c++) {
356
- const ch = laned[c];
357
- const axes = ch.axes;
358
- // Never `delete`: it drops a reused out object into dictionary mode for good.
359
- if (ch.op === 'last' && Number.isNaN(ch.values[slot * axes])) {
360
- if (pose[ch.name] !== undefined)
361
- pose[ch.name] = undefined;
362
- continue;
363
- }
364
- if (ch.scalar) {
365
- pose[ch.name] = ch.values[slot];
366
- continue;
367
- }
368
- // Made as the general path makes it, a copy of rest, so poses from either path share a shape.
369
- const arr = [...ch.start];
370
- const base = slot * axes;
371
- for (let a = 0; a < axes; a++)
372
- arr[a] = ch.values[base + a];
373
- pose[ch.name] = arr;
374
- }
375
- }
376
372
  forget(slot) {
377
373
  this.host.forgot(slot);
378
374
  this.live--;
@@ -417,6 +413,39 @@ export class Lanes {
417
413
  if (q >= 0)
418
414
  lane.fix(q);
419
415
  }
416
+ /**
417
+ * A voice that kept one record for every subject keeps one each from now on: each of its lane's
418
+ * positions takes the record `of` gives its subject.
419
+ */
420
+ rerecord(id, of) {
421
+ const lane = this.byId.get(id);
422
+ if (lane === undefined)
423
+ return;
424
+ for (let p = 0; p < lane.list.length; p++) {
425
+ const slot = lane.list[p];
426
+ const subject = this.subjectAt(slot);
427
+ if (subject !== absent)
428
+ lane.records[p] = of(subject, slot);
429
+ }
430
+ }
431
+ /** A voice's clock moved the `shown` of records it had already placed: copies them again. */
432
+ reshown(id) {
433
+ const c = this.crowdOf.get(id);
434
+ const p = c?.rowOf.get(id);
435
+ if (c !== undefined && p !== undefined) {
436
+ const held = c.records[p];
437
+ if (held !== undefined)
438
+ c.data[p * 8 /* Row.STRIDE */ + 1 /* Row.SINCE */] = held.shown;
439
+ }
440
+ const lane = this.byId.get(id);
441
+ if (lane === undefined)
442
+ return;
443
+ for (let q = 0; q < lane.list.length; q++) {
444
+ const held = lane.records[q];
445
+ if (held !== undefined)
446
+ lane.data[q * 8 /* Row.STRIDE */ + 1 /* Row.SINCE */] = held.shown;
447
+ }
448
+ }
420
449
  /** Something on a voice changed: a crowd copies it again at the next fill. */
421
450
  voiceChanged(id) {
422
451
  const c = this.crowdOf.get(id);
@@ -430,12 +459,14 @@ export class Lanes {
430
459
  // A fill's per-subject path is methods, installed here from the modules that own them: as
431
460
  // functions called directly they made fills 5-8% slower (bench/ab.sh on teitou, 2026-10-05).
432
461
  const methods = {
462
+ copy,
433
463
  row,
434
464
  signalled,
435
465
  parting,
436
466
  subjectAt,
437
467
  reset,
438
468
  run,
469
+ runEveryone,
439
470
  one,
440
471
  foldKeys,
441
472
  readKeyed,
@@ -446,6 +477,7 @@ const methods = {
446
477
  foldRun,
447
478
  foldInto,
448
479
  gate,
480
+ unplain,
449
481
  };
450
482
  for (const [name, value] of Object.entries(methods))
451
483
  Object.defineProperty(Lanes.prototype, name, { value, writable: true, configurable: true });
@@ -0,0 +1,26 @@
1
+ import { type Cut } from './transport.js';
2
+ import type { Left, Subject } from './voice.js';
3
+ /**
4
+ * Under history, the records subjects left a voice with, while a seek or a read back may reach
5
+ * them: in the order they left, the oldest let go of from the front, and each subject's found
6
+ * without looking through the others'.
7
+ */
8
+ export declare class Leavings<I> {
9
+ private list;
10
+ /** How many at the front of `list` have been let go of, cleared out once they are half of it. */
11
+ private head;
12
+ private readonly by;
13
+ static from<I>(entries: readonly Left<I>[]): Leavings<I> | null;
14
+ push(e: Left<I>): void;
15
+ /**
16
+ * Lets go of every entry that left before `reach`, handing each to `out` first. They are at the
17
+ * front: entries are in the order they left, and mix time does not run back along it.
18
+ */
19
+ expire(reach: number, out: ((e: Left<I>) => void) | null): void;
20
+ /** The record `subject` had at `cut` and left with after it, if it left after `cut`. */
21
+ at(subject: I, cut: Cut): Subject<unknown> | undefined;
22
+ /** Each time `subject` left, oldest first. */
23
+ of(subject: I): readonly Left<I>[];
24
+ /** Every entry, oldest first. */
25
+ all(): readonly Left<I>[];
26
+ }
@@ -0,0 +1,70 @@
1
+ import { Store } from './store.js';
2
+ import { within } from './transport.js';
3
+ /**
4
+ * Under history, the records subjects left a voice with, while a seek or a read back may reach
5
+ * them: in the order they left, the oldest let go of from the front, and each subject's found
6
+ * without looking through the others'.
7
+ */
8
+ export class Leavings {
9
+ list = [];
10
+ /** How many at the front of `list` have been let go of, cleared out once they are half of it. */
11
+ head = 0;
12
+ by = new Store();
13
+ static from(entries) {
14
+ if (entries.length === 0)
15
+ return null;
16
+ const out = new Leavings();
17
+ for (const e of entries)
18
+ out.push(e);
19
+ return out;
20
+ }
21
+ push(e) {
22
+ this.list.push(e);
23
+ const own = this.by.get(e.subject);
24
+ if (own === undefined)
25
+ this.by.set(e.subject, [e]);
26
+ else
27
+ own.push(e);
28
+ }
29
+ /**
30
+ * Lets go of every entry that left before `reach`, handing each to `out` first. They are at the
31
+ * front: entries are in the order they left, and mix time does not run back along it.
32
+ */
33
+ expire(reach, out) {
34
+ const list = this.list;
35
+ let head = this.head;
36
+ while (head < list.length && !(list[head].at >= reach)) {
37
+ const e = list[head++];
38
+ if (out !== null)
39
+ out(e);
40
+ const own = this.by.get(e.subject);
41
+ if (own.length === 1)
42
+ this.by.delete(e.subject);
43
+ else
44
+ own.shift();
45
+ }
46
+ this.head = head;
47
+ if (head >= 32 && head * 2 >= list.length) {
48
+ this.list = list.slice(head);
49
+ this.head = 0;
50
+ }
51
+ }
52
+ /** The record `subject` had at `cut` and left with after it, if it left after `cut`. */
53
+ at(subject, cut) {
54
+ const own = this.by.get(subject);
55
+ if (own === undefined)
56
+ return undefined;
57
+ for (const e of own)
58
+ if (!within(cut, e.seq, e.sync))
59
+ return e.held;
60
+ return undefined;
61
+ }
62
+ /** Each time `subject` left, oldest first. */
63
+ of(subject) {
64
+ return this.by.get(subject) ?? [];
65
+ }
66
+ /** Every entry, oldest first. */
67
+ all() {
68
+ return this.head === 0 ? this.list : this.list.slice(this.head);
69
+ }
70
+ }
package/dist/locus.js CHANGED
@@ -1,4 +1,4 @@
1
- import { copy } from './clone.js';
1
+ import { first } from './clone.js';
2
2
  export function locusScratch(channels) {
3
3
  return {
4
4
  n: 0,
@@ -70,7 +70,7 @@ export function foldLocus(k, group) {
70
70
  k.values[slot] =
71
71
  this.lerpsInto[slot] !== undefined && channel.scale && Array.isArray(value)
72
72
  ? own(k, slot, value, channel.rest)
73
- : copy(value);
73
+ : first(value);
74
74
  continue;
75
75
  }
76
76
  const total = taken[slot] + weight;
package/dist/meet.js CHANGED
@@ -8,21 +8,27 @@ export function owable(lanes, slot) {
8
8
  if (lanes.metCrowd || lanes.owes(slot))
9
9
  return false;
10
10
  // Subjects meeting the same lanes in one fill get the same answer, and a list is kept only once true.
11
- if (lanes.owed.last(lanes.met))
12
- return true;
13
11
  const met = lanes.met;
12
+ const n = lanes.metN;
13
+ if (lanes.owed.last(met, n))
14
+ return true;
14
15
  let other = Number.NEGATIVE_INFINITY;
15
16
  for (const lane of lanes.lanes) {
16
17
  const id = lane.voice.id;
17
- if (id > other && !met.includes(id))
18
+ if (id <= other)
19
+ continue;
20
+ const at = met.indexOf(id);
21
+ if (at < 0 || at >= n)
18
22
  other = id;
19
23
  }
20
24
  for (const c of lanes.crowds)
21
25
  if (c.size > 0)
22
26
  other = Math.max(other, c.hot[(c.size - 1) * c.stride + 7 /* Hot.ID */]);
23
- for (const id of met)
27
+ for (let i = 0; i < n; i++) {
28
+ const id = met[i];
24
29
  if (id <= other || lanes.byId.get(id).group !== null)
25
30
  return false;
31
+ }
26
32
  return true;
27
33
  }
28
34
  /**
@@ -35,7 +41,7 @@ export function meet(lanes, slot, subject, head, lane) {
35
41
  const from = seen < 0 ? -1 - seen : seen;
36
42
  lanes.per[slot * 6 /* Per.SLOT */ + 1 /* Per.SEEN */] = lanes.epochs;
37
43
  let met = false;
38
- lanes.met.length = 0;
44
+ lanes.metN = 0;
39
45
  lanes.metCrowd = false;
40
46
  const dense = lanes.dense;
41
47
  for (let i = dense.length - 1; i >= 0; i--) {
@@ -50,7 +56,7 @@ export function meet(lanes, slot, subject, head, lane) {
50
56
  lane.add(slot, held);
51
57
  if (lane.idle)
52
58
  reach(lanes, slot, 1);
53
- lanes.met.push(lane.voice.id);
59
+ lanes.met[lanes.metN++] = lane.voice.id;
54
60
  met = true;
55
61
  }
56
62
  // `open` marks every subject a voice naming it starts on, so an unmarked one has none to meet.
@@ -73,7 +79,7 @@ export function meet(lanes, slot, subject, head, lane) {
73
79
  lane.add(slot, held);
74
80
  if (lane.idle)
75
81
  reach(lanes, slot, 1);
76
- lanes.met.push(voice.id);
82
+ lanes.met[lanes.metN++] = voice.id;
77
83
  met = true;
78
84
  }
79
85
  if (!met)
@@ -83,7 +89,7 @@ export function meet(lanes, slot, subject, head, lane) {
83
89
  // otherwise it reads the general path all frame.
84
90
  const o = slot * 6 /* Per.SLOT */;
85
91
  if (lane && lanes.per[o + 2 /* Per.IDLE */] === 0 && owable(lanes, slot)) {
86
- lanes.owed.owe(slot, lanes.met);
92
+ lanes.owed.owe(slot, lanes.met, lanes.metN);
87
93
  return true;
88
94
  }
89
95
  lanes.per[o + 0 /* Per.FILLED */] = lanes.fills;
package/dist/mixer.d.ts CHANGED
@@ -2,9 +2,10 @@ import { blended, laneRead, shareKept } from './blend.js';
2
2
  import type { Book } from './book.js';
3
3
  import { aims, chain, linkable, relink } from './chain.js';
4
4
  import { type LerpInto } from './channels.js';
5
+ import { DepartedIndex } from './departed.js';
5
6
  import type { Due } from './due.js';
6
7
  import { parting } from './fade.js';
7
- import { apply, baseFor, clamp, contribution, fold, folded, foldLoci, foldWith, isRest, type Key, keyed, linked, owedBy, passes, read, slopeFor, tick } from './fold.js';
8
+ import { apply, baseFor, clamp, contribution, fold, foldDetour, folded, foldLoci, foldSharers, foldWith, isRest, type Key, keyed, linked, owedBy, passes, read, shared, slopeFor, tick } from './fold.js';
8
9
  import { GoneIndex } from './gone.js';
9
10
  import type { HandleHost } from './handle.js';
10
11
  import { held, shownOf, sinceOf, unreachedOf } from './held.js';
@@ -23,6 +24,13 @@ import { type Announced, Transport } from './transport.js';
23
24
  import type { Booker, BookOptions, Channel, Columns, Engine, Handle, Kit, Marked, Mix, MixOptions, OwnerSpec, Patch, Projection, Sent, Signal, SpanHandle, SpanSpec, Tape, VoiceSpec } from './types.js';
24
25
  import { type Subject, type Voice } from './voice.js';
25
26
  import { base, capped, fadeOf, horizonFor, ownedBy, ownerBase, prime, weigh, weighOwned } from './weigh.js';
27
+ /**
28
+ * What a mix takes where its options say nothing. Not exported from the package: the suite turns
29
+ * `lanes` off to run every test on the general path, which is what makes each one an equivalence check.
30
+ */
31
+ export declare const defaults: {
32
+ lanes: boolean;
33
+ };
26
34
  export declare class Mixer<I, O> implements Mix<I, O> {
27
35
  readonly kit: Kit<O>;
28
36
  readonly opts: MixOptions;
@@ -37,12 +45,18 @@ export declare class Mixer<I, O> implements Mix<I, O> {
37
45
  backward: boolean;
38
46
  /** True during a sync, so a control change it makes is recorded as showing in that frame. */
39
47
  moving: boolean;
48
+ /**
49
+ * Whether a probe or pull has read the mix since its clock last moved. A control change made
50
+ * before that shows in this frame live, so it is recorded as showing in it.
51
+ */
52
+ looked: boolean;
40
53
  /**
41
54
  * Under `history` with `inputs`, the host fields patches read, copied each frame they changed. In
42
55
  * a projection reading back, the copy in force then.
43
56
  */
44
57
  hostLog: {
45
58
  at: number;
59
+ seq: number;
46
60
  fields: Record<string, unknown>;
47
61
  }[];
48
62
  hostThen: {
@@ -62,20 +76,23 @@ export declare class Mixer<I, O> implements Mix<I, O> {
62
76
  /** Taken off its transport, so it takes no more calls until a seek puts it back. */
63
77
  dropped: boolean;
64
78
  readonly name: string | undefined;
65
- /** This mix's frame among every mix's: moved by a sync, and by a control change or drop within one. */
79
+ /**
80
+ * This mix's frame among every mix's: moved by a sync or seek, and by a control change, drop,
81
+ * or touch within one.
82
+ */
66
83
  frame: number;
67
84
  /** Pending voices whose start the host gave, by its host time, kept where the rate puts it. */
68
85
  pins: Map<Voice<I, O>, number> | null;
69
86
  wantsPose: boolean;
70
87
  /** The pose `pull` folds a subject into where it cannot read straight from the lanes. */
71
88
  scratch: O | undefined;
72
- /** The last array `pull` read, by position, with each subject's chain head, to skip the lookup. */
73
89
  /** Live voices by the earliest mix time `moveTo` would change each, a binary heap. */
74
90
  due: Due<I, O>[];
75
91
  /** Voices retired since `moveTo` last pruned them, in the order they retired. */
76
92
  retired: Voice<I, O>[];
77
93
  /** Visit every voice each sync, as before the due queue; for tests that compare the two. */
78
94
  walkAll: boolean;
95
+ /** The last array `pull` read, by position, with each subject's chain head, to skip the lookup. */
79
96
  pulled: I[];
80
97
  pulledHeads: (Subject<unknown> | undefined)[];
81
98
  /** Each remembered head's lane slot, and the `version` and `relinks` they were all current at. */
@@ -97,16 +114,18 @@ export declare class Mixer<I, O> implements Mix<I, O> {
97
114
  onWake(fn: () => void): () => void;
98
115
  /**
99
116
  * Per subject, the first record of the chain through every live voice that reaches it, or a stub
100
- * where none does. Relinked when `version` moves, which is whenever the list or a voice's pending
101
- * state changes.
117
+ * where none does. Relinked when `version` moves, or when a voice naming the subject marks its
118
+ * head stale: see `changed` in `chain.ts`.
102
119
  */
103
120
  chains: Store<I, Subject<unknown>>;
104
121
  version: number;
105
- readonly steps: Steps<Voice<I, O>>;
122
+ readonly steps: Steps<Voice<I, O>, I>;
106
123
  /** Per subject, the voices whose `subjects` name it, in voice order. */
107
124
  named: Store<I, Voice<I, O>[]>;
108
125
  /** Voices a subject has been faded out of, which `drop` looks in besides those that reach it. */
109
126
  readonly parters: Set<Voice<I, O>>;
127
+ /** What an anchor can still find of voices that left without history, or past its reach. */
128
+ readonly departed: DepartedIndex;
110
129
  /**
111
130
  * Motion patches that may hold state for a subject no voice still reaching it plays, which `drop`
112
131
  * frees besides those voices' own: per subject, a patch a voice naming it left while another
@@ -120,6 +139,15 @@ export declare class Mixer<I, O> implements Mix<I, O> {
120
139
  naming: number;
121
140
  /** The voices in the list that name no subjects, in voice order. */
122
141
  general: Voice<I, O>[];
142
+ /** Those of `general` that keep one record for every subject, in voice order: see `everyone.ts`. */
143
+ sharers: Voice<I, O>[];
144
+ /**
145
+ * Whether a voice stopped sharing this frame: a probe or pull under way then holds chains linked
146
+ * before it joined them, and takes each again.
147
+ */
148
+ unshared: boolean;
149
+ /** 0 where a fold is a walk of the chain: see `detour` in `everyone.ts`. */
150
+ detours: number;
123
151
  /** How many voices in the list carry a locus; with none, a fold allocates nothing. */
124
152
  loci: number;
125
153
  /** What every motion patch it plays asks for its voice's time, holding this mix weakly. */
@@ -216,6 +244,7 @@ export declare class Mixer<I, O> implements Mix<I, O> {
216
244
  over?: number;
217
245
  }): void;
218
246
  drop(subject: I): void;
247
+ touch(subject?: I): void;
219
248
  drain<E = unknown>(tag?: string): Sent<I, E>[];
220
249
  /** Voices whose stop 0 is mid-computation, so a fold for one cannot re-enter itself. */
221
250
  readonly folding: Set<number>;
@@ -225,7 +254,7 @@ export declare class Mixer<I, O> implements Mix<I, O> {
225
254
  * is first asked, when probes start writing it.
226
255
  */
227
256
  restStamps: Store<I, number> | null;
228
- /** What a probe's record of rest is good for: moved by a new frame, version or relink. */
257
+ /** What a probe's record of rest is good for: moved by a new frame, version, relink, or stir. */
229
258
  private restsEpoch;
230
259
  private restsFrame;
231
260
  private restsVersion;
@@ -243,6 +272,9 @@ declare const methods: {
243
272
  foldWith: typeof foldWith;
244
273
  owedBy: typeof owedBy;
245
274
  foldLoci: typeof foldLoci;
275
+ foldDetour: typeof foldDetour;
276
+ foldSharers: typeof foldSharers;
277
+ shared: typeof shared;
246
278
  read: typeof read;
247
279
  contribution: typeof contribution;
248
280
  keyed: typeof keyed;