@idle-screens/schema 3.4.5 → 3.6.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 +153 -20
- package/dist/index.d.ts +368 -10
- package/dist/index.js +1204 -84
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/saver-spec.schema.json +230 -35
package/FORMAT.md
CHANGED
|
@@ -32,7 +32,11 @@ sprites, `colorWeights`, `pulse.wave`, `layout` (grid), `life`,
|
|
|
32
32
|
`links.mode/falloff/closed`, `blend: screen|multiply`, orbit layer-parents
|
|
33
33
|
(2026-07-21 — "the v1 ceiling");
|
|
34
34
|
`textBlock` sprite — deterministic multi-line text with viewport-unit sizing;
|
|
35
|
-
`textBlock.reveal` — animated typing/deleting via one steerable paint param
|
|
35
|
+
`textBlock.reveal` — animated typing/deleting via one steerable paint param;
|
|
36
|
+
`emit` (sparse events), `clock` (phase-lock), `motion.ease` (settle / buoyant)
|
|
37
|
+
(2026-09-05 — time structure); `polygon` / `stroke` sprites and `rect.feather`
|
|
38
|
+
(2026-09-05 — shape glyphs); `layout: list | table` and the `bar` sprite
|
|
39
|
+
(2026-09-05 — data layout).
|
|
36
40
|
|
|
37
41
|
## Safety invariants
|
|
38
42
|
|
|
@@ -48,7 +52,10 @@ These hold **by construction** — no spec can violate them:
|
|
|
48
52
|
so a layer will generally not pulse in unison (edge case: if spawn positions
|
|
49
53
|
alias the wavelength, phases may coincide). `ghosting` only *smooths* luminance
|
|
50
54
|
changes (it composites frames over a faded copy of the last), never sharpens
|
|
51
|
-
them.
|
|
55
|
+
them. `clock` deliberately removes the per-entity phase, so the validator
|
|
56
|
+
floors a clocked layer's periods at 1000 ms (1 Hz) instead — a layer in
|
|
57
|
+
unison breathes, it never flashes. `emit` events are floored at one per
|
|
58
|
+
second per entity and always ride a smooth envelope, never a cut.
|
|
52
59
|
3. **Motion is bounded.** Speeds are capped (4000 px/sec; warp at 1.5
|
|
53
60
|
depth-units/sec; orbit at 180 deg/sec; path laps at ≥ 2 s).
|
|
54
61
|
4. **Work is bounded.** ≤ 36 layers, ≤ 400 entities per layer, ≤ 800 total,
|
|
@@ -72,7 +79,10 @@ Author against the JSON Schema; ship through the runtime validator.
|
|
|
72
79
|
|
|
73
80
|
Rules the JSON Schema cannot fully express (the runtime enforces them):
|
|
74
81
|
total entities across all layers ≤ 800; every `[min, max]` range must satisfy
|
|
75
|
-
`min ≤ max`; `colorWeights` length must match `colors`;
|
|
82
|
+
`min ≤ max`; `colorWeights` length must match `colors`; `emit.life ≤
|
|
83
|
+
emit.every`; a `clock`ed layer's `pulse`/`grow`/`cycle` period must satisfy
|
|
84
|
+
`period / rate ≥ 1000`; a `polygon` takes `sides` or `points`, not both (the
|
|
85
|
+
JSON Schema enforces this one too); orbit layer-parents
|
|
76
86
|
must exist, have `count: 1`, and not themselves orbit a layer.
|
|
77
87
|
|
|
78
88
|
## Structure
|
|
@@ -84,6 +94,7 @@ must exist, have `count: 1`, and not themselves orbit a layer.
|
|
|
84
94
|
"label": "Snowfall",
|
|
85
95
|
"seed": 42, // optional; falls back to the host's seed
|
|
86
96
|
"motionIntensity": "calm", // optional: calm | moderate | energetic
|
|
97
|
+
"density": "normal", // optional: sparse | normal | dense — declared intent, see below
|
|
87
98
|
"units": "viewport", // optional: viewport (default) | px
|
|
88
99
|
"referenceViewport": 1080, // optional; design resolution for density scaling
|
|
89
100
|
"ghosting": 0.9, // optional 0..0.95; frame-persistence smear
|
|
@@ -102,6 +113,16 @@ use that CSS font verbatim and do not scale with the viewport.
|
|
|
102
113
|
clearing: moving entities leave decaying after-images (Mystify smears, Matrix
|
|
103
114
|
trails, long-exposure light). 0.85–0.95 is the useful range; 0 (default) is off.
|
|
104
115
|
|
|
116
|
+
`density` declares what the emptiness or the crowding means. `sparse` says
|
|
117
|
+
the scene is *meant* to be almost empty — one faint mark on a dark ground,
|
|
118
|
+
long silences — so `adviseSpec` withholds `sparse-scene` and a scorer that
|
|
119
|
+
gates on coverage should read the declaration before calling a faithful
|
|
120
|
+
scene broken. `dense` withholds `dense-scene` the same way. The declaration
|
|
121
|
+
is checked, not trusted: a `sparse` scene whose alpha-weighted coverage
|
|
122
|
+
exceeds 2 %, or a `dense` one that would have tripped `sparse-scene`, gets a
|
|
123
|
+
`density-mismatch` advisory instead. Omitted means `normal`. Like
|
|
124
|
+
`motionIntensity`, it is a hint — it changes no pixel.
|
|
125
|
+
|
|
105
126
|
### `background`
|
|
106
127
|
|
|
107
128
|
- `{ "type": "solid", "color": "#06060c" }`
|
|
@@ -127,12 +148,14 @@ trails, long-exposure light). 0.85–0.95 is the useful range; 0 (default) is of
|
|
|
127
148
|
| `pulse` | `{amp ≤ 0.5, period ≥ 500, wave?}` | none | opacity breathing; `wave: {wavelength, angle?}` turns it into a traveling wave across the field |
|
|
128
149
|
| `spin` | number \| `[min,max]` ±360 deg/sec | none | per-entity rotation (seeded start angle); a range gives each entity a seeded speed (confetti, tumbling debris) |
|
|
129
150
|
| `grow` | `{amp ≤ 0.8, period ≥ 500}` | none | size breathing (seeded phase) |
|
|
130
|
-
| `trail` | `{length ≤ 5000, fade?}` | none | analytic afterglow trail
|
|
151
|
+
| `trail` | `{length ≤ 5000, fade?}` | none | analytic afterglow trail; `length` is **milliseconds** of history (max 5000), `fade` a number 0..1 (not a boolean — `links.falloff` in the next row is the boolean) |
|
|
131
152
|
| `links` | see below | none | inter-entity lines |
|
|
132
|
-
| `layout` | `{type: "grid", columns?, jitter?}` | scatter | grid
|
|
153
|
+
| `layout` | `{type: "grid", columns?, jitter?}` \| `{type: "list", gap?}` \| `{type: "table", columns, gap?}` | scatter | `grid`: cells fill `region`, `jitter` scalar or `{x?, y?}` 0..1 per axis. **`list` / `table`: data layouts** — entities in reading order from `position` (any count) or centred in `region`, `gap` apart (viewport units of `min(w,h)`, default 0.06); text/emoji take `strings[i]` / `glyphs[i]` and palettes `colors[i]` **in order**, so N labels are one layer |
|
|
133
154
|
| `life` | `{enter?, exit?, fade?}` ms | always on | act structure: fade the layer in at `enter`, out at `exit` |
|
|
155
|
+
| `emit` | `{every ≥ 1000, life ≥ 500, jitter?, grow?}` (`life ≤ every`) | always lit | **sparse events**: each entity is dark except a `life`-ms window every `every` ms, fading in fast and out slow; `jitter` 0 staggers entities evenly (one event at a time while `life ≤ every / count`), 1 scatters the offsets (a fixed sequence, not seeded — declaring `emit` disturbs no other draw); `grow: [from, to]` scales size across the window — expansion rather than travel |
|
|
156
|
+
| `clock` | `{phase?, rate?}` | seeded phases | **phase-lock**: `pulse`, `grow` and `cycle` share one phase (turns, 0..1) and run at `rate` × time; two layers with the same clock breathe in step. Clocked periods must satisfy `period / rate ≥ 1000` |
|
|
134
157
|
| `key` | string | none | addressable name → `setParam("key.field", …)` |
|
|
135
|
-
| `position` | `{x, y}` 0..1 | none | exact placement; **requires `count: 1
|
|
158
|
+
| `position` | `{x, y}` 0..1 | none | exact placement; **requires `count: 1`** — except with a `list`/`table` layout, where it anchors the block's top-left; overrides `region` |
|
|
136
159
|
|
|
137
160
|
`links`: `{ k: 1..8, maxDist, color?, alpha?, width?, mode?, falloff?, closed? }`.
|
|
138
161
|
`mode: "nearest"` (default) wires each entity to its k nearest neighbors within
|
|
@@ -155,8 +178,31 @@ with distance, removing pop-in at the cutoff.
|
|
|
155
178
|
- `{ "kind": "streak", "length": [0.01, 0.03], "color": "#cfd8ff", "width": 0.002 }` —
|
|
156
179
|
a line oriented along the entity's analytic heading with a faded tail
|
|
157
180
|
(rain that reads as rain, shooting stars, warp stars)
|
|
158
|
-
- `{ "kind": "rect", "width": [0.012, 0.02], "aspect": [1.3, 1.7], "color": "#ffb347" }` —
|
|
159
|
-
rectangle; `aspect` is the height/width ratio range (rotates with `spin`)
|
|
181
|
+
- `{ "kind": "rect", "width": [0.012, 0.02], "aspect": [1.3, 1.7], "color": "#ffb347", "feather": 0.6 }` —
|
|
182
|
+
rectangle; `aspect` is the height/width ratio range (rotates with `spin`);
|
|
183
|
+
`feather` 0..1 softens the edges — that fraction of the half-size fades out
|
|
184
|
+
toward the border (Rothko's block at 0.5–0.8)
|
|
185
|
+
- `{ "kind": "bar", "values": [82, 64, 91], "max": 100, "length": 0.5, "thickness": 0.02, "color": "#17e8c8", "direction": "right" }` —
|
|
186
|
+
a data bar: entity *i* draws `length × values[i] / max` (viewport units),
|
|
187
|
+
`thickness` thick, growing from its position toward `direction` (`right`
|
|
188
|
+
default, `left`, `up`, `down`). `values` are **paint**: `setParam("bars.values", […])`
|
|
189
|
+
glides every bar. With `layout: { type: "list" }` a chart is one layer and
|
|
190
|
+
its labels another — the dashboard genre stops needing one layer per number
|
|
191
|
+
- `{ "kind": "polygon", "radius": [0.02, 0.05], "sides": 3, "color": "#f2e8c9", "soft": false }` —
|
|
192
|
+
regular n-gon of the seeded circumradius, point up (`sides` 3..12, default
|
|
193
|
+
6); or `"points": [[-1, 0.9], [0.2, -1], [1, 0.4]]` — 3..24 unit
|
|
194
|
+
coordinates in −1..1 scaled by the radius — for any facet (a Picasso shard,
|
|
195
|
+
a Kandinsky triangle, a Mondrian plane). `soft` feathers the fill from the
|
|
196
|
+
centre. Rotates with `spin`. `points` are paint, so two polygons with the
|
|
197
|
+
same point count `morph` into each other
|
|
198
|
+
- `{ "kind": "stroke", "length": [0.08, 0.18], "points": [[-1, 0.3], [-0.5, -0.6], [0.3, -0.7], [1, 0.1]], "width": 0.004, "taper": true, "color": "#f2e8c9" }` —
|
|
199
|
+
a freehand mark: a path through 2..24 unit-coordinate points, scaled so the
|
|
200
|
+
unit box spans the seeded `length`, stroked `width` wide with round joins.
|
|
201
|
+
`curve: "smooth"` (default) is a Catmull-Rom spline, `"linear"` a polyline;
|
|
202
|
+
`taper` thins the mark to almost nothing at both ends (a brush stroke rather
|
|
203
|
+
than a line); `orient: true` turns the path's +x along the entity's heading,
|
|
204
|
+
like `streak`. Rotates with `spin`. Van Gogh's stroke, Hokusai's contour,
|
|
205
|
+
O'Keeffe's petal, Basquiat's scrawl
|
|
160
206
|
- `{ "kind": "textBlock", "text": "Multi-line text with wrapping.", "maxWidth": 0.8, "fontSize": 0.04, "lineHeight": 1.4, "align": "left", "color": "#e6e8ef" }` —
|
|
161
207
|
multi-line text block with deterministic line-breaking. All dimensions are
|
|
162
208
|
viewport fractions (of `min(w,h)`), not px — `units: "px"` specs reject
|
|
@@ -180,7 +226,14 @@ with distance, removing pop-in at the cutoff.
|
|
|
180
226
|
frontier per grapheme (`typewriter`, default), per `word`, or per `line`.
|
|
181
227
|
`speed` (graphemes/sec, max 120) makes the block type itself from scene
|
|
182
228
|
start: effective progress is `min(progress, speed·t/total)`, so a steered
|
|
183
|
-
`progress` can still hold or delete a self-typing block.
|
|
229
|
+
`progress` can still hold or delete a self-typing block. A fourth mode,
|
|
230
|
+
`glyphFade`, fades each glyph in over a staggered alpha ramp instead of
|
|
231
|
+
popping it whole (the caption look); `fade` (0–1 exclusive of 0, default
|
|
232
|
+
0.15, `glyphFade` only) is each glyph's fade window as a fraction of
|
|
233
|
+
progress — small reads as a soft typewriter, large as a wave of
|
|
234
|
+
overlapping fades. Still a pure function of `progress`, so it glides and
|
|
235
|
+
deletes exactly like the other modes. Native clients render `glyphFade`
|
|
236
|
+
as `typewriter` until they gain per-glyph alpha. `caret` draws a
|
|
184
237
|
blinking caret at the frontier (`true`, or `{ "blink": 1.2, "color": "#fff" }`;
|
|
185
238
|
blink is in full cycles/sec, capped at 3 for flash safety, and is a square
|
|
186
239
|
wave of `t` like every other animation). With `align: "center"`/`"right"`
|
|
@@ -189,9 +242,20 @@ with distance, removing pop-in at the cutoff.
|
|
|
189
242
|
glide `reveal.progress` to 0, swap `text` while nothing is visible, glide
|
|
190
243
|
back to 1.
|
|
191
244
|
|
|
192
|
-
`circle`, `ring`, `streak`,
|
|
193
|
-
per-entity palette pick) and `colorWeights: [...]`
|
|
194
|
-
length — "mostly cool tones, occasional ember").
|
|
245
|
+
`circle`, `ring`, `streak`, `rect`, `bar`, `polygon` and `stroke` all accept
|
|
246
|
+
`colors: [...]` (seeded per-entity palette pick) and `colorWeights: [...]`
|
|
247
|
+
(relative weights, same length — "mostly cool tones, occasional ember").
|
|
248
|
+
|
|
249
|
+
**Drawing a shape out of sprites.** Overlapping sprites merge into one seamless
|
|
250
|
+
silhouette under exactly one set of conditions: a single flat colour, `alpha`
|
|
251
|
+
of `[1, 1]`, no `blend`, no `pulse`, and hard edges (no `soft`, no
|
|
252
|
+
`rect.feather`). Anything else leaves every overlap visible as an internal edge,
|
|
253
|
+
so the layer reads as a pile of sprites rather than a form — `soft` in
|
|
254
|
+
particular reads as smoke, not mass. Depth then comes from *layer order* and a
|
|
255
|
+
different flat colour per layer, painting back to front, and a later layer can
|
|
256
|
+
be used to cut a clean edge across an earlier one. `layerCohesion` measures
|
|
257
|
+
which of the two you got; `overlap-seams` warns when you asked for the first
|
|
258
|
+
and configured the second.
|
|
195
259
|
|
|
196
260
|
### `motion` (one of)
|
|
197
261
|
|
|
@@ -221,6 +285,26 @@ length — "mostly cool tones, occasional ember").
|
|
|
221
285
|
|
|
222
286
|
All speeds are ranges; each entity draws its own value (seeded).
|
|
223
287
|
|
|
288
|
+
`drift`, `rise` and `wander` also accept **`ease`** — closed-form velocity
|
|
289
|
+
shaping, no integration state:
|
|
290
|
+
`{ "ease": { "type": "settle", "tau": 2500 } }` starts at the entity's speed
|
|
291
|
+
and decelerates to rest with time constant `tau` ms (it travels `speed × tau`
|
|
292
|
+
and stops — a mark flung and coming to rest); `"buoyant"` starts at rest and
|
|
293
|
+
approaches the speed over `tau` (a bubble reaching terminal velocity). With
|
|
294
|
+
`emit`, the eased travel restarts from the spawn point on every event. For
|
|
295
|
+
`wander` only the base velocity eases — the harmonic meander keeps breathing,
|
|
296
|
+
so a settled wanderer hovers rather than freezes.
|
|
297
|
+
|
|
298
|
+
**Time structure, composed.** `emit` is how a scene does *almost nothing,
|
|
299
|
+
almost never*: one ring every twelve seconds that expands and fades
|
|
300
|
+
(`emit: { every: 12000, life: 5000, jitter: 0, grow: [0.15, 2.4] }`) reads as
|
|
301
|
+
an event, where the same ring pulsing forever reads as wallpaper. Long
|
|
302
|
+
`every`, low `count`, `jitter: 0` for a metronome, `1` for weather; pair with
|
|
303
|
+
`ease: settle` so a mark is flung and comes to rest before it fades ("expand,
|
|
304
|
+
drift a little, settle, fade" is one layer). `clock` is the other half: two
|
|
305
|
+
layers on the same clock are a duet, not a coincidence. The shipped `pings`
|
|
306
|
+
example is the whole idea in two layers.
|
|
307
|
+
|
|
224
308
|
## Determinism contract
|
|
225
309
|
|
|
226
310
|
Entity construction consumes the seeded RNG in a **fixed draw order** per layer.
|
|
@@ -286,10 +370,39 @@ trade-offs for a zero-dependency, renderer-free analysis tool.
|
|
|
286
370
|
line-salience boost so they aren't crushed by filled discs.
|
|
287
371
|
- `motionStats(spec, opts)` — per-layer mean/max on-screen speed from analytic
|
|
288
372
|
displacement — choreography as numbers.
|
|
373
|
+
- `layerCohesion(spec, opts)` — **does a layer read as one form or as N marks?**
|
|
374
|
+
The luminance maps measure ink, not edges, so a layer whose sprites merged
|
|
375
|
+
into a silhouette and one that stayed a pile of discs are identical in every
|
|
376
|
+
other channel. Two independent facts, both reported (also on
|
|
377
|
+
`perceiveScene().form`): `overlap` is geometry — the mean fraction of an
|
|
378
|
+
entity's outline buried inside a sibling (shipped particle fields measure
|
|
379
|
+
≤ 0.154; a packed silhouette measures 0.79–0.85; `null` for lines, glyphs and
|
|
380
|
+
text, where a merged silhouette is not a meaningful idea). `seamless` is
|
|
381
|
+
paint — whether those overlaps vanish or draw an internal edge, with
|
|
382
|
+
`seamCause` naming the field to change. `reads` combines them into `mass`,
|
|
383
|
+
`seamed` or `marks`.
|
|
289
384
|
- `textSprites(spec, opts)` — the literal strings and rendered sizes of every
|
|
290
385
|
text layer. Glyphs are invisible in the luminance maps, so this is the only
|
|
291
386
|
way to confirm *what words* are on screen and how big. (Also on
|
|
292
387
|
`perceiveScene().text`.)
|
|
388
|
+
- `adviseSpec(spec)` — non-blocking advisories (also on
|
|
389
|
+
`perceiveScene().advisories`). Two of them are **spatial**, for static text:
|
|
390
|
+
`text-off-screen` (a text/textBlock box crosses the viewport edge by more
|
|
391
|
+
than 1%) and `text-overlap` (two static text layers share more than 10% of
|
|
392
|
+
the smaller box). Boxes come from the same character-class width table the
|
|
393
|
+
textBlock line-breaker uses, so they are estimates of the renderer's own
|
|
394
|
+
layout — a caption that fails these will look wrong on the wall; one that
|
|
395
|
+
passes may still sit a few px off.
|
|
396
|
+
`overlap-seams` catches the trap the other channels are blind to: a layer
|
|
397
|
+
whose entities bury each other (so it was drawn as one shape) but whose paint
|
|
398
|
+
settings make every overlap visible. It is gated to stay off deliberate
|
|
399
|
+
washes — it needs at least 6 entities and a mean base alpha of 0.45, and
|
|
400
|
+
never fires under `blend: lighter` / `screen`. No shipped example trips it.
|
|
401
|
+
The two density advisories read the spec's declared `density` first:
|
|
402
|
+
`sparse-scene` is withheld under `density: "sparse"` and `dense-scene`
|
|
403
|
+
under `"dense"`, and `density-mismatch` fires instead when the measured
|
|
404
|
+
coverage contradicts the declaration (a "sparse" scene covering more than
|
|
405
|
+
2 % of the frame, a "dense" one that would have read as empty).
|
|
293
406
|
- `diffScenes(a, b, opts)` — **relative sight**: coverage/luminance deltas,
|
|
294
407
|
visual-balance shift, 3×3 region deltas, dominance-rank movement, and
|
|
295
408
|
advisory codes added/removed. Agents judge "is B better than A" far more
|
|
@@ -330,8 +443,12 @@ timeline. Discriminated from SaverSpec by `format: 'idle-sequence'`.
|
|
|
330
443
|
be unique. Duration is in milliseconds (minimum 1000 ms for flash safety). Only
|
|
331
444
|
the final segment may omit duration (holds indefinitely).
|
|
332
445
|
|
|
333
|
-
**Advance mode:** `auto` (default)
|
|
334
|
-
|
|
446
|
+
**Advance mode:** `auto` (default) and `either` advance on the timer. `input`
|
|
447
|
+
makes a timed segment **hold** at the end of its `duration` until a
|
|
448
|
+
`sequence.segment` steer releases it — the clicker (see Steering below). The
|
|
449
|
+
held scene keeps animating (its `localT` keeps growing), so a slide waiting for
|
|
450
|
+
the presenter never freezes. A durationless final segment holds regardless of
|
|
451
|
+
`advance`.
|
|
335
452
|
|
|
336
453
|
**Transitions:** `{ type: 'cut' }` (default) performs a hard switch.
|
|
337
454
|
`{ type: 'morph', dur: number }` smoothly interpolates paint properties
|
|
@@ -345,7 +462,8 @@ seed is unused. `dur` must be between 200 and 5000 ms.
|
|
|
345
462
|
**Time mapping:** global clock `T` maps to `(segmentIndex, localT)` via prefix
|
|
346
463
|
sums of durations. Half-open segments: `[start, start+duration)`. With
|
|
347
464
|
`loop: true`, `T` wraps at the sum of all durations (loop is incompatible with
|
|
348
|
-
a durationless final segment).
|
|
465
|
+
a durationless final segment). An unreleased `advance: 'input'` hold blocks the
|
|
466
|
+
wrap; after a wrap every hold is armed again.
|
|
349
467
|
|
|
350
468
|
**Compilation:** `compileSequence()` returns an ordinary `SaverPlugin` — the
|
|
351
469
|
viewer needs zero changes. All children share a single canvas; only the active
|
|
@@ -353,8 +471,20 @@ segment's `SpecInstance` is alive at any time. `workerReady` is `false` (the
|
|
|
353
471
|
worker compile-hook does not dispatch sequences).
|
|
354
472
|
|
|
355
473
|
**Steering:** segment switching uses the `sequence.segment` delta path via
|
|
356
|
-
`applyTrack
|
|
357
|
-
|
|
474
|
+
`applyTrack` (`setParam("sequence.segment", n)` over MCP). The
|
|
475
|
+
`SequenceInstance` intercepts this path before delegation; remaining deltas are
|
|
476
|
+
forwarded to the active child's `applyTrack`.
|
|
477
|
+
|
|
478
|
+
The steer **moves the clock, not the frame**: it displaces the timeline so the
|
|
479
|
+
target segment starts at its own `localT` 0 (its `life.enter` build replays)
|
|
480
|
+
and then runs on the timer from there — the next animation frame resolves to
|
|
481
|
+
the same segment instead of snapping back to the wall clock. A steer to
|
|
482
|
+
segment `n` also counts as the presenter clicking past every `advance: 'input'`
|
|
483
|
+
hold before `n`; holds at and after `n` stay armed, so steering backwards
|
|
484
|
+
re-arms the ones in between. This is the whole clicker: a deck is a sequence
|
|
485
|
+
whose slides carry `advance: 'input'`, and "next" is one `setParam`. Pinned by
|
|
486
|
+
`sequence.test.ts` → "sequence.segment steering is sticky" and
|
|
487
|
+
"advance: 'input' holds until released".
|
|
358
488
|
|
|
359
489
|
**Seed:** `seq.seed` is forwarded to children that lack a scene-level seed
|
|
360
490
|
(offset by segment index for independence). Children with their own seed are
|
|
@@ -368,6 +498,9 @@ Shipped working specs (also exposed as `EXAMPLE_SPECS` /
|
|
|
368
498
|
showcases — `aurora` (wander + coherence + ghosting + pulse.wave),
|
|
369
499
|
`warp-tunnel` (warp + streaks), `polygons` (chain links + heavy ghosting),
|
|
370
500
|
`matrix-rain` (grid layout + glyph cycle + ghosting), and `procession`
|
|
371
|
-
(path + layer-parented orbit + life staging + ring/rect sprites)
|
|
372
|
-
|
|
373
|
-
|
|
501
|
+
(path + layer-parented orbit + life staging + ring/rect sprites);
|
|
502
|
+
`nostalghia-candle` and `haiku` (restraint and text); and the 2026-09 trio —
|
|
503
|
+
`pings` (emit + grow + ease: one event at a time), `facets` (polygon, stroke
|
|
504
|
+
and feathered rect) and `relay-board` (list layout + bar: a chart in five
|
|
505
|
+
layers). See [`src/examples/`](./src/examples/). The dashboard exercises the
|
|
506
|
+
static/HUD subset at scale (34 layers of keyed, positioned text).
|