@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 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) **or** against the brightest
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 under the brightest additive layer that can
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`. No shipped example trips it.
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: number;
341
- maxDist: number;
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: number;
1175
- maxDist: number;
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): soft circles use a linear
1647
- * falloff, background drift is sampled at its rest position. Good enough to
1648
- * perceive composition, focus, balance — and now smear.
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
- 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, 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, MURMUR_SPEC, type MotionEase, type MotionSpec, NOSTALGHIA_CANDLE_SPEC, ORRERY_SPEC, PHASE_DUET_SPEC, PINGS_SPEC, POLYGONS_SPEC, PROCESSION_SPEC, type PerceiveOptions, type PerceiveSequenceOptions, type Placed, RAIN_SPEC, RELAY_BOARD_SPEC, type ResolveOptions, 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, grainOffset, grainTilePixels, graphemeClusters, hasHotSwapSequence, headingAt, hexToRgb255, ignoredPropertyPaths, latticeHash, layerCohesion, lerpSpec, lerpValue, lifeAlphaAt, linkEdges, linkPairs, luminanceGrid, manifestFor, morphChainRoot, morphNothingMorphable, motionStats, nextKeyAfter, normalizeSeed, 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 };
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 };