@msb235/blits 0.3.0 → 0.4.0
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.
- package/CHANGELOG.md +52 -0
- package/README.md +2 -2
- package/dist/lanes.d.ts +149 -3
- package/dist/lanes.js +1031 -109
- package/dist/mixer.d.ts +29 -2
- package/dist/mixer.js +588 -82
- package/dist/motion.d.ts +46 -6
- package/dist/motion.js +173 -65
- package/dist/types.d.ts +46 -10
- package/docs/schema.html +37 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,58 @@ This package follows [semver](https://semver.org). Below 1.0.0, a breaking chang
|
|
|
4
4
|
version and everything else the patch. Each release lists its changes as **Breaking**, **Added** and
|
|
5
5
|
**Fixed**, and the release workflow refuses a tag with no section here.
|
|
6
6
|
|
|
7
|
+
## 0.4.0
|
|
8
|
+
|
|
9
|
+
### Breaking
|
|
10
|
+
|
|
11
|
+
- A tween's `MotionSpec` reads `ms: undefined` where its `ms` is a function of the subject.
|
|
12
|
+
- `Handle.state` can read `'held'`. Only a voice cued with `hold` reaches it, so nothing changes at
|
|
13
|
+
run time for one cued without, but a `switch` over every state no longer covers them all.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- `mix.inert` is true when another frame would change no pose, so a host's loop may sleep while
|
|
18
|
+
`live` is still true: every voice is done, held after its passes at a plain weight, or a motion
|
|
19
|
+
whose every subject has landed on its target. A retarget, a fade or a cue makes it false again.
|
|
20
|
+
- `handle.fade({ subject, over })` fades one subject out of a voice, which plays on for the rest.
|
|
21
|
+
Once the ramp ends the voice forgets the subject (its record, its lane position and a motion
|
|
22
|
+
patch's state for it) and no longer reaches it; a motion patch's `to` brings it back, met afresh.
|
|
23
|
+
`mix.drop(subject)` clears it too. It keeps the voice on its lane.
|
|
24
|
+
- `tween`'s `ms` may be a function of the subject, asked each time one of its stretches starts.
|
|
25
|
+
- `hold: 'before' | 'after' | 'both'` on a voice. Before, a subject shows the voice's first frame
|
|
26
|
+
while the voice is pending and while the subject waits out its `stagger`; after, a finite loop
|
|
27
|
+
shows its last frame once its passes are done and stays, `held`, until faded. `fade.in` counts
|
|
28
|
+
from the first frame shown. What WAAPI calls `fill`.
|
|
29
|
+
- `handle.played` resolves true when a finite loop's last pass ends, false if the voice leaves
|
|
30
|
+
first. It never rejects.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- A `keys` or stateless `fn` voice naming one subject and writing one channel fills from its
|
|
35
|
+
channel's crowd, as a motion voice does, where it took the general path. On teitou 10k `fn`
|
|
36
|
+
voices of one subject each read by `pull` take 0.7 of the time they did.
|
|
37
|
+
- A voice naming its subjects that is cued, starts or retires relinks only those subjects, and
|
|
38
|
+
joins or leaves its crowd in place, where it made every subject relink and the lanes qualify
|
|
39
|
+
every voice again: a frame stopping one of 10k such voices and cueing another fell from about
|
|
40
|
+
9 ms to 0.6 by `pull` on teitou. `mix.drop` looks only at the voices over every subject and
|
|
41
|
+
those naming the one dropped, where it walked every voice.
|
|
42
|
+
- `mix.drop` frees a subject from a voice faded off it with `handle.fade({ subject })`, and a mix
|
|
43
|
+
whose last ramp of that kind is dropped can read `inert` again; it also frees the subject from a
|
|
44
|
+
motion patch that another voice plays on after the voice naming the subject left.
|
|
45
|
+
- `pull` reads a list it read before in the same order from the lanes in one run, checking the
|
|
46
|
+
frame once rather than per subject: a tween voice over 10k subjects read by `pull` takes about
|
|
47
|
+
0.83 of the time it did on teitou.
|
|
48
|
+
- A lane gives a `vec(1)` channel an array of one, as the general path does, where it gave a
|
|
49
|
+
bare number; and a motion voice on one folds without making an array per subject per frame.
|
|
50
|
+
- A `tween` divides elapsed by its length in milliseconds, so a point a whole fraction of the way
|
|
51
|
+
in lands exactly: 150 ms into a 200 ms linear tween from 0 to 100 reads 75, not 74.99999999999999.
|
|
52
|
+
- `pull` over a list read again in the same order skips looking each subject up, and copies lane
|
|
53
|
+
values a column at a time; a motion voice's lane fills in one loop. One tween voice over 10k
|
|
54
|
+
subjects read by `pull` takes 0.59–0.66 of the time it did on teitou; a spring 0.68–0.74.
|
|
55
|
+
- Motion voices of one subject each fill from one crowd of flat rows per channel, and `sync` visits
|
|
56
|
+
only voices with something due: 10k tween voices of one subject each take 0.54–0.59 of the time
|
|
57
|
+
they did on teitou, 100k take 0.31–0.33.
|
|
58
|
+
|
|
7
59
|
## 0.3.0
|
|
8
60
|
|
|
9
61
|
### Breaking
|
package/README.md
CHANGED
|
@@ -76,9 +76,9 @@ so the engine keeps no dependencies; its README says how it works.
|
|
|
76
76
|
|
|
77
77
|
## Status
|
|
78
78
|
|
|
79
|
-
v0.
|
|
79
|
+
v0.4.0, with two consumers. magicsmoke runs every fault on it, and
|
|
80
80
|
[klieg](https://github.com/orochi235/klieg) composes all three of its systems — letter motion,
|
|
81
|
-
part effects, and the environment a sign is lit by — on a mix each
|
|
81
|
+
part effects, and the environment a sign is lit by — on a mix each.
|
|
82
82
|
That port was an extraction rather than a rewrite, so it is also the evidence that the arithmetic
|
|
83
83
|
here is the arithmetic a working renderer already had: klieg's suite of 1974 cases and its
|
|
84
84
|
screenshot baselines came through it unchanged.
|
package/dist/lanes.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { type Numeric } from './channels.js';
|
|
1
2
|
import type { Subject, Voice } from './mixer.js';
|
|
3
|
+
import { type Motions } from './motion.js';
|
|
2
4
|
import type { Channel } from './types.js';
|
|
3
5
|
/** What qualifying needs of one voice: whether its patch and spec can run on a lane, and its channels. */
|
|
4
6
|
export interface Candidate {
|
|
@@ -26,8 +28,12 @@ export interface LaneHost<I, O> {
|
|
|
26
28
|
meet(voice: Voice<I, O>, subject: I): Subject<unknown>;
|
|
27
29
|
/** The voices whose `subjects` name the subject, in voice order. */
|
|
28
30
|
naming(subject: I): readonly Voice<I, O>[] | undefined;
|
|
31
|
+
/** The subject's number, -1 where it has none yet. */
|
|
32
|
+
slotOf(subject: I): number;
|
|
29
33
|
/** The voice's own fade for a subject whose delay ran out at `since`. */
|
|
30
34
|
envelope(voice: Voice<I, O>, since: number): number;
|
|
35
|
+
/** What a subject's own ramp out of the voice leaves of its weight, 0..1. */
|
|
36
|
+
parting(voice: Voice<I, O>, subject: I): number;
|
|
31
37
|
/** Fills the voice's setting and the send target for a call to its patch. */
|
|
32
38
|
ready(voice: Voice<I, O>, subject: I, held: Subject<unknown>, elapsed: number, pass: number, weight: number): void;
|
|
33
39
|
/** Sets `reading.horizon` for the voice and a subject delayed `delay` voice ms. */
|
|
@@ -39,6 +45,18 @@ export interface LaneHost<I, O> {
|
|
|
39
45
|
/** A patch call left kept state on a record: the voice is stateful from now on. */
|
|
40
46
|
kept(voice: Voice<I, O>): void;
|
|
41
47
|
}
|
|
48
|
+
/** One laned channel: its arithmetic, and every numbered subject's folded value, `axes` per subject. */
|
|
49
|
+
interface Laned {
|
|
50
|
+
name: string;
|
|
51
|
+
/** Its value is a number, its rest a number; otherwise an array, which `vec(1)` is too. */
|
|
52
|
+
scalar: boolean;
|
|
53
|
+
op: Numeric['op'];
|
|
54
|
+
rest: number;
|
|
55
|
+
axes: number;
|
|
56
|
+
values: Float64Array;
|
|
57
|
+
/** An array channel's rest, which a copy starts from as the general path's does. */
|
|
58
|
+
start: readonly number[];
|
|
59
|
+
}
|
|
42
60
|
/** One channel `pull` writes: where in the kit, how many numbers a subject, and its rest. */
|
|
43
61
|
export interface Column {
|
|
44
62
|
key: string;
|
|
@@ -50,6 +68,23 @@ export interface Column {
|
|
|
50
68
|
bounds: readonly [number, number] | undefined;
|
|
51
69
|
}
|
|
52
70
|
export declare function clampRun(out: Float64Array, at: number, n: number, [lo, hi]: readonly [number, number]): void;
|
|
71
|
+
/**
|
|
72
|
+
* Where a motion voice's per-subject numbers live, by position: a lane of one voice, or the crowd
|
|
73
|
+
* of single-subject motion voices, whose positions each belong to a voice of their own.
|
|
74
|
+
*/
|
|
75
|
+
export interface Positions<I, O> {
|
|
76
|
+
/** `STRIDE` numbers per position. */
|
|
77
|
+
data: Float64Array;
|
|
78
|
+
/** Each position's last delta, built from its kept sample only when asked. */
|
|
79
|
+
deltas: (Record<string, unknown> | null)[];
|
|
80
|
+
/** Keeps a position's sample, `n` numbers from `xs`. */
|
|
81
|
+
keep(p: number, xs: Float64Array, n: number): void;
|
|
82
|
+
/** Reads a position's kept sample back into `xs`. */
|
|
83
|
+
kept(p: number, xs: Float64Array): void;
|
|
84
|
+
chans: Laned[];
|
|
85
|
+
voiceAt(p: number): Voice<I, O>;
|
|
86
|
+
motionAt(p: number): Motions<I> | undefined;
|
|
87
|
+
}
|
|
53
88
|
/**
|
|
54
89
|
* The lanes of one mix: subject numbers, which channels and voices run on lanes, and the values a
|
|
55
90
|
* frame's fill leaves for probes to copy.
|
|
@@ -63,6 +98,10 @@ export declare class Lanes<I, O> {
|
|
|
63
98
|
/** The lanes over every subject that have started playing, by epoch. */
|
|
64
99
|
private dense;
|
|
65
100
|
private readonly byId;
|
|
101
|
+
/** The crowds of single-subject motion voices, one per channel, and which holds each voice. */
|
|
102
|
+
private crowds;
|
|
103
|
+
private readonly crowdOf;
|
|
104
|
+
private readonly lawsByKey;
|
|
66
105
|
private laned;
|
|
67
106
|
/** By kit slot, the laned channel there. */
|
|
68
107
|
private bySlot;
|
|
@@ -72,6 +111,12 @@ export declare class Lanes<I, O> {
|
|
|
72
111
|
whole: boolean;
|
|
73
112
|
private cap;
|
|
74
113
|
private epochs;
|
|
114
|
+
/** The latest epoch of a lane over every subject, which every subject meets on its next probe. */
|
|
115
|
+
private wide;
|
|
116
|
+
/** Voices that joined, started or left since the last qualify, settled at the next probe. */
|
|
117
|
+
private touched;
|
|
118
|
+
/** The highest voice id the last qualify or settling of `touched` considered. */
|
|
119
|
+
private known;
|
|
75
120
|
/**
|
|
76
121
|
* Per subject number, `SLOT` numbers side by side, so a probe reads one place in memory: which
|
|
77
122
|
* fill last wrote it (0 for none); the latest lane epoch a probe of it has checked it against;
|
|
@@ -98,6 +143,12 @@ export declare class Lanes<I, O> {
|
|
|
98
143
|
* the general path has to make.
|
|
99
144
|
*/
|
|
100
145
|
private readonly late;
|
|
146
|
+
/** What `writeLater` has queued: lane slots and the rows they go to, for one set of columns. */
|
|
147
|
+
private queueSlots;
|
|
148
|
+
private queueRows;
|
|
149
|
+
private queued;
|
|
150
|
+
private inOrder;
|
|
151
|
+
private queuedFor;
|
|
101
152
|
private keeps;
|
|
102
153
|
private now;
|
|
103
154
|
private filledAt;
|
|
@@ -123,9 +174,20 @@ export declare class Lanes<I, O> {
|
|
|
123
174
|
invalidate(): void;
|
|
124
175
|
/** A change to a laned voice's weight or clock between probes: fill again. */
|
|
125
176
|
refill(): void;
|
|
177
|
+
/**
|
|
178
|
+
* A voice joined, started playing or left: the next probe takes it on or off its crowd where it
|
|
179
|
+
* can, and qualifies again where it cannot.
|
|
180
|
+
*/
|
|
181
|
+
touch(voice: Voice<I, O>): void;
|
|
126
182
|
/** Numbers a subject the mix sees for the first time. */
|
|
127
183
|
number(subject: I): number;
|
|
128
184
|
release(slot: number): void;
|
|
185
|
+
/** A voice faded a subject out of itself alone: its lane lets the subject's position go. */
|
|
186
|
+
part(id: number, slot: number): void;
|
|
187
|
+
/** Takes a crowd row off its subject, to be placed again if a probe meets it again. */
|
|
188
|
+
private unplace;
|
|
189
|
+
/** A voice brought a subject back: its next probe meets every lane again, as it met them first. */
|
|
190
|
+
rejoin(slot: number): void;
|
|
129
191
|
/**
|
|
130
192
|
* A laned voice's weight for a subject at the last frame a probe read it from the lane; undefined
|
|
131
193
|
* where the subject's record holds it instead, because its last probe took the general path or
|
|
@@ -139,14 +201,33 @@ export declare class Lanes<I, O> {
|
|
|
139
201
|
* this frame, which is where it is first seen; so does one probed from inside a fill.
|
|
140
202
|
*/
|
|
141
203
|
prepare(slot: number, subject: I, now: number, version: number): boolean;
|
|
204
|
+
/**
|
|
205
|
+
* For a `pull` of a list it read last time in the same order, with every voice on a lane, where
|
|
206
|
+
* `heads` holds each position's remembered chain head unless all are known current: what
|
|
207
|
+
* `prepare` and `writeLater` do for each subject from position `from`, while the subject reads
|
|
208
|
+
* from the lanes; returns the first position that does not, or the list's length.
|
|
209
|
+
*/
|
|
210
|
+
pullRun(slots: Int32Array, list: readonly I[], was: readonly I[], heads: readonly ({
|
|
211
|
+
version: number;
|
|
212
|
+
} | undefined)[] | null, from: number, columns: readonly Column[], now: number, version: number): number;
|
|
213
|
+
/** Whether this frame's lanes are filled and nothing since asks `begin` to look again. */
|
|
214
|
+
private filled;
|
|
215
|
+
/**
|
|
216
|
+
* What a probe does before its subject, once a frame's lanes are settled: qualifies and fills as
|
|
217
|
+
* needed. Says whether the subject takes the general path, whether only solo lanes exist, so
|
|
218
|
+
* there is nothing to fill, or whether the lanes are filled.
|
|
219
|
+
*/
|
|
220
|
+
private begin;
|
|
142
221
|
private general;
|
|
143
222
|
/** Whether a probe has read the subject since the frame began, on either path. */
|
|
144
223
|
private probedThisFrame;
|
|
145
224
|
/**
|
|
146
|
-
*
|
|
147
|
-
* the lanes while every voice is on one
|
|
225
|
+
* Queues a subject's values for each column at subject `n`'s places, for a `pull` that reads
|
|
226
|
+
* from the lanes while every voice is on one; `flush` writes what is queued, column by column. A
|
|
227
|
+
* channel no lane holds is at rest. Anything queued is written before the lanes fill again.
|
|
148
228
|
*/
|
|
149
|
-
|
|
229
|
+
writeLater(slot: number, columns: readonly Column[], n: number): void;
|
|
230
|
+
flush(): void;
|
|
150
231
|
/** Writes a subject's laned values into a pose, each array channel into a new array. */
|
|
151
232
|
copy(slot: number, pose: Record<string, unknown>): void;
|
|
152
233
|
/**
|
|
@@ -154,11 +235,52 @@ export declare class Lanes<I, O> {
|
|
|
154
235
|
* checked and that reaches it. True when any did, so the probe takes the general path.
|
|
155
236
|
*/
|
|
156
237
|
private meet;
|
|
238
|
+
/** Places a crowd voice's row on the subject a probe met, as `Lane.add` adds a position. */
|
|
239
|
+
private place;
|
|
157
240
|
/** Counts an idle lane more or fewer reaching a subject. */
|
|
158
241
|
private reach;
|
|
159
242
|
private forget;
|
|
160
243
|
private grow;
|
|
161
244
|
private requalify;
|
|
245
|
+
/**
|
|
246
|
+
* Rebuilds the crowds from the voices that belong in one, in voice order: a voice already in a
|
|
247
|
+
* crowd keeps its row's numbers, a new one gets a row a probe will place, and a voice no longer in
|
|
248
|
+
* one is handed back to the general path, as a lane's voice is when it leaves.
|
|
249
|
+
*/
|
|
250
|
+
private regroup;
|
|
251
|
+
/**
|
|
252
|
+
* Gives a voice the next row of its channel's crowd, made if there is none: a copy of the row it
|
|
253
|
+
* had in `from` at `at`, or a new one. `old` holds the crowds being replaced, whose idle state a
|
|
254
|
+
* new crowd on the same channel keeps. Rows stay in voice order only because voices join in it.
|
|
255
|
+
*/
|
|
256
|
+
private join;
|
|
257
|
+
/**
|
|
258
|
+
* A voice starts playing on a lane or crowd: its epoch. Every subject meets a voice over all of
|
|
259
|
+
* them on its next probe; a voice naming its subjects marks only those.
|
|
260
|
+
*/
|
|
261
|
+
private open;
|
|
262
|
+
/**
|
|
263
|
+
* Takes the voices `touch` named on or off their crowds, each as it now stands; false at the
|
|
264
|
+
* first that cannot be, which needs a qualify: one that leaves a lane or the general path, which
|
|
265
|
+
* may open a channel, or one that joins anything but a crowd on a laned channel. Neither a voice
|
|
266
|
+
* that fits joining laned channels nor a laned one leaving moves the fixed point `qualify` finds.
|
|
267
|
+
* False too once a crowd is more than half empty rows, which a qualify rebuilds without.
|
|
268
|
+
*/
|
|
269
|
+
private retouch;
|
|
270
|
+
/** A crowd voice left: its row stays, empty, until the crowds are next rebuilt. */
|
|
271
|
+
private bury;
|
|
272
|
+
private copyRow;
|
|
273
|
+
private newRow;
|
|
274
|
+
/** A patch's law, as the one array every crowd row with the same law shares. */
|
|
275
|
+
private lawOf;
|
|
276
|
+
/** A crowd voice's patch changed a stretch: its copy is read again at the next fill. */
|
|
277
|
+
private touchedCrowd;
|
|
278
|
+
/** Something on a voice changed: a crowd copies it again at the next fill. */
|
|
279
|
+
voiceChanged(id: number): void;
|
|
280
|
+
/** Hands a crowd voice back to the general path, as `leave` does a lane's. */
|
|
281
|
+
private leaveCrowd;
|
|
282
|
+
/** A crowd row's weight as `reported` reads a lane position's. */
|
|
283
|
+
private reportedRow;
|
|
162
284
|
/** Hands a voice back to the general path, with the weights `weightOf` reports kept on its records. */
|
|
163
285
|
private leave;
|
|
164
286
|
private fillAll;
|
|
@@ -177,6 +299,29 @@ export declare class Lanes<I, O> {
|
|
|
177
299
|
/** One voice's contribution to every subject it plays on. */
|
|
178
300
|
private run;
|
|
179
301
|
private one;
|
|
302
|
+
/** Reads a keys voice's stops at `phase` into `delta`, interpolating into the reader's `scratch`. */
|
|
303
|
+
private readKeyed;
|
|
304
|
+
/** Calls a stateless fn voice's patch for a subject its general path has met, and folds the delta. */
|
|
305
|
+
private call;
|
|
306
|
+
/** Runs every crowd's rows whose voices come before voice `id`, from where each crowd reached. */
|
|
307
|
+
private crowdsUpTo;
|
|
308
|
+
/**
|
|
309
|
+
* A crowd's rows from its cursor up to voice `id`: what `runMotion` does for a lane's subjects,
|
|
310
|
+
* reading each row's voice and stretch from `hot`, and through `move` for whatever `hot` cannot
|
|
311
|
+
* answer, as `runMotion` does.
|
|
312
|
+
*/
|
|
313
|
+
private runCrowd;
|
|
314
|
+
/** A keys or fn row's contribution, as `one` makes a lane position's. */
|
|
315
|
+
private row;
|
|
316
|
+
/** Copies what a fill reads of a crowd row's voice into `hot`; returns the row's flags. */
|
|
317
|
+
private copyVoice;
|
|
318
|
+
/** Copies a crowd row's current stretch from its patch into `hot`; returns the row's flags. */
|
|
319
|
+
private copyStretch;
|
|
320
|
+
/**
|
|
321
|
+
* A motion voice's fill: what `one` and `move` do for each subject, in one loop for the common
|
|
322
|
+
* case, a subject not yet probed this frame with nothing pending, and through `move` for the rest.
|
|
323
|
+
*/
|
|
324
|
+
private runMotion;
|
|
180
325
|
/**
|
|
181
326
|
* A motion voice's value for a subject, sampled from the patch's state as its `at` would, but only
|
|
182
327
|
* where that sample changes nothing: one with a change to stamp, commit or let go of goes to the
|
|
@@ -205,3 +350,4 @@ export declare class Lanes<I, O> {
|
|
|
205
350
|
private foldRun;
|
|
206
351
|
private foldInto;
|
|
207
352
|
}
|
|
353
|
+
export {};
|