@msb235/blits 0.15.1 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +184 -0
  2. package/README.md +1 -1
  3. package/dist/ahead.js +11 -13
  4. package/dist/chain.js +2 -3
  5. package/dist/clock.d.ts +11 -1
  6. package/dist/clock.js +28 -9
  7. package/dist/color.d.ts +36 -14
  8. package/dist/color.js +186 -58
  9. package/dist/columns.d.ts +1 -11
  10. package/dist/columns.js +1 -6
  11. package/dist/contribution.js +11 -4
  12. package/dist/crowd.d.ts +0 -6
  13. package/dist/crowd.js +0 -11
  14. package/dist/cue.js +19 -7
  15. package/dist/due.d.ts +18 -0
  16. package/dist/due.js +101 -2
  17. package/dist/everyone.d.ts +17 -6
  18. package/dist/everyone.js +17 -8
  19. package/dist/fade.d.ts +22 -1
  20. package/dist/fade.js +81 -12
  21. package/dist/fill.js +2 -1
  22. package/dist/fit.js +8 -0
  23. package/dist/fold.js +4 -7
  24. package/dist/gone.js +1 -0
  25. package/dist/handle.d.ts +1 -1
  26. package/dist/handle.js +3 -1
  27. package/dist/held.d.ts +2 -1
  28. package/dist/held.js +17 -10
  29. package/dist/hosts.d.ts +7 -0
  30. package/dist/hosts.js +23 -6
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +1 -1
  33. package/dist/lane.d.ts +2 -0
  34. package/dist/lane.js +2 -0
  35. package/dist/lanes.d.ts +26 -1
  36. package/dist/lanes.js +9 -1
  37. package/dist/lch.d.ts +23 -0
  38. package/dist/lch.js +126 -0
  39. package/dist/meet.js +9 -0
  40. package/dist/mixer.d.ts +6 -1
  41. package/dist/mixer.js +5 -0
  42. package/dist/motion.js +6 -6
  43. package/dist/motions.d.ts +4 -1
  44. package/dist/motions.js +16 -2
  45. package/dist/move.js +311 -79
  46. package/dist/origin.d.ts +2 -0
  47. package/dist/origin.js +4 -0
  48. package/dist/owner.d.ts +7 -2
  49. package/dist/owner.js +17 -5
  50. package/dist/paging.d.ts +5 -1
  51. package/dist/paging.js +13 -3
  52. package/dist/patch.js +24 -4
  53. package/dist/place.js +67 -31
  54. package/dist/project.d.ts +5 -2
  55. package/dist/project.js +9 -11
  56. package/dist/pull.d.ts +11 -0
  57. package/dist/pull.js +10 -6
  58. package/dist/qualify.js +7 -2
  59. package/dist/rests.js +9 -1
  60. package/dist/revive.d.ts +11 -1
  61. package/dist/revive.js +7 -0
  62. package/dist/roles.js +6 -5
  63. package/dist/roster.d.ts +6 -0
  64. package/dist/roster.js +8 -1
  65. package/dist/rows.js +4 -1
  66. package/dist/sample.js +2 -1
  67. package/dist/seat.d.ts +5 -1
  68. package/dist/seat.js +5 -1
  69. package/dist/seek.js +6 -16
  70. package/dist/shared.d.ts +1 -1
  71. package/dist/shared.js +1 -1
  72. package/dist/spans.d.ts +13 -4
  73. package/dist/spans.js +125 -32
  74. package/dist/touch.js +1 -2
  75. package/dist/trace.js +7 -2
  76. package/dist/turns.js +2 -1
  77. package/dist/types/transport.d.ts +2 -1
  78. package/dist/types/voice.d.ts +3 -2
  79. package/dist/voice.d.ts +59 -2
  80. package/dist/voice.js +76 -2
  81. package/dist/weigh.js +1 -1
  82. package/docs/schema.html +38 -12
  83. package/package.json +3 -1
package/dist/place.js CHANGED
@@ -56,39 +56,75 @@ export function checkPlacement(mix, spec, anchor, owner) {
56
56
  throw new Error('blits: a placement anchors out or end, not both');
57
57
  if (spec.start !== undefined && (anchor.start !== undefined || anchor.in !== undefined))
58
58
  throw new Error('blits: a voice takes start or an anchored start, not both');
59
- const name = spec.name;
60
- if (name === undefined)
61
- return;
62
- // A voice is known by its owner, its score and its name, and by its mix where it names no score;
63
- // a bare name in an anchor is in the asker's score under the asker's owner.
64
- const key = (o, score, n, slot) => `${score === undefined ? slot : ''}\u0000${o?.id ?? 0}\u0000${score ?? ''}\u0000${n}`;
65
- const named = (a, score, o, slot) => {
59
+ const self = {
60
+ id: mix.nextId,
61
+ spec,
62
+ owner,
63
+ slot: mix.slot,
64
+ writes: spec.patch?.writes ?? none,
65
+ };
66
+ const voices = [self];
67
+ for (const m of mix.transport.members)
68
+ for (const list of [m.gone, m.cued])
69
+ for (const v of list)
70
+ voices.push({
71
+ id: v.id,
72
+ spec: v.spec,
73
+ owner: v.owner,
74
+ slot: m.slot,
75
+ writes: v.patch.writes,
76
+ });
77
+ const refuse = () => {
78
+ throw new Error(`blits: ${spec.name ?? 'a voice'}'s placement waits on itself`);
79
+ };
80
+ // Where the marks a query asks for are fixed: a voice's start, in, coast and inner marks by its
81
+ // start's anchors, its out and end by its out's anchors and its start.
82
+ const targets = (asker, a) => {
66
83
  if ('all' in a)
67
- return a.all.flatMap((m) => named(m, score, o, slot));
84
+ return a.all.flatMap((m) => targets(asker, m));
68
85
  if ('any' in a)
69
- return a.any.flatMap((m) => named(m, score, o, slot));
70
- const q = 'of' in a ? a.of : 'after' in a ? a.after : 'with' in a ? a.with : a.before;
71
- if (typeof q === 'string')
72
- return [key(o, score, q, slot)];
73
- return q.name === undefined ? [] : [key(o, q.score ?? score, q.name, slot)];
86
+ return a.any.flatMap((m) => targets(asker, m));
87
+ const given = 'of' in a ? a.of : 'after' in a ? a.after : 'with' in a ? a.with : a.before;
88
+ const mark = 'of' in a ? a.mark : 'after' in a ? 'end' : 'start';
89
+ const q = typeof given === 'string' ? { name: given } : given;
90
+ const score = q.score ?? asker.spec.score;
91
+ const anywhere = q.score !== undefined;
92
+ if (asker === self && q.name !== undefined && q.name === spec.name && score === spec.score)
93
+ refuse();
94
+ const met = voices.filter((v) => v !== asker &&
95
+ v.spec.score === score &&
96
+ (anywhere || (v.owner === asker.owner && v.slot === asker.slot)) &&
97
+ (q.name === undefined || v.spec.name === q.name) &&
98
+ (q.tag === undefined || (v.spec.tags ?? none).includes(q.tag)) &&
99
+ (q.writes === undefined || v.writes.includes(q.writes)));
100
+ const resolver = q.resolver ?? 'last';
101
+ const picked = met.length > 1 && (resolver === 'first' || resolver === 'last')
102
+ ? [met.reduce((p, v) => ((resolver === 'last' ? v.id > p.id : v.id < p.id) ? v : p))]
103
+ : met;
104
+ const side = mark === 'out' || mark === 'end' ? 'out' : 'start';
105
+ return picked.map((v) => ({ voice: v, side }));
74
106
  };
75
- const names = (p, score, o, slot) => [p.start, p.in, p.out, p.end].flatMap((a) => a === undefined || typeof a === 'number' ? [] : named(a, score, o, slot));
76
- const self = key(owner, spec.score, name, mix.slot);
77
- const seen = new Set();
78
- const waits = names(anchor, spec.score, owner, mix.slot);
79
- while (waits.length > 0) {
80
- const n = waits.pop();
81
- if (n === self)
82
- throw new Error(`blits: ${name}'s placement waits on itself`);
83
- if (seen.has(n))
84
- continue;
85
- seen.add(n);
86
- for (const m of mix.transport.members)
87
- for (const v of m.cued)
88
- if (v.spec.name !== undefined &&
89
- key(v.owner, v.spec.score, v.spec.name, m.slot) === n &&
90
- v.spec.anchor)
91
- waits.push(...names(v.spec.anchor, v.spec.score, v.owner, m.slot));
107
+ const waitsOf = (p, side) => {
108
+ const a = p.spec.anchor;
109
+ const by = side === 'start' ? [a?.start, a?.in] : [a?.out, a?.end];
110
+ const out = side === 'out' ? [{ voice: p, side: 'start' }] : [];
111
+ for (const b of by)
112
+ if (b !== undefined && typeof b !== 'number')
113
+ out.push(...targets(p, b));
114
+ return out;
115
+ };
116
+ for (const from of ['start', 'out']) {
117
+ const seen = { start: new Set(), out: new Set() };
118
+ const stack = waitsOf(self, from);
119
+ while (stack.length > 0) {
120
+ const { voice, side } = stack.pop();
121
+ if (voice === self && side === from)
122
+ refuse();
123
+ if (seen[side].has(voice))
124
+ continue;
125
+ seen[side].add(voice);
126
+ stack.push(...waitsOf(voice, side));
127
+ }
92
128
  }
93
129
  }
94
130
  /**
@@ -158,7 +194,7 @@ export function place(mix) {
158
194
  export function resolve(mix, a, self, before) {
159
195
  const o = self.owner;
160
196
  if (typeof a === 'number')
161
- return o === null ? mixAt(mix, a - mix.offset) : a;
197
+ return o === null ? mixAt(mix, a - self.offset) : a;
162
198
  if ('all' in a) {
163
199
  let latest = Number.NEGATIVE_INFINITY;
164
200
  for (const m of a.all) {
package/dist/project.d.ts CHANGED
@@ -12,8 +12,11 @@ import type { Voice } from './voice.js';
12
12
  export declare function projectAll(transport: Transport, at: number | {
13
13
  readonly frame: number;
14
14
  }): TransportProjection;
15
- /** Sets each copied voice's state to what its clock and fade make it at `t`. */
16
- export declare function stateAt<I, O>(c: Mixer<I, O>, t: number): void;
15
+ /**
16
+ * Sets each copied voice's state to what its clock and fade make it at `t`; with `cut`, the frames
17
+ * up to which history says which had started.
18
+ */
19
+ export declare function stateAt<I, O>(c: Mixer<I, O>, t: number, cut?: Cut): void;
17
20
  /**
18
21
  * A projection back starts each subject from the latest copy kept at or before `t`, else from its
19
22
  * voice's start with fresh state, which is exact for a voice stepped at a fixed interval.
package/dist/project.js CHANGED
@@ -7,7 +7,7 @@ import { fadesAt } from './fade.js';
7
7
  import { meet, origin } from './held.js';
8
8
  import { copyHeld, last, lastWithin } from './history.js';
9
9
  import { Mixer } from './mixer.js';
10
- import { heldByInput, ownerReading, relink } from './owner.js';
10
+ import { heldByInput, relink } from './owner.js';
11
11
  import { reading } from './reading.js';
12
12
  import { record } from './record.js';
13
13
  import { recount } from './roster.js';
@@ -72,6 +72,7 @@ export function projectAll(transport, at) {
72
72
  }, null);
73
73
  copy.slot = mix.slot;
74
74
  copy.projecting = true;
75
+ copy.share = mix.share;
75
76
  copy.places = mix.places;
76
77
  copies.set(mix, copy);
77
78
  if (ahead)
@@ -177,19 +178,16 @@ function copyBack(mix, c, t, cut) {
177
178
  ownBlends(c.cued);
178
179
  if (mix.owners !== null)
179
180
  relink(c.cued, cut.seq);
180
- stateAt(c, t);
181
+ stateAt(c, t, cut);
181
182
  recount(c);
182
183
  }
183
- /** Sets each copied voice's state to what its clock and fade make it at `t`. */
184
- export function stateAt(c, t) {
184
+ /**
185
+ * Sets each copied voice's state to what its clock and fade make it at `t`; with `cut`, the frames
186
+ * up to which history says which had started.
187
+ */
188
+ export function stateAt(c, t, cut) {
185
189
  for (const copy of c.cued)
186
- copy.state = copy.out
187
- ? 'fading'
188
- : (copy.owner === null ? t : ownerReading(copy.owner, t)) < copy.start
189
- ? 'pending'
190
- : copy.freezesAfter && copy.elapsedAt(t) >= copy.span + copy.latest
191
- ? 'frozen'
192
- : 'live';
190
+ copy.state = copy.stateAt(t, cut);
193
191
  }
194
192
  /**
195
193
  * A projection back starts each subject from the latest copy kept at or before `t`, else from its
package/dist/pull.d.ts CHANGED
@@ -1,5 +1,16 @@
1
1
  import type { Mixer } from './mixer.js';
2
2
  import type { Columns } from './types.js';
3
+ /** One channel `pull` writes: where in the kit, how many numbers a subject, and its rest. */
4
+ export interface Column {
5
+ key: string;
6
+ slot: number;
7
+ axes: number;
8
+ out: Float64Array;
9
+ /** The rest, clamped, or NaN for a channel with none. */
10
+ rest: Float64Array;
11
+ bounds: readonly [number, number] | undefined;
12
+ }
13
+ export declare function clampRun(out: Float64Array, at: number, n: number, [lo, hi]: readonly [number, number]): void;
3
14
  /** After a probe, keeps the pose a retarget from `'current'` reads as the subject's last. */
4
15
  export declare function keep<I, O>(this: Mixer<I, O>, subject: I, pose: O, out: O | undefined): void;
5
16
  export declare function pull<I, O>(mix: Mixer<I, O>, subjects: Iterable<I>, into: Columns<O>): void;
package/dist/pull.js CHANGED
@@ -1,9 +1,13 @@
1
1
  import { axesOf } from './channels.js';
2
2
  import { copy } from './clone.js';
3
- import { clampRun, flush, pullRun, writeLater } from './columns.js';
4
- import { renumber } from './renumber.js';
5
3
  /** What `pull` remembers at a position it has read no subject at. */
6
4
  const UNREAD = Symbol('unread');
5
+ export function clampRun(out, at, n, [lo, hi]) {
6
+ for (let i = at; i < at + n; i++) {
7
+ const x = out[i];
8
+ out[i] = x < lo ? lo : x > hi ? hi : x;
9
+ }
10
+ }
7
11
  /** Writes a folded pose's value into subject `n`'s places in a column. */
8
12
  function writeValue(c, v, n) {
9
13
  const at = n * c.axes;
@@ -112,7 +116,7 @@ export function pull(mix, subjects, into) {
112
116
  }
113
117
  finally {
114
118
  if (lanes !== null)
115
- flush(lanes);
119
+ lanes.pulls.flush(lanes);
116
120
  }
117
121
  // Every head is current only if nothing relinked or changed version while they were read.
118
122
  const unchanged = live && mix.version === version && mix.relinks === relinks;
@@ -136,7 +140,7 @@ function readAll(mix, list, columns, live, current, since, whole) {
136
140
  const runs = live && whole;
137
141
  for (let n = 0; n < list.length; n++) {
138
142
  if (runs) {
139
- n = pullRun(lanes, slots, list, was, since, n, columns, now, mix.version);
143
+ n = lanes.pulls.pullRun(lanes, slots, list, was, since, n, columns, now, mix.version);
140
144
  if (n === list.length)
141
145
  break;
142
146
  }
@@ -156,7 +160,7 @@ function readAll(mix, list, columns, live, current, since, whole) {
156
160
  // Numbered out of the list's order, as swap-remove leaves a subject: renumbered into it.
157
161
  const to = n === 0 ? 0 : slots[n - 1] + 1;
158
162
  if (runs && slot >= 0 && slot !== to && to >= 0) {
159
- if (renumber(mix, subject, head, to)) {
163
+ if (lanes.pulls.renumber(mix, subject, head, to)) {
160
164
  for (let j = was.indexOf(subject); j >= 0; j = was.indexOf(subject, j + 1))
161
165
  slots[j] = to;
162
166
  slot = to;
@@ -167,7 +171,7 @@ function readAll(mix, list, columns, live, current, since, whole) {
167
171
  }
168
172
  const laned = live && lanes?.prepare(slot, subject, mix.version, heads[n] ?? null) === true;
169
173
  if (laned && whole && lanes.whole && !lanes.owes(slot)) {
170
- writeLater(lanes, slot, columns, n, slots);
174
+ lanes.pulls.writeLater(lanes, slot, columns, n, slots);
171
175
  continue;
172
176
  }
173
177
  const head = live ? heads[n] : null;
package/dist/qualify.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { numericOf } from './channels.js';
2
2
  import { flush } from './columns.js';
3
- import { crowdable } from './crowd.js';
4
3
  import { byId } from './due.js';
5
4
  import { weighAt } from './everyone.js';
6
5
  import { wake } from './fill.js';
6
+ import { crowdable } from './hosts.js';
7
7
  import { Lane } from './lane.js';
8
8
  import { reach } from './meet.js';
9
9
  import { bury, compactSparse, join, regroup, retouchRow, sameCrowds } from './rows.js';
@@ -96,7 +96,11 @@ export function requalify(lanes, version) {
96
96
  crowded++;
97
97
  continue;
98
98
  }
99
- const lane = lanes.byId.get(v.id) ?? new Lane(v);
99
+ let lane = lanes.byId.get(v.id);
100
+ if (lane === undefined) {
101
+ lane = new Lane(v);
102
+ lane.made = lanes.probes;
103
+ }
100
104
  v.laned = true;
101
105
  if (lane.epoch === 0 && v.state !== 'pending')
102
106
  opened(lanes, lane);
@@ -395,6 +399,7 @@ function placeAll(lanes, lane) {
395
399
  */
396
400
  function enlane(lanes, v) {
397
401
  const lane = new Lane(v);
402
+ lane.made = lanes.probes;
398
403
  v.laned = true;
399
404
  if (v.state !== 'pending')
400
405
  opened(lanes, lane);
package/dist/rests.js CHANGED
@@ -56,7 +56,15 @@ export function still(voice, now) {
56
56
  if (voice.state === 'frozen' || voice.holding !== null)
57
57
  return true;
58
58
  const motion = voice.motion;
59
- return motion !== undefined && voice.elapsedAt(now) >= voice.latest && motion.landed();
59
+ if (motion === undefined || !(voice.elapsedAt(now) >= voice.latest))
60
+ return false;
61
+ // A subject met inside its stagger and not read since has no run yet, and moves once it is.
62
+ const early = voice.early;
63
+ if (early !== null)
64
+ for (const h of early)
65
+ if (h.since <= now && !(h.probed >= h.since))
66
+ return false;
67
+ return motion.landed();
60
68
  }
61
69
  export function restsLaned() {
62
70
  return this.lanes.whole && this.bounded.length === 0;
package/dist/revive.d.ts CHANGED
@@ -5,7 +5,7 @@ import { type Key } from './paging.js';
5
5
  import type { Run } from './stretch.js';
6
6
  import type { Parted, Parting } from './subject.js';
7
7
  import type { Described } from './types.js';
8
- import { type Controls, type Ramp, type Rise, Voice } from './voice.js';
8
+ import { type Controls, type Earlier, type Ramp, type Rise, Voice } from './voice.js';
9
9
  /** A voice that has left, as plain data: the descriptor it was cued with, its controls and its records. */
10
10
  interface PackedVoice {
11
11
  as: Described;
@@ -21,6 +21,8 @@ interface PackedVoice {
21
21
  weight: number;
22
22
  out: Ramp | null;
23
23
  back: Rise | null;
24
+ /** Absent from a store written before a voice kept them. */
25
+ stalls?: readonly number[] | null;
24
26
  outAt: number;
25
27
  outOver: number;
26
28
  outSet: boolean;
@@ -28,12 +30,20 @@ interface PackedVoice {
28
30
  hold: number;
29
31
  past: number;
30
32
  clocks: Past[] | null;
33
+ /** Absent from a store written before a voice kept it. */
34
+ earlier?: Earlier | null;
35
+ /** Absent from a store written before a voice kept it. */
36
+ later?: Earlier | null;
31
37
  cuedAt: number;
32
38
  cuedSeq: number;
39
+ /** Absent from a store written before a voice kept it. */
40
+ begunSeq?: number;
33
41
  doneAt: number;
34
42
  doneSeq: number;
35
43
  opened: number;
36
44
  pinned: number;
45
+ /** Absent from a store written before a voice kept it. */
46
+ offset?: number;
37
47
  latest: number;
38
48
  /** Absent from a store written before a voice kept it. */
39
49
  soonest?: number;
package/dist/revive.js CHANGED
@@ -79,19 +79,24 @@ function packVoice(voice, keys) {
79
79
  weight: voice.weight,
80
80
  out: voice.out,
81
81
  back: voice.back,
82
+ stalls: voice.stalls,
82
83
  outAt: voice.outAt,
83
84
  outOver: voice.outOver,
84
85
  outSet: voice.outSet,
85
86
  rebuilds: voice.rebuilds,
86
87
  hold: voice.hold,
87
88
  past: voice.clocks?.length ?? 0,
89
+ earlier: voice.earlier,
90
+ later: voice.later,
88
91
  clocks: voice.clocks,
89
92
  cuedAt: voice.cuedAt,
90
93
  cuedSeq: voice.cuedSeq,
94
+ begunSeq: voice.begunSeq,
91
95
  doneAt: voice.doneAt,
92
96
  doneSeq: voice.doneSeq,
93
97
  opened: voice.opened,
94
98
  pinned: voice.pinned,
99
+ offset: voice.offset,
95
100
  latest: voice.latest,
96
101
  soonest: voice.soonest,
97
102
  keeping: voice.keeping,
@@ -127,10 +132,12 @@ export function reviveVoice(mix, id, d) {
127
132
  voice.take(d);
128
133
  voice.cuedAt = d.cuedAt;
129
134
  voice.cuedSeq = d.cuedSeq;
135
+ voice.begunSeq = d.begunSeq ?? Number.POSITIVE_INFINITY;
130
136
  voice.doneAt = d.doneAt;
131
137
  voice.doneSeq = d.doneSeq;
132
138
  voice.opened = d.opened;
133
139
  voice.pinned = d.pinned;
140
+ voice.offset = d.offset ?? 0;
134
141
  voice.latest = d.latest;
135
142
  voice.soonest = d.soonest ?? 0;
136
143
  voice.keeping = d.keeping;
package/dist/roles.js CHANGED
@@ -1,9 +1,7 @@
1
1
  import { first } from './clone.js';
2
- import { open as openRecord } from './everyone.js';
3
2
  import { merged, unband } from './fold.js';
4
3
  import { roleScratch } from './role.js';
5
4
  // V8 reads a local const as a constant, where it reads an import from its module on every call.
6
- const open = openRecord;
7
5
  /**
8
6
  * Whether a fold's next voice is its chain's next record, `held`, and not the next voice sharing
9
7
  * one record, `sharers[q]`: the two lists are each in voice order, and a fold takes them as one.
@@ -54,15 +52,18 @@ export function foldRoles(subject, pose, head, laned, dry, except, owed) {
54
52
  continue;
55
53
  const held = rec;
56
54
  if (!mine)
57
- open(voice, held, head.slot);
55
+ this.share.open(voice, held, head.slot);
58
56
  const delta = this.read(voice, subject, now, dry, held);
59
57
  if (owed >= 0 && voice.laned)
60
58
  this.lanes.paid(voice.id, owed, held.weight);
61
59
  if (!mine)
62
60
  q = this.shared(voice, head.slot, held, q);
63
- if (delta === null)
64
- continue;
65
61
  const role = voice.spec.role;
62
+ if (delta === null) {
63
+ if (role === undefined && !(held.weight > 0))
64
+ unband(held);
65
+ continue;
66
+ }
66
67
  let group = -1;
67
68
  if (role !== undefined) {
68
69
  group = k.names.indexOf(role);
package/dist/roster.d.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  import type { Mixer } from './mixer.js';
2
2
  import type { Voice } from './voice.js';
3
+ /**
4
+ * Counts what makes a mix's fold more than a walk of each chain: a role, a voice sharing one
5
+ * record, a voice that stopped sharing this frame. Called wherever one of the three changes, so
6
+ * `foldWith` asks one number.
7
+ */
8
+ export declare function detour<I, O>(mix: Mixer<I, O>): void;
3
9
  /** Counts a voice just put in the list into everything a sync and a fold skip work by. */
4
10
  export declare function enlist<I, O>(mix: Mixer<I, O>, voice: Voice<I, O>): void;
5
11
  /**
package/dist/roster.js CHANGED
@@ -1,6 +1,13 @@
1
1
  import { index, unindex } from './chain.js';
2
- import { detour } from './everyone.js';
3
2
  import { Store } from './store.js';
3
+ /**
4
+ * Counts what makes a mix's fold more than a walk of each chain: a role, a voice sharing one
5
+ * record, a voice that stopped sharing this frame. Called wherever one of the three changes, so
6
+ * `foldWith` asks one number.
7
+ */
8
+ export function detour(mix) {
9
+ mix.detours = mix.roleVoices + mix.sharers.length + (mix.unshared ? 1 : 0);
10
+ }
4
11
  function tally(mix, voice, by) {
5
12
  if (voice.spec.role !== undefined)
6
13
  mix.roleVoices += by;
package/dist/rows.js CHANGED
@@ -106,7 +106,7 @@ export function join(lanes, v, from, at, old) {
106
106
  newRow(lanes, c, p, v);
107
107
  if (c.hot[p * c.stride + 6 /* Hot.EPOCH */] === 0 && v.state !== 'pending')
108
108
  c.hot[p * c.stride + 6 /* Hot.EPOCH */] = open(lanes, v);
109
- const seat = new Seat(lanes, c, p);
109
+ const seat = new Seat(lanes, c, p, from === undefined ? lanes.probes : 0);
110
110
  lanes.seats.set(v.id, seat);
111
111
  v.seat = seat;
112
112
  v.laned = true;
@@ -298,5 +298,8 @@ export function reportedRow(lanes, c, p, slot) {
298
298
  return undefined;
299
299
  if (c.list[p] !== slot)
300
300
  return undefined;
301
+ const made = seatOf(lanes, c.voices[p])?.made ?? 0;
302
+ if (!(lanes.per[o + 3 /* Per.LANE_PROBE */] > made))
303
+ return undefined;
301
304
  return c.data[p * 8 /* Row.STRIDE */ + (lanes.per[o + 5 /* Per.LANE_FILL */] === lanes.fills ? 2 /* Row.WEIGHT */ : 3 /* Row.PROBED */)];
302
305
  }
package/dist/sample.js CHANGED
@@ -43,7 +43,8 @@ export function runMotion(lanes, lane, run) {
43
43
  }
44
44
  const samples = lane.samples;
45
45
  const xs = run.xs;
46
- const ease = run.ease;
46
+ // A tween round the hue is no straight line between its ends: `sampleBare` reads it round the hue.
47
+ const ease = run.round ? undefined : run.ease;
47
48
  // A tween whose stretches tell the lane before they change keeps no sample: see `unbareLane`.
48
49
  if (run.watcher === null) {
49
50
  run.watcher = lanes;
package/dist/seat.d.ts CHANGED
@@ -9,7 +9,11 @@ export declare class Seat<I, O> {
9
9
  lanes: object | null;
10
10
  readonly crowd: Crowd<I, O>;
11
11
  row: number;
12
- constructor(lanes: object | null, crowd: Crowd<I, O>, row: number);
12
+ /** The lanes' probe count when its row was made new, 0 for a row copied from another crowd. */
13
+ readonly made: number;
14
+ constructor(lanes: object | null, crowd: Crowd<I, O>, row: number,
15
+ /** The lanes' probe count when its row was made new, 0 for a row copied from another crowd. */
16
+ made?: number);
13
17
  }
14
18
  /** `voice`'s seat in `lanes`, null where it has none there. */
15
19
  export declare function seatOf<I, O>(lanes: object, voice: Voice<I, O>): Seat<I, O> | null;
package/dist/seat.js CHANGED
@@ -7,10 +7,14 @@ export class Seat {
7
7
  lanes;
8
8
  crowd;
9
9
  row;
10
- constructor(lanes, crowd, row) {
10
+ made;
11
+ constructor(lanes, crowd, row,
12
+ /** The lanes' probe count when its row was made new, 0 for a row copied from another crowd. */
13
+ made = 0) {
11
14
  this.lanes = lanes;
12
15
  this.crowd = crowd;
13
16
  this.row = row;
17
+ this.made = made;
14
18
  }
15
19
  }
16
20
  /** `voice`'s seat in `lanes`, null where it has none there. */
package/dist/seek.js CHANGED
@@ -6,13 +6,11 @@ import { apart, copyHeld, copyHeldInto, lastIndex, lastWithin, unread } from './
6
6
  import { laneHost } from './hosts.js';
7
7
  import { Leavings } from './leavings.js';
8
8
  import { move, nextFrame } from './move.js';
9
- import { ownerReading } from './owner.js';
10
9
  import { place } from './place.js';
11
10
  import { unpull } from './pull.js';
12
11
  import { isRested } from './record.js';
13
12
  import { Restored } from './restored.js';
14
13
  import { recount } from './roster.js';
15
- import { refitAll } from './spans.js';
16
14
  import { Store } from './store.js';
17
15
  import { hostAhead, hostAt, replay } from './tape.js';
18
16
  import { cover, coverFrame, cut } from './unpage.js';
@@ -209,17 +207,6 @@ function restoreVoice(mix, voice, t, seq) {
209
207
  }
210
208
  records.back(t, cut, future);
211
209
  }
212
- /** Each voice's state at mix time `t`, once every clock above it is back where it was. */
213
- function stateAt(voice, t) {
214
- // A fade begins at once, even one whose ramp starts later, at the voice's start.
215
- if (voice.out !== null)
216
- return 'fading';
217
- if ((voice.owner === null ? t : ownerReading(voice.owner, t)) < voice.start)
218
- return 'pending';
219
- if (voice.freezesAfter && voice.elapsedAt(t) >= voice.span + voice.latest)
220
- return 'frozen';
221
- return 'live';
222
- }
223
210
  /**
224
211
  * Moves every mix on the transport to mix time `to`, as it stood at the end of that frame, or to
225
212
  * the end of the frame numbered `to.frame`.
@@ -333,8 +320,12 @@ function back(mix, t, seq, on) {
333
320
  kept.push(voice);
334
321
  for (const voice of kept)
335
322
  restoreVoice(mix, voice, t, seq);
336
- for (const voice of kept)
337
- voice.state = stateAt(voice, t);
323
+ const cut = { seq, strict: false };
324
+ for (const voice of kept) {
325
+ voice.state = voice.stateAt(t, cut);
326
+ if (voice.begunSeq > seq)
327
+ voice.begunSeq = Number.POSITIVE_INFINITY;
328
+ }
338
329
  mix.cued = kept;
339
330
  mix.gone = gone;
340
331
  mix.retired = [];
@@ -400,7 +391,6 @@ function back(mix, t, seq, on) {
400
391
  mix.pins ??= new Map();
401
392
  mix.pins.set(voice, voice.pinned);
402
393
  }
403
- refitAll(mix);
404
394
  mix.due.clear();
405
395
  for (const voice of kept)
406
396
  schedule(mix, voice);
package/dist/shared.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * The package version, for a fact whose shape follows the code too closely to version by hand:
3
3
  * copies of different releases keep apart. `test/copies.test.ts` holds it to `package.json`.
4
4
  */
5
- export declare const release = "0.15.1";
5
+ export declare const release = "0.16.1";
6
6
  /**
7
7
  * A WeakMap every copy of blits loaded in one realm shares, so a fact one copy records about a
8
8
  * patch or channel it made is read by another copy's mix. The name carries a version, bumped when
package/dist/shared.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * The package version, for a fact whose shape follows the code too closely to version by hand:
3
3
  * copies of different releases keep apart. `test/copies.test.ts` holds it to `package.json`.
4
4
  */
5
- export const release = '0.15.1';
5
+ export const release = '0.16.1';
6
6
  /**
7
7
  * A WeakMap every copy of blits loaded in one realm shares, so a fact one copy records about a
8
8
  * patch or channel it made is read by another copy's mix. The name carries a version, bumped when
package/dist/spans.d.ts CHANGED
@@ -15,11 +15,17 @@ export declare class Fitting {
15
15
  }
16
16
  /** Refuses a voice a span cannot fit: one it would not own the timing of, or one with no end. */
17
17
  export declare function checkChild<I, O>(spec: VoiceSpec<I, O>, child: Voice<I, O>): void;
18
+ /**
19
+ * Puts a child whose owner's clock ran back to before a fit changed its rate, or skipped it, back
20
+ * on the clock it ran on before, so it reads, run back over, what it read going forward; and once
21
+ * the owner's clock reaches that change again, takes the change again, as the layout stands.
22
+ */
23
+ export declare function unfit<I, O>(mix: Mixer<I, O>, child: Voice<I, O>, now: number): void;
18
24
  /**
19
25
  * Lays out a span's children and fits them into what is left of its budget, from its clock at mix
20
26
  * time `from`, default now. Only what is still to come moves: a playing child keeps its place and
21
27
  * may change rate, a pending one is placed. A span held by a span is fitted again by its own span
22
- * after.
28
+ * after. A span its clock ran back over keeps the layout it made going forward.
23
29
  */
24
30
  export declare function refit<I, O>(mix: Mixer<I, O>, span: Voice<I, O>, from?: number): void;
25
31
  /**
@@ -29,7 +35,10 @@ export declare function refit<I, O>(mix: Mixer<I, O>, span: Voice<I, O>, from?:
29
35
  export declare function refloor<I, O>(mix: Mixer<I, O>): void;
30
36
  /** Whether a span whose last child just left at mix time `at` stays, its budget not yet out. */
31
37
  export declare function lingers<I, O>(span: Voice<I, O>, at: number): boolean;
32
- /** Lets the spans that are empty and out of budget leave, at the moment their budget ran out. */
38
+ /**
39
+ * Lets the spans out of budget that hold no child still playing leave, at the moment their budget
40
+ * ran out, or, under history, stay quiet with an owner holding them.
41
+ */
33
42
  export declare function lapse<I, O>(mix: Mixer<I, O>, now: number): void;
34
- /** Fits every span again, as after a seek put its children back. */
35
- export declare function refitAll<I, O>(mix: Mixer<I, O>): void;
43
+ /** Whether a span's budget is out by `now` with no child of it still playing. */
44
+ export declare function lapses<I, O>(span: Voice<I, O>, now: number): boolean;