@idle-screens/schema 3.12.0 → 3.13.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 +111 -19
- package/dist/index.d.ts +170 -9
- package/dist/index.js +1998 -344
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/saver-spec.schema.json +105 -9
package/FORMAT.md
CHANGED
|
@@ -46,7 +46,9 @@ sequence-level) (2026-09-10 — the print look); `timeline` (keyframes on the
|
|
|
46
46
|
scene's own clock), paint-level layer `opacity` and `transform`,
|
|
47
47
|
`position.dx` / `dy`, `wrapMorph` on the sequence envelope and
|
|
48
48
|
`text: 'dip'` on `morph` (2026-09-25 — timed scenes); layer `groups` and
|
|
49
|
-
`transform.origin` (2026-09-26 — groups and origin)
|
|
49
|
+
`transform.origin` (2026-09-26 — groups and origin); path drawings on
|
|
50
|
+
`polygon` — `path`, `fillRule`, `fill`, `gradient`, `stroke`, `parts`,
|
|
51
|
+
`span`, `origin` (2026-10-17 — vector drawings).
|
|
50
52
|
|
|
51
53
|
## Safety invariants
|
|
52
54
|
|
|
@@ -192,8 +194,11 @@ exceeds 2 %, or a `dense` one that would have tripped `sparse-scene`, gets a
|
|
|
192
194
|
|
|
193
195
|
**Steering.** Every numeric knob and every band is a paint path —
|
|
194
196
|
`background.scale`, `background.warp`, `background.quantize`,
|
|
195
|
-
`background.bands.2`, `background.drift.amount` — and glides
|
|
196
|
-
|
|
197
|
+
`background.bands.2`, `background.drift.amount` — and glides. `quantize`
|
|
198
|
+
and `octaves` are level counts, so a glide (`setParam`, a `timeline` key, a
|
|
199
|
+
`morph`) steps through **whole** levels: the in-between value rounds, and a
|
|
200
|
+
`quantize` under 1.5 reads `0` (smooth) — so `quantize` 0 → 6 eases smooth,
|
|
201
|
+
then 2, 3 … 6 bands. Authored values must still be whole. `background.seed` is not
|
|
197
202
|
steerable. The background is paint, not structure, so a `morph` between
|
|
198
203
|
two field segments glides the field; a gradient → field morph steps
|
|
199
204
|
(different shape).
|
|
@@ -269,7 +274,7 @@ own `finish` is not steerable; steer a segment's, or republish.
|
|
|
269
274
|
|
|
270
275
|
| Field | Type | Default | Meaning |
|
|
271
276
|
|---|---|---|---|
|
|
272
|
-
| `count` | int 1..400 | — | entities in this layer |
|
|
277
|
+
| `count` | int 1..400 | — | entities in this layer. **At most 800 entities across all layers** (and ≤ 36 layers): the validator rejects a spec whose counts sum past 800, and density scaling never takes a screen past it |
|
|
273
278
|
| `sprite` | SpriteSpec | — | what each entity looks like |
|
|
274
279
|
| `motion` | MotionSpec | — | how entities move |
|
|
275
280
|
| `size` | `[min,max]` > 0 | sprite-dependent | font size for emoji/text; ignored for shaped sprites |
|
|
@@ -278,13 +283,13 @@ own `finish` is not steerable; steer a segment's, or republish.
|
|
|
278
283
|
| `alpha` | `[min,max]` 0..1 | `[1,1]` | per-entity opacity range |
|
|
279
284
|
| `blend` | `lighter` \| `screen` \| `multiply` | source-over | additive glow / gentle additive / darkening |
|
|
280
285
|
| `region` | `{x?, y?}` ranges 0..1 | full viewport | fractional spawn window (placement only, not travel) |
|
|
281
|
-
| `pulse` | `{amp ≤ 0.5, period ≥ 500, wave?}` | none | opacity breathing; `wave: {wavelength, angle?}` turns it into a traveling wave across the field |
|
|
282
|
-
| `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). `[0, 0]` does **not** hold the seeded angle — a zero speed renders at 0; use `rotate` |
|
|
286
|
+
| `pulse` | `{amp ≤ 0.5, period ≥ 500, wave?}` | none | opacity breathing: `amp` is **added** to the entity's `alpha` (alpha ± amp, clamped to 0..1), not a fraction of it — a dim layer (`alpha` 0.2, `amp` 0.3) swings through 0 and sits fully dark for part of every period; keep `amp` ≤ the alpha floor for a breath that never blacks out. `wave: {wavelength, angle?}` turns it into a traveling wave across the field |
|
|
287
|
+
| `spin` | number \| `[min,max]` ±360 deg/sec | none | per-entity rotation (seeded start angle — drawn from the scene's one shared seeded stream, so it is reproducible for a given spec, seed **and viewport** but not a fixed per-entity value: density scaling changes how many entities draw before it, and declaring a `rotate` range adds a draw per entity, so either one changes the angles; use `rotate` for an angle you need to pin); a range gives each entity a seeded speed (confetti, tumbling debris). `[0, 0]` does **not** hold the seeded angle — a zero speed renders at 0; use `rotate` |
|
|
283
288
|
| `rotate` | number \| `[min,max]` ±360 deg | none | **static** per-entity rotation: a scalar turns the whole layer, a range gives each entity a seeded angle (thrown blades, scattered glyphs, a tilted grid). Adds to `spin`'s start angle. Structural. **Native:** tvOS reads `spin` only ⇒ 0 |
|
|
284
289
|
| `grow` | `{amp ≤ 0.8, period ≥ 500}` | none | size breathing (seeded phase) |
|
|
285
|
-
| `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) |
|
|
290
|
+
| `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). Echoes of a shaped sprite (`polygon`, `stroke`, `rect`, `ring`, `streak`, `bar`) are the sprite's own shape as it was then, shrinking toward the tail; `circle`, `emoji` and text trail discs. A hard `circle`'s trail is one continuous ribbon tapering behind it, so a quick path draws a line rather than a string of dots (a layer whose ribbons would cover more than a tenth of the screen draws its echo discs instead, to bound the cost); a `soft` circle's echoes are filled in where they would part, within a small per-layer budget |
|
|
286
291
|
| `links` | see below | none | inter-entity lines |
|
|
287
|
-
| `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
|
|
292
|
+
| `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; a number or `{x?, y?}`). **`gap` is the pitch between cell anchors, not the space between cells** — a label wider than the column pitch runs into its neighbour (the `layout-text-overlap` advisory names the gap that clears it), so size `gap.x` to the widest label. Text/emoji take `strings[i]` / `glyphs[i]` **in order**, so N labels are one layer; a shaped sprite's palette (`colors[i]` on `bar`, `rect`, `circle`…) is read in order too. **Text and emoji have no `colors`**: a `text` layer paints every label in its one `color` (a `colors` on it is an unknown property, ignored with a warning) — use one layer per label colour |
|
|
288
293
|
| `life` | `{enter?, exit?, fade?}` ms | always on | act structure: fade the layer in at `enter`, out at `exit` |
|
|
289
294
|
| `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 |
|
|
290
295
|
| `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` |
|
|
@@ -299,7 +304,9 @@ own `finish` is not steerable; steer a segment's, or republish.
|
|
|
299
304
|
`maxDist`; `"chain"` wires entities in order (Mystify polygons — ignores
|
|
300
305
|
`k`/`maxDist`, `closed: true` closes the loop); `"random"` uses a fixed
|
|
301
306
|
deterministic wiring filtered by `maxDist`. `falloff: true` fades link alpha
|
|
302
|
-
with distance, removing pop-in at the cutoff.
|
|
307
|
+
with distance, removing pop-in at the cutoff. `k` and `maxDist` are required
|
|
308
|
+
for `"nearest"` and `"random"`; a `"chain"` may omit both, except that
|
|
309
|
+
`falloff: true` needs `maxDist` (the distance at which a link has faded out).
|
|
303
310
|
|
|
304
311
|
### `sprite` (one of)
|
|
305
312
|
|
|
@@ -308,20 +315,24 @@ with distance, removing pop-in at the cutoff.
|
|
|
308
315
|
- `{ "kind": "text", "strings": ["HELLO"], "color": "#e6e8ef", "font": "bold monospace", "align": "center", "baseline": "middle", "maxWidth": 300, "cycle": ..., "role": "read" }`
|
|
309
316
|
— a `font` **with** a px size keeps that shorthand, its number rescaled to the
|
|
310
317
|
viewport (verbatim only under `units: "px"`); a family/weight only
|
|
311
|
-
(`"bold monospace"`) composes with the seeded per-entity `size
|
|
318
|
+
(`"bold monospace"`) composes with the seeded per-entity `size` (faces
|
|
319
|
+
installed on the viewer only — see `font` under `textBlock`); `role` is
|
|
312
320
|
the legibility opt-in described under `textBlock` below (same field, same
|
|
313
321
|
meaning on both text sprites)
|
|
314
322
|
- `{ "kind": "circle", "radius": [0.001, 0.003], "color": "#ffffff", "soft": true }` —
|
|
315
323
|
`soft` renders a radial-falloff glow orb instead of a hard disc
|
|
316
324
|
- `{ "kind": "ring", "radius": [0.002, 0.005], "color": "#d8f6ff", "width": 0.001 }` —
|
|
317
|
-
unfilled circle (bubbles, portals, sonar)
|
|
325
|
+
unfilled circle (bubbles, portals, sonar). The line is **centred on the
|
|
326
|
+
radius**: half of `width` falls inside, half outside, so the outer edge is
|
|
327
|
+
`radius + width / 2`
|
|
318
328
|
- `{ "kind": "streak", "length": [0.01, 0.03], "color": "#cfd8ff", "width": 0.002 }` —
|
|
319
329
|
a line oriented along the entity's analytic heading with a faded tail
|
|
320
330
|
(rain that reads as rain, shooting stars, warp stars)
|
|
321
331
|
- `{ "kind": "rect", "width": [0.012, 0.02], "aspect": [1.3, 1.7], "color": "#ffb347", "feather": 0.6 }` —
|
|
322
332
|
rectangle; `aspect` is the height/width ratio range (rotates with `spin`);
|
|
323
333
|
`feather` 0..1 softens the edges — that fraction of the half-size fades out
|
|
324
|
-
toward the border (Rothko's block
|
|
334
|
+
toward the border in one smooth linear falloff on each axis (Rothko's block
|
|
335
|
+
at 0.5–0.8)
|
|
325
336
|
- `{ "kind": "bar", "values": [82, 64, 91], "max": 100, "length": 0.5, "thickness": 0.02, "color": "#17e8c8", "direction": "right" }` —
|
|
326
337
|
a data bar: entity *i* draws `length × values[i] / max` (viewport units),
|
|
327
338
|
`thickness` thick, growing from its position toward `direction` (`right`
|
|
@@ -334,14 +345,83 @@ with distance, removing pop-in at the cutoff.
|
|
|
334
345
|
6); or `"points": [[-1, 0.9], [0.2, -1], [1, 0.4]]` — 3..24 unit
|
|
335
346
|
coordinates in −1..1 scaled by the radius — for any facet (a Picasso shard,
|
|
336
347
|
a Kandinsky triangle, a Mondrian plane). `soft` feathers the fill from the
|
|
337
|
-
centre. Rotates with `spin`.
|
|
338
|
-
|
|
348
|
+
centre. Rotates with `spin`. The outline fills with the **nonzero** winding
|
|
349
|
+
rule: a self-crossing outline paints both lobes, and a hole needs its inner
|
|
350
|
+
loop traced in the **opposite** direction (a keyhole: outer ring clockwise,
|
|
351
|
+
then back round the hole counter-clockwise, joined by a zero-width slit). `points` are paint, so two polygons with the
|
|
352
|
+
same point count `morph` into each other — and a `setParam`, `timeline` key
|
|
353
|
+
or `morph` target for `sprite.points` is in the **same unit coordinates**,
|
|
354
|
+
scaled by each entity's seeded radius (not by the keyed shape's own extent):
|
|
355
|
+
keep every keyed point inside −1..1 (the validator rejects one outside), and
|
|
356
|
+
shrink a target shape by scaling its points, not by exceeding the box.
|
|
357
|
+
For anything more detailed than a 24-point facet, give the polygon a
|
|
358
|
+
**path** instead (next entry)
|
|
359
|
+
- **Path drawings** — `{ "kind": "polygon", "radius": [0.3, 0.3], "color": "#2f6a3e", "path": "M 0 -0.9 C 0.5 -0.9 0.7 -0.3 0.5 0.1 … Z" }`:
|
|
360
|
+
an SVG path `d` string in the same −1..1 unit box (`M L H V C S Q T A Z`,
|
|
361
|
+
absolute or relative, curves and arcs included), flattened once to a smooth
|
|
362
|
+
outline. Use it for illustration: leaves, creatures, lettering-like marks,
|
|
363
|
+
architecture. Everything below is **only** for polygons with a `path` (or
|
|
364
|
+
`parts`).
|
|
365
|
+
- **Holes and pieces.** Several subpaths in one `path` are one drawing.
|
|
366
|
+
`fillRule: "auto"` (default) decides by containment: a subpath lying
|
|
367
|
+
entirely inside another is a hole; one inside a hole is solid again (a
|
|
368
|
+
pupil in an eye-spot ring); pieces that merely overlap merge (branches,
|
|
369
|
+
veins, leaflets on a stem), and a hole cuts through every overlapping
|
|
370
|
+
piece of the layer (a window across two walls). `"nonzero"` /
|
|
371
|
+
`"evenodd"` are SVG's own rules — use them for pasted SVG, for overlap
|
|
372
|
+
patterns (a rosette of overlapping circles wants `evenodd`), and when a
|
|
373
|
+
same-colour shape sits inside another on purpose (`auto` would cut it).
|
|
374
|
+
- **`gradient`** replaces the flat colour of the fill:
|
|
375
|
+
`{ "type": "linear", "from": [0, -1], "to": [0, 1], "stops": [{ "at": 0, "color": "#2c6a3a" }, { "at": 1, "color": "#7ab070", "opacity": 0.6 }] }`
|
|
376
|
+
or `{ "type": "radial", "center": [0, 0], "radius": 1, "focus": [-0.3, -0.4], "stops": [...] }`
|
|
377
|
+
(2..8 stops, `at` 0..1 non-decreasing, optional per-stop `opacity`).
|
|
378
|
+
Coordinates are in the drawing's unit box, so the shading scales and
|
|
379
|
+
rotates with it; `"units": "part"` reads them in the part's own bounding
|
|
380
|
+
box (−1..1), so one gradient lights every piece on its own (a radial
|
|
381
|
+
there is the piece's ellipse). A `focus` off-centre gives a lit sphere.
|
|
382
|
+
- **`stroke`** — `{ "color": "#1f2a1c", "width": 0.006, "opacity": 0.8, "join": "round", "cap": "round" }`
|
|
383
|
+
— an outline along every subpath **as drawn** (holes included; open
|
|
384
|
+
subpaths too). `width` is in unit-box units (0.01 = 1% of the drawing's
|
|
385
|
+
half-size). Overlapping pieces show their inner edges, as in SVG.
|
|
386
|
+
- **`fill: false`** draws the stroke only — line art. Then open paths
|
|
387
|
+
(no `Z`) are fine: veins, hatching, antennae, rigging.
|
|
388
|
+
- **`parts`** — a multi-colour drawing in one layer:
|
|
389
|
+
`"parts": [{ "path": "…", "color": "#c8501e" }, { "path": "…", "gradient": {…}, "stroke": {…}, "opacity": 0.85 }, …]`
|
|
390
|
+
(1..256), drawn back to front, each with its own `path`, `color`
|
|
391
|
+
(default: the entity's colour, so `colors` palettes still pick per
|
|
392
|
+
entity), `fillRule`, `fill`, `gradient`, `stroke` and `opacity`. With
|
|
393
|
+
`parts`, put that paint on the parts, never on the sprite. One layer
|
|
394
|
+
holds a whole illustration, and a counted layer reuses it: a balloon with
|
|
395
|
+
gores, shading, ropes and basket × 7 drifting entities, all parts moving
|
|
396
|
+
as one.
|
|
397
|
+
- **`span`** — how the unit box lands on screen: `"box"` (default, the
|
|
398
|
+
square sized by `radius`), `"width"` (x spans the full screen width; y
|
|
399
|
+
keeps `radius`), `"height"`, `"screen"` (both). **`origin`** `[x, y]` is
|
|
400
|
+
the unit-box point placed at `position`: hills and ground bands are
|
|
401
|
+
`span: "width", origin: [0, 1]` at `position: { x: 0.5, y: 1 }` — their
|
|
402
|
+
base sits on the bottom edge, edge to edge, on 16:9, 9:16 and 1:1 alike.
|
|
403
|
+
Strokes keep one width when a drawing stretches. Anything meant to sit
|
|
404
|
+
at a screen position (a moon beside its glow) is its own layer, not a
|
|
405
|
+
part of a big centred drawing — positions are screen fractions, a
|
|
406
|
+
drawing's interior is not.
|
|
407
|
+
- **Budget.** A path flattens to at most 4096 points; a scene's path
|
|
408
|
+
outlines total at most 16384 (flattened points, strokes included, ×
|
|
409
|
+
`count`). Curves cost points; straight segments and gentle curves are
|
|
410
|
+
nearly free (flattening drops points within 0.0005 of the line).
|
|
411
|
+
- **Steering and morphs.** Part colours, gradient stops, stroke widths
|
|
412
|
+
and opacities glide like any paint. Two paths with the **same commands**
|
|
413
|
+
(same letters, same count of numbers — draw both from one template)
|
|
414
|
+
morph number by number; any other path change steps.
|
|
415
|
+
- Drawings are rendered once and reused while they look the same (moving
|
|
416
|
+
copies are resampled), so a detailed still drawing costs about what a
|
|
417
|
+
flat one does per frame.
|
|
339
418
|
- `{ "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" }` —
|
|
340
419
|
a freehand mark: a path through 2..24 unit-coordinate points, scaled so the
|
|
341
420
|
unit box spans the seeded `length`, stroked `width` wide with round joins.
|
|
342
421
|
`curve: "smooth"` (default) is a Catmull-Rom spline, `"linear"` a polyline;
|
|
343
422
|
`taper` thins the mark to almost nothing at both ends (a brush stroke rather
|
|
344
|
-
than a line
|
|
423
|
+
than a line; it paints as one filled mark, so a translucent stroke has no
|
|
424
|
+
overlap beads); `orient: true` turns the path's +x along the entity's heading,
|
|
345
425
|
like `streak`. Rotates with `spin`. Van Gogh's stroke, Hokusai's contour,
|
|
346
426
|
O'Keeffe's petal, Basquiat's scrawl
|
|
347
427
|
- `{ "kind": "textBlock", "text": "Multi-line text with wrapping.", "maxWidth": 0.8, "fontSize": 0.04, "lineHeight": 1.4, "align": "left", "color": "#e6e8ef", "anchor": "top-left", "font": "system-ui, sans-serif", "opacity": 1 }` —
|
|
@@ -384,6 +464,12 @@ with distance, removing pop-in at the cutoff.
|
|
|
384
464
|
advance so mono wraps land where the real face puts them; every other
|
|
385
465
|
family uses the proportional table. Default `system-ui, sans-serif`.
|
|
386
466
|
**Native clients fall back to the system face** until a face table exists.
|
|
467
|
+
`font` names faces **installed on the viewing device** — nothing is
|
|
468
|
+
bundled and no web font is fetched, so `'Inter'` only appears where Inter
|
|
469
|
+
is installed and every other screen falls through the stack. End every
|
|
470
|
+
stack in a generic `sans-serif`, `serif` or `monospace`; avoid ending on
|
|
471
|
+
`cursive` or `fantasy`, which resolve to a different (sometimes absurd)
|
|
472
|
+
face on every platform.
|
|
387
473
|
|
|
388
474
|
`opacity` (0–1, default 1) multiplies the block's paint alpha — the whole
|
|
389
475
|
block, on top of the layer's per-entity `alpha`. It is **paint**, not
|
|
@@ -479,7 +565,10 @@ and configured the second.
|
|
|
479
565
|
- `{ "type": "wander", "speed": [0.004, 0.012], "angle": 0, "meander": 0.1, "coherence": 0.7 }`
|
|
480
566
|
— organic harmonic drift: base velocity plus 3 seeded sine octaves per axis.
|
|
481
567
|
Flowing curved paths with zero simulation state. `meander` scales the
|
|
482
|
-
curvature
|
|
568
|
+
curvature (it is the harmonics' amplitude, so it also sets how fast an
|
|
569
|
+
entity sways — keep it ≤ ~0.015 for a slow drift); it is capped at
|
|
570
|
+
**0.463** (500 px ÷ `referenceViewport`, 1080 by default — 500 under
|
|
571
|
+
`units: "px"`), and the validator rejects anything above; `coherence` 0..1 blends every entity toward a shared harmonic set
|
|
483
572
|
(1 = the layer undulates as one body — fake flocking)
|
|
484
573
|
- `{ "type": "warp", "speed": [0.1, 0.5], "center": { "x": 0.5, "y": 0.5 } }`
|
|
485
574
|
— perspective starfield: entities stream toward the viewer on a depth axis,
|
|
@@ -860,11 +949,14 @@ moves and scales its positions and sizes (sprite rotation is not modelled).
|
|
|
860
949
|
its own × its group's — is 0 in the base scene and at every key boundary of
|
|
861
950
|
its `timeline` never shows, so it is not judged and its entities do not count
|
|
862
951
|
toward `dense-scene`; coverage is weighed by paint opacity) (also on
|
|
863
|
-
`perceiveScene().advisories`).
|
|
952
|
+
`perceiveScene().advisories`). Three of them are **spatial**, for static text:
|
|
864
953
|
`text-off-screen` (a text/textBlock box crosses the viewport edge by more
|
|
865
|
-
than 1%)
|
|
954
|
+
than 1%), `text-overlap` (two static text layers share more than 10% of
|
|
866
955
|
the smaller box; it carries both boxes as `boxes: [{x, y, w, h}]` in
|
|
867
|
-
viewport fractions so the fix needs no re-derivation)
|
|
956
|
+
viewport fractions so the fix needs no re-derivation) and
|
|
957
|
+
`layout-text-overlap` (two cells of one `list`/`table` layer do — its
|
|
958
|
+
`gap` pitch is narrower than its labels; the message names the `gap` that
|
|
959
|
+
clears them, and it carries the two boxes too). Boxes come from the
|
|
868
960
|
same character-class width table the textBlock line-breaker uses, so they
|
|
869
961
|
are estimates of the renderer's own layout — a caption that fails these
|
|
870
962
|
will look wrong on the wall; one that passes may still sit a few px off.
|
package/dist/index.d.ts
CHANGED
|
@@ -334,11 +334,13 @@ interface LayerSpec {
|
|
|
334
334
|
* - 'random': a fixed golden-ratio-stride wiring (k partners per entity,
|
|
335
335
|
* deterministic, no RNG draws) — breaks the crystalline k-nearest look.
|
|
336
336
|
* `falloff` fades link alpha with distance/maxDist, killing pop-in at the cutoff.
|
|
337
|
+
* `k` and `maxDist` are required for 'nearest' and 'random'; 'chain' needs
|
|
338
|
+
* neither, except `maxDist` when `falloff` is set (it is the fade distance).
|
|
337
339
|
* Capped at LIMITS.maxLinksK. Layer count must be <= LIMITS.maxLinkLayerCount when set.
|
|
338
340
|
*/
|
|
339
341
|
links?: {
|
|
340
|
-
k
|
|
341
|
-
maxDist
|
|
342
|
+
k?: number;
|
|
343
|
+
maxDist?: number;
|
|
342
344
|
color?: string;
|
|
343
345
|
alpha?: number;
|
|
344
346
|
width?: number;
|
|
@@ -547,6 +549,14 @@ type SpriteSpec = {
|
|
|
547
549
|
color: string;
|
|
548
550
|
sides?: number;
|
|
549
551
|
points?: Array<[number, number]>;
|
|
552
|
+
path?: string;
|
|
553
|
+
fillRule?: 'auto' | 'nonzero' | 'evenodd';
|
|
554
|
+
fill?: boolean;
|
|
555
|
+
gradient?: PathGradient;
|
|
556
|
+
stroke?: PathStroke;
|
|
557
|
+
parts?: PathPart[];
|
|
558
|
+
span?: 'box' | 'width' | 'height' | 'screen';
|
|
559
|
+
origin?: [number, number];
|
|
550
560
|
soft?: boolean;
|
|
551
561
|
colors?: string[];
|
|
552
562
|
colorWeights?: number[];
|
|
@@ -961,6 +971,54 @@ interface IdleSequence {
|
|
|
961
971
|
finish?: FinishSpec;
|
|
962
972
|
segments: SequenceSegment[];
|
|
963
973
|
}
|
|
974
|
+
/**
|
|
975
|
+
* A gradient fill for a path polygon, in the path's own −1..1 unit box (so it
|
|
976
|
+
* scales and rotates with the drawing). Overrides `color` for the fill;
|
|
977
|
+
* `color` stays what perception and palettes read when no gradient is known.
|
|
978
|
+
*/
|
|
979
|
+
type PathGradient = {
|
|
980
|
+
type: 'linear';
|
|
981
|
+
from: [number, number];
|
|
982
|
+
to: [number, number];
|
|
983
|
+
stops: PathGradientStop[];
|
|
984
|
+
units?: 'box' | 'part';
|
|
985
|
+
} | {
|
|
986
|
+
type: 'radial';
|
|
987
|
+
center: [number, number];
|
|
988
|
+
radius: number;
|
|
989
|
+
focus?: [number, number];
|
|
990
|
+
stops: PathGradientStop[];
|
|
991
|
+
units?: 'box' | 'part';
|
|
992
|
+
};
|
|
993
|
+
interface PathGradientStop {
|
|
994
|
+
at: number;
|
|
995
|
+
color: string;
|
|
996
|
+
opacity?: number;
|
|
997
|
+
}
|
|
998
|
+
/** An outline drawn along every subpath of a path polygon (open ones too). `width` is in unit-box units. */
|
|
999
|
+
interface PathStroke {
|
|
1000
|
+
color: string;
|
|
1001
|
+
width: number;
|
|
1002
|
+
opacity?: number;
|
|
1003
|
+
join?: 'round' | 'miter' | 'bevel';
|
|
1004
|
+
cap?: 'round' | 'butt' | 'square';
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* One piece of a multi-colour path drawing (`polygon.parts`), drawn back to
|
|
1008
|
+
* front in the drawing's unit box: its own outline, colour (default: the
|
|
1009
|
+
* sprite's), gradient, stroke and opacity. A gradient with `units: 'part'`
|
|
1010
|
+
* reads its coordinates in this part's own bounding box mapped to −1..1, so
|
|
1011
|
+
* one gradient spec shades each piece on its own.
|
|
1012
|
+
*/
|
|
1013
|
+
interface PathPart {
|
|
1014
|
+
path: string;
|
|
1015
|
+
color?: string;
|
|
1016
|
+
fillRule?: 'auto' | 'nonzero' | 'evenodd';
|
|
1017
|
+
fill?: boolean;
|
|
1018
|
+
gradient?: PathGradient;
|
|
1019
|
+
stroke?: PathStroke;
|
|
1020
|
+
opacity?: number;
|
|
1021
|
+
}
|
|
964
1022
|
|
|
965
1023
|
declare function validateSpec(spec: unknown): ValidationResult;
|
|
966
1024
|
/**
|
|
@@ -1164,15 +1222,25 @@ interface LinkEdge {
|
|
|
1164
1222
|
j: number;
|
|
1165
1223
|
dist: number;
|
|
1166
1224
|
}
|
|
1225
|
+
/**
|
|
1226
|
+
* `links.maxDist` in px at `scale` (px per spec unit), or `Infinity` when it is
|
|
1227
|
+
* absent — a chain needs no cutoff. Callers apply `falloff` only to a finite
|
|
1228
|
+
* value, so an absent `maxDist` can never put NaN into an alpha.
|
|
1229
|
+
*/
|
|
1230
|
+
declare function linkMaxDistPx(links: {
|
|
1231
|
+
maxDist?: number;
|
|
1232
|
+
}, scale: number): number;
|
|
1167
1233
|
/**
|
|
1168
1234
|
* Edge list for a layer's `links`, dispatching on mode:
|
|
1169
1235
|
* 'nearest' (default) k-nearest within maxDist; 'chain' sequential order
|
|
1170
1236
|
* (Mystify — ignores k/maxDist, `closed` joins last to first); 'random' a fixed
|
|
1171
1237
|
* golden-ratio-stride wiring filtered by maxDist (deterministic, no RNG draws).
|
|
1238
|
+
* An absent `k` reads as 1 and an absent `maxDist` (`Infinity` from
|
|
1239
|
+
* `linkMaxDistPx`) as no cutoff; the validator requires both outside chain mode.
|
|
1172
1240
|
*/
|
|
1173
1241
|
declare function linkEdges(links: {
|
|
1174
|
-
k
|
|
1175
|
-
maxDist
|
|
1242
|
+
k?: number;
|
|
1243
|
+
maxDist?: number;
|
|
1176
1244
|
mode?: 'nearest' | 'chain' | 'random';
|
|
1177
1245
|
closed?: boolean;
|
|
1178
1246
|
}, positions: Array<{
|
|
@@ -1379,7 +1447,8 @@ declare function applyDeltasToSpec(spec: SaverSpec, deltas: SteerDelta[]): Saver
|
|
|
1379
1447
|
/**
|
|
1380
1448
|
* Interpolate between two structurally-similar specs at progress k (0..1).
|
|
1381
1449
|
* Numbers lerp, hex colours lerp per channel, everything else steps to the
|
|
1382
|
-
* target immediately (k > 0). Layer counts
|
|
1450
|
+
* target immediately (k > 0). Layer counts, and a field background's
|
|
1451
|
+
* `octaves` / `quantize`, round to whole values.
|
|
1383
1452
|
*/
|
|
1384
1453
|
declare function lerpSpec(from: SaverSpec, to: SaverSpec, k: number): SaverSpec;
|
|
1385
1454
|
/**
|
|
@@ -1390,6 +1459,13 @@ declare function lerpSpec(from: SaverSpec, to: SaverSpec, k: number): SaverSpec;
|
|
|
1390
1459
|
* timeline, so every stored morph and glide is unchanged.
|
|
1391
1460
|
*/
|
|
1392
1461
|
declare function lerpValue(from: unknown, to: unknown, k: number, specRoot?: boolean, rootKey?: string): unknown;
|
|
1462
|
+
/**
|
|
1463
|
+
* Interpolate two SVG path d strings that share their structure — the same
|
|
1464
|
+
* command letters in the same order and the same count of numbers — by
|
|
1465
|
+
* lerping every number; null when they differ in structure (the morph then
|
|
1466
|
+
* steps). Arc flags (0/1) round, so a half-way arc stays a valid arc.
|
|
1467
|
+
*/
|
|
1468
|
+
declare function lerpPathD(a: string, b: string, k: number): string | null;
|
|
1393
1469
|
/**
|
|
1394
1470
|
* The string(s) a text layer paints — `strings` of a `text` sprite, `text`
|
|
1395
1471
|
* of a `textBlock` — or null for every other sprite.
|
|
@@ -1642,9 +1718,9 @@ interface LuminanceGridOptions extends PerceiveOptions {
|
|
|
1642
1718
|
* re-uses drawTrail's sampling — past positions with decaying alpha and
|
|
1643
1719
|
* shrinking radius. Polygons larger than POLYGON_OUTLINE_CELLS are scanned by
|
|
1644
1720
|
* their real (rotated) outline; smaller ones splat as a fill-weighted disc.
|
|
1645
|
-
* Remaining approximations (documented, deliberate):
|
|
1646
|
-
*
|
|
1647
|
-
*
|
|
1721
|
+
* Remaining approximations (documented, deliberate): background drift is
|
|
1722
|
+
* sampled at its rest position. Good enough to perceive composition, focus,
|
|
1723
|
+
* balance — and now smear.
|
|
1648
1724
|
*/
|
|
1649
1725
|
declare function luminanceGrid(spec: SaverSpec, opts?: LuminanceGridOptions): LuminanceGrid;
|
|
1650
1726
|
/**
|
|
@@ -2677,4 +2753,89 @@ declare const EXAMPLE_SPECS: SaverSpec[];
|
|
|
2677
2753
|
/** Lookup by saver id. */
|
|
2678
2754
|
declare const EXAMPLE_BY_ID: Readonly<Record<string, SaverSpec>>;
|
|
2679
2755
|
|
|
2680
|
-
|
|
2756
|
+
/**
|
|
2757
|
+
* Shape glyph geometry — pure, renderer-free, shared by the compiler (what to
|
|
2758
|
+
* stroke/fill), perception (how much ink that is) and advise. Everything is
|
|
2759
|
+
* computed from the sprite spec + the entity's seeded size, so the canvas and
|
|
2760
|
+
* the analytic model agree by construction.
|
|
2761
|
+
*
|
|
2762
|
+
* Unit coordinates: `polygon.points` and `stroke.points` live in a −1..1 box
|
|
2763
|
+
* that is scaled by the entity's half-size (circumradius for polygons, half of
|
|
2764
|
+
* `length` for strokes), so the seeded size range is the glyph's bounding
|
|
2765
|
+
* diameter — the same rule `circle.radius` / `streak.length` already follow.
|
|
2766
|
+
*/
|
|
2767
|
+
|
|
2768
|
+
type PolygonSprite = Extract<SpriteSpec, {
|
|
2769
|
+
kind: 'polygon';
|
|
2770
|
+
}>;
|
|
2771
|
+
/** Most outline points one flattened path may produce. */
|
|
2772
|
+
declare const MAX_PATH_POINTS = 4096;
|
|
2773
|
+
interface FlattenedPath {
|
|
2774
|
+
/** One closed ring: subpaths joined by zero-width bridges, inner ones wound as holes. */
|
|
2775
|
+
points: Array<[number, number]>;
|
|
2776
|
+
subpaths: number;
|
|
2777
|
+
/** Every subpath as drawn (simplified, no bridges), for strokes: open unless it ended in Z. */
|
|
2778
|
+
lines: Array<{
|
|
2779
|
+
points: Array<[number, number]>;
|
|
2780
|
+
closed: boolean;
|
|
2781
|
+
}>;
|
|
2782
|
+
/** Parse error, if the string is not a usable path. */
|
|
2783
|
+
error?: string;
|
|
2784
|
+
}
|
|
2785
|
+
/**
|
|
2786
|
+
* How a path's subpaths combine. `auto` (default): containment decides which
|
|
2787
|
+
* subpaths are holes (see joinRings) — for authors who never think about
|
|
2788
|
+
* winding. `nonzero` / `evenodd`: SVG's own fill rules, honouring the winding
|
|
2789
|
+
* as drawn — for pasted SVG, and for a deliberate same-colour shape that sits
|
|
2790
|
+
* inside another (auto would read it as a hole).
|
|
2791
|
+
*/
|
|
2792
|
+
type PathFillRule = 'auto' | 'nonzero' | 'evenodd';
|
|
2793
|
+
declare function flattenPath(d: string, rule?: PathFillRule): FlattenedPath;
|
|
2794
|
+
/** A polygon drawn from SVG paths: one `path`, or a multi-colour drawing of `parts`. */
|
|
2795
|
+
declare function isPathPolygon(s: SpriteSpec): boolean;
|
|
2796
|
+
/** One drawable piece of a path polygon, resolved: its outline, paint and part-space box. */
|
|
2797
|
+
interface ResolvedPart {
|
|
2798
|
+
flat: FlattenedPath;
|
|
2799
|
+
/** Its own colour, or null to use the entity's (sprite colour / palette pick). */
|
|
2800
|
+
color: string | null;
|
|
2801
|
+
fill: boolean;
|
|
2802
|
+
rule: 'nonzero' | 'evenodd';
|
|
2803
|
+
gradient?: PathGradient;
|
|
2804
|
+
stroke?: PathStroke;
|
|
2805
|
+
opacity: number;
|
|
2806
|
+
/** The fill's bounding box in the unit box (centre + half extents) — the space of `gradient.units: 'part'`. */
|
|
2807
|
+
box: {
|
|
2808
|
+
cx: number;
|
|
2809
|
+
cy: number;
|
|
2810
|
+
hw: number;
|
|
2811
|
+
hh: number;
|
|
2812
|
+
};
|
|
2813
|
+
}
|
|
2814
|
+
/**
|
|
2815
|
+
* A path polygon as an ordered list of parts, back to front. A single
|
|
2816
|
+
* `path` is a drawing of one part (its colour the entity's), so the renderer,
|
|
2817
|
+
* perception and the budget treat both forms one way. Memoised per sprite.
|
|
2818
|
+
*/
|
|
2819
|
+
declare function pathParts(s: PolygonSprite): ResolvedPart[];
|
|
2820
|
+
/**
|
|
2821
|
+
* How a path drawing's −1..1 unit box lands on screen, in px: scale per axis
|
|
2822
|
+
* (sx, sy) and the unit-box point (ox, oy) that sits at the entity position.
|
|
2823
|
+
* span 'box' (default): uniform, the entity radius r on both axes — the
|
|
2824
|
+
* classic square box sized by the short side;
|
|
2825
|
+
* 'width': x spans the full screen width (sx = w/2), y keeps r — skylines,
|
|
2826
|
+
* hills and ground bands that must reach both side edges on any aspect;
|
|
2827
|
+
* 'height': y spans the full height, x keeps r;
|
|
2828
|
+
* 'screen': both — a frame or wash that hugs every edge.
|
|
2829
|
+
* `origin` [ox, oy] picks the anchored point: [0, 1] with position y = 1 sets
|
|
2830
|
+
* the drawing's bottom on the screen's bottom edge. Strokes keep the uniform
|
|
2831
|
+
* r for their width, so stretching never fattens an outline sideways.
|
|
2832
|
+
*/
|
|
2833
|
+
declare function pathFrame(s: PolygonSprite, r: number, w: number, h: number): {
|
|
2834
|
+
sx: number;
|
|
2835
|
+
sy: number;
|
|
2836
|
+
ox: number;
|
|
2837
|
+
oy: number;
|
|
2838
|
+
uniform: boolean;
|
|
2839
|
+
};
|
|
2840
|
+
|
|
2841
|
+
export { AQUARIUM_SPEC, AURORA_SPEC, type BackgroundDrift, type BackgroundSpec, type BandSpec, COHESION_T, COMETS_SPEC, CONSTELLATION_SPEC, type CapabilityTier, type CohesionRead, type CycleSpec, DASHBOARD_SPEC, DEFAULT_KEY_DUR, DITHER_ALPHA, DITHER_TILE, type DominanceEntry, EXAMPLE_BY_ID, EXAMPLE_SEQUENCES, EXAMPLE_SPECS, type Entity, FACETS_SPEC, FIELD_BUCKET_MS, FIELD_DRIFT_DEFAULT_AMOUNT, FIELD_RASTER_SHORT_SIDE, FIELD_RASTER_SHORT_SIDE_LOW, FINISH_ANIMATE_HZ, type FieldBackground, type FieldDrift, type FinishPass, type FinishSpec, type FlattenedPath, GRAIN_ALPHA, GRAIN_TILE, type GradientStop, HAIKU_SPEC, type IdleSequence, LANTERNS_SPEC, LIMITS, LOBBY_TALK_SPEC, type LayerCohesion, type LayerGroup, type LayerMotionStats, type LayerSnapshot, type LayerSpec, type LayerTransform, type LinkEdge, type LuminanceGrid, type LuminanceGridOptions, MATRIX_RAIN_SPEC, MAX_PATH_POINTS, MURMUR_SPEC, type MotionEase, type MotionSpec, NOSTALGHIA_CANDLE_SPEC, ORRERY_SPEC, PHASE_DUET_SPEC, PINGS_SPEC, POLYGONS_SPEC, PROCESSION_SPEC, type PathGradient, type PathGradientStop, type PathPart, type PathStroke, type PerceiveOptions, type PerceiveSequenceOptions, type Placed, RAIN_SPEC, RELAY_BOARD_SPEC, type ResolveOptions, type ResolvedPart, type ResolvedSegment, type RevealState, SAKURA_SPEC, SCHEMA_EXAMPLES, SCHEMA_VERSION, SHARD_FALL_SPEC, SIGNAL_BOARD_SPEC, SNOWFALL_SPEC, SPARSE_NIGHT_SPEC, type SaverSpec, type SceneColour, type SceneDescription, type SceneDiff, type ScenePerception, type SceneSnapshot, type SchemaExample, type ScreenOp, type SequenceExample, type SequenceFramePerception, type SequenceMountContext, type SequenceSaverInstance, type SequenceSaverPlugin, type SequenceSegment, type SequenceTransition, type SpecError, type SpecMountContext, type SpecWarning, type SpriteSpec, type SteerDelta, THERMAL_FIELD_SPEC, THREE_MOVEMENTS_SEQUENCE, TYPED_CUE_SPEC, type TextBlockAnchor, type TextBlockLine, type TextMetricsClass, type TextRevealInput, type TextRevealSpec, type TextRole, type TextSpriteInfo, type Timeline, type TimelineKey, type ValidationResult, WARP_MAX_SCALE, WARP_NEAR, WARP_TUNNEL_SPEC, WEB_WORK_SPEC, type WanderOsc, type WarningBox, adviseSequence, adviseSpec, alphaAt, applyDeltasToSpec, assertValidSequence, assertValidSpec, bayerMatrix, bayerTilePixels, bedRenderSeed, breakTextBlock, buildEntities, canMorph, canWrapMorph, canonicalSpecPath, cohesionOf, compileSaver, compileSequence, createFinishPass, createSceneCanvas, describeScene, diffScenes, dominanceRanking, easeSmooth, emitEnvelope, emitWindow, entityScales, fieldAt, fieldOctaves, fieldRgb, fieldRgbAt, fieldSampleTime, finishStrength, flattenPath, grainOffset, grainTilePixels, graphemeClusters, hasHotSwapSequence, headingAt, hexToRgb255, ignoredPropertyPaths, isPathPolygon, latticeHash, layerCohesion, lerpPathD, lerpSpec, lerpValue, lifeAlphaAt, linkEdges, linkMaxDistPx, linkPairs, luminanceGrid, manifestFor, morphChainRoot, morphNothingMorphable, motionStats, nextKeyAfter, normalizeSeed, pathFrame, pathParts, perceiveScene, perceiveSequenceFrame, pickScreenOp, positionAt, presentWithFinish, readSpecPath, renderBrailleMap, renderDensityMap, resolveSegment, resolveSpecPath, resolveTimelineAt, revealState, rgb255Luma, rotationAt, sceneColour, seamCauseOf, seamsWorthWarning, segmentRenderSeed, segmentStart, sequenceSteerablePaths, sequenceSwapCompatible, sequenceWantsFinish, sizeAt, spriteIndexAt, spriteVariants, steerablePaths, structuralSignature, textBlockAnchorOffset, textMetricsClassFor, textSprites, textStringsDiffer, textStringsOf, textWidthEm, timelineLocalTime, timelineSampleTimes, timelineSampleTimesAfter, timelineTracks, validateSequence, validateSpec, validateSpecPaths, withoutTimeline };
|