@idle-screens/schema 3.13.0 → 3.15.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/FORMAT.md +183 -22
- package/dist/index.d.ts +362 -18
- package/dist/index.js +1419 -174
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
- package/saver-spec.schema.json +69 -5
package/dist/index.d.ts
CHANGED
|
@@ -194,11 +194,22 @@ interface TimelineKey {
|
|
|
194
194
|
path: string;
|
|
195
195
|
/** The target value; the key must still validate when applied. */
|
|
196
196
|
value: unknown;
|
|
197
|
-
/** Default `smooth`. */
|
|
198
|
-
ease?:
|
|
197
|
+
/** Default `smooth`. See `GlideEase`. */
|
|
198
|
+
ease?: GlideEase;
|
|
199
199
|
/** Glide length in ms, starting at `t`. Default 1000 (as live steering); 0 = a cut. */
|
|
200
200
|
dur?: number;
|
|
201
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* How a glide moves between its two values. `smooth` (default) starts and
|
|
204
|
+
* ends at rest; `linear` is constant speed; `step` holds, then switches at
|
|
205
|
+
* the glide's end. `in` accelerates from rest and arrives at speed (a
|
|
206
|
+
* departure); `out` leaves at speed and decelerates into place (an
|
|
207
|
+
* arrival); `spring` is a critically damped spring — a fast start that
|
|
208
|
+
* settles with no overshoot, so every value stays between its two ends.
|
|
209
|
+
* Every ease is monotone, so a glide never leaves the range its endpoints
|
|
210
|
+
* bound. **Native:** an ease it does not know reads as `smooth`.
|
|
211
|
+
*/
|
|
212
|
+
type GlideEase = 'step' | 'linear' | 'smooth' | 'in' | 'out' | 'spring';
|
|
202
213
|
/**
|
|
203
214
|
* Authored change on the scene's own clock — keyframes. A scene without a
|
|
204
215
|
* timeline is unchanged (ambient, today's behaviour); with one, each key
|
|
@@ -373,7 +384,8 @@ interface LayerSpec {
|
|
|
373
384
|
}
|
|
374
385
|
/**
|
|
375
386
|
* Data layouts — entities in READING ORDER, no scatter. `list` stacks them
|
|
376
|
-
* in one column `gap` apart (viewport units of min(w,h), default 0.06
|
|
387
|
+
* in one column `gap` apart (viewport units of min(w,h), default 0.06;
|
|
388
|
+
* `{y}` alone also works — a list has no columns, so `x` does nothing);
|
|
377
389
|
* `table` fills `columns` row-major with `gap` `{x, y}` (one number = both).
|
|
378
390
|
* Text and emoji sprites take `strings[i]` / `glyphs[i]` in order instead of
|
|
379
391
|
* a seeded pick, and palette `colors[i]` likewise, so N labels or N bars are
|
|
@@ -382,7 +394,10 @@ interface LayerSpec {
|
|
|
382
394
|
*/
|
|
383
395
|
| {
|
|
384
396
|
type: 'list';
|
|
385
|
-
gap?: number
|
|
397
|
+
gap?: number | {
|
|
398
|
+
x?: number;
|
|
399
|
+
y?: number;
|
|
400
|
+
};
|
|
386
401
|
} | {
|
|
387
402
|
type: 'table';
|
|
388
403
|
columns: number;
|
|
@@ -395,11 +410,33 @@ interface LayerSpec {
|
|
|
395
410
|
* Layer lifecycle for act structure — a pure function of t, no state. Alpha is 0
|
|
396
411
|
* before `enter` (ms), ramps up over `fade` ms (default 1000), holds at 1, then
|
|
397
412
|
* ramps down starting at `exit`. Entities are skipped entirely while at alpha 0.
|
|
413
|
+
* `stagger` (ms, default 0) shifts entity i's whole envelope — its enter and
|
|
414
|
+
* its exit — by i × stagger, in the layer's entity order (reading order for
|
|
415
|
+
* `list` / `table` / `grid`), so a row of labels builds one after another
|
|
416
|
+
* instead of together. Paint, like the rest of `life`: it glides, draws
|
|
417
|
+
* nothing from the RNG, and on screens where density scaling changes the
|
|
418
|
+
* count the whole build takes (count − 1) × stagger. **Native:** ignored ⇒
|
|
419
|
+
* every entity enters together.
|
|
398
420
|
*/
|
|
399
421
|
life?: {
|
|
400
422
|
enter?: number;
|
|
401
423
|
exit?: number;
|
|
402
424
|
fade?: number;
|
|
425
|
+
stagger?: number;
|
|
426
|
+
};
|
|
427
|
+
/**
|
|
428
|
+
* A swing back and forth: each entity rotates `amp` degrees either side of
|
|
429
|
+
* its resting angle (`rotate` plus any `spin`), once per `period` ms, on a
|
|
430
|
+
* seeded per-entity phase — a boat on a swell, a hanging sign, a lantern,
|
|
431
|
+
* a jellyfish's lean. A `clock` replaces the seeded phase like `pulse`'s,
|
|
432
|
+
* so a clocked layer swings in step. Structural (baked per entity like
|
|
433
|
+
* `pulse`). Its phase is one seeded draw per entity, made last and only
|
|
434
|
+
* when declared, so existing specs keep identical streams. **Native:**
|
|
435
|
+
* ignored ⇒ no swing.
|
|
436
|
+
*/
|
|
437
|
+
rock?: {
|
|
438
|
+
amp: number;
|
|
439
|
+
period: number;
|
|
403
440
|
};
|
|
404
441
|
/**
|
|
405
442
|
* Sparse events — marks that appear, expand and fade in place, one at a time,
|
|
@@ -579,6 +616,8 @@ type SpriteSpec = {
|
|
|
579
616
|
curve?: 'smooth' | 'linear';
|
|
580
617
|
taper?: boolean;
|
|
581
618
|
orient?: boolean;
|
|
619
|
+
/** Draw-on: the stretch of the mark that is painted — see `StrokeDraw`. Absent ⇒ the whole mark. */
|
|
620
|
+
draw?: StrokeDraw;
|
|
582
621
|
colors?: string[];
|
|
583
622
|
colorWeights?: number[];
|
|
584
623
|
}
|
|
@@ -624,7 +663,28 @@ type SpriteSpec = {
|
|
|
624
663
|
opacity?: number;
|
|
625
664
|
/** See `TextRole`. Absent ⇒ no legibility advisories, no pixel change. */
|
|
626
665
|
role?: TextRole;
|
|
666
|
+
/** A number the block shows instead of `text` — see `TextNumber`. */
|
|
667
|
+
number?: TextNumber;
|
|
627
668
|
};
|
|
669
|
+
/**
|
|
670
|
+
* A counter: the block paints `prefix + value + suffix` instead of `text`,
|
|
671
|
+
* with `value` rounded to `decimals` places (default 0) and, unless
|
|
672
|
+
* `grouping: false`, its thousands separated by commas — formatted the same
|
|
673
|
+
* way on every device (never by locale). `value` is **paint**: steer
|
|
674
|
+
* `sprite.number.value` (or key it on the timeline) and the digits roll
|
|
675
|
+
* there over the glide. Line-breaking runs on the formatted string, so a
|
|
676
|
+
* counter that gains a digit can re-wrap — give it room (`maxWidth`) or an
|
|
677
|
+
* `anchor` that pins the edge you want still. `text` stays what clients
|
|
678
|
+
* that do not read `number` show — author it as the counter's rest value.
|
|
679
|
+
* **Native:** ignored ⇒ `text`.
|
|
680
|
+
*/
|
|
681
|
+
interface TextNumber {
|
|
682
|
+
value: number;
|
|
683
|
+
decimals?: number;
|
|
684
|
+
prefix?: string;
|
|
685
|
+
suffix?: string;
|
|
686
|
+
grouping?: boolean;
|
|
687
|
+
}
|
|
628
688
|
/**
|
|
629
689
|
* What a text layer is *for* — a declaration, not a pixel. `read` says the
|
|
630
690
|
* words must be readable from across the room and opts the layer into
|
|
@@ -679,6 +739,14 @@ interface TextRevealSpec {
|
|
|
679
739
|
* wave of overlapping fades. Ignored by the other modes.
|
|
680
740
|
*/
|
|
681
741
|
fade?: number;
|
|
742
|
+
/**
|
|
743
|
+
* `glyphFade` only: each glyph rises into place as it fades in, from
|
|
744
|
+
* `rise` em below its line (0..1, default 0) — the caption that lifts as
|
|
745
|
+
* it appears. A pure function of the glyph's fade, so it glides, deletes
|
|
746
|
+
* and seeks like the rest of reveal; layout never moves. **Native:**
|
|
747
|
+
* ignored ⇒ no rise.
|
|
748
|
+
*/
|
|
749
|
+
rise?: number;
|
|
682
750
|
}
|
|
683
751
|
/** Rotate through sprite variants over time. Each entity offsets by its seeded phase. */
|
|
684
752
|
interface CycleSpec {
|
|
@@ -686,11 +754,12 @@ interface CycleSpec {
|
|
|
686
754
|
}
|
|
687
755
|
type MotionSpec =
|
|
688
756
|
/**
|
|
689
|
-
* Drift at a per-entity speed picked from `speed` (px/sec) along `angle` degrees
|
|
757
|
+
* Drift at a per-entity speed picked from `speed` (min(w,h) per second; px/sec under `units: 'px'`) along `angle` degrees
|
|
690
758
|
* (0 = right, 90 = down; default 0). `bidirectional` randomly flips horizontal
|
|
691
759
|
* direction per entity (e.g. fish swimming both ways); `bob` adds a small vertical
|
|
692
|
-
* wobble amplitude
|
|
693
|
-
* (
|
|
760
|
+
* wobble amplitude, in the spec's dimensional unit like `speed` — a fraction of
|
|
761
|
+
* min(w,h) (0.004 ≈ 4 px at 1080p), px only under `units: 'px'`. Covers
|
|
762
|
+
* horizontal fields, diagonals (toasters) and rain (angle 90).
|
|
694
763
|
*/
|
|
695
764
|
{
|
|
696
765
|
type: 'drift';
|
|
@@ -700,14 +769,18 @@ type MotionSpec =
|
|
|
700
769
|
bob?: number;
|
|
701
770
|
ease?: MotionEase;
|
|
702
771
|
}
|
|
703
|
-
/**
|
|
772
|
+
/**
|
|
773
|
+
* Rise upward with an optional horizontal `sway` amplitude — bubbles. Speed
|
|
774
|
+
* is min(w,h) per second and `sway` a fraction of min(w,h) (px/sec and px
|
|
775
|
+
* only under `units: 'px'`).
|
|
776
|
+
*/
|
|
704
777
|
| {
|
|
705
778
|
type: 'rise';
|
|
706
779
|
speed: [number, number];
|
|
707
780
|
sway?: number;
|
|
708
781
|
ease?: MotionEase;
|
|
709
782
|
}
|
|
710
|
-
/** Bounce diagonally at a per-entity speed, reflecting off the edges (px/sec). */
|
|
783
|
+
/** Bounce diagonally at a per-entity speed, reflecting off the edges (min(w,h) per second; px/sec under `units: 'px'`). */
|
|
711
784
|
| {
|
|
712
785
|
type: 'bounce';
|
|
713
786
|
speed: [number, number];
|
|
@@ -878,11 +951,18 @@ declare const LIMITS: {
|
|
|
878
951
|
readonly minClockedPeriod: 1000;
|
|
879
952
|
readonly minClockRate: 0.1;
|
|
880
953
|
readonly maxClockRate: 4;
|
|
954
|
+
readonly maxWobble: 0.25;
|
|
881
955
|
readonly defaultListGap: 0.06;
|
|
882
956
|
readonly defaultListGapPx: 40;
|
|
883
957
|
readonly minPolygonSides: 3;
|
|
884
958
|
readonly maxPolygonSides: 12;
|
|
885
959
|
readonly maxShapePoints: 24;
|
|
960
|
+
readonly maxTextNumberDecimals: 6;
|
|
961
|
+
readonly maxTextAffix: 64;
|
|
962
|
+
readonly maxRevealRise: 1;
|
|
963
|
+
readonly maxRockAmp: 45;
|
|
964
|
+
readonly minRockPeriod: 1000;
|
|
965
|
+
readonly maxLifeStagger: 60000;
|
|
886
966
|
readonly minEaseTau: 100;
|
|
887
967
|
readonly maxEaseTau: 120000;
|
|
888
968
|
};
|
|
@@ -915,7 +995,32 @@ type SequenceTransition = {
|
|
|
915
995
|
| {
|
|
916
996
|
type: 'fade';
|
|
917
997
|
dur: number;
|
|
918
|
-
}
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Wipe into the next segment over `dur` ms: the incoming segment is
|
|
1001
|
+
* revealed through a moving edge instead of a dissolve — `shape: 'circle'`
|
|
1002
|
+
* (default) is an iris opening from `origin` ({x, y} 0..1, default the
|
|
1003
|
+
* centre) until it clears the farthest corner; `'line'` sweeps a straight
|
|
1004
|
+
* edge across the frame along `angle` degrees (0 = left to right, 90 = top
|
|
1005
|
+
* to bottom). `feather` (0..1, default 0.08) softens the edge, as a
|
|
1006
|
+
* fraction of the distance it travels. Works between unlike segments,
|
|
1007
|
+
* like `fade`, on the same two-live-segment machinery and the same tier
|
|
1008
|
+
* gate (`basic` / `minimal` hosts cut). Every pixel changes once, so it is
|
|
1009
|
+
* a spatial ramp, never a flash. **Native:** cross-fades over `dur`.
|
|
1010
|
+
*/
|
|
1011
|
+
| WipeTransition;
|
|
1012
|
+
/** See the `wipe` member of `SequenceTransition`. */
|
|
1013
|
+
interface WipeTransition {
|
|
1014
|
+
type: 'wipe';
|
|
1015
|
+
dur: number;
|
|
1016
|
+
shape?: 'circle' | 'line';
|
|
1017
|
+
origin?: {
|
|
1018
|
+
x: number;
|
|
1019
|
+
y: number;
|
|
1020
|
+
};
|
|
1021
|
+
angle?: number;
|
|
1022
|
+
feather?: number;
|
|
1023
|
+
}
|
|
919
1024
|
interface SequenceSegment {
|
|
920
1025
|
key: string;
|
|
921
1026
|
scene: SaverSpec;
|
|
@@ -995,13 +1100,31 @@ interface PathGradientStop {
|
|
|
995
1100
|
color: string;
|
|
996
1101
|
opacity?: number;
|
|
997
1102
|
}
|
|
998
|
-
/**
|
|
1103
|
+
/**
|
|
1104
|
+
* An outline drawn along every subpath of a path polygon (open ones too).
|
|
1105
|
+
* `width` is in unit-box units. `draw` paints only a stretch of it — see
|
|
1106
|
+
* `StrokeDraw`: every subpath of the part counts as one pen line, in the
|
|
1107
|
+
* order the path draws them.
|
|
1108
|
+
*/
|
|
999
1109
|
interface PathStroke {
|
|
1000
1110
|
color: string;
|
|
1001
1111
|
width: number;
|
|
1002
1112
|
opacity?: number;
|
|
1003
1113
|
join?: 'round' | 'miter' | 'bevel';
|
|
1004
1114
|
cap?: 'round' | 'butt' | 'square';
|
|
1115
|
+
draw?: StrokeDraw;
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Draw-on for a line: only the stretch from `start` to `end` (fractions of
|
|
1119
|
+
* the line's arc length, 0..1, defaults 0 and 1) is painted, so gliding
|
|
1120
|
+
* `end` from 0 to 1 draws the line on as if by a pen, and gliding `start`
|
|
1121
|
+
* after it erases it from the tail. Paint: both glide under a steer, a
|
|
1122
|
+
* `timeline` key or a `morph` without a rebuild. `start ≥ end` paints
|
|
1123
|
+
* nothing. **Native:** ignored ⇒ the whole line.
|
|
1124
|
+
*/
|
|
1125
|
+
interface StrokeDraw {
|
|
1126
|
+
start?: number;
|
|
1127
|
+
end?: number;
|
|
1005
1128
|
}
|
|
1006
1129
|
/**
|
|
1007
1130
|
* One piece of a multi-colour path drawing (`polygon.parts`), drawn back to
|
|
@@ -1020,6 +1143,21 @@ interface PathPart {
|
|
|
1020
1143
|
opacity?: number;
|
|
1021
1144
|
}
|
|
1022
1145
|
|
|
1146
|
+
/**
|
|
1147
|
+
* Every motion type's fields besides `type` — the validator's allowlist, and
|
|
1148
|
+
* the source FORMAT.md's motion table is checked against (F74), so the table
|
|
1149
|
+
* cannot drift from what the validator accepts.
|
|
1150
|
+
*/
|
|
1151
|
+
declare const MOTION_FIELDS: {
|
|
1152
|
+
readonly drift: readonly ["speed", "angle", "bidirectional", "bob", "ease"];
|
|
1153
|
+
readonly rise: readonly ["speed", "sway", "ease"];
|
|
1154
|
+
readonly bounce: readonly ["speed"];
|
|
1155
|
+
readonly static: readonly [];
|
|
1156
|
+
readonly orbit: readonly ["speed", "radius", "center"];
|
|
1157
|
+
readonly wander: readonly ["speed", "angle", "meander", "coherence", "ease"];
|
|
1158
|
+
readonly warp: readonly ["speed", "center"];
|
|
1159
|
+
readonly path: readonly ["points", "duration", "curve", "closed", "scatter"];
|
|
1160
|
+
};
|
|
1023
1161
|
declare function validateSpec(spec: unknown): ValidationResult;
|
|
1024
1162
|
/**
|
|
1025
1163
|
* Dot-paths (numeric layer indices, as `steerablePaths` spells them) of every
|
|
@@ -1038,8 +1176,11 @@ declare function ignoredPropertyPaths(spec: unknown): string[];
|
|
|
1038
1176
|
* `validateSpec`. For the timeline's composed-state checks, which run at every
|
|
1039
1177
|
* key boundary and on every live steer: the whole-spec check costs a
|
|
1040
1178
|
* full validation per sample, this one a layer or two.
|
|
1179
|
+
*
|
|
1180
|
+
* `keyed`: the spec is a timeline state (a key's value, or mid-glide), where a
|
|
1181
|
+
* path that collapses to nothing draws nothing instead of failing (X583).
|
|
1041
1182
|
*/
|
|
1042
|
-
declare function validateSpecPaths(spec: SaverSpec, paths: Iterable<string
|
|
1183
|
+
declare function validateSpecPaths(spec: SaverSpec, paths: Iterable<string>, keyed?: boolean): boolean;
|
|
1043
1184
|
/** Narrowing helper: validate + cast. Throws with a joined message when invalid. */
|
|
1044
1185
|
declare function assertValidSpec(spec: unknown): SaverSpec;
|
|
1045
1186
|
/**
|
|
@@ -1156,6 +1297,12 @@ interface Entity {
|
|
|
1156
1297
|
};
|
|
1157
1298
|
/** Static rotation (radians), added to any spin. Only set when `layer.rotate` is declared. */
|
|
1158
1299
|
rotate?: number;
|
|
1300
|
+
/** `rock`: amplitude (radians), period (ms) and phase (radians; a clock's shared phase when clocked). */
|
|
1301
|
+
rock?: {
|
|
1302
|
+
amp: number;
|
|
1303
|
+
period: number;
|
|
1304
|
+
phase: number;
|
|
1305
|
+
};
|
|
1159
1306
|
/** Spline params (pts in px, shared per layer). Only set for path motion. */
|
|
1160
1307
|
path?: {
|
|
1161
1308
|
pts: Array<{
|
|
@@ -1180,6 +1327,16 @@ declare function spriteVariants(sprite: SpriteSpec): number;
|
|
|
1180
1327
|
* `scale` multiplies every dimensional draw (default 1). Used for viewport units.
|
|
1181
1328
|
* `countScale` multiplies entity count for density-aware scaling (default 1). */
|
|
1182
1329
|
declare function buildEntities(layer: LayerSpec, rng: Rng, w: number, h: number, scale?: number, countScale?: number): Entity[];
|
|
1330
|
+
/**
|
|
1331
|
+
* The string a `textBlock` paints: its `text`, or with `number` the
|
|
1332
|
+
* counter's `prefix + value + suffix` — rounded to `decimals`, thousands
|
|
1333
|
+
* grouped with commas unless `grouping: false`. Locale-free, so every
|
|
1334
|
+
* device draws the same digits.
|
|
1335
|
+
*/
|
|
1336
|
+
declare function textBlockText(s: {
|
|
1337
|
+
text: string;
|
|
1338
|
+
number?: TextNumber;
|
|
1339
|
+
}): string;
|
|
1183
1340
|
/**
|
|
1184
1341
|
* Where entity `e` sits in its current emit window at time `t`: 0..1 across
|
|
1185
1342
|
* the visible `life` ms, or null while dark (or when the layer has no `emit`).
|
|
@@ -1200,12 +1357,29 @@ declare function sizeAt(e: Entity, t: number): number;
|
|
|
1200
1357
|
* `rotate` is for).
|
|
1201
1358
|
*/
|
|
1202
1359
|
declare function rotationAt(e: Entity, t: number): number;
|
|
1203
|
-
/**
|
|
1360
|
+
/**
|
|
1361
|
+
* Layer lifecycle alpha multiplier at time `t` (ms). Pure. 1 when `life` is
|
|
1362
|
+
* unset. With `stagger` this is the layer's GATE only — 0 before the first
|
|
1363
|
+
* entity enters, else 1 — and each entity's own envelope is
|
|
1364
|
+
* `lifeStaggerAt(life, t, i)`.
|
|
1365
|
+
*/
|
|
1204
1366
|
declare function lifeAlphaAt(life: {
|
|
1205
1367
|
enter?: number;
|
|
1206
1368
|
exit?: number;
|
|
1207
1369
|
fade?: number;
|
|
1370
|
+
stagger?: number;
|
|
1208
1371
|
} | undefined, t: number): number;
|
|
1372
|
+
/**
|
|
1373
|
+
* Entity i's share of a staggered `life`: its whole envelope shifted by
|
|
1374
|
+
* i × stagger. 1 without a stagger (the layer's `lifeAlphaAt` already
|
|
1375
|
+
* carries the envelope), so callers multiply it in unconditionally.
|
|
1376
|
+
*/
|
|
1377
|
+
declare function lifeStaggerAt(life: {
|
|
1378
|
+
enter?: number;
|
|
1379
|
+
exit?: number;
|
|
1380
|
+
fade?: number;
|
|
1381
|
+
stagger?: number;
|
|
1382
|
+
} | undefined, t: number, i: number): number;
|
|
1209
1383
|
/** Analytic position of an entity at logical time `t` (ms). Pure & deterministic. */
|
|
1210
1384
|
declare function positionAt(e: Entity, t: number, w: number, h: number): Placed;
|
|
1211
1385
|
/**
|
|
@@ -1266,11 +1440,23 @@ type TextMetricsClass = 'proportional' | 'mono';
|
|
|
1266
1440
|
* family), else `proportional`. Absent font ⇒ proportional, as before.
|
|
1267
1441
|
*/
|
|
1268
1442
|
declare function textMetricsClassFor(font?: string): TextMetricsClass;
|
|
1443
|
+
/** Combining (semi-)voiced sound marks: they ride on the preceding kana and advance nothing. */
|
|
1444
|
+
declare function isCombiningKana(cp: number): boolean;
|
|
1445
|
+
/**
|
|
1446
|
+
* Whether a code point is set full-width — a CJK ideograph, kana, Hangul
|
|
1447
|
+
* syllable, CJK punctuation or a fullwidth form — so it advances ~1 em, not
|
|
1448
|
+
* the 0.55 of a Latin letter (X614: a centred katakana line measured at half
|
|
1449
|
+
* its real width and spilled off the frame). Halfwidth kana and Hangul
|
|
1450
|
+
* (U+FF61–FFDC) stay narrow. A fixed table, for the same determinism reason
|
|
1451
|
+
* as the buckets below.
|
|
1452
|
+
*/
|
|
1453
|
+
declare function isFullWidth(cp: number): boolean;
|
|
1269
1454
|
/**
|
|
1270
1455
|
* Em-width of a string under the same character-class table `breakTextBlock`
|
|
1271
1456
|
* uses. `mono` counts grapheme clusters, not UTF-16 code units — an emoji or
|
|
1272
1457
|
* combining/ZWJ sequence is one advance-width cell, matching what's painted,
|
|
1273
|
-
* not one cell per surrogate half or combining mark.
|
|
1458
|
+
* not one cell per surrogate half or combining mark. Under either table a
|
|
1459
|
+
* full-width (CJK) glyph advances a full em.
|
|
1274
1460
|
*/
|
|
1275
1461
|
declare function textWidthEm(str: string, metrics?: TextMetricsClass): number;
|
|
1276
1462
|
interface TextBlockLine {
|
|
@@ -1356,6 +1542,14 @@ type CapabilityTier = 'minimal' | 'basic' | 'standard' | 'high';
|
|
|
1356
1542
|
interface SpecMountContext extends SaverContext {
|
|
1357
1543
|
capabilityTier?: CapabilityTier;
|
|
1358
1544
|
}
|
|
1545
|
+
/**
|
|
1546
|
+
* A wipe's revealed region at progress `k` as a fill: opaque where the
|
|
1547
|
+
* incoming segment shows, clear where the outgoing still does, ramping over
|
|
1548
|
+
* the feathered edge — in the canvas's own pixels (`cw` × `ch`). The edge
|
|
1549
|
+
* starts just outside the frame and ends past its far side (feather
|
|
1550
|
+
* included), so k = 0 reveals nothing and k = 1 everything.
|
|
1551
|
+
*/
|
|
1552
|
+
declare function wipeMask(g: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, w: WipeTransition, k: number, cw: number, ch: number): CanvasGradient;
|
|
1359
1553
|
/**
|
|
1360
1554
|
* Compile a declarative spec into a runnable SaverPlugin. Throws if the spec is invalid
|
|
1361
1555
|
* (so an agent-authored spec is validated before it can run). The result is seeded,
|
|
@@ -1444,6 +1638,16 @@ interface SteerDelta {
|
|
|
1444
1638
|
}
|
|
1445
1639
|
/** Apply deltas to a deep copy of the spec (last-wins, target values). */
|
|
1446
1640
|
declare function applyDeltasToSpec(spec: SaverSpec, deltas: SteerDelta[]): SaverSpec;
|
|
1641
|
+
/**
|
|
1642
|
+
* `applyDeltasToSpec` for read-only callers: the same result, but every
|
|
1643
|
+
* subtree a delta does not touch is shared with `spec` — only the containers
|
|
1644
|
+
* on each delta's path are copied. The validator checks a timeline by applying
|
|
1645
|
+
* each key, and the composed keys at every sample time, to the whole spec; a
|
|
1646
|
+
* full deep copy per check was most of the cost of validating a long timeline
|
|
1647
|
+
* (a 132 KB scene spent ~60% of its CPU in JSON round-trips). The result
|
|
1648
|
+
* aliases `spec`, so it must never be mutated.
|
|
1649
|
+
*/
|
|
1650
|
+
declare function applyDeltasShared(spec: SaverSpec, deltas: SteerDelta[]): SaverSpec;
|
|
1447
1651
|
/**
|
|
1448
1652
|
* Interpolate between two structurally-similar specs at progress k (0..1).
|
|
1449
1653
|
* Numbers lerp, hex colours lerp per channel, everything else steps to the
|
|
@@ -1506,6 +1710,16 @@ declare function steerablePaths(spec: unknown): string[];
|
|
|
1506
1710
|
declare function sequenceSteerablePaths(seq: IdleSequence): string[];
|
|
1507
1711
|
/** Smooth (ease-in-out) progress curve used for glides. */
|
|
1508
1712
|
declare function easeSmooth(k: number): number;
|
|
1713
|
+
/**
|
|
1714
|
+
* A glide's progress curve, by name (`GlideEase`), for progress `k` (0..1).
|
|
1715
|
+
* Every curve is monotone and runs 0 → 1, so an eased value never leaves the
|
|
1716
|
+
* range its two endpoints bound. `spring` is the critically damped step
|
|
1717
|
+
* response 1 − (1 + ωk)e^(−ωk), normalised to land exactly on 1 at k = 1.
|
|
1718
|
+
* Anything unknown is `smooth` (today's default).
|
|
1719
|
+
*/
|
|
1720
|
+
declare function easeGlide(ease: string | undefined, k: number): number;
|
|
1721
|
+
/** The named eases a live steer honours (the ones that did not exist before steers carried an ease). */
|
|
1722
|
+
declare const STEER_EASES: ReadonlySet<string>;
|
|
1509
1723
|
/**
|
|
1510
1724
|
* Fields that require re-seeding entities when they change (placement/motion
|
|
1511
1725
|
* are baked at build time). Colour/alpha/pulse-only changes redraw in place.
|
|
@@ -1616,12 +1830,25 @@ declare function cohesionOf(layers: Array<{
|
|
|
1616
1830
|
layer: LayerSpec;
|
|
1617
1831
|
entities: Entity[];
|
|
1618
1832
|
}>, t: number, w: number, h: number): LayerCohesion[];
|
|
1833
|
+
/**
|
|
1834
|
+
* Mean fraction of an entity's filled AREA covered by a same-layer sibling,
|
|
1835
|
+
* 0..1 — the counterpart of `overlap`, which measures outline. Edge contact
|
|
1836
|
+
* buries outline and no area; a real pile buries both. `null` where
|
|
1837
|
+
* `cohesionOf` reports no overlap.
|
|
1838
|
+
*/
|
|
1839
|
+
declare function buriedAreaOf(layer: LayerSpec, entities: Entity[], t: number, w: number, h: number): number | null;
|
|
1619
1840
|
/**
|
|
1620
1841
|
* True when a `seamed` layer is worth warning about: dense enough to be
|
|
1621
1842
|
* deliberate, and opaque enough that it cannot be an atmospheric wash. Keeps
|
|
1622
1843
|
* the advisory off the soft low-alpha glow fields the playbook recommends.
|
|
1844
|
+
*
|
|
1845
|
+
* `buriedArea` (lazy, from {@link buriedAreaOf}) lets it also stay off a
|
|
1846
|
+
* TILING whose only seam is its palette: opaque hard tiles of unlike colour
|
|
1847
|
+
* that butt edge to edge show one clean colour boundary per join, which is
|
|
1848
|
+
* the banding the author drew (X574). A translucent tiling is still warned
|
|
1849
|
+
* about — each hairline overlap paints a darker line.
|
|
1623
1850
|
*/
|
|
1624
|
-
declare function seamsWorthWarning(layer: LayerSpec, c: LayerCohesion, entityCount: number): boolean;
|
|
1851
|
+
declare function seamsWorthWarning(layer: LayerSpec, c: LayerCohesion, entityCount: number, buriedArea?: () => number | null): boolean;
|
|
1625
1852
|
|
|
1626
1853
|
interface LayerSnapshot {
|
|
1627
1854
|
key: string | undefined;
|
|
@@ -1723,6 +1950,15 @@ interface LuminanceGridOptions extends PerceiveOptions {
|
|
|
1723
1950
|
* balance — and now smear.
|
|
1724
1951
|
*/
|
|
1725
1952
|
declare function luminanceGrid(spec: SaverSpec, opts?: LuminanceGridOptions): LuminanceGrid;
|
|
1953
|
+
/**
|
|
1954
|
+
* The luminance-grid cells each layer can paint (same viewport, seed and
|
|
1955
|
+
* grid as `luminanceGrid` with these options), for the timeline flash gate's
|
|
1956
|
+
* shortcut: per layer its cell indices, or null when its footprint is not
|
|
1957
|
+
* fixed — it moves, emits, links, trails, orbits or cycles its text — and so may reach
|
|
1958
|
+
* any cell. Each entity is a disc about its position that holds its whole
|
|
1959
|
+
* outline at its largest size under any rotation: generous, never short.
|
|
1960
|
+
*/
|
|
1961
|
+
declare function layerCellFootprints(spec: SaverSpec, opts?: LuminanceGridOptions): Array<number[] | null>;
|
|
1726
1962
|
/**
|
|
1727
1963
|
* Encode a luminance grid as a braille "image": each output character is a 2×4
|
|
1728
1964
|
* pixel cell, ordered-dithered so mid-tones render as dot density. A grid of
|
|
@@ -2027,6 +2263,28 @@ interface PerceiveSequenceOptions extends Omit<LuminanceGridOptions, 't'> {
|
|
|
2027
2263
|
*/
|
|
2028
2264
|
declare function perceiveSequenceFrame(seq: IdleSequence, T: number, opts?: PerceiveSequenceOptions): SequenceFramePerception;
|
|
2029
2265
|
|
|
2266
|
+
interface TimelineFlashReport {
|
|
2267
|
+
/** More than 3 flashes in some one-second window over at least a quarter of the frame (WCAG 2.3.1). */
|
|
2268
|
+
fails: boolean;
|
|
2269
|
+
/** Worst flashes per second any part of the frame reaches (0 when nothing was dense enough to check). */
|
|
2270
|
+
flashesPerSecond: number;
|
|
2271
|
+
/** Fraction of the frame flashing more than 3 times a second. */
|
|
2272
|
+
flashingArea: number;
|
|
2273
|
+
/** Start (scene ms, within the lap) of the worst window checked, or null when none was checked. */
|
|
2274
|
+
at: number | null;
|
|
2275
|
+
/**
|
|
2276
|
+
* Set (with `fails`) when dense sections remained beyond the
|
|
2277
|
+
* {@link MAX_TOTAL_SAMPLES} budget: unchecked is never reported as safe.
|
|
2278
|
+
* `flashesPerSecond` and `flashingArea` then describe only what was checked
|
|
2279
|
+
* (often nothing), so a message must say the timeline was too dense to
|
|
2280
|
+
* verify, not quote them as a strobe.
|
|
2281
|
+
*/
|
|
2282
|
+
incomplete?: true;
|
|
2283
|
+
/** With `incomplete`: the paint-key boundaries in the busiest second of the window left unchecked. */
|
|
2284
|
+
boundariesPerSecond?: number;
|
|
2285
|
+
}
|
|
2286
|
+
declare function timelineFlashReport(spec: SaverSpec): TimelineFlashReport;
|
|
2287
|
+
|
|
2030
2288
|
/**
|
|
2031
2289
|
* The `field` background sampler — seeded, dependency-free value noise that
|
|
2032
2290
|
* is a PURE function of `(u, v, t, config, seed)`.
|
|
@@ -2326,7 +2584,7 @@ declare function sequenceSwapCompatible(prev: IdleSequence, next: IdleSequence):
|
|
|
2326
2584
|
* exist on the spec (a key cannot create a field).
|
|
2327
2585
|
* - A key glides from wherever its path is at `t` to `value` over `dur` ms
|
|
2328
2586
|
* (default 1000, the live-steering default; 0 is a cut), eased `smooth`
|
|
2329
|
-
* unless it
|
|
2587
|
+
* unless it names another `GlideEase`. Overlapping glides are fine: the later one
|
|
2330
2588
|
* starts from the earlier one's in-flight value.
|
|
2331
2589
|
* - Without `loop`, a path holds the base spec's value before its first key.
|
|
2332
2590
|
* - With `loop`, time wraps every `duration` ms and a path's value before its
|
|
@@ -2352,8 +2610,9 @@ declare function timelineLocalTime(tl: Timeline, t: number): number;
|
|
|
2352
2610
|
* The scene at time `t` with every key applied — and without its `timeline`,
|
|
2353
2611
|
* so the result can be rendered, perceived, lerped or steered like any plain
|
|
2354
2612
|
* spec. A spec without a timeline comes back as the same object.
|
|
2613
|
+
* `shared` (read-only callers): untouched subtrees alias `spec` — never mutate the result.
|
|
2355
2614
|
*/
|
|
2356
|
-
declare function resolveTimelineAt(spec: SaverSpec, t: number): SaverSpec;
|
|
2615
|
+
declare function resolveTimelineAt(spec: SaverSpec, t: number, shared?: boolean): SaverSpec;
|
|
2357
2616
|
/**
|
|
2358
2617
|
* When the timeline next takes a path back: the scene time of that path's
|
|
2359
2618
|
* next key after `t`, and that key's glide length — or null when no key will
|
|
@@ -2378,6 +2637,42 @@ declare function timelineSampleTimes(spec: SaverSpec, dense?: boolean, cap?: num
|
|
|
2378
2637
|
/** The absolute scene times at or after `now` where `timelineSampleTimes` fall — the next lap's occurrences under `loop`. */
|
|
2379
2638
|
declare function timelineSampleTimesAfter(spec: SaverSpec, now: number, dense?: boolean, only?: (path: string) => boolean): number[];
|
|
2380
2639
|
|
|
2640
|
+
/**
|
|
2641
|
+
* The scene's own repeat period — how long until every frame comes round
|
|
2642
|
+
* again — read analytically from the spec, never by rendering.
|
|
2643
|
+
*
|
|
2644
|
+
* A scene is a pure function of time, so it repeats exactly when every
|
|
2645
|
+
* time-varying part does: each periodic part contributes its period, and the
|
|
2646
|
+
* scene's is their least common multiple (in whole ms). Anything that never
|
|
2647
|
+
* repeats — a seeded speed range, a drift that wraps at a per-entity speed,
|
|
2648
|
+
* a `life` build, a non-looping timeline, a self-typing reveal, animated
|
|
2649
|
+
* grain — makes the scene aperiodic, and the reason says which part. A scene
|
|
2650
|
+
* with nothing time-varying is static: every frame is the same (period 0).
|
|
2651
|
+
*
|
|
2652
|
+
* Who reads it: an export that wants an exact loop (#84), and the
|
|
2653
|
+
* `loop-seam` advisory, which fires only when a scene declares a looping
|
|
2654
|
+
* timeline whose lap the rest of the scene does not divide.
|
|
2655
|
+
*/
|
|
2656
|
+
|
|
2657
|
+
/** Periods that share no cycle shorter than this are reported as aperiodic. */
|
|
2658
|
+
declare const MAX_SCENE_PERIOD = 3600000;
|
|
2659
|
+
interface ScenePeriod {
|
|
2660
|
+
/** ms until the scene repeats exactly; 0 for a static scene; null when it never does (within an hour). */
|
|
2661
|
+
period: number | null;
|
|
2662
|
+
/** One line per part that sets or breaks the period, e.g. `layers[2] pulse: 4000 ms`. */
|
|
2663
|
+
parts: string[];
|
|
2664
|
+
/** Why it is null (empty otherwise). */
|
|
2665
|
+
reasons: string[];
|
|
2666
|
+
}
|
|
2667
|
+
/** The repeat period of `spec` — see the module comment. */
|
|
2668
|
+
declare function scenePeriod(spec: SaverSpec): ScenePeriod;
|
|
2669
|
+
/**
|
|
2670
|
+
* The period of everything in the scene except its timeline — what a
|
|
2671
|
+
* looping timeline's lap has to be a multiple of for one lap to loop
|
|
2672
|
+
* seamlessly.
|
|
2673
|
+
*/
|
|
2674
|
+
declare function periodWithoutTimeline(spec: SaverSpec): ScenePeriod;
|
|
2675
|
+
|
|
2381
2676
|
/**
|
|
2382
2677
|
* Reproduces the hand-coded Fish Aquarium saver as pure DATA — the discriminating test
|
|
2383
2678
|
* that the schema is actually expressive: two layers (fish drifting both ways at varied
|
|
@@ -2768,6 +3063,54 @@ declare const EXAMPLE_BY_ID: Readonly<Record<string, SaverSpec>>;
|
|
|
2768
3063
|
type PolygonSprite = Extract<SpriteSpec, {
|
|
2769
3064
|
kind: 'polygon';
|
|
2770
3065
|
}>;
|
|
3066
|
+
type StrokeSprite = Extract<SpriteSpec, {
|
|
3067
|
+
kind: 'stroke';
|
|
3068
|
+
}>;
|
|
3069
|
+
/** A stroke sprite's sampled path as painted: `strokeSamples`, cut to its `draw` span when it has one. */
|
|
3070
|
+
declare function strokeDrawnSamples(s: StrokeSprite, halfSize: number): Array<{
|
|
3071
|
+
x: number;
|
|
3072
|
+
y: number;
|
|
3073
|
+
}>;
|
|
3074
|
+
/**
|
|
3075
|
+
* The [start, end] arc-length fractions a `draw` paints, clamped to 0..1 — or
|
|
3076
|
+
* null when it paints the whole line (absent, or 0..1), so the caller keeps
|
|
3077
|
+
* its untrimmed (cached) geometry.
|
|
3078
|
+
*/
|
|
3079
|
+
declare function drawSpan(d: StrokeDraw | undefined): [number, number] | null;
|
|
3080
|
+
/** The stretch of a polyline between arc-length fractions `a` and `b` (a ≥ b ⇒ empty). Endpoints are interpolated. */
|
|
3081
|
+
declare function trimPolyline(pts: Array<{
|
|
3082
|
+
x: number;
|
|
3083
|
+
y: number;
|
|
3084
|
+
}>, a: number, b: number): Array<{
|
|
3085
|
+
x: number;
|
|
3086
|
+
y: number;
|
|
3087
|
+
}>;
|
|
3088
|
+
/**
|
|
3089
|
+
* A part's subpaths cut to a `draw` span: the lines are one pen run in draw
|
|
3090
|
+
* order (a closed ring counts its closing edge), so `end` sweeps through the
|
|
3091
|
+
* first subpath, then the next. A trimmed ring comes back open; untouched
|
|
3092
|
+
* lines are returned as they were. Memoised per (lines, span).
|
|
3093
|
+
*/
|
|
3094
|
+
declare function trimLines(lines: Array<{
|
|
3095
|
+
points: Array<[number, number]>;
|
|
3096
|
+
closed: boolean;
|
|
3097
|
+
}>, a: number, b: number): Array<{
|
|
3098
|
+
points: Array<[number, number]>;
|
|
3099
|
+
closed: boolean;
|
|
3100
|
+
}>;
|
|
3101
|
+
/** A part's stroke lines as drawn: its subpaths, cut to its stroke's `draw` span when it has one. */
|
|
3102
|
+
declare function strokeLines(part: {
|
|
3103
|
+
flat: {
|
|
3104
|
+
lines: Array<{
|
|
3105
|
+
points: Array<[number, number]>;
|
|
3106
|
+
closed: boolean;
|
|
3107
|
+
}>;
|
|
3108
|
+
};
|
|
3109
|
+
stroke?: PathStroke;
|
|
3110
|
+
}): Array<{
|
|
3111
|
+
points: Array<[number, number]>;
|
|
3112
|
+
closed: boolean;
|
|
3113
|
+
}>;
|
|
2771
3114
|
/** Most outline points one flattened path may produce. */
|
|
2772
3115
|
declare const MAX_PATH_POINTS = 4096;
|
|
2773
3116
|
interface FlattenedPath {
|
|
@@ -2838,4 +3181,5 @@ declare function pathFrame(s: PolygonSprite, r: number, w: number, h: number): {
|
|
|
2838
3181
|
uniform: boolean;
|
|
2839
3182
|
};
|
|
2840
3183
|
|
|
2841
|
-
export { AQUARIUM_SPEC, AURORA_SPEC,
|
|
3184
|
+
export { AQUARIUM_SPEC, AURORA_SPEC, COHESION_T, COMETS_SPEC, CONSTELLATION_SPEC, DASHBOARD_SPEC, DEFAULT_KEY_DUR, DITHER_ALPHA, DITHER_TILE, EXAMPLE_BY_ID, EXAMPLE_SEQUENCES, EXAMPLE_SPECS, FACETS_SPEC, FIELD_BUCKET_MS, FIELD_DRIFT_DEFAULT_AMOUNT, FIELD_RASTER_SHORT_SIDE, FIELD_RASTER_SHORT_SIDE_LOW, FINISH_ANIMATE_HZ, GRAIN_ALPHA, GRAIN_TILE, HAIKU_SPEC, LANTERNS_SPEC, LIMITS, LOBBY_TALK_SPEC, MATRIX_RAIN_SPEC, MAX_PATH_POINTS, MAX_SCENE_PERIOD, MOTION_FIELDS, MURMUR_SPEC, NOSTALGHIA_CANDLE_SPEC, ORRERY_SPEC, PHASE_DUET_SPEC, PINGS_SPEC, POLYGONS_SPEC, PROCESSION_SPEC, RAIN_SPEC, RELAY_BOARD_SPEC, SAKURA_SPEC, SCHEMA_EXAMPLES, SCHEMA_VERSION, SHARD_FALL_SPEC, SIGNAL_BOARD_SPEC, SNOWFALL_SPEC, SPARSE_NIGHT_SPEC, STEER_EASES, THERMAL_FIELD_SPEC, THREE_MOVEMENTS_SEQUENCE, TYPED_CUE_SPEC, WARP_MAX_SCALE, WARP_NEAR, WARP_TUNNEL_SPEC, WEB_WORK_SPEC, adviseSequence, adviseSpec, alphaAt, applyDeltasShared, applyDeltasToSpec, assertValidSequence, assertValidSpec, bayerMatrix, bayerTilePixels, bedRenderSeed, breakTextBlock, buildEntities, buriedAreaOf, canMorph, canWrapMorph, canonicalSpecPath, cohesionOf, compileSaver, compileSequence, createFinishPass, createSceneCanvas, describeScene, diffScenes, dominanceRanking, drawSpan, easeGlide, easeSmooth, emitEnvelope, emitWindow, entityScales, fieldAt, fieldOctaves, fieldRgb, fieldRgbAt, fieldSampleTime, finishStrength, flattenPath, grainOffset, grainTilePixels, graphemeClusters, hasHotSwapSequence, headingAt, hexToRgb255, ignoredPropertyPaths, isCombiningKana, isFullWidth, isPathPolygon, latticeHash, layerCellFootprints, layerCohesion, lerpPathD, lerpSpec, lerpValue, lifeAlphaAt, lifeStaggerAt, linkEdges, linkMaxDistPx, linkPairs, luminanceGrid, manifestFor, morphChainRoot, morphNothingMorphable, motionStats, nextKeyAfter, normalizeSeed, pathFrame, pathParts, perceiveScene, perceiveSequenceFrame, periodWithoutTimeline, pickScreenOp, positionAt, presentWithFinish, readSpecPath, renderBrailleMap, renderDensityMap, resolveSegment, resolveSpecPath, resolveTimelineAt, revealState, rgb255Luma, rotationAt, sceneColour, scenePeriod, seamCauseOf, seamsWorthWarning, segmentRenderSeed, segmentStart, sequenceSteerablePaths, sequenceSwapCompatible, sequenceWantsFinish, sizeAt, spriteIndexAt, spriteVariants, steerablePaths, strokeDrawnSamples, strokeLines, structuralSignature, textBlockAnchorOffset, textBlockText, textMetricsClassFor, textSprites, textStringsDiffer, textStringsOf, textWidthEm, timelineFlashReport, timelineLocalTime, timelineSampleTimes, timelineSampleTimesAfter, timelineTracks, trimLines, trimPolyline, validateSequence, validateSpec, validateSpecPaths, wipeMask, withoutTimeline };
|
|
3185
|
+
export type { BackgroundDrift, BackgroundSpec, BandSpec, CapabilityTier, CohesionRead, CycleSpec, DominanceEntry, Entity, FieldBackground, FieldDrift, FinishPass, FinishSpec, FlattenedPath, GlideEase, GradientStop, IdleSequence, LayerCohesion, LayerGroup, LayerMotionStats, LayerSnapshot, LayerSpec, LayerTransform, LinkEdge, LuminanceGrid, LuminanceGridOptions, MotionEase, MotionSpec, PathGradient, PathGradientStop, PathPart, PathStroke, PerceiveOptions, PerceiveSequenceOptions, Placed, ResolveOptions, ResolvedPart, ResolvedSegment, RevealState, SaverSpec, SceneColour, SceneDescription, SceneDiff, ScenePerception, ScenePeriod, SceneSnapshot, SchemaExample, ScreenOp, SequenceExample, SequenceFramePerception, SequenceMountContext, SequenceSaverInstance, SequenceSaverPlugin, SequenceSegment, SequenceTransition, SpecError, SpecMountContext, SpecWarning, SpriteSpec, SteerDelta, StrokeDraw, TextBlockAnchor, TextBlockLine, TextMetricsClass, TextNumber, TextRevealInput, TextRevealSpec, TextRole, TextSpriteInfo, Timeline, TimelineFlashReport, TimelineKey, ValidationResult, WanderOsc, WarningBox, WipeTransition };
|