@msb235/blits 0.14.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/CHANGELOG.md +198 -0
  2. package/README.md +3 -3
  3. package/dist/ahead.d.ts +26 -0
  4. package/dist/ahead.js +264 -0
  5. package/dist/around.d.ts +11 -0
  6. package/dist/around.js +32 -0
  7. package/dist/bare.js +37 -14
  8. package/dist/blank.d.ts +10 -0
  9. package/dist/blank.js +26 -0
  10. package/dist/blend.d.ts +2 -1
  11. package/dist/blend.js +1 -6
  12. package/dist/bound.d.ts +70 -0
  13. package/dist/bound.js +351 -0
  14. package/dist/chain.d.ts +2 -1
  15. package/dist/chain.js +7 -5
  16. package/dist/changes.d.ts +24 -0
  17. package/dist/changes.js +189 -0
  18. package/dist/channels.d.ts +39 -13
  19. package/dist/channels.js +14 -5
  20. package/dist/color.d.ts +12 -10
  21. package/dist/color.js +25 -25
  22. package/dist/columns.d.ts +2 -1
  23. package/dist/columns.js +62 -38
  24. package/dist/contribution.d.ts +30 -0
  25. package/dist/contribution.js +374 -0
  26. package/dist/copyout.d.ts +10 -0
  27. package/dist/copyout.js +69 -24
  28. package/dist/crowd.d.ts +16 -6
  29. package/dist/crowd.js +20 -11
  30. package/dist/cue.d.ts +8 -1
  31. package/dist/cue.js +39 -13
  32. package/dist/cut.d.ts +8 -0
  33. package/dist/cut.js +4 -0
  34. package/dist/delay.d.ts +3 -0
  35. package/dist/delay.js +7 -0
  36. package/dist/drop.d.ts +9 -0
  37. package/dist/drop.js +84 -0
  38. package/dist/due.d.ts +24 -5
  39. package/dist/due.js +114 -34
  40. package/dist/easing.d.ts +6 -4
  41. package/dist/easing.js +7 -3
  42. package/dist/everyone.d.ts +8 -2
  43. package/dist/everyone.js +33 -3
  44. package/dist/fade.d.ts +2 -1
  45. package/dist/fade.js +2 -1
  46. package/dist/fill.d.ts +3 -2
  47. package/dist/fill.js +52 -20
  48. package/dist/fit.d.ts +44 -26
  49. package/dist/fit.js +10 -9
  50. package/dist/fold.d.ts +4 -37
  51. package/dist/fold.js +5 -513
  52. package/dist/gather.d.ts +12 -7
  53. package/dist/gather.js +179 -44
  54. package/dist/gone.js +2 -2
  55. package/dist/grid.d.ts +47 -0
  56. package/dist/grid.js +247 -0
  57. package/dist/handle.d.ts +2 -0
  58. package/dist/handle.js +14 -8
  59. package/dist/held.d.ts +2 -1
  60. package/dist/held.js +2 -1
  61. package/dist/history.d.ts +6 -5
  62. package/dist/history.js +7 -9
  63. package/dist/hosts.js +9 -15
  64. package/dist/index.d.ts +5 -4
  65. package/dist/keyfill.d.ts +1 -1
  66. package/dist/keyfill.js +5 -3
  67. package/dist/keyrows.d.ts +12 -0
  68. package/dist/keyrows.js +99 -12
  69. package/dist/lane.d.ts +38 -11
  70. package/dist/lane.js +46 -11
  71. package/dist/lanes.d.ts +30 -12
  72. package/dist/lanes.js +56 -36
  73. package/dist/leavings.d.ts +2 -2
  74. package/dist/leavings.js +1 -1
  75. package/dist/lives.d.ts +47 -0
  76. package/dist/lives.js +180 -0
  77. package/dist/meet.d.ts +2 -2
  78. package/dist/meet.js +95 -7
  79. package/dist/mixer.d.ts +56 -37
  80. package/dist/mixer.js +57 -171
  81. package/dist/motion.d.ts +14 -118
  82. package/dist/motion.js +16 -8
  83. package/dist/motions.d.ts +62 -155
  84. package/dist/motions.js +56 -370
  85. package/dist/moved.d.ts +1 -1
  86. package/dist/moved.js +16 -7
  87. package/dist/named.d.ts +6 -2
  88. package/dist/named.js +49 -3
  89. package/dist/numbers.d.ts +7 -0
  90. package/dist/numbers.js +33 -0
  91. package/dist/owed.d.ts +2 -0
  92. package/dist/owed.js +4 -0
  93. package/dist/pack.d.ts +2 -1
  94. package/dist/pack.js +2 -3
  95. package/dist/patch.d.ts +7 -3
  96. package/dist/patch.js +6 -2
  97. package/dist/places.d.ts +53 -0
  98. package/dist/places.js +171 -0
  99. package/dist/plot.d.ts +12 -0
  100. package/dist/plot.js +52 -0
  101. package/dist/project.d.ts +4 -4
  102. package/dist/project.js +9 -212
  103. package/dist/pull.js +22 -9
  104. package/dist/qualify.js +93 -31
  105. package/dist/reading.d.ts +6 -0
  106. package/dist/reading.js +5 -0
  107. package/dist/record.d.ts +5 -1
  108. package/dist/record.js +3 -2
  109. package/dist/relink.d.ts +1 -1
  110. package/dist/renumber.d.ts +10 -0
  111. package/dist/renumber.js +75 -0
  112. package/dist/restored.d.ts +2 -2
  113. package/dist/restored.js +1 -1
  114. package/dist/rests.d.ts +9 -0
  115. package/dist/rests.js +76 -0
  116. package/dist/revive.d.ts +3 -2
  117. package/dist/revive.js +2 -2
  118. package/dist/{locus.d.ts → role.d.ts} +10 -9
  119. package/dist/{locus.js → role.js} +3 -3
  120. package/dist/roles.d.ts +10 -0
  121. package/dist/roles.js +158 -0
  122. package/dist/roster.js +3 -3
  123. package/dist/rotation.d.ts +1 -1
  124. package/dist/rotation.js +1 -1
  125. package/dist/rows.js +28 -20
  126. package/dist/sample.d.ts +2 -2
  127. package/dist/sample.js +33 -7
  128. package/dist/seat.d.ts +17 -0
  129. package/dist/seat.js +25 -0
  130. package/dist/seek.js +5 -5
  131. package/dist/setting.d.ts +27 -0
  132. package/dist/setting.js +55 -0
  133. package/dist/shared.d.ts +1 -1
  134. package/dist/shared.js +1 -1
  135. package/dist/signals.d.ts +64 -32
  136. package/dist/signals.js +20 -18
  137. package/dist/stack.d.ts +5 -5
  138. package/dist/stack.js +5 -5
  139. package/dist/standing.d.ts +2 -1
  140. package/dist/standing.js +3 -2
  141. package/dist/store.d.ts +9 -0
  142. package/dist/store.js +24 -0
  143. package/dist/strays.d.ts +2 -1
  144. package/dist/strays.js +23 -15
  145. package/dist/stretch.d.ts +112 -0
  146. package/dist/stretch.js +9 -0
  147. package/dist/subject.d.ts +142 -0
  148. package/dist/subject.js +1 -0
  149. package/dist/tape.d.ts +9 -5
  150. package/dist/tape.js +5 -1
  151. package/dist/then.d.ts +9 -0
  152. package/dist/then.js +57 -0
  153. package/dist/ticker.d.ts +4 -4
  154. package/dist/trace.d.ts +4 -0
  155. package/dist/trace.js +255 -0
  156. package/dist/transport.d.ts +0 -8
  157. package/dist/transport.js +0 -4
  158. package/dist/turns.d.ts +4 -3
  159. package/dist/turns.js +4 -3
  160. package/dist/types/arc.d.ts +12 -11
  161. package/dist/types/channel.d.ts +12 -8
  162. package/dist/types/history.d.ts +35 -19
  163. package/dist/types/mix.d.ts +237 -163
  164. package/dist/types/motion.d.ts +151 -0
  165. package/dist/types/motion.js +1 -0
  166. package/dist/types/patch.d.ts +67 -34
  167. package/dist/types/place.d.ts +51 -36
  168. package/dist/types/trace.d.ts +51 -0
  169. package/dist/types/trace.js +1 -0
  170. package/dist/types/transport.d.ts +20 -18
  171. package/dist/types/voice.d.ts +144 -73
  172. package/dist/types/watch.d.ts +88 -40
  173. package/dist/types.d.ts +2 -0
  174. package/dist/unreached.d.ts +2 -0
  175. package/dist/unreached.js +7 -0
  176. package/dist/voice.d.ts +10 -143
  177. package/dist/voice.js +37 -82
  178. package/dist/warn.d.ts +2 -0
  179. package/dist/warn.js +1 -1
  180. package/dist/watch.d.ts +2 -2
  181. package/dist/watch.js +5 -3
  182. package/dist/wave.d.ts +4 -3
  183. package/dist/wave.js +3 -2
  184. package/dist/weigh.d.ts +7 -1
  185. package/dist/weigh.js +27 -0
  186. package/docs/schema.html +223 -34
  187. package/package.json +2 -2
package/dist/gather.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { inside, leave, meetBound } from './bound.js';
1
2
  import { foldNumber, lerpNumber } from './channels.js';
2
3
  import { shortWay, turnFrom, turnInto, turnToward } from './turns.js';
3
4
  /** `xs`, or a copy at least `n` long where it is shorter. */
@@ -9,12 +10,12 @@ function sized(xs, n) {
9
10
  return grown;
10
11
  }
11
12
  /**
12
- * Every member of a locus, in voice order, gathered rather than folded: a member's delta for a
13
+ * Every member of a role, in voice order, gathered rather than folded: a member's delta for a
13
14
  * subject is lerped into what the members before it left there by its share of the weight
14
- * taken, as the general path's `foldLocus` does, so each subject's locus can fold in at its
15
+ * taken, as the general path's `foldRole` does, so each subject's role can fold in at its
15
16
  * first member's place in the order.
16
17
  */
17
- export function gatherLocus(lanes, g) {
18
+ export function gatherRole(lanes, g) {
18
19
  const cap = lanes.cap;
19
20
  if (g.met.length < cap) {
20
21
  g.met = sized(g.met, cap);
@@ -30,6 +31,9 @@ export function gatherLocus(lanes, g) {
30
31
  if (taken !== undefined)
31
32
  g.taken[k] = sized(taken, cap);
32
33
  }
34
+ g.inline = inlined(g);
35
+ if (g.inline)
36
+ return;
33
37
  lanes.into = g;
34
38
  try {
35
39
  for (const lane of g.members) {
@@ -40,10 +44,18 @@ export function gatherLocus(lanes, g) {
40
44
  lanes.reset(lane);
41
45
  lane.elapsedNow = voice.elapsedAt(lanes.now);
42
46
  const list = lane.list;
47
+ const b = voice.bound;
48
+ if (b !== null)
49
+ meetBound(lanes, b);
50
+ const fills = lanes.fills;
43
51
  for (let p = 0; p < list.length; p++) {
44
52
  const slot = list[p];
53
+ if (b !== null && !inside(b, slot, fills)) {
54
+ leave(lanes, lane, p, slot);
55
+ continue;
56
+ }
45
57
  if (lanes.one(lane, p, slot)) {
46
- meetLocus(lanes, g, slot);
58
+ meetRole(lanes, g, slot);
47
59
  g.sum[slot] =
48
60
  g.sum[slot] + lane.data[p * 8 /* Row.STRIDE */ + 2 /* Row.WEIGHT */];
49
61
  }
@@ -56,24 +68,47 @@ export function gatherLocus(lanes, g) {
56
68
  lanes.into = null;
57
69
  }
58
70
  }
59
- /** The first a fill hears of a subject in a locus: from the member now gathering. */
60
- function meetLocus(lanes, g, slot) {
71
+ /** The first a fill hears of a subject in a role: from the member now gathering. */
72
+ function meetRole(lanes, g, slot) {
61
73
  if (g.met[slot] === lanes.fills)
62
74
  return;
63
75
  g.met[slot] = lanes.fills;
64
76
  g.first[slot] = lanes.intoId;
65
77
  g.sum[slot] = 0;
66
- for (const taken of g.taken)
78
+ const takens = g.taken;
79
+ for (let k = 0; k < takens.length; k++) {
80
+ const taken = takens[k];
67
81
  if (taken !== undefined)
68
82
  taken[slot] = 0;
83
+ }
84
+ }
85
+ /**
86
+ * `gatherInto` for a plain scalar channel and the number at `Arg.VALUE`, which as an argument
87
+ * would be boxed, 16 bytes a channel a member a subject (`role`, bench/allocs.mjs, 2026-10-25).
88
+ */
89
+ function gatherScalar(lanes, g, ch, slot) {
90
+ const arg = lanes.arg;
91
+ const value = arg[7 /* Arg.VALUE */];
92
+ const values = g.values[ch.index];
93
+ const takenAt = g.taken[ch.index];
94
+ if (values === undefined || takenAt === undefined) {
95
+ gatherInto(lanes, g, ch, slot, value);
96
+ return;
97
+ }
98
+ const w = arg[0 /* Arg.WEIGHT */];
99
+ meetRole(lanes, g, slot);
100
+ const taken = takenAt[slot];
101
+ const total = taken + w;
102
+ values[slot] = taken === 0 ? value : lerpNumber(values[slot], value, w / total);
103
+ takenAt[slot] = total;
69
104
  }
70
105
  /**
71
- * A member's value for one channel of a subject, lerped into its locus: the step of the general
72
- * path's `foldLocus`.
106
+ * A member's value for one channel of a subject, lerped into its role: the step of the general
107
+ * path's `foldRole`.
73
108
  */
74
109
  function gatherInto(lanes, g, ch, slot, value) {
75
110
  const w = lanes.arg[0 /* Arg.WEIGHT */];
76
- meetLocus(lanes, g, slot);
111
+ meetRole(lanes, g, slot);
77
112
  const k = ch.index;
78
113
  let values = g.values[k];
79
114
  let takenAt = g.taken[k];
@@ -86,7 +121,7 @@ function gatherInto(lanes, g, ch, slot, value) {
86
121
  const taken = takenAt[slot];
87
122
  const total = taken + w;
88
123
  const u = w / total;
89
- if (!ch.plain)
124
+ if (!ch.plain || !ch.straight)
90
125
  turnToward(ch, values, slot * ch.axes, value, taken, u);
91
126
  else if (ch.scalar) {
92
127
  values[slot] =
@@ -103,8 +138,8 @@ function gatherInto(lanes, g, ch, slot, value) {
103
138
  }
104
139
  takenAt[slot] = total;
105
140
  }
106
- /** A locus member's turn in the order: each subject it was the first member of takes the locus. */
107
- export function foldLocus(lanes, lane, g) {
141
+ /** A role member's turn in the order: each subject it was the first member of takes the role. */
142
+ export function foldRole(lanes, lane, g) {
108
143
  const id = lane.voice.id;
109
144
  const fills = lanes.fills;
110
145
  const list = lane.list;
@@ -112,28 +147,100 @@ export function foldLocus(lanes, lane, g) {
112
147
  const slot = list[p];
113
148
  if (g.met[slot] !== fills || g.first[slot] !== id)
114
149
  continue;
115
- const sum = g.sum[slot];
116
- if (sum <= 0)
150
+ if (g.sum[slot] > 0)
151
+ foldAt(lanes, g, slot);
152
+ }
153
+ }
154
+ /** Folds what a role gathered for the subject at `slot` into its laned values. */
155
+ function foldAt(lanes, g, slot) {
156
+ for (let k = 0; k < g.values.length; k++) {
157
+ const takenAt = g.taken[k];
158
+ const taken = takenAt === undefined ? 0 : takenAt[slot];
159
+ if (!(taken > 0))
117
160
  continue;
118
- for (let k = 0; k < g.values.length; k++) {
119
- const takenAt = g.taken[k];
120
- const taken = takenAt === undefined ? 0 : takenAt[slot];
121
- if (!(taken > 0))
122
- continue;
123
- // A channel folds at the weight of the members that wrote it, not the whole locus.
124
- const w = taken > 1 ? 1 : taken;
125
- const ch = lanes.laned[k];
126
- const values = g.values[k];
127
- const into = ch.values;
128
- const base = slot * ch.axes;
129
- if (!ch.plain) {
130
- turnFrom(ch, into, base, values, base, w);
131
- continue;
161
+ // A channel folds at the weight of the members that wrote it, not the whole role.
162
+ const w = taken > 1 ? 1 : taken;
163
+ const ch = lanes.laned[k];
164
+ const values = g.values[k];
165
+ const into = ch.values;
166
+ const base = slot * ch.axes;
167
+ if (!ch.plain) {
168
+ turnFrom(ch, into, base, values, base, w);
169
+ continue;
170
+ }
171
+ for (let a = 0; a < ch.axes; a++)
172
+ into[base + a] = foldNumber(ch.op, into[base + a], values[base + a], w);
173
+ }
174
+ }
175
+ /**
176
+ * Whether a fill can run a role a subject at a time: every member playing, on its lane, keeping
177
+ * no state, and over the same subjects in the same places, with no other voice between them.
178
+ */
179
+ function inlined(g) {
180
+ if (!g.contiguous)
181
+ return false;
182
+ const members = g.members;
183
+ const list = members[0].list;
184
+ for (const lane of members) {
185
+ const voice = lane.voice;
186
+ if (lane.idle || voice.keeping || voice.state === 'pending' || voice.state === 'done')
187
+ return false;
188
+ const own = lane.list;
189
+ if (own === list)
190
+ continue;
191
+ if (own.length !== list.length)
192
+ return false;
193
+ for (let p = 0; p < list.length; p++)
194
+ if (own[p] !== list[p])
195
+ return false;
196
+ }
197
+ return true;
198
+ }
199
+ /**
200
+ * An inlined role at its first member's turn: each subject's members gathered in voice order, as
201
+ * `gatherRole` gathers them, then folded at once, with no pass of its own and no array of sums.
202
+ */
203
+ export function runRole(lanes, g) {
204
+ const members = g.members;
205
+ const arg = lanes.arg;
206
+ for (const lane of members) {
207
+ lanes.reset(lane);
208
+ lane.elapsedNow = lane.voice.elapsedAt(lanes.now);
209
+ const b = lane.voice.bound;
210
+ if (b !== null)
211
+ meetBound(lanes, b);
212
+ }
213
+ const fills = lanes.fills;
214
+ const list = members[0].list;
215
+ lanes.into = g;
216
+ try {
217
+ for (let p = 0; p < list.length; p++) {
218
+ const slot = list[p];
219
+ let sum = 0;
220
+ for (let i = 0; i < members.length; i++) {
221
+ const lane = members[i];
222
+ // Its patch kept state on an earlier subject: as `gatherRole` leaves it after that call.
223
+ if (lane.voice.keeping)
224
+ continue;
225
+ const b = lane.voice.bound;
226
+ if (b !== null && !inside(b, slot, fills)) {
227
+ leave(lanes, lane, p, slot);
228
+ continue;
229
+ }
230
+ // Each lane's placing is numbered on its own; the last member placed left its number.
231
+ arg[6 /* Arg.MARK */] = lane.mark;
232
+ if (lanes.one(lane, p, slot)) {
233
+ meetRole(lanes, g, slot);
234
+ sum += lane.data[p * 8 /* Row.STRIDE */ + 2 /* Row.WEIGHT */];
235
+ }
132
236
  }
133
- for (let a = 0; a < ch.axes; a++)
134
- into[base + a] = foldNumber(ch.op, into[base + a], values[base + a], w);
237
+ if (sum > 0)
238
+ foldAt(lanes, g, slot);
135
239
  }
136
240
  }
241
+ finally {
242
+ lanes.into = null;
243
+ }
137
244
  }
138
245
  /** For a keys voice: the delta's value for each channel it writes, read once per phase. */
139
246
  export function gather(lane, delta) {
@@ -161,9 +268,16 @@ export function foldDelta(chans, slot, delta) {
161
268
  let ch = chans[0];
162
269
  if (n > 0) {
163
270
  const v = delta[ch.name];
164
- if (typeof v === 'number' && ch.scalar && ch.plain && this.into === null) {
165
- const values = ch.values;
166
- values[slot] = foldNumber(ch.op, values[slot], v, arg[0 /* Arg.WEIGHT */]);
271
+ if (typeof v === 'number' && ch.scalar && ch.plain) {
272
+ const into = this.into;
273
+ if (into === null) {
274
+ const values = ch.values;
275
+ values[slot] = foldNumber(ch.op, values[slot], v, arg[0 /* Arg.WEIGHT */]);
276
+ }
277
+ else {
278
+ arg[7 /* Arg.VALUE */] = v;
279
+ gatherScalar(this, into, ch, slot);
280
+ }
167
281
  }
168
282
  else
169
283
  this.foldInto(ch, slot, v);
@@ -171,9 +285,16 @@ export function foldDelta(chans, slot, delta) {
171
285
  ch = chans[1];
172
286
  if (n > 1) {
173
287
  const v = delta[ch.name];
174
- if (typeof v === 'number' && ch.scalar && ch.plain && this.into === null) {
175
- const values = ch.values;
176
- values[slot] = foldNumber(ch.op, values[slot], v, arg[0 /* Arg.WEIGHT */]);
288
+ if (typeof v === 'number' && ch.scalar && ch.plain) {
289
+ const into = this.into;
290
+ if (into === null) {
291
+ const values = ch.values;
292
+ values[slot] = foldNumber(ch.op, values[slot], v, arg[0 /* Arg.WEIGHT */]);
293
+ }
294
+ else {
295
+ arg[7 /* Arg.VALUE */] = v;
296
+ gatherScalar(this, into, ch, slot);
297
+ }
177
298
  }
178
299
  else
179
300
  this.foldInto(ch, slot, v);
@@ -181,9 +302,16 @@ export function foldDelta(chans, slot, delta) {
181
302
  ch = chans[2];
182
303
  if (n > 2) {
183
304
  const v = delta[ch.name];
184
- if (typeof v === 'number' && ch.scalar && ch.plain && this.into === null) {
185
- const values = ch.values;
186
- values[slot] = foldNumber(ch.op, values[slot], v, arg[0 /* Arg.WEIGHT */]);
305
+ if (typeof v === 'number' && ch.scalar && ch.plain) {
306
+ const into = this.into;
307
+ if (into === null) {
308
+ const values = ch.values;
309
+ values[slot] = foldNumber(ch.op, values[slot], v, arg[0 /* Arg.WEIGHT */]);
310
+ }
311
+ else {
312
+ arg[7 /* Arg.VALUE */] = v;
313
+ gatherScalar(this, into, ch, slot);
314
+ }
187
315
  }
188
316
  else
189
317
  this.foldInto(ch, slot, v);
@@ -191,9 +319,16 @@ export function foldDelta(chans, slot, delta) {
191
319
  for (let i = 3; i < n; i++) {
192
320
  ch = chans[i];
193
321
  const v = delta[ch.name];
194
- if (typeof v === 'number' && ch.scalar && ch.plain && this.into === null) {
195
- const values = ch.values;
196
- values[slot] = foldNumber(ch.op, values[slot], v, arg[0 /* Arg.WEIGHT */]);
322
+ if (typeof v === 'number' && ch.scalar && ch.plain) {
323
+ const into = this.into;
324
+ if (into === null) {
325
+ const values = ch.values;
326
+ values[slot] = foldNumber(ch.op, values[slot], v, arg[0 /* Arg.WEIGHT */]);
327
+ }
328
+ else {
329
+ arg[7 /* Arg.VALUE */] = v;
330
+ gatherScalar(this, into, ch, slot);
331
+ }
197
332
  }
198
333
  else
199
334
  this.foldInto(ch, slot, v);
@@ -211,7 +346,7 @@ export function foldRun(ch, slot, xs) {
211
346
  for (let a = 0; a < ch.axes; a++)
212
347
  values[base + a] = foldNumber(ch.op, values[base + a], xs[a], w);
213
348
  }
214
- /** Folds one channel's value into a subject's laned values, or its locus, at `Arg.WEIGHT`. */
349
+ /** Folds one channel's value into a subject's laned values, or its role, at `Arg.WEIGHT`. */
215
350
  export function foldInto(ch, slot, value) {
216
351
  if (value === undefined)
217
352
  return;
package/dist/gone.js CHANGED
@@ -26,7 +26,7 @@ function add(ix, voice) {
26
26
  ix.general.push(voice);
27
27
  return;
28
28
  }
29
- for (const subject of voice.named) {
29
+ for (const subject of voice.named.list) {
30
30
  const list = ix.named.get(subject);
31
31
  if (list === undefined)
32
32
  ix.named.set(subject, [voice]);
@@ -41,7 +41,7 @@ function remove(ix, out) {
41
41
  general = true;
42
42
  continue;
43
43
  }
44
- for (const subject of v.named) {
44
+ for (const subject of v.named.list) {
45
45
  const named = ix.named.get(subject);
46
46
  if (named === undefined)
47
47
  continue;
package/dist/grid.d.ts ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Positions side by side, `x` then `y`, by place, and a uniform grid over them built again at the
3
+ * first query after they move, so a circle finds the places within it without visiting the rest.
4
+ */
5
+ export declare class Grid {
6
+ xy: Float64Array;
7
+ /** How many places the list had. */
8
+ n: number;
9
+ /** The cell size asked for, NaN for one from the spread; the grid widens it where the positions are spread too thin. */
10
+ cell: number;
11
+ dirty: boolean;
12
+ /** The grid as last built: its origin, cell size and shape. */
13
+ x0: number;
14
+ y0: number;
15
+ size: number;
16
+ cols: number;
17
+ rows: number;
18
+ /** By cell, where its entries start in `order`, one past the last cell included. */
19
+ starts: Int32Array<ArrayBuffer>;
20
+ order: Int32Array<ArrayBuffer>;
21
+ /** By place, the cell the last build put it in, -1 for none. */
22
+ home: Int32Array<ArrayBuffer>;
23
+ /**
24
+ * Places `put` moved out of their cell since the last build, which every query tests on their own
25
+ * and skips in the cells, and by place whether it is one. Past a sixteenth of the places, the grid
26
+ * is built again.
27
+ */
28
+ strays: number[];
29
+ stray: Uint8Array<ArrayBuffer>;
30
+ /** What the last `query` or `ring` found, the first `count` of it. */
31
+ found: Int32Array<ArrayBuffer>;
32
+ count: number;
33
+ /** Moves place `i` to (`x`, `y`) in `xy`, leaving the grid as it is where the place keeps its cell. */
34
+ put(i: number, x: number, y: number): void;
35
+ private build;
36
+ private push;
37
+ /**
38
+ * Leaves in `found` the places within `r` of (`cx`, `cy`), by the test `inside` makes, `r2`
39
+ * being `r * r`: the cells the circle touches, and one more where an edge falls a hair from one.
40
+ */
41
+ query(cx: number, cy: number, r: number, r2: number): number;
42
+ /**
43
+ * Leaves in `found` the places beyond `r` of (`cx`, `cy`) and within a cell's width more, from
44
+ * the cells that ring touches.
45
+ */
46
+ ring(cx: number, cy: number, r: number): number;
47
+ }
package/dist/grid.js ADDED
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Positions side by side, `x` then `y`, by place, and a uniform grid over them built again at the
3
+ * first query after they move, so a circle finds the places within it without visiting the rest.
4
+ */
5
+ export class Grid {
6
+ xy = new Float64Array(0);
7
+ /** How many places the list had. */
8
+ n = 0;
9
+ /** The cell size asked for, NaN for one from the spread; the grid widens it where the positions are spread too thin. */
10
+ cell = Number.NaN;
11
+ dirty = true;
12
+ /** The grid as last built: its origin, cell size and shape. */
13
+ x0 = 0;
14
+ y0 = 0;
15
+ size = 1;
16
+ cols = 0;
17
+ rows = 0;
18
+ /** By cell, where its entries start in `order`, one past the last cell included. */
19
+ starts = new Int32Array(1);
20
+ order = new Int32Array(0);
21
+ /** By place, the cell the last build put it in, -1 for none. */
22
+ home = new Int32Array(0);
23
+ /**
24
+ * Places `put` moved out of their cell since the last build, which every query tests on their own
25
+ * and skips in the cells, and by place whether it is one. Past a sixteenth of the places, the grid
26
+ * is built again.
27
+ */
28
+ strays = [];
29
+ stray = new Uint8Array(0);
30
+ /** What the last `query` or `ring` found, the first `count` of it. */
31
+ found = new Int32Array(64);
32
+ count = 0;
33
+ /** Moves place `i` to (`x`, `y`) in `xy`, leaving the grid as it is where the place keeps its cell. */
34
+ put(i, x, y) {
35
+ this.xy[2 * i] = x;
36
+ this.xy[2 * i + 1] = y;
37
+ if (this.dirty)
38
+ return;
39
+ if (this.cols === 0 || i >= this.home.length) {
40
+ this.dirty = true;
41
+ return;
42
+ }
43
+ const col = Math.floor((x - this.x0) / this.size);
44
+ const row = Math.floor((y - this.y0) / this.size);
45
+ const c = col >= 0 && col < this.cols && row >= 0 && row < this.rows ? row * this.cols + col : -2;
46
+ if (c === this.home[i] || this.stray[i] === 1)
47
+ return;
48
+ this.stray[i] = 1;
49
+ this.strays.push(i);
50
+ if (this.strays.length > Math.max(32, this.n >> 4))
51
+ this.dirty = true;
52
+ }
53
+ build() {
54
+ this.dirty = false;
55
+ for (const i of this.strays)
56
+ this.stray[i] = 0;
57
+ this.strays.length = 0;
58
+ const xy = this.xy;
59
+ const n = Math.min(this.n, xy.length >> 1);
60
+ let x0 = Number.POSITIVE_INFINITY;
61
+ let y0 = Number.POSITIVE_INFINITY;
62
+ let x1 = Number.NEGATIVE_INFINITY;
63
+ let y1 = Number.NEGATIVE_INFINITY;
64
+ for (let i = 0; i < n; i++) {
65
+ const x = xy[2 * i];
66
+ const y = xy[2 * i + 1];
67
+ if (x < x0)
68
+ x0 = x;
69
+ if (x > x1)
70
+ x1 = x;
71
+ if (y < y0)
72
+ y0 = y;
73
+ if (y > y1)
74
+ y1 = y;
75
+ }
76
+ if (!(x1 >= x0) || !(y1 >= y0)) {
77
+ this.cols = 0;
78
+ this.rows = 0;
79
+ return;
80
+ }
81
+ // Unasked, about two subjects a cell where they are spread evenly.
82
+ let size = Number.isNaN(this.cell)
83
+ ? Math.sqrt((2 * Math.max(x1 - x0, 1e-9) * Math.max(y1 - y0, 1e-9)) / n)
84
+ : this.cell;
85
+ // At most about four cells a subject: a few far-flung positions would otherwise ask for millions.
86
+ const most = 4 * n + 64;
87
+ if ((Math.floor((x1 - x0) / size) + 1) * (Math.floor((y1 - y0) / size) + 1) > most)
88
+ size = Math.max(size, Math.sqrt(((x1 - x0) * (y1 - y0)) / n), (x1 - x0 + y1 - y0) / most);
89
+ const cols = Math.floor((x1 - x0) / size) + 1;
90
+ const rows = Math.floor((y1 - y0) / size) + 1;
91
+ this.x0 = x0;
92
+ this.y0 = y0;
93
+ this.size = size;
94
+ this.cols = cols;
95
+ this.rows = rows;
96
+ const cells = cols * rows;
97
+ if (this.starts.length < cells + 1)
98
+ this.starts = new Int32Array(cells + 1);
99
+ else
100
+ this.starts.fill(0, 0, cells + 1);
101
+ if (this.order.length < n)
102
+ this.order = new Int32Array(n);
103
+ if (this.home.length < n) {
104
+ this.home = new Int32Array(n);
105
+ this.stray = new Uint8Array(n);
106
+ }
107
+ const starts = this.starts;
108
+ const order = this.order;
109
+ // A position that is not a number is in no cell, and no circle finds it.
110
+ for (let i = 0; i < n; i++) {
111
+ const c = cellOf(xy, i, x0, y0, size, cols);
112
+ this.home[i] = c;
113
+ if (c >= 0)
114
+ starts[c + 1] = starts[c + 1] + 1;
115
+ }
116
+ for (let c = 0; c < cells; c++)
117
+ starts[c + 1] = starts[c + 1] + starts[c];
118
+ const placed = starts[cells];
119
+ // Filled from each cell's end back to its start: `starts[c + 1]`, the end of cell c, ends at its start.
120
+ for (let i = n - 1; i >= 0; i--) {
121
+ const c = cellOf(xy, i, x0, y0, size, cols);
122
+ if (c < 0)
123
+ continue;
124
+ const k = starts[c + 1] - 1;
125
+ starts[c + 1] = k;
126
+ order[k] = i;
127
+ }
128
+ for (let c = 0; c < cells; c++)
129
+ starts[c] = starts[c + 1];
130
+ starts[cells] = placed;
131
+ }
132
+ push(i, count) {
133
+ if (count === this.found.length) {
134
+ const grown = new Int32Array(this.found.length * 2);
135
+ grown.set(this.found);
136
+ this.found = grown;
137
+ }
138
+ this.found[count] = i;
139
+ return count + 1;
140
+ }
141
+ /**
142
+ * Leaves in `found` the places within `r` of (`cx`, `cy`), by the test `inside` makes, `r2`
143
+ * being `r * r`: the cells the circle touches, and one more where an edge falls a hair from one.
144
+ */
145
+ query(cx, cy, r, r2) {
146
+ if (this.dirty)
147
+ this.build();
148
+ this.count = 0;
149
+ if (this.cols === 0 || !(r >= 0))
150
+ return 0;
151
+ const size = this.size;
152
+ const c0 = Math.max(0, edge((cx - r - this.x0) / size, -1));
153
+ const c1 = Math.min(this.cols - 1, edge((cx + r - this.x0) / size, 1));
154
+ const r0 = Math.max(0, edge((cy - r - this.y0) / size, -1));
155
+ const r1 = Math.min(this.rows - 1, edge((cy + r - this.y0) / size, 1));
156
+ const xy = this.xy;
157
+ const order = this.order;
158
+ const starts = this.starts;
159
+ const any = this.strays.length > 0;
160
+ let count = 0;
161
+ for (let row = r0; row <= r1 && c0 <= c1; row++) {
162
+ const from = starts[row * this.cols + c0];
163
+ const to = starts[row * this.cols + c1 + 1];
164
+ for (let k = from; k < to; k++) {
165
+ const i = order[k];
166
+ if (any && this.stray[i] === 1)
167
+ continue;
168
+ const dx = xy[2 * i] - cx;
169
+ const dy = xy[2 * i + 1] - cy;
170
+ if (dx * dx + dy * dy <= r2)
171
+ count = this.push(i, count);
172
+ }
173
+ }
174
+ for (const i of this.strays) {
175
+ const dx = xy[2 * i] - cx;
176
+ const dy = xy[2 * i + 1] - cy;
177
+ if (dx * dx + dy * dy <= r2)
178
+ count = this.push(i, count);
179
+ }
180
+ this.count = count;
181
+ return count;
182
+ }
183
+ /**
184
+ * Leaves in `found` the places beyond `r` of (`cx`, `cy`) and within a cell's width more, from
185
+ * the cells that ring touches.
186
+ */
187
+ ring(cx, cy, r) {
188
+ if (this.dirty)
189
+ this.build();
190
+ this.count = 0;
191
+ if (this.cols === 0 || !(r >= 0))
192
+ return 0;
193
+ const size = this.size;
194
+ const out = r + size;
195
+ const r2 = r * r;
196
+ const out2 = out * out;
197
+ const c0 = Math.max(0, Math.floor((cx - out - this.x0) / size));
198
+ const c1 = Math.min(this.cols - 1, Math.floor((cx + out - this.x0) / size));
199
+ const r0 = Math.max(0, Math.floor((cy - out - this.y0) / size));
200
+ const r1 = Math.min(this.rows - 1, Math.floor((cy + out - this.y0) / size));
201
+ const xy = this.xy;
202
+ let count = 0;
203
+ for (let row = r0; row <= r1; row++)
204
+ for (let col = c0; col <= c1; col++) {
205
+ // A cell wholly inside the circle holds nothing beyond it.
206
+ const ax = this.x0 + col * size - cx;
207
+ const ay = this.y0 + row * size - cy;
208
+ const far = Math.max(ax * ax, (ax + size) ** 2) + Math.max(ay * ay, (ay + size) ** 2);
209
+ if (far <= r2)
210
+ continue;
211
+ const c = row * this.cols + col;
212
+ for (let k = this.starts[c]; k < this.starts[c + 1]; k++) {
213
+ const i = this.order[k];
214
+ if (this.stray[i] === 1)
215
+ continue;
216
+ const dx = xy[2 * i] - cx;
217
+ const dy = xy[2 * i + 1] - cy;
218
+ const d2 = dx * dx + dy * dy;
219
+ if (d2 > r2 && d2 <= out2)
220
+ count = this.push(i, count);
221
+ }
222
+ }
223
+ for (const i of this.strays) {
224
+ const dx = xy[2 * i] - cx;
225
+ const dy = xy[2 * i + 1] - cy;
226
+ const d2 = dx * dx + dy * dy;
227
+ if (d2 > r2 && d2 <= out2)
228
+ count = this.push(i, count);
229
+ }
230
+ this.count = count;
231
+ return count;
232
+ }
233
+ }
234
+ function cellOf(xy, i, x0, y0, size, cols) {
235
+ const x = xy[2 * i];
236
+ const y = xy[2 * i + 1];
237
+ if (!(x >= x0) || !(y >= y0))
238
+ return -1;
239
+ return Math.floor((x - x0) / size) + Math.floor((y - y0) / size) * cols;
240
+ }
241
+ /** The cell index `u` falls in, one further out toward `side` where it lies within 1e-6 of an edge. */
242
+ function edge(u, side) {
243
+ const c = Math.floor(u);
244
+ if (side < 0)
245
+ return u - c < 1e-6 ? c - 1 : c;
246
+ return c + 1 - u < 1e-6 ? c + 1 : c;
247
+ }
package/dist/handle.d.ts CHANGED
@@ -23,6 +23,8 @@ export interface HandleHost<I, O> {
23
23
  * it, on the handle it is given: `h`, or a read ahead's handle on its copy of the voice.
24
24
  */
25
25
  record(h: VoiceHandle<I, O>, label: string, again: (h: VoiceHandle<I, O>) => void): void;
26
+ /** Whether `record` keeps a write made now, so a handle can skip making one it would drop. */
27
+ taping(): boolean;
26
28
  }
27
29
  /** Refuses a voice rate no clock can run at. */
28
30
  export declare function finiteRate(rate: number): void;