@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
@@ -0,0 +1,75 @@
1
+ import { resight } from './everyone.js';
2
+ import { goneIndex } from './gone.js';
3
+ import { reunreach } from './unreached.js';
4
+ /**
5
+ * Gives a subject the free number `to` in place of its own, carrying everything kept under the old
6
+ * one, so `pull` can keep a list's numbers in its order: a list kept dense by swap-remove moves its
7
+ * last subject into the place a dropped one left, whose number the drop just freed. Says whether it
8
+ * did; it does not where some store it would have to carry is in use: while a fill runs, under
9
+ * history, in a read ahead, or with roles, a bound or anything owed on the lanes.
10
+ */
11
+ export function renumber(mix, subject, head, to) {
12
+ const lanes = mix.lanes;
13
+ const from = head.slot;
14
+ if (from < 0 ||
15
+ lanes.filling ||
16
+ lanes.host.keeps ||
17
+ mix.projecting ||
18
+ lanes.roles.length > 0 ||
19
+ lanes.owed.any() ||
20
+ lanes.lanes.some((lane) => lane.voice.bound !== null) ||
21
+ !lanes.numbers.move(from, to))
22
+ return false;
23
+ for (const lane of lanes.lanes)
24
+ lane.renumber(from, to);
25
+ const placed = lanes.placed;
26
+ for (const id of placed.all(from)) {
27
+ placed.remove(from, id);
28
+ const s = lanes.seats.get(id);
29
+ if (s === undefined || s.crowd.list[s.row] !== from)
30
+ continue;
31
+ s.crowd.list[s.row] = to;
32
+ placed.add(to, id);
33
+ }
34
+ const per = lanes.per;
35
+ per.copyWithin(to * 6 /* Per.SLOT */, from * 6 /* Per.SLOT */, (from + 1) * 6 /* Per.SLOT */);
36
+ per[from * 6 /* Per.SLOT */ + 0 /* Per.FILLED */] = lanes.fills;
37
+ lanes.unlinked[to] = lanes.unlinked[from];
38
+ lanes.subjects[to] = lanes.subjects[from];
39
+ lanes.subjects[from] = undefined;
40
+ for (const ch of lanes.laned)
41
+ ch.values.copyWithin(to * ch.axes, from * ch.axes, (from + 1) * ch.axes);
42
+ mix.places?.forgot(from);
43
+ mix.places?.number(subject, to);
44
+ const cap = lanes.cap;
45
+ for (const voice of mix.general) {
46
+ reunreach(voice, from, to);
47
+ resight(voice, from, to, cap);
48
+ reblend(mix, voice, from, to);
49
+ }
50
+ for (const voice of goneIndex(mix).general)
51
+ reunreach(voice, from, to);
52
+ for (let held = head; held !== null; held = held.next)
53
+ if (held.voice !== null)
54
+ reblend(mix, held.voice, from, to);
55
+ head.slot = to;
56
+ return true;
57
+ }
58
+ /** Moves a blend's read for the subject this frame, if it has made one, to its new number. */
59
+ function reblend(mix, voice, from, to) {
60
+ const of = voice.blend?.of;
61
+ if (of === undefined || from >= of.frames.length || of.frames[from] !== mix.frame)
62
+ return;
63
+ if (to >= of.frames.length) {
64
+ const size = Math.max(to + 1, of.frames.length * 2, 64);
65
+ const frames = new Float64Array(size);
66
+ const values = new Float64Array(size);
67
+ frames.set(of.frames);
68
+ values.set(of.values);
69
+ of.frames = frames;
70
+ of.values = values;
71
+ }
72
+ of.frames[to] = mix.frame;
73
+ of.values[to] = of.values[from];
74
+ of.frames[from] = Number.NaN;
75
+ }
@@ -1,7 +1,7 @@
1
+ import type { Cut } from './cut.js';
1
2
  import type { Leavings } from './leavings.js';
2
3
  import { Store } from './store.js';
3
- import type { Cut } from './transport.js';
4
- import type { Subject } from './voice.js';
4
+ import type { Subject } from './subject.js';
5
5
  /**
6
6
  * The seeks back after the one a record was last put back, written or deleted at, which every
7
7
  * record stamped then shares. The next seek fills it; a later seek to as early a moment joins it
package/dist/restored.js CHANGED
@@ -138,7 +138,7 @@ export class Restored extends Store {
138
138
  }
139
139
  /** `get`, given what `own` holds for `key`. */
140
140
  from(key, own) {
141
- const mark = own !== undefined && own.reaches;
141
+ const mark = own?.reaches;
142
142
  const at = (mark ? own.restored : this.at.get(key)) ?? this.first;
143
143
  const now = this.stamp;
144
144
  if (at === now)
@@ -0,0 +1,9 @@
1
+ import type { Mixer } from './mixer.js';
2
+ import type { Voice } from './voice.js';
3
+ export declare function atRest<I, O>(this: Mixer<I, O>, subject: I): boolean;
4
+ export declare function rests<I, O>(this: Mixer<I, O>, pose: O): boolean;
5
+ export declare function restful<I, O>(this: Mixer<I, O>): boolean;
6
+ /** Whether a voice will change no pose from `now` on, short of a change made to it. */
7
+ export declare function still<I, O>(this: Mixer<I, O>, voice: Voice<I, O>, now: number): boolean;
8
+ export declare function restsLaned<I, O>(this: Mixer<I, O>): boolean;
9
+ export declare function restsKey<I, O>(this: Mixer<I, O>): number;
package/dist/rests.js ADDED
@@ -0,0 +1,76 @@
1
+ import { near } from './fold.js';
2
+ import { Store } from './store.js';
3
+ export function atRest(subject) {
4
+ this.restStamps ??= new Store();
5
+ const head = this.linked(subject);
6
+ const laned = this.linkedLaned;
7
+ const lanes = this.lanes;
8
+ // Every voice on lanes and nothing to clamp: the pose a fold would make is the lanes' values.
9
+ if (laned && this.restsLaned()) {
10
+ const slot = head.slot;
11
+ if (!lanes.owes(slot))
12
+ return lanes.rests(slot);
13
+ }
14
+ // A probe this frame answers for the pose it gave the host.
15
+ const since = (this.restStamps.get(subject) ?? Number.NaN) - 2 * this.restsKey();
16
+ if (since >= 0)
17
+ return since === 1;
18
+ this.restScratch ??= this.pose();
19
+ const pose = this.foldWith(subject, this.restScratch, head, laned, true);
20
+ return this.rests((this.bounded.length === 0 ? pose : this.clamp(pose)));
21
+ }
22
+ export function rests(pose) {
23
+ for (let i = 0; i < this.names.length; i++) {
24
+ const channel = this.channels[i];
25
+ const value = pose[this.names[i]];
26
+ if (channel.rest === undefined) {
27
+ if (value !== undefined)
28
+ return false;
29
+ }
30
+ else if (!near(value, channel.rest))
31
+ return false;
32
+ }
33
+ return true;
34
+ }
35
+ export function restful() {
36
+ // Standing still, a voice changes no pose but by its weight signal, or by an anchor or start
37
+ // that host time may yet reach.
38
+ if (this.pace?.stopped(this.u))
39
+ return this.cued.every((v) => v.state === 'done' ||
40
+ (v.state !== 'pending' &&
41
+ typeof v.spec.weight !== 'function' &&
42
+ v.spec.anchor === undefined));
43
+ return this.cued.every((v) => this.still(v, this.now));
44
+ }
45
+ /** Whether a voice will change no pose from `now` on, short of a change made to it. */
46
+ export function still(voice, now) {
47
+ if (voice.state === 'done')
48
+ return true;
49
+ if (voice.state !== 'frozen' && voice.state !== 'live')
50
+ return false;
51
+ if (typeof voice.spec.weight === 'function' || voice.parts !== null)
52
+ return false;
53
+ const fadeIn = this.reducedNow ? 0 : (voice.fade.in ?? 0);
54
+ if (fadeIn > 0 && now - this.sinceOf(voice, voice.latest) < fadeIn)
55
+ return false;
56
+ if (voice.state === 'frozen' || voice.holding !== null)
57
+ return true;
58
+ const motion = voice.motion;
59
+ return motion !== undefined && voice.elapsedAt(now) >= voice.latest && motion.landed();
60
+ }
61
+ export function restsLaned() {
62
+ return this.lanes.whole && this.bounded.length === 0;
63
+ }
64
+ export function restsKey() {
65
+ if (this.frame !== this.restsFrame ||
66
+ this.version !== this.restsVersion ||
67
+ this.relinks !== this.restsRelinks ||
68
+ this.stirs !== this.restsStirs) {
69
+ this.restsEpoch++;
70
+ this.restsFrame = this.frame;
71
+ this.restsVersion = this.version;
72
+ this.restsRelinks = this.relinks;
73
+ this.restsStirs = this.stirs;
74
+ }
75
+ return this.restsEpoch;
76
+ }
package/dist/revive.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import type { Mixer } from './mixer.js';
2
- import type { Run } from './motions.js';
3
2
  import type { Past } from './origin.js';
4
3
  import { type PackedRecord } from './pack.js';
5
4
  import { type Key } from './paging.js';
5
+ import type { Run } from './stretch.js';
6
+ import type { Parted, Parting } from './subject.js';
6
7
  import type { Described } from './types.js';
7
- import { type Controls, type Parted, type Parting, type Ramp, type Rise, Voice } from './voice.js';
8
+ import { type Controls, type Ramp, type Rise, Voice } from './voice.js';
8
9
  /** A voice that has left, as plain data: the descriptor it was cued with, its controls and its records. */
9
10
  interface PackedVoice {
10
11
  as: Described;
package/dist/revive.js CHANGED
@@ -1,4 +1,4 @@
1
- import { playable } from './cue.js';
1
+ import { playable, roleOf } from './cue.js';
2
2
  import { VoiceHandle } from './handle.js';
3
3
  import { handle } from './hosts.js';
4
4
  import { Leavings } from './leavings.js';
@@ -120,7 +120,7 @@ export function reviveVoice(mix, id, d) {
120
120
  const revive = mix.opts.history?.revive;
121
121
  if (revive === undefined)
122
122
  throw new Error('blits: a paged voice needs history.revive');
123
- const spec = { ...revive(d.as), as: d.as };
123
+ const spec = roleOf(mix, { ...revive(d.as), as: d.as });
124
124
  playable(mix, spec);
125
125
  const voice = new Voice(id, spec, spec.patch, spec.fade ?? {}, Number.NEGATIVE_INFINITY, d.start, mix.slotOf, mix.channels, mix.opts.host, mix.send, null);
126
126
  voice.clocks = d.clocks;
@@ -1,11 +1,12 @@
1
1
  import type { Mixer } from './mixer.js';
2
- import type { Subject, Voice } from './voice.js';
2
+ import type { Subject } from './subject.js';
3
+ import type { Voice } from './voice.js';
3
4
  /**
4
- * What a fold with a locus in play gathers, kept from one fold to the next: the contributions in
5
- * voice order, each a voice's own or a locus's first member; every locus member; and per channel
6
- * slot, a locus's folding value, the weight taken into it and the array it owns.
5
+ * What a fold with a role in play gathers, kept from one fold to the next: the contributions in
6
+ * voice order, each a voice's own or a role's first member; every role member; and per channel
7
+ * slot, a role's folding value, the weight taken into it and the array it owns.
7
8
  */
8
- export interface LocusScratch<I, O> {
9
+ export interface RoleScratch<I, O> {
9
10
  n: number;
10
11
  voices: (Voice<I, O> | null)[];
11
12
  helds: (Subject<unknown> | null)[];
@@ -27,17 +28,17 @@ export interface LocusScratch<I, O> {
27
28
  touched: number[];
28
29
  owned: (number[] | undefined)[];
29
30
  }
30
- export declare function locusScratch<I, O>(channels: number): LocusScratch<I, O>;
31
+ export declare function roleScratch<I, O>(channels: number): RoleScratch<I, O>;
31
32
  /**
32
33
  * `value` copied into the array `k` owns for the slot, as long as the channel's rest, where a
33
34
  * shorter value leaves the rest's own fill: what the channel's fold and lerp read past its end.
34
35
  */
35
- export declare function own<I, O>(k: LocusScratch<I, O>, slot: number, value: number[], rest: number[]): number[];
36
+ export declare function own<I, O>(k: RoleScratch<I, O>, slot: number, value: number[], rest: number[]): number[];
36
37
  /**
37
- * Folds one locus's members into one contribution through each channel's own `lerp`: its value per
38
+ * Folds one role's members into one contribution through each channel's own `lerp`: its value per
38
39
  * channel goes in `k.values`, the channels in `k.touched` in the order first met, and its weight
39
40
  * is returned. A stock `last` channel has nothing to lerp, so it takes its heaviest member's value,
40
41
  * the later cued of two as heavy. A channel folding by scale takes its value in an array of `k`'s own, lerped in
41
42
  * place, since the fold only reads it.
42
43
  */
43
- export declare function foldLocus<I, O>(this: Mixer<I, O>, k: LocusScratch<I, O>, group: number): number;
44
+ export declare function foldRole<I, O>(this: Mixer<I, O>, k: RoleScratch<I, O>, group: number): number;
@@ -1,5 +1,5 @@
1
1
  import { first } from './clone.js';
2
- export function locusScratch(channels) {
2
+ export function roleScratch(channels) {
3
3
  return {
4
4
  n: 0,
5
5
  voices: [],
@@ -36,13 +36,13 @@ export function own(k, slot, value, rest) {
36
36
  return out;
37
37
  }
38
38
  /**
39
- * Folds one locus's members into one contribution through each channel's own `lerp`: its value per
39
+ * Folds one role's members into one contribution through each channel's own `lerp`: its value per
40
40
  * channel goes in `k.values`, the channels in `k.touched` in the order first met, and its weight
41
41
  * is returned. A stock `last` channel has nothing to lerp, so it takes its heaviest member's value,
42
42
  * the later cued of two as heavy. A channel folding by scale takes its value in an array of `k`'s own, lerped in
43
43
  * place, since the fold only reads it.
44
44
  */
45
- export function foldLocus(k, group) {
45
+ export function foldRole(k, group) {
46
46
  k.touched.length = 0;
47
47
  let sum = 0;
48
48
  for (let m = 0; m < k.m; m++)
@@ -0,0 +1,10 @@
1
+ import type { Mixer } from './mixer.js';
2
+ import type { Subject } from './subject.js';
3
+ import type { Voice } from './voice.js';
4
+ /**
5
+ * Whether a fold's next voice is its chain's next record, `held`, and not the next voice sharing
6
+ * one record, `sharers[q]`: the two lists are each in voice order, and a fold takes them as one.
7
+ */
8
+ export declare function chainFirst<I, O>(held: Subject<unknown> | null, sharers: readonly Voice<I, O>[], q: number): boolean;
9
+ /** `foldWith`'s voices with a role in play: each role folds at its first member's place. */
10
+ export declare function foldRoles<I, O>(this: Mixer<I, O>, subject: I, pose: Record<string, unknown>, head: Subject<unknown>, laned: boolean, dry: boolean, except: number | undefined, owed: number): O;
package/dist/roles.js ADDED
@@ -0,0 +1,158 @@
1
+ import { first } from './clone.js';
2
+ import { open as openRecord } from './everyone.js';
3
+ import { merged, unband } from './fold.js';
4
+ import { roleScratch } from './role.js';
5
+ // V8 reads a local const as a constant, where it reads an import from its module on every call.
6
+ const open = openRecord;
7
+ /**
8
+ * Whether a fold's next voice is its chain's next record, `held`, and not the next voice sharing
9
+ * one record, `sharers[q]`: the two lists are each in voice order, and a fold takes them as one.
10
+ */
11
+ export function chainFirst(held, sharers, q) {
12
+ if (held === null)
13
+ return false;
14
+ if (q >= sharers.length || held.voice === null)
15
+ return true;
16
+ const next = sharers[q];
17
+ return next.id > held.voice.id;
18
+ }
19
+ /** `foldWith`'s voices with a role in play: each role folds at its first member's place. */
20
+ export function foldRoles(subject, pose, head, laned, dry, except, owed) {
21
+ const now = this.now;
22
+ // A patch reading the pose can fold again from inside this one, so each depth has its own.
23
+ let k = this.roleScratch[this.roleDepth];
24
+ if (k === undefined) {
25
+ k = roleScratch(this.channels.length);
26
+ this.roleScratch[this.roleDepth] = k;
27
+ }
28
+ this.roleDepth++;
29
+ try {
30
+ k.n = 0;
31
+ k.m = 0;
32
+ k.names.length = 0;
33
+ const sharers = this.sharers;
34
+ let q = 0;
35
+ let at = head;
36
+ while (at !== null || q < sharers.length) {
37
+ const mine = chainFirst(at, sharers, q);
38
+ let voice;
39
+ let rec;
40
+ if (mine) {
41
+ rec = at;
42
+ voice = rec.voice;
43
+ at = rec.next;
44
+ }
45
+ else {
46
+ voice = sharers[q++];
47
+ rec = voice.state === 'pending' ? null : voice.everyone;
48
+ }
49
+ if (voice === null || rec === null)
50
+ continue;
51
+ if (laned && voice.laned && !this.owedBy(owed, voice))
52
+ continue;
53
+ if (voice.id === except || voice.state === 'done')
54
+ continue;
55
+ const held = rec;
56
+ if (!mine)
57
+ open(voice, held, head.slot);
58
+ const delta = this.read(voice, subject, now, dry, held);
59
+ if (owed >= 0 && voice.laned)
60
+ this.lanes.paid(voice.id, owed, held.weight);
61
+ if (!mine)
62
+ q = this.shared(voice, head.slot, held, q);
63
+ if (delta === null)
64
+ continue;
65
+ const role = voice.spec.role;
66
+ let group = -1;
67
+ if (role !== undefined) {
68
+ group = k.names.indexOf(role);
69
+ const m = k.m++;
70
+ k.mVoices[m] = voice;
71
+ k.mDeltas[m] = delta;
72
+ k.mWeights[m] = this.w;
73
+ if (group >= 0) {
74
+ k.mGroups[m] = group;
75
+ continue;
76
+ }
77
+ group = k.names.length;
78
+ k.names.push(role);
79
+ k.mGroups[m] = group;
80
+ }
81
+ const n = k.n++;
82
+ k.voices[n] = voice;
83
+ k.helds[n] = held;
84
+ k.deltas[n] = delta;
85
+ k.weights[n] = role === undefined ? this.w : 0;
86
+ k.groups[n] = group;
87
+ }
88
+ let bands = head.roles;
89
+ if (bands === null) {
90
+ bands = new Map();
91
+ head.roles = bands;
92
+ }
93
+ for (let n = 0; n < k.n; n++) {
94
+ const group = k.groups[n];
95
+ if (group < 0) {
96
+ const weight = k.weights[n];
97
+ if (weight > 0)
98
+ this.apply(pose, k.voices[n], k.helds[n], k.deltas[n], weight);
99
+ else
100
+ unband(k.helds[n]);
101
+ continue;
102
+ }
103
+ const weight = this.foldRole(k, group);
104
+ const name = k.names[group];
105
+ if (!(weight > 0)) {
106
+ bands.get(name)?.fill(2);
107
+ continue;
108
+ }
109
+ let on = bands.get(name);
110
+ if (on === undefined) {
111
+ on = new Uint8Array(this.channels.length);
112
+ bands.set(name, on);
113
+ }
114
+ for (let t = 0; t < k.touched.length; t++) {
115
+ const slot = k.touched[t];
116
+ const value = k.values[slot];
117
+ k.values[slot] = undefined;
118
+ const key = this.names[slot];
119
+ const channel = this.channels[slot];
120
+ // A channel folds at the weight of the members that wrote it, not the whole role.
121
+ const taken = k.taken[slot];
122
+ const w = taken > 1 ? 1 : taken;
123
+ if (channel.rest !== undefined && channel.scale) {
124
+ // pose[key] is the copy of rest this fold made, so it is ours to write into.
125
+ pose[key] = channel.fold
126
+ ? channel.fold(pose[key], value, w)
127
+ : merged(channel, pose[key], channel.scale(value, w));
128
+ continue;
129
+ }
130
+ const band = on[slot];
131
+ const passes = this.passes(band === 0 ? undefined : band === 1, w);
132
+ on[slot] = passes ? 1 : 2;
133
+ if (passes)
134
+ pose[key] = pose[key] === undefined ? first(value) : merged(channel, pose[key], value);
135
+ }
136
+ }
137
+ return pose;
138
+ }
139
+ finally {
140
+ for (let n = 0; n < k.n; n++) {
141
+ k.voices[n] = null;
142
+ k.helds[n] = null;
143
+ k.deltas[n] = null;
144
+ }
145
+ for (let m = 0; m < k.m; m++) {
146
+ k.mVoices[m] = null;
147
+ k.mDeltas[m] = null;
148
+ }
149
+ // Left set only where a channel's lerp threw partway, which the next fold must not inherit.
150
+ for (let t = 0; t < k.touched.length; t++) {
151
+ const slot = k.touched[t];
152
+ k.met[slot] = 0;
153
+ k.values[slot] = undefined;
154
+ }
155
+ k.touched.length = 0;
156
+ this.roleDepth--;
157
+ }
158
+ }
package/dist/roster.js CHANGED
@@ -2,8 +2,8 @@ import { index, unindex } from './chain.js';
2
2
  import { detour } from './everyone.js';
3
3
  import { Store } from './store.js';
4
4
  function tally(mix, voice, by) {
5
- if (voice.spec.locus !== undefined)
6
- mix.loci += by;
5
+ if (voice.spec.role !== undefined)
6
+ mix.roleVoices += by;
7
7
  const e = voice.spec.earliest;
8
8
  if (voice.spec.anchor !== undefined || (e !== undefined && typeof e !== 'number'))
9
9
  mix.anchored += by;
@@ -36,7 +36,7 @@ export function recount(mix) {
36
36
  mix.naming = 0;
37
37
  mix.general = [];
38
38
  mix.sharers = [];
39
- mix.loci = 0;
39
+ mix.roleVoices = 0;
40
40
  mix.anchored = 0;
41
41
  for (const voice of mix.cued) {
42
42
  index(mix, voice);
@@ -10,7 +10,7 @@ export interface AngleOptions {
10
10
  turn?: number;
11
11
  }
12
12
  /**
13
- * A rotation about one axis, which adds as `sum` does and interpolates the short way round: from
13
+ * A rotation about one axis that interpolates the short way round. It adds as `sum` does, and from
14
14
  * 350 to 10 it passes 0, not 180. A weight below 1 fades it the short way to rest too. Its values
15
15
  * are angles, so two a whole number of turns apart are the same value, and a fold may answer with
16
16
  * either.
package/dist/rotation.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { doubles, vouch } from './channels.js';
2
2
  import { shortWay } from './turns.js';
3
3
  /**
4
- * A rotation about one axis, which adds as `sum` does and interpolates the short way round: from
4
+ * A rotation about one axis that interpolates the short way round. It adds as `sum` does, and from
5
5
  * 350 to 10 it passes 0, not 180. A weight below 1 fades it the short way to rest too. Its values
6
6
  * are angles, so two a whole number of turns apart are the same value, and a fold may answer with
7
7
  * either.
package/dist/rows.js CHANGED
@@ -4,6 +4,7 @@ import { reach } from './meet.js';
4
4
  import { motionOf } from './motion.js';
5
5
  import { open } from './qualify.js';
6
6
  import { settle } from './sample.js';
7
+ import { Seat, seatOf } from './seat.js';
7
8
  /** Whether two crowds' channels are the same list. */
8
9
  function same(a, b) {
9
10
  if (a.length !== b.length)
@@ -25,7 +26,7 @@ export function unplace(lanes, c, p) {
25
26
  c.records[p] = undefined;
26
27
  c.deltas[p] = null;
27
28
  const f = p * c.stride + 0 /* Hot.FLAGS */;
28
- c.hot[f] = c.hot[f] & ~16 /* Flag.PLACED */;
29
+ c.hot[f] = c.hot[f] & ~(16 /* Flag.PLACED */ | 2048 /* Flag.READY */);
29
30
  const o = p * 8 /* Row.STRIDE */;
30
31
  c.data[o + 4 /* Row.MSLOT */] = -1;
31
32
  c.data[o + 5 /* Row.SAMPLED */] = -1;
@@ -38,14 +39,13 @@ export function unplace(lanes, c, p) {
38
39
  export function sameCrowds(lanes, members) {
39
40
  let live = 0;
40
41
  for (const v of members) {
41
- if (!lanes.crowdOf.has(v.id))
42
+ if (!lanes.seats.has(v.id))
42
43
  return false;
43
44
  live++;
44
45
  }
45
46
  let rows = 0;
46
- for (const [id, c] of lanes.crowdOf) {
47
- const p = c.rowOf.get(id);
48
- if (c.voices[p].state !== 'done')
47
+ for (const s of lanes.seats.values()) {
48
+ if (s.crowd.voices[s.row].state !== 'done')
49
49
  rows++;
50
50
  }
51
51
  return rows === live;
@@ -64,13 +64,15 @@ export function regroup(lanes, members) {
64
64
  leaveCrowd(lanes, c, p);
65
65
  }
66
66
  const old = lanes.crowds;
67
- const was = new Map(lanes.crowdOf);
67
+ const was = new Map(lanes.seats);
68
+ for (const s of was.values())
69
+ s.lanes = null;
68
70
  lanes.crowds = [];
69
- lanes.crowdOf.clear();
71
+ lanes.seats.clear();
70
72
  lanes.overlap = false;
71
73
  for (const v of members) {
72
74
  const from = was.get(v.id);
73
- join(lanes, v, from, from?.rowOf.get(v.id), old);
75
+ join(lanes, v, from?.crowd, from?.row, old);
74
76
  }
75
77
  if (lanes.crowds.length === 0) {
76
78
  lanes.lawsByKey.clear();
@@ -104,8 +106,9 @@ export function join(lanes, v, from, at, old) {
104
106
  newRow(lanes, c, p, v);
105
107
  if (c.hot[p * c.stride + 6 /* Hot.EPOCH */] === 0 && v.state !== 'pending')
106
108
  c.hot[p * c.stride + 6 /* Hot.EPOCH */] = open(lanes, v);
107
- c.rowOf.set(v.id, p);
108
- lanes.crowdOf.set(v.id, c);
109
+ const seat = new Seat(lanes, c, p);
110
+ lanes.seats.set(v.id, seat);
111
+ v.seat = seat;
109
112
  v.laned = true;
110
113
  }
111
114
  /** A crowd row takes up its voice as it now stands: opened once it starts, its fields copied again. */
@@ -122,27 +125,28 @@ export function retouchRow(lanes, c, p, v) {
122
125
  export function compactSparse(lanes) {
123
126
  for (const c of lanes.crowds)
124
127
  if (c.dead > 64 && c.dead * 4 >= c.size)
125
- compact(c);
128
+ compact(lanes, c);
126
129
  }
127
130
  /**
128
131
  * Slides a crowd's rows down over the empty ones its departed voices left, in order, so its rows
129
132
  * stay in voice order; a qualify did this by rebuilding every crowd, a dropped frame at 10k rows.
130
133
  */
131
- function compact(c) {
134
+ function compact(lanes, c) {
132
135
  // The rows' stops are copied into a fresh pool, leaving the departed rows' behind.
133
136
  const keys = c.keys;
134
137
  c.keys = new KeyRows();
135
138
  let q = 0;
136
139
  for (let p = 0; p < c.size; p++) {
137
140
  const v = c.voices[p];
138
- if (c.rowOf.get(v.id) !== p) {
141
+ const s = seatOf(lanes, v);
142
+ if (s === null || s.crowd !== c || s.row !== p) {
139
143
  c.odd?.delete(p);
140
144
  continue;
141
145
  }
142
146
  if (q !== p) {
143
147
  copyRow(c, p, c, q, keys);
144
148
  c.odd?.delete(p);
145
- c.rowOf.set(v.id, q);
149
+ s.row = q;
146
150
  }
147
151
  else
148
152
  copyKeys(keys, c, q);
@@ -166,8 +170,12 @@ export function bury(lanes, c, p) {
166
170
  c.deltas[p] = null;
167
171
  c.motions[p] = undefined;
168
172
  c.hot[p * c.stride + 0 /* Hot.FLAGS */] = 0;
169
- c.rowOf.delete(v.id);
170
- lanes.crowdOf.delete(v.id);
173
+ lanes.seats.delete(v.id);
174
+ const s = v.seat;
175
+ if (s !== null && s.lanes === lanes) {
176
+ s.lanes = null;
177
+ v.seat = null;
178
+ }
171
179
  c.dead++;
172
180
  }
173
181
  /** Copies `n` numbers of a row; a view of them to hand `set` was three objects a row moved. */
@@ -202,7 +210,7 @@ function copyRow(from, p, to, q, keys = from.keys) {
202
210
  function copyKeys(keys, c, q) {
203
211
  const h = q * c.stride;
204
212
  if ((c.hot[h + 0 /* Hot.FLAGS */] & 512 /* Flag.FLAT */) !== 0)
205
- c.hot[h + 8 /* Hot.AT */] = c.keys.copy(keys, c.hot[h + 8 /* Hot.AT */]);
213
+ c.hot[h + 9 /* Hot.AT */] = c.keys.copy(keys, c.hot[h + 9 /* Hot.AT */]);
206
214
  }
207
215
  function newRow(lanes, c, p, v) {
208
216
  const run = motionOf(v.patch);
@@ -210,9 +218,9 @@ function newRow(lanes, c, p, v) {
210
218
  c.hot.fill(0, h, h + c.stride);
211
219
  const at = v.built === null ? -1 : c.keys.add(v.built, c.chans);
212
220
  if (at >= 0) {
213
- c.hot[h + 8 /* Hot.AT */] = at;
214
- c.hot[h + 10 /* Hot.X0 */] = v.duration;
215
- c.hot[h + 9 /* Hot.MS */] = v.passes;
221
+ c.hot[h + 9 /* Hot.AT */] = at;
222
+ c.hot[h + 11 /* Hot.X0 */] = v.duration;
223
+ c.hot[h + 10 /* Hot.MS */] = v.passes;
216
224
  }
217
225
  c.restale(p, 64 /* Flag.VOICE */ |
218
226
  (run === undefined ? 0 : 128 /* Flag.MOTION */) |
package/dist/sample.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { type Lane, type Positions } from './lane.js';
2
2
  import type { Lanes } from './lanes.js';
3
- import type { Motions } from './motions.js';
4
- import type { Subject } from './voice.js';
3
+ import { type Motions } from './motions.js';
4
+ import type { Subject } from './subject.js';
5
5
  /**
6
6
  * A motion voice's fill: what `one` and `move` do for each subject, in one loop for the common
7
7
  * case, a subject not yet probed this frame with nothing pending, and through `move` for the rest.