@msb235/blits 0.16.0 → 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.
- package/CHANGELOG.md +149 -0
- package/README.md +1 -1
- package/dist/ahead.js +6 -9
- package/dist/clock.d.ts +11 -1
- package/dist/clock.js +28 -9
- package/dist/contribution.js +11 -4
- package/dist/cue.js +12 -3
- package/dist/due.d.ts +18 -0
- package/dist/due.js +101 -2
- package/dist/fade.d.ts +12 -2
- package/dist/fade.js +41 -8
- package/dist/fill.js +2 -1
- package/dist/fit.js +8 -0
- package/dist/gone.js +1 -0
- package/dist/handle.d.ts +1 -1
- package/dist/handle.js +3 -1
- package/dist/held.d.ts +2 -1
- package/dist/held.js +14 -6
- package/dist/hosts.js +5 -1
- package/dist/lane.d.ts +2 -0
- package/dist/lane.js +2 -0
- package/dist/lanes.js +2 -1
- package/dist/meet.js +9 -0
- package/dist/mixer.d.ts +2 -0
- package/dist/mixer.js +2 -0
- package/dist/motion.js +6 -6
- package/dist/move.js +309 -82
- package/dist/origin.d.ts +2 -0
- package/dist/origin.js +4 -0
- package/dist/owner.js +4 -2
- package/dist/paging.d.ts +5 -1
- package/dist/paging.js +13 -3
- package/dist/patch.js +24 -4
- package/dist/place.js +67 -31
- package/dist/project.d.ts +5 -2
- package/dist/project.js +7 -4
- package/dist/qualify.js +6 -1
- package/dist/rests.js +9 -1
- package/dist/revive.d.ts +11 -1
- package/dist/revive.js +7 -0
- package/dist/roles.js +5 -2
- package/dist/rows.js +4 -1
- package/dist/seat.d.ts +5 -1
- package/dist/seat.js +5 -1
- package/dist/seek.js +6 -4
- package/dist/shared.d.ts +1 -1
- package/dist/shared.js +1 -1
- package/dist/spans.d.ts +10 -4
- package/dist/spans.js +121 -31
- package/dist/trace.js +7 -2
- package/dist/types/transport.d.ts +2 -1
- package/dist/voice.d.ts +42 -4
- package/dist/voice.js +45 -5
- package/dist/weigh.js +1 -1
- package/docs/schema.html +1 -1
- package/package.json +3 -1
package/dist/voice.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Arc } from './arcs.js';
|
|
|
2
2
|
import type { Blend } from './blend.js';
|
|
3
3
|
import type { Bound } from './bound.js';
|
|
4
4
|
import { type Clock } from './clock.js';
|
|
5
|
+
import { type Cut } from './cut.js';
|
|
5
6
|
import { type Curve } from './easing.js';
|
|
6
7
|
import type { Leavings } from './leavings.js';
|
|
7
8
|
import type { Motions } from './motions.js';
|
|
@@ -11,10 +12,10 @@ import { Holding } from './owner.js';
|
|
|
11
12
|
import { type Built, intosOf, type Scratch } from './patch.js';
|
|
12
13
|
import type { Seat } from './seat.js';
|
|
13
14
|
import { VoiceSetting } from './setting.js';
|
|
14
|
-
import type
|
|
15
|
+
import { type Fitting } from './spans.js';
|
|
15
16
|
import { Store } from './store.js';
|
|
16
17
|
import type { Parted, Parting, Snap, Subject } from './subject.js';
|
|
17
|
-
import type { Anchor, Channel, FadeSpec, Handle, Patch, VoiceSpec } from './types.js';
|
|
18
|
+
import type { Anchor, Channel, FadeSpec, FitResult, Handle, Patch, VoiceSpec } from './types.js';
|
|
18
19
|
export declare const none: readonly string[];
|
|
19
20
|
/** A voice's clock, weight and fade from one mix time on, kept under `history`. */
|
|
20
21
|
export interface Controls extends Clock {
|
|
@@ -24,6 +25,8 @@ export interface Controls extends Clock {
|
|
|
24
25
|
weight: number;
|
|
25
26
|
out: Ramp | null;
|
|
26
27
|
back: Rise | null;
|
|
28
|
+
/** Absent from a store written before a voice kept them. */
|
|
29
|
+
stalls?: readonly number[] | null;
|
|
27
30
|
/** Where its anchors had placed it then: start, and the start of an anchored fade out. */
|
|
28
31
|
start: number;
|
|
29
32
|
outAt: number;
|
|
@@ -35,9 +38,27 @@ export interface Controls extends Clock {
|
|
|
35
38
|
hold: number;
|
|
36
39
|
/** How many of the voice's past clocks it had then. */
|
|
37
40
|
past: number;
|
|
41
|
+
/** The clocks a fit replaced; absent from a store written before a voice kept them. */
|
|
42
|
+
earlier?: Earlier | null;
|
|
43
|
+
/** The clocks its owner ran back before; absent from a store written before a voice kept them. */
|
|
44
|
+
later?: Earlier | null;
|
|
45
|
+
/** A span's end and how its fit came out; absent for any other voice. */
|
|
46
|
+
fitted?: {
|
|
47
|
+
end: number;
|
|
48
|
+
report: FitResult;
|
|
49
|
+
} | null;
|
|
38
50
|
/** Shows in its own frame: made by a sync, or before anything read the mix that frame. */
|
|
39
51
|
sync: boolean;
|
|
40
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* A clock a fit replaced, which its voice runs on again where its owner's clock runs back to
|
|
55
|
+
* before the one that replaced it, with whether the fit had skipped it; `prev` is the one before.
|
|
56
|
+
* In `later`, one its owner ran back before, taken again where the owner's clock reaches it.
|
|
57
|
+
*/
|
|
58
|
+
export interface Earlier extends Clock {
|
|
59
|
+
readonly skipped: boolean;
|
|
60
|
+
readonly prev: Earlier | null;
|
|
61
|
+
}
|
|
41
62
|
export interface Ramp {
|
|
42
63
|
from: number;
|
|
43
64
|
at: number;
|
|
@@ -87,6 +108,11 @@ export declare class Voice<I, O> {
|
|
|
87
108
|
out: Ramp | null;
|
|
88
109
|
/** A fade out turned around, still climbing back; null once back in, or never turned. */
|
|
89
110
|
back: Rise | null;
|
|
111
|
+
/**
|
|
112
|
+
* Where its fade in stood still under a fade out later turned around, as pairs of mix times,
|
|
113
|
+
* from and to; null where it never did. Replaced, never changed, so a noted copy stays good.
|
|
114
|
+
*/
|
|
115
|
+
stalls: readonly number[] | null;
|
|
90
116
|
subjects: Store<I, Subject<unknown>>;
|
|
91
117
|
/** The subjects its spec names, or null where it names none. */
|
|
92
118
|
readonly named: Named<I> | null;
|
|
@@ -170,6 +196,10 @@ export declare class Voice<I, O> {
|
|
|
170
196
|
early: Subject<unknown>[] | null;
|
|
171
197
|
/** The clocks it ran on before its current one, where a subject's origin may lie; null for none. */
|
|
172
198
|
clocks: Past[] | null;
|
|
199
|
+
/** The clocks a fit replaced, latest first; null for none. */
|
|
200
|
+
earlier: Earlier | null;
|
|
201
|
+
/** The clocks its owner's clock ran back before, which it takes again going forward, soonest first. */
|
|
202
|
+
later: Earlier | null;
|
|
173
203
|
/** Reused for every call this voice makes, so it is valid only during the call. */
|
|
174
204
|
readonly setting: VoiceSetting<I, O>;
|
|
175
205
|
/** Every subject this voice has been asked about, so a handover knows when it is finished. */
|
|
@@ -185,6 +215,11 @@ export declare class Voice<I, O> {
|
|
|
185
215
|
cuedAt: number;
|
|
186
216
|
/** The frame it was cued in. */
|
|
187
217
|
cuedSeq: number;
|
|
218
|
+
/**
|
|
219
|
+
* The frame it last went live in, Infinity while pending: a seek of an owner back before its
|
|
220
|
+
* start leaves it live, which a read back by its clock alone would take for still to start.
|
|
221
|
+
*/
|
|
222
|
+
begunSeq: number;
|
|
188
223
|
/** The mix time it left at, which a fade or seek given a time already passed puts behind now. */
|
|
189
224
|
doneAt: number;
|
|
190
225
|
/** The frame its leaving was decided in, which a seek or read back partitions by. */
|
|
@@ -205,6 +240,8 @@ export declare class Voice<I, O> {
|
|
|
205
240
|
outSet: boolean;
|
|
206
241
|
/** The host time a pinned start was given at, NaN for none: a seek back pins it again. */
|
|
207
242
|
pinned: number;
|
|
243
|
+
/** The mix's offset when it was cued: a timestamp in its anchor converts at this one. */
|
|
244
|
+
offset: number;
|
|
208
245
|
/** Cued with no start while its mix slept, so the next sync sets its start. */
|
|
209
246
|
waking: boolean;
|
|
210
247
|
/** Whether its start is still to be fixed by an anchor, so it waits pending. */
|
|
@@ -336,9 +373,10 @@ export declare class Voice<I, O> {
|
|
|
336
373
|
/**
|
|
337
374
|
* Its state at mix time `t`, from its clock and fade, once every clock above it is back where it
|
|
338
375
|
* was: a fade begins at once, even one whose ramp starts later, at the voice's start, and one
|
|
339
|
-
* from where an owner under history keeps it ends with it quiet and live.
|
|
376
|
+
* from where an owner under history keeps it ends with it quiet and live. With `cut`, one that
|
|
377
|
+
* had started by then is live, though an owner's seek has put its clock back before its start.
|
|
340
378
|
*/
|
|
341
|
-
stateAt(t: number): Voice<I, O>['state'];
|
|
379
|
+
stateAt(t: number, cut?: Cut): Voice<I, O>['state'];
|
|
342
380
|
/**
|
|
343
381
|
* Whether it is kept quiet past its passes at mix time `now`: its passes ran out while an owner
|
|
344
382
|
* held it under history, and its clock has not come back into them. At their very edge it is
|
package/dist/voice.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { backWith, elapsedWith, frozenTime, passesOf, rateWith, rebaseWith, timeWith, } from './clock.js';
|
|
2
|
+
import { within } from './cut.js';
|
|
2
3
|
import { curve } from './easing.js';
|
|
3
4
|
import { motionOf } from './motion.js';
|
|
4
5
|
import { Named } from './named.js';
|
|
5
|
-
import { childPlayed, Holding, mixTime, ownedElapsed, ownerPatch, ownerReading, turnedBack, } from './owner.js';
|
|
6
|
+
import { childPlayed, Holding, mixTime, ownedElapsed, ownerPatch, ownerReading, plays, turnedBack, } from './owner.js';
|
|
6
7
|
import { builtOf, intosOf } from './patch.js';
|
|
7
8
|
import { keeping, VoiceSetting } from './setting.js';
|
|
9
|
+
import { lingers } from './spans.js';
|
|
8
10
|
import { Filled, Store } from './store.js';
|
|
9
11
|
import { warnOnce } from './warn.js';
|
|
10
12
|
export const none = Object.freeze([]);
|
|
@@ -52,6 +54,11 @@ export class Voice {
|
|
|
52
54
|
out = null;
|
|
53
55
|
/** A fade out turned around, still climbing back; null once back in, or never turned. */
|
|
54
56
|
back = null;
|
|
57
|
+
/**
|
|
58
|
+
* Where its fade in stood still under a fade out later turned around, as pairs of mix times,
|
|
59
|
+
* from and to; null where it never did. Replaced, never changed, so a noted copy stays good.
|
|
60
|
+
*/
|
|
61
|
+
stalls = null;
|
|
55
62
|
subjects = new Store();
|
|
56
63
|
/** The subjects its spec names, or null where it names none. */
|
|
57
64
|
named;
|
|
@@ -137,6 +144,10 @@ export class Voice {
|
|
|
137
144
|
early = null;
|
|
138
145
|
/** The clocks it ran on before its current one, where a subject's origin may lie; null for none. */
|
|
139
146
|
clocks = null;
|
|
147
|
+
/** The clocks a fit replaced, latest first; null for none. */
|
|
148
|
+
earlier = null;
|
|
149
|
+
/** The clocks its owner's clock ran back before, which it takes again going forward, soonest first. */
|
|
150
|
+
later = null;
|
|
140
151
|
/** Reused for every call this voice makes, so it is valid only during the call. */
|
|
141
152
|
setting;
|
|
142
153
|
/** Every subject this voice has been asked about, so a handover knows when it is finished. */
|
|
@@ -152,6 +163,11 @@ export class Voice {
|
|
|
152
163
|
cuedAt = Number.NEGATIVE_INFINITY;
|
|
153
164
|
/** The frame it was cued in. */
|
|
154
165
|
cuedSeq = Number.NEGATIVE_INFINITY;
|
|
166
|
+
/**
|
|
167
|
+
* The frame it last went live in, Infinity while pending: a seek of an owner back before its
|
|
168
|
+
* start leaves it live, which a read back by its clock alone would take for still to start.
|
|
169
|
+
*/
|
|
170
|
+
begunSeq = Number.POSITIVE_INFINITY;
|
|
155
171
|
/** The mix time it left at, which a fade or seek given a time already passed puts behind now. */
|
|
156
172
|
doneAt = Number.POSITIVE_INFINITY;
|
|
157
173
|
/** The frame its leaving was decided in, which a seek or read back partitions by. */
|
|
@@ -174,6 +190,8 @@ export class Voice {
|
|
|
174
190
|
outSet = false;
|
|
175
191
|
/** The host time a pinned start was given at, NaN for none: a seek back pins it again. */
|
|
176
192
|
pinned = Number.NaN;
|
|
193
|
+
/** The mix's offset when it was cued: a timestamp in its anchor converts at this one. */
|
|
194
|
+
offset = 0;
|
|
177
195
|
/** Cued with no start while its mix slept, so the next sync sets its start. */
|
|
178
196
|
waking = false;
|
|
179
197
|
/** Whether its start is still to be fixed by an anchor, so it waits pending. */
|
|
@@ -327,12 +345,20 @@ export class Voice {
|
|
|
327
345
|
this.weight = c.weight;
|
|
328
346
|
this.out = c.out;
|
|
329
347
|
this.back = c.back;
|
|
348
|
+
this.stalls = c.stalls ?? null;
|
|
330
349
|
this.start = c.start;
|
|
331
350
|
this.outAt = c.outAt;
|
|
332
351
|
this.outOver = c.outOver;
|
|
333
352
|
this.outSet = c.outSet;
|
|
334
353
|
this.rebuilds = c.rebuilds;
|
|
335
354
|
this.hold = c.hold;
|
|
355
|
+
this.earlier = c.earlier ?? null;
|
|
356
|
+
this.later = c.later ?? null;
|
|
357
|
+
const fitted = c.fitted;
|
|
358
|
+
if (fitted && this.fitting !== null) {
|
|
359
|
+
this.fitting.end = fitted.end;
|
|
360
|
+
this.fitting.report = fitted.report;
|
|
361
|
+
}
|
|
336
362
|
const clocks = this.clocks;
|
|
337
363
|
if (clocks !== null && clocks.length !== c.past)
|
|
338
364
|
this.clocks = c.past === 0 ? null : clocks.slice(0, c.past);
|
|
@@ -365,6 +391,7 @@ export class Voice {
|
|
|
365
391
|
this.restedCount = 0;
|
|
366
392
|
this.cuedAt = Number.NEGATIVE_INFINITY;
|
|
367
393
|
this.cuedSeq = Number.NEGATIVE_INFINITY;
|
|
394
|
+
this.begunSeq = Number.POSITIVE_INFINITY;
|
|
368
395
|
this.doneAt = Number.POSITIVE_INFINITY;
|
|
369
396
|
this.doneSeq = Number.POSITIVE_INFINITY;
|
|
370
397
|
this.placing = false;
|
|
@@ -482,13 +509,15 @@ export class Voice {
|
|
|
482
509
|
/**
|
|
483
510
|
* Its state at mix time `t`, from its clock and fade, once every clock above it is back where it
|
|
484
511
|
* was: a fade begins at once, even one whose ramp starts later, at the voice's start, and one
|
|
485
|
-
* from where an owner under history keeps it ends with it quiet and live.
|
|
512
|
+
* from where an owner under history keeps it ends with it quiet and live. With `cut`, one that
|
|
513
|
+
* had started by then is live, though an owner's seek has put its clock back before its start.
|
|
486
514
|
*/
|
|
487
|
-
stateAt(t) {
|
|
515
|
+
stateAt(t, cut) {
|
|
488
516
|
const out = this.out;
|
|
489
517
|
if (out !== null)
|
|
490
518
|
return out.stays && t >= out.at + out.over ? 'live' : 'fading';
|
|
491
|
-
if ((this.owner === null ? t : ownerReading(this.owner, t)) < this.start
|
|
519
|
+
if ((this.owner === null ? t : ownerReading(this.owner, t)) < this.start &&
|
|
520
|
+
!(cut !== undefined && within(cut, this.begunSeq, true)))
|
|
492
521
|
return 'pending';
|
|
493
522
|
if (this.freezesAfter && this.elapsedAt(t) >= this.span + this.latest)
|
|
494
523
|
return 'frozen';
|
|
@@ -500,8 +529,15 @@ export class Voice {
|
|
|
500
529
|
* quiet unless its clock runs into them.
|
|
501
530
|
*/
|
|
502
531
|
quietAt(now) {
|
|
503
|
-
|
|
532
|
+
const out = this.out;
|
|
533
|
+
if (this.state !== 'live' || out?.stays !== true)
|
|
504
534
|
return false;
|
|
535
|
+
// An owner kept quiet while its children were, rather than run back to its start.
|
|
536
|
+
if (this.holding !== null && out.back !== true)
|
|
537
|
+
return !plays(this, now) && !lingers(this, now);
|
|
538
|
+
// A fit that skipped it put it at its end at its start, so before its start it plays nothing.
|
|
539
|
+
if (this.skipped && ownerReading(this.owner, now) < this.start)
|
|
540
|
+
return true;
|
|
505
541
|
const at = this.elapsedAt(now);
|
|
506
542
|
const end = this.span + this.latest;
|
|
507
543
|
const back = this.backs !== (this.owner !== null && turnedBack(this));
|
|
@@ -579,6 +615,7 @@ export class Voice {
|
|
|
579
615
|
weight: this.weight,
|
|
580
616
|
out: this.out,
|
|
581
617
|
back: this.back,
|
|
618
|
+
stalls: this.stalls,
|
|
582
619
|
start: this.start,
|
|
583
620
|
outAt: this.outAt,
|
|
584
621
|
outOver: this.outOver,
|
|
@@ -586,6 +623,9 @@ export class Voice {
|
|
|
586
623
|
rebuilds: this.rebuilds,
|
|
587
624
|
hold: this.hold,
|
|
588
625
|
past: this.clocks?.length ?? 0,
|
|
626
|
+
earlier: this.earlier,
|
|
627
|
+
later: this.later,
|
|
628
|
+
fitted: this.fitting === null ? null : { end: this.fitting.end, report: this.fitting.report },
|
|
589
629
|
});
|
|
590
630
|
}
|
|
591
631
|
/**
|
package/dist/weigh.js
CHANGED
|
@@ -60,7 +60,7 @@ export function horizonFor(voice, delay, now) {
|
|
|
60
60
|
}
|
|
61
61
|
/** The ramp a voice's own fade envelope applies this frame, 0..1. */
|
|
62
62
|
export function fadeOf(voice, now, since) {
|
|
63
|
-
return envelope(voice.fade.in ?? 0, voice.out, voice.ease, this.reducedNow, now, since, voice.back);
|
|
63
|
+
return envelope(voice.fade.in ?? 0, voice.out, voice.ease, this.reducedNow, now, since, voice.back, voice.stalls);
|
|
64
64
|
}
|
|
65
65
|
/** What every owner above a voice multiplies into its weight for a subject this frame. */
|
|
66
66
|
export function ownedBy(voice, subject) {
|
package/docs/schema.html
CHANGED
|
@@ -898,7 +898,7 @@ phase.result; <span class="cm">// { budget, length, over, skipped, fell }</spa
|
|
|
898
898
|
<tr><td><code>pipe(...fits)</code></td><td>Tries each in turn, handing the plan on, until the children fit.</td></tr>
|
|
899
899
|
<tr><td><code>lax({ cap })</code></td><td><code>pipe(condense(), shed(), overrun({ cap }))</code>: every way of giving way, running long only once the rest are spent.</td></tr>
|
|
900
900
|
</table></div>
|
|
901
|
-
A strategy of the host's own takes the same arguments; <code>layout</code> says where a plan puts each child and how long they take, and <code>plain</code> is the plan before any fit. A child's start and rate are the span's: it takes no <code>start</code> or anchored start, only an <code>earliest</code>, a rate the host writes on its handle lasts until the next fit, and a voice that loops for good or a plain owner is refused. The span fits again when a child joins and when one leaves, and
|
|
901
|
+
A strategy of the host's own takes the same arguments; <code>layout</code> says where a plan puts each child and how long they take, and <code>plain</code> is the plan before any fit. A child's start and rate are the span's: it takes no <code>start</code> or anchored start, only an <code>earliest</code>, a rate the host writes on its handle lasts until the next fit, and a voice that loops for good or a plain owner is refused. The span fits again when a child joins and when one leaves; a seek puts its layout back as it stood. An owner sought forward passes the children it crosses as played straight through, whatever its rate: each starts, comes back from where it was kept quiet, and ends in turn, and the span fits from each end. Only what is still to come moves: a playing child keeps its place and changes rate without a jump, as a handle's <code>rate</code> write does, and a pending one is placed. A fit sees only the children that have arrived, so late ones are squeezed into what is left. A skipped child lands on its final state at that moment and its <code>end</code> mark is there, but the events its patch would have sent on the way are not sent. A span with a budget stays until the budget has passed, though its children finish sooner, so a late child can still join, and its <code>end</code> mark is wherever its children end or its budget does, whichever is later. A span held by a span is fitted through its own rate. A <code>seek</code> on its spec starts it that far into its budget. <code>span</code> refuses a rate below 0, since a fit lays the children out forward from the span's clock; a span turned back by its handle runs its children back as an owner does. A fit is made on the live mix and logged as the children's control changes, so a read back, a seek and its replay all see it, and the tape records nothing for it. Designed and built 2026-10-07; <code>earliest</code> and <code>rank</code> built 2026-10-22 for astv's change order, which sorts its changes by line and starts each no sooner than its orb lands:
|
|
902
902
|
<pre><code>const phase = mix.span({ name: 'phase', order: 'queue', duration: 2000 });
|
|
903
903
|
mix.cue({ patch: writeChange(change), owner: phase, rank: change.line,
|
|
904
904
|
earliest: { of: { name: `orb ${change.id}`, score: 'orbs' }, mark: 'end' } });</code></pre></li>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@msb235/blits",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.1",
|
|
4
4
|
"description": "Concurrent effects: each on its own clock with its own state and weight, folded into one value per subject per frame",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"animation",
|
|
@@ -60,6 +60,8 @@
|
|
|
60
60
|
"site": "npm run dev -w site",
|
|
61
61
|
"site:build": "npm run build -w site",
|
|
62
62
|
"site:smoke": "npm run smoke -w site",
|
|
63
|
+
"stress": "node apps/stress/scripts/stress.mjs",
|
|
64
|
+
"stress:dev": "npm run dev -w @blits/stress",
|
|
63
65
|
"playground": "npm run dev -w @blits/playground",
|
|
64
66
|
"playground:smoke": "npm run smoke -w @blits/playground",
|
|
65
67
|
"playground:check": "npm run typecheck -w @blits/playground && vitest run apps/playground/test && npm run playground:smoke"
|