@idle-screens/schema 3.12.1 → 3.14.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 +95 -8
- package/dist/index.d.ts +209 -10
- package/dist/index.js +2069 -422
- package/dist/index.js.map +1 -1
- package/package.json +4 -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
|
|
|
@@ -302,7 +304,9 @@ own `finish` is not steerable; steer a segment's, or republish.
|
|
|
302
304
|
`maxDist`; `"chain"` wires entities in order (Mystify polygons — ignores
|
|
303
305
|
`k`/`maxDist`, `closed: true` closes the loop); `"random"` uses a fixed
|
|
304
306
|
deterministic wiring filtered by `maxDist`. `falloff: true` fades link alpha
|
|
305
|
-
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).
|
|
306
310
|
|
|
307
311
|
### `sprite` (one of)
|
|
308
312
|
|
|
@@ -349,7 +353,68 @@ with distance, removing pop-in at the cutoff.
|
|
|
349
353
|
or `morph` target for `sprite.points` is in the **same unit coordinates**,
|
|
350
354
|
scaled by each entity's seeded radius (not by the keyed shape's own extent):
|
|
351
355
|
keep every keyed point inside −1..1 (the validator rejects one outside), and
|
|
352
|
-
shrink a target shape by scaling its points, not by exceeding the box
|
|
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.
|
|
353
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" }` —
|
|
354
419
|
a freehand mark: a path through 2..24 unit-coordinate points, scaled so the
|
|
355
420
|
unit box spans the seeded `length`, stroked `width` wide with round joins.
|
|
@@ -425,7 +490,9 @@ with distance, removing pop-in at the cutoff.
|
|
|
425
490
|
across the room, and opts that layer into two advisories:
|
|
426
491
|
`text-legibility` fires when the text colour's WCAG ratio falls below
|
|
427
492
|
**4.5:1** against the background sampled at the box centre (gradient stops
|
|
428
|
-
interpolated at rest, `band` honoured)
|
|
493
|
+
interpolated at rest, `band` honoured) — or, where an earlier static,
|
|
494
|
+
hard-edged, unblended layer covers the whole box (a backing chip), against
|
|
495
|
+
that layer composited over the page — **or** against the brightest
|
|
429
496
|
additive layer (`blend: lighter` / `screen`) whose entities can reach the
|
|
430
497
|
box and are at least a glyph tall, taken at its peak alpha (base + pulse)
|
|
431
498
|
composited over that background — a glow parked under a caption is
|
|
@@ -744,7 +811,16 @@ its drift, orbit and pulse run on (from mount; a sequence segment's `localT`):
|
|
|
744
811
|
Keys are also checked **together**: two keys that are each valid can
|
|
745
812
|
combine past a floor (a `clock.rate` and a `pulse.period` share one), so
|
|
746
813
|
the validator resolves the scene at every key's start, end and glide
|
|
747
|
-
quarter-points and rejects it there.
|
|
814
|
+
quarter-points and rejects it there. Paint keys on different paths can
|
|
815
|
+
also combine into a faster flicker than any one path allows: a top
|
|
816
|
+
layer's `opacity` switching between its own colour and the colour
|
|
817
|
+
beneath, each keyed at the floor and out of step, flickers the whole
|
|
818
|
+
frame 5 times a second. So where any one second holds 7 or more key
|
|
819
|
+
boundaries on paint paths (opacity, alpha, colours, stops, gradients,
|
|
820
|
+
strokes), publishing resolves the frame there and refuses more than 3
|
|
821
|
+
flashes a second over a quarter of the screen (WCAG 2.3.1,
|
|
822
|
+
`timelineFlashReport`). Keep big brightness changes at least ~170 ms
|
|
823
|
+
apart across the whole frame, not just per path.
|
|
748
824
|
- A key on a **structural** path rebuilds the scene at that key — a pop, and
|
|
749
825
|
the validator warns `timeline-structural-key`. Animate paint: colours,
|
|
750
826
|
`opacity`, `transform`, polygon `points` (a whole equal-length array
|
|
@@ -897,14 +973,25 @@ moves and scales its positions and sizes (sprite rotation is not modelled).
|
|
|
897
973
|
will look wrong on the wall; one that passes may still sit a few px off.
|
|
898
974
|
Two more are **opt-in** through the text sprite's `role: "read"` (see the
|
|
899
975
|
`textBlock` sprite): `text-legibility` (WCAG ratio below 4.5:1 against the
|
|
900
|
-
background at the box centre or
|
|
901
|
-
sit beneath the line) and `text-safe-area` (the box within 5 % of an edge,
|
|
976
|
+
background at the box centre, or the solid chip painted under the whole box,
|
|
977
|
+
or under the brightest additive layer that can sit beneath the line) and `text-safe-area` (the box within 5 % of an edge,
|
|
902
978
|
with its box in `boxes`). Undeclared text is never measured for either.
|
|
903
979
|
`overlap-seams` catches the trap the other channels are blind to: a layer
|
|
904
980
|
whose entities bury each other (so it was drawn as one shape) but whose paint
|
|
905
981
|
settings make every overlap visible. It is gated to stay off deliberate
|
|
906
982
|
washes — it needs at least 6 entities and a mean base alpha of 0.45, and
|
|
907
|
-
never fires under `blend: lighter` / `screen`.
|
|
983
|
+
never fires under `blend: lighter` / `screen`. Nor does it fire when the
|
|
984
|
+
only seam is a multi-colour palette and the shapes bury their neighbours'
|
|
985
|
+
outlines but under 15 % of their area — rows or tiles butted edge to edge
|
|
986
|
+
(overlapping by a hairline to hide the gap) are banding, not a pile. No
|
|
987
|
+
shipped example trips it.
|
|
988
|
+
`motion-intensity-mismatch` fires when a scene declares
|
|
989
|
+
`motionIntensity: "calm"` but a layer of shapes, glyphs or text spins
|
|
990
|
+
faster than 90°/s on average (shipped calm spins stay at or under 32°/s).
|
|
991
|
+
`flash-near-limit` fires when a timeline's luminance keys, resolved over
|
|
992
|
+
their densest one-second window, flicker at least 2 times a second over a
|
|
993
|
+
quarter of the frame — a full-frame step at the 200 ms key floor is 2.5, which
|
|
994
|
+
the publish-time WCAG gate (more than 3) lets through.
|
|
908
995
|
The two density advisories read the spec's declared `density` first:
|
|
909
996
|
`sparse-scene` is withheld under `density: "sparse"` and `dense-scene`
|
|
910
997
|
under `"dense"`, and `density-mismatch` fires instead when the measured
|
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<{
|
|
@@ -1376,6 +1444,16 @@ interface SteerDelta {
|
|
|
1376
1444
|
}
|
|
1377
1445
|
/** Apply deltas to a deep copy of the spec (last-wins, target values). */
|
|
1378
1446
|
declare function applyDeltasToSpec(spec: SaverSpec, deltas: SteerDelta[]): SaverSpec;
|
|
1447
|
+
/**
|
|
1448
|
+
* `applyDeltasToSpec` for read-only callers: the same result, but every
|
|
1449
|
+
* subtree a delta does not touch is shared with `spec` — only the containers
|
|
1450
|
+
* on each delta's path are copied. The validator checks a timeline by applying
|
|
1451
|
+
* each key, and the composed keys at every sample time, to the whole spec; a
|
|
1452
|
+
* full deep copy per check was most of the cost of validating a long timeline
|
|
1453
|
+
* (a 132 KB scene spent ~60% of its CPU in JSON round-trips). The result
|
|
1454
|
+
* aliases `spec`, so it must never be mutated.
|
|
1455
|
+
*/
|
|
1456
|
+
declare function applyDeltasShared(spec: SaverSpec, deltas: SteerDelta[]): SaverSpec;
|
|
1379
1457
|
/**
|
|
1380
1458
|
* Interpolate between two structurally-similar specs at progress k (0..1).
|
|
1381
1459
|
* Numbers lerp, hex colours lerp per channel, everything else steps to the
|
|
@@ -1391,6 +1469,13 @@ declare function lerpSpec(from: SaverSpec, to: SaverSpec, k: number): SaverSpec;
|
|
|
1391
1469
|
* timeline, so every stored morph and glide is unchanged.
|
|
1392
1470
|
*/
|
|
1393
1471
|
declare function lerpValue(from: unknown, to: unknown, k: number, specRoot?: boolean, rootKey?: string): unknown;
|
|
1472
|
+
/**
|
|
1473
|
+
* Interpolate two SVG path d strings that share their structure — the same
|
|
1474
|
+
* command letters in the same order and the same count of numbers — by
|
|
1475
|
+
* lerping every number; null when they differ in structure (the morph then
|
|
1476
|
+
* steps). Arc flags (0/1) round, so a half-way arc stays a valid arc.
|
|
1477
|
+
*/
|
|
1478
|
+
declare function lerpPathD(a: string, b: string, k: number): string | null;
|
|
1394
1479
|
/**
|
|
1395
1480
|
* The string(s) a text layer paints — `strings` of a `text` sprite, `text`
|
|
1396
1481
|
* of a `textBlock` — or null for every other sprite.
|
|
@@ -1541,12 +1626,25 @@ declare function cohesionOf(layers: Array<{
|
|
|
1541
1626
|
layer: LayerSpec;
|
|
1542
1627
|
entities: Entity[];
|
|
1543
1628
|
}>, t: number, w: number, h: number): LayerCohesion[];
|
|
1629
|
+
/**
|
|
1630
|
+
* Mean fraction of an entity's filled AREA covered by a same-layer sibling,
|
|
1631
|
+
* 0..1 — the counterpart of `overlap`, which measures outline. Edge contact
|
|
1632
|
+
* buries outline and no area; a real pile buries both. `null` where
|
|
1633
|
+
* `cohesionOf` reports no overlap.
|
|
1634
|
+
*/
|
|
1635
|
+
declare function buriedAreaOf(layer: LayerSpec, entities: Entity[], t: number, w: number, h: number): number | null;
|
|
1544
1636
|
/**
|
|
1545
1637
|
* True when a `seamed` layer is worth warning about: dense enough to be
|
|
1546
1638
|
* deliberate, and opaque enough that it cannot be an atmospheric wash. Keeps
|
|
1547
1639
|
* the advisory off the soft low-alpha glow fields the playbook recommends.
|
|
1640
|
+
*
|
|
1641
|
+
* `buriedArea` (lazy, from {@link buriedAreaOf}) lets it also stay off a
|
|
1642
|
+
* TILING whose only seam is its palette: opaque hard tiles of unlike colour
|
|
1643
|
+
* that butt edge to edge show one clean colour boundary per join, which is
|
|
1644
|
+
* the banding the author drew (X574). A translucent tiling is still warned
|
|
1645
|
+
* about — each hairline overlap paints a darker line.
|
|
1548
1646
|
*/
|
|
1549
|
-
declare function seamsWorthWarning(layer: LayerSpec, c: LayerCohesion, entityCount: number): boolean;
|
|
1647
|
+
declare function seamsWorthWarning(layer: LayerSpec, c: LayerCohesion, entityCount: number, buriedArea?: () => number | null): boolean;
|
|
1550
1648
|
|
|
1551
1649
|
interface LayerSnapshot {
|
|
1552
1650
|
key: string | undefined;
|
|
@@ -1643,9 +1741,9 @@ interface LuminanceGridOptions extends PerceiveOptions {
|
|
|
1643
1741
|
* re-uses drawTrail's sampling — past positions with decaying alpha and
|
|
1644
1742
|
* shrinking radius. Polygons larger than POLYGON_OUTLINE_CELLS are scanned by
|
|
1645
1743
|
* their real (rotated) outline; smaller ones splat as a fill-weighted disc.
|
|
1646
|
-
* Remaining approximations (documented, deliberate):
|
|
1647
|
-
*
|
|
1648
|
-
*
|
|
1744
|
+
* Remaining approximations (documented, deliberate): background drift is
|
|
1745
|
+
* sampled at its rest position. Good enough to perceive composition, focus,
|
|
1746
|
+
* balance — and now smear.
|
|
1649
1747
|
*/
|
|
1650
1748
|
declare function luminanceGrid(spec: SaverSpec, opts?: LuminanceGridOptions): LuminanceGrid;
|
|
1651
1749
|
/**
|
|
@@ -1952,6 +2050,20 @@ interface PerceiveSequenceOptions extends Omit<LuminanceGridOptions, 't'> {
|
|
|
1952
2050
|
*/
|
|
1953
2051
|
declare function perceiveSequenceFrame(seq: IdleSequence, T: number, opts?: PerceiveSequenceOptions): SequenceFramePerception;
|
|
1954
2052
|
|
|
2053
|
+
interface TimelineFlashReport {
|
|
2054
|
+
/** More than 3 flashes in some one-second window over at least a quarter of the frame (WCAG 2.3.1). */
|
|
2055
|
+
fails: boolean;
|
|
2056
|
+
/** Worst flashes per second any part of the frame reaches (0 when nothing was dense enough to check). */
|
|
2057
|
+
flashesPerSecond: number;
|
|
2058
|
+
/** Fraction of the frame flashing more than 3 times a second. */
|
|
2059
|
+
flashingArea: number;
|
|
2060
|
+
/** Start (scene ms, within the lap) of the worst window checked, or null when none was checked. */
|
|
2061
|
+
at: number | null;
|
|
2062
|
+
/** Set (with `fails`) when dense sections remained beyond the {@link MAX_TOTAL_SAMPLES} budget: unchecked is never reported as safe. */
|
|
2063
|
+
incomplete?: true;
|
|
2064
|
+
}
|
|
2065
|
+
declare function timelineFlashReport(spec: SaverSpec): TimelineFlashReport;
|
|
2066
|
+
|
|
1955
2067
|
/**
|
|
1956
2068
|
* The `field` background sampler — seeded, dependency-free value noise that
|
|
1957
2069
|
* is a PURE function of `(u, v, t, config, seed)`.
|
|
@@ -2277,8 +2389,9 @@ declare function timelineLocalTime(tl: Timeline, t: number): number;
|
|
|
2277
2389
|
* The scene at time `t` with every key applied — and without its `timeline`,
|
|
2278
2390
|
* so the result can be rendered, perceived, lerped or steered like any plain
|
|
2279
2391
|
* spec. A spec without a timeline comes back as the same object.
|
|
2392
|
+
* `shared` (read-only callers): untouched subtrees alias `spec` — never mutate the result.
|
|
2280
2393
|
*/
|
|
2281
|
-
declare function resolveTimelineAt(spec: SaverSpec, t: number): SaverSpec;
|
|
2394
|
+
declare function resolveTimelineAt(spec: SaverSpec, t: number, shared?: boolean): SaverSpec;
|
|
2282
2395
|
/**
|
|
2283
2396
|
* When the timeline next takes a path back: the scene time of that path's
|
|
2284
2397
|
* next key after `t`, and that key's glide length — or null when no key will
|
|
@@ -2678,4 +2791,90 @@ declare const EXAMPLE_SPECS: SaverSpec[];
|
|
|
2678
2791
|
/** Lookup by saver id. */
|
|
2679
2792
|
declare const EXAMPLE_BY_ID: Readonly<Record<string, SaverSpec>>;
|
|
2680
2793
|
|
|
2681
|
-
|
|
2794
|
+
/**
|
|
2795
|
+
* Shape glyph geometry — pure, renderer-free, shared by the compiler (what to
|
|
2796
|
+
* stroke/fill), perception (how much ink that is) and advise. Everything is
|
|
2797
|
+
* computed from the sprite spec + the entity's seeded size, so the canvas and
|
|
2798
|
+
* the analytic model agree by construction.
|
|
2799
|
+
*
|
|
2800
|
+
* Unit coordinates: `polygon.points` and `stroke.points` live in a −1..1 box
|
|
2801
|
+
* that is scaled by the entity's half-size (circumradius for polygons, half of
|
|
2802
|
+
* `length` for strokes), so the seeded size range is the glyph's bounding
|
|
2803
|
+
* diameter — the same rule `circle.radius` / `streak.length` already follow.
|
|
2804
|
+
*/
|
|
2805
|
+
|
|
2806
|
+
type PolygonSprite = Extract<SpriteSpec, {
|
|
2807
|
+
kind: 'polygon';
|
|
2808
|
+
}>;
|
|
2809
|
+
/** Most outline points one flattened path may produce. */
|
|
2810
|
+
declare const MAX_PATH_POINTS = 4096;
|
|
2811
|
+
interface FlattenedPath {
|
|
2812
|
+
/** One closed ring: subpaths joined by zero-width bridges, inner ones wound as holes. */
|
|
2813
|
+
points: Array<[number, number]>;
|
|
2814
|
+
subpaths: number;
|
|
2815
|
+
/** Every subpath as drawn (simplified, no bridges), for strokes: open unless it ended in Z. */
|
|
2816
|
+
lines: Array<{
|
|
2817
|
+
points: Array<[number, number]>;
|
|
2818
|
+
closed: boolean;
|
|
2819
|
+
}>;
|
|
2820
|
+
/** Parse error, if the string is not a usable path. */
|
|
2821
|
+
error?: string;
|
|
2822
|
+
}
|
|
2823
|
+
/**
|
|
2824
|
+
* How a path's subpaths combine. `auto` (default): containment decides which
|
|
2825
|
+
* subpaths are holes (see joinRings) — for authors who never think about
|
|
2826
|
+
* winding. `nonzero` / `evenodd`: SVG's own fill rules, honouring the winding
|
|
2827
|
+
* as drawn — for pasted SVG, and for a deliberate same-colour shape that sits
|
|
2828
|
+
* inside another (auto would read it as a hole).
|
|
2829
|
+
*/
|
|
2830
|
+
type PathFillRule = 'auto' | 'nonzero' | 'evenodd';
|
|
2831
|
+
declare function flattenPath(d: string, rule?: PathFillRule): FlattenedPath;
|
|
2832
|
+
/** A polygon drawn from SVG paths: one `path`, or a multi-colour drawing of `parts`. */
|
|
2833
|
+
declare function isPathPolygon(s: SpriteSpec): boolean;
|
|
2834
|
+
/** One drawable piece of a path polygon, resolved: its outline, paint and part-space box. */
|
|
2835
|
+
interface ResolvedPart {
|
|
2836
|
+
flat: FlattenedPath;
|
|
2837
|
+
/** Its own colour, or null to use the entity's (sprite colour / palette pick). */
|
|
2838
|
+
color: string | null;
|
|
2839
|
+
fill: boolean;
|
|
2840
|
+
rule: 'nonzero' | 'evenodd';
|
|
2841
|
+
gradient?: PathGradient;
|
|
2842
|
+
stroke?: PathStroke;
|
|
2843
|
+
opacity: number;
|
|
2844
|
+
/** The fill's bounding box in the unit box (centre + half extents) — the space of `gradient.units: 'part'`. */
|
|
2845
|
+
box: {
|
|
2846
|
+
cx: number;
|
|
2847
|
+
cy: number;
|
|
2848
|
+
hw: number;
|
|
2849
|
+
hh: number;
|
|
2850
|
+
};
|
|
2851
|
+
}
|
|
2852
|
+
/**
|
|
2853
|
+
* A path polygon as an ordered list of parts, back to front. A single
|
|
2854
|
+
* `path` is a drawing of one part (its colour the entity's), so the renderer,
|
|
2855
|
+
* perception and the budget treat both forms one way. Memoised per sprite.
|
|
2856
|
+
*/
|
|
2857
|
+
declare function pathParts(s: PolygonSprite): ResolvedPart[];
|
|
2858
|
+
/**
|
|
2859
|
+
* How a path drawing's −1..1 unit box lands on screen, in px: scale per axis
|
|
2860
|
+
* (sx, sy) and the unit-box point (ox, oy) that sits at the entity position.
|
|
2861
|
+
* span 'box' (default): uniform, the entity radius r on both axes — the
|
|
2862
|
+
* classic square box sized by the short side;
|
|
2863
|
+
* 'width': x spans the full screen width (sx = w/2), y keeps r — skylines,
|
|
2864
|
+
* hills and ground bands that must reach both side edges on any aspect;
|
|
2865
|
+
* 'height': y spans the full height, x keeps r;
|
|
2866
|
+
* 'screen': both — a frame or wash that hugs every edge.
|
|
2867
|
+
* `origin` [ox, oy] picks the anchored point: [0, 1] with position y = 1 sets
|
|
2868
|
+
* the drawing's bottom on the screen's bottom edge. Strokes keep the uniform
|
|
2869
|
+
* r for their width, so stretching never fattens an outline sideways.
|
|
2870
|
+
*/
|
|
2871
|
+
declare function pathFrame(s: PolygonSprite, r: number, w: number, h: number): {
|
|
2872
|
+
sx: number;
|
|
2873
|
+
sy: number;
|
|
2874
|
+
ox: number;
|
|
2875
|
+
oy: number;
|
|
2876
|
+
uniform: boolean;
|
|
2877
|
+
};
|
|
2878
|
+
|
|
2879
|
+
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, 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, 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, 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, timelineFlashReport, timelineLocalTime, timelineSampleTimes, timelineSampleTimesAfter, timelineTracks, validateSequence, validateSpec, validateSpecPaths, withoutTimeline };
|
|
2880
|
+
export type { BackgroundDrift, BackgroundSpec, BandSpec, CapabilityTier, CohesionRead, CycleSpec, DominanceEntry, Entity, FieldBackground, FieldDrift, FinishPass, FinishSpec, FlattenedPath, 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, SceneSnapshot, SchemaExample, ScreenOp, SequenceExample, SequenceFramePerception, SequenceMountContext, SequenceSaverInstance, SequenceSaverPlugin, SequenceSegment, SequenceTransition, SpecError, SpecMountContext, SpecWarning, SpriteSpec, SteerDelta, TextBlockAnchor, TextBlockLine, TextMetricsClass, TextRevealInput, TextRevealSpec, TextRole, TextSpriteInfo, Timeline, TimelineFlashReport, TimelineKey, ValidationResult, WanderOsc, WarningBox };
|