@idle-screens/schema 3.7.0 → 3.9.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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Rng, SaverContext, SaverPlugin, SaverManifest } from '@idle-screens/core';
1
+ import { Rng, SaverContext, SaverInstance, ControlTrack, SaverPlugin, SaverManifest } from '@idle-screens/core';
2
2
 
3
3
  /**
4
4
  * Declarative, agent-authorable saver format. A `SaverSpec` describes a saver as DATA
@@ -40,6 +40,12 @@ interface SaverSpec {
40
40
  * `renderFrame(t)` stays deterministic for seeks via a fixed-step warm-up replay.
41
41
  */
42
42
  ghosting?: number;
43
+ /**
44
+ * The print finish — grain and a dither screen composited over the
45
+ * finished frame as the last step of every render. See `FinishSpec`.
46
+ * Absent ⇒ the frame is presented exactly as drawn.
47
+ */
48
+ finish?: FinishSpec;
43
49
  /** Dimensional unit system. 'viewport' (default) = all sizes/speeds/distances are fractions of min(w,h). */
44
50
  units?: 'viewport' | 'px';
45
51
  /**
@@ -49,6 +55,25 @@ interface SaverSpec {
49
55
  */
50
56
  referenceViewport?: number;
51
57
  }
58
+ /**
59
+ * A print finish over the finished frame: `grain` (0..1) screens a seeded
60
+ * zero-mean noise tile at up to 35 % alpha; `dither` (0..1) screens an 8×8
61
+ * ordered Bayer tile at up to 25 % — a stylistic screen, not per-pixel
62
+ * dithering of the image. Both composite with a symmetric blend (`overlay`)
63
+ * so the frame's mean luminance is unchanged: flash-safe by construction,
64
+ * and invisible to `luminanceGrid`. The grain tile is generated once per
65
+ * mount from the spec seed and sits still; `animate: true` steps its offset
66
+ * deterministically from the frame's time bucket (12 Hz), never randomly —
67
+ * ignored on the `basic` / `minimal` capability tiers (static tile). The
68
+ * finish is applied on a presentation canvas, never fed back into
69
+ * `ghosting`'s persistence. `grain` and `dither` are numeric paint paths
70
+ * (`finish.grain`) and glide.
71
+ */
72
+ interface FinishSpec {
73
+ grain?: number;
74
+ dither?: number;
75
+ animate?: boolean;
76
+ }
52
77
  type BackgroundSpec = {
53
78
  type: 'solid';
54
79
  color: string;
@@ -57,7 +82,45 @@ type BackgroundSpec = {
57
82
  stops: GradientStop[];
58
83
  band?: BandSpec;
59
84
  drift?: BackgroundDrift;
60
- };
85
+ } | FieldBackground;
86
+ /**
87
+ * A scalar-field background: seeded, warped value noise quantised into
88
+ * `bands` — thermal maps, contour terrain, sonar landmasses, riso washes.
89
+ * Pure in `(x, y, t, seed)`: the renderer paints it as a low-res raster
90
+ * (short side 96 px; 48 on the `basic` / `minimal` tiers) scaled to the
91
+ * canvas, recomputed once per 100 ms bucket while drifting and once ever
92
+ * otherwise, and the perception grid samples the same function directly.
93
+ * The sampler uses its own hash, never the entity RNG, so adding a field to
94
+ * a spec leaves every entity where it was.
95
+ */
96
+ interface FieldBackground {
97
+ type: 'field';
98
+ /** Noise features across the short side, 0.5..8 (LIMITS.minFieldScale / maxFieldScale). */
99
+ scale: number;
100
+ /** Octaves of detail, integer 1..4 (default 2). */
101
+ octaves?: number;
102
+ /** Domain-warp amount 0..1 (default 0) — folds round blobs into smeared contours. */
103
+ warp?: number;
104
+ /**
105
+ * Levels to posterise the field into, integer 2..8, or 0 for smooth
106
+ * interpolation between bands (Aura, Mist). Default `bands.length`: one
107
+ * hard band per level.
108
+ */
109
+ quantize?: number;
110
+ /** 2..8 hex colours, darkest-to-lightest or whatever ramp the look wants. */
111
+ bands: string[];
112
+ /** Slow domain travel — the field's "animate". Period floored at 10 s (flash guard). */
113
+ drift?: FieldDrift;
114
+ /** Field seed; defaults to the spec seed. Independent of the entity stream. */
115
+ seed?: number;
116
+ }
117
+ /** Slow travel of a field's sample domain: a circle of radius `amount` feature units per `period`. */
118
+ interface FieldDrift {
119
+ /** Full loop period in ms. Floor: LIMITS.minDriftPeriod (10 s) — the flash guard. */
120
+ period: number;
121
+ /** Radius of the domain travel in feature units, 0..1. Default 0.3. */
122
+ amount?: number;
123
+ }
61
124
  /** A vertical gradient stop (`at` 0 = top, 1 = bottom). */
62
125
  interface GradientStop {
63
126
  at: number;
@@ -131,6 +194,17 @@ interface LayerSpec {
131
194
  * form draws one extra seeded value, only when present).
132
195
  */
133
196
  spin?: number | [number, number];
197
+ /**
198
+ * Static per-entity rotation in degrees (positive = clockwise), ±360. A
199
+ * scalar turns every entity the same way; a `[min, max]` range gives each
200
+ * entity a seeded angle — thrown blades, scattered glyphs, a tilted grid.
201
+ * Composes with `spin` (added to its seeded start angle). Note that `spin:
202
+ * [0, 0]` does NOT do this: a zero spin speed renders at angle 0, its
203
+ * seeded phase unused. Structural (baked into entities). The range form
204
+ * draws one seeded value per entity, only when declared, so existing
205
+ * specs keep identical streams.
206
+ */
207
+ rotate?: number | [number, number];
134
208
  /**
135
209
  * Sinusoidal size breathing, parallel to `pulse` for opacity. `amp` is a
136
210
  * fraction of base size (0.3 = ±30 %). `period` in ms with the same
@@ -644,6 +718,7 @@ declare const LIMITS: {
644
718
  readonly maxPulseAmp: 0.5;
645
719
  readonly minPulsePeriod: 500;
646
720
  readonly maxSpin: 360;
721
+ readonly maxRotate: 360;
647
722
  readonly maxGrowAmp: 0.8;
648
723
  readonly maxOrbitSpeed: 180;
649
724
  readonly maxLinksK: 8;
@@ -654,6 +729,13 @@ declare const LIMITS: {
654
729
  readonly maxTrailSamples: 24;
655
730
  readonly minDriftPeriod: 10000;
656
731
  readonly maxDriftAmount: 0.3;
732
+ readonly minFieldScale: 0.5;
733
+ readonly maxFieldScale: 8;
734
+ readonly maxFieldOctaves: 4;
735
+ readonly minFieldBands: 2;
736
+ readonly maxFieldBands: 8;
737
+ readonly maxFieldQuantize: 8;
738
+ readonly maxFieldDriftAmount: 1;
657
739
  readonly maxGhosting: 0.95;
658
740
  readonly maxGhostReplayFrames: 120;
659
741
  readonly maxMeander: 500;
@@ -749,6 +831,14 @@ interface IdleSequence {
749
831
  * paints its own ground, exactly as before the field existed.
750
832
  */
751
833
  bed?: SaverSpec;
834
+ /**
835
+ * A print finish applied once per composed frame — over bed, segment and
836
+ * any fade together, on the sequence's presentation canvas. Overrides
837
+ * every segment's own `finish` while set; absent, the active segment's
838
+ * `finish` applies when it declares one (a bed's is ignored — the bed is
839
+ * ground, finish the sequence). Children never apply their own.
840
+ */
841
+ finish?: FinishSpec;
752
842
  segments: SequenceSegment[];
753
843
  }
754
844
 
@@ -848,6 +938,8 @@ interface Entity {
848
938
  type: 'settle' | 'buoyant';
849
939
  tau: number;
850
940
  };
941
+ /** Static rotation (radians), added to any spin. Only set when `layer.rotate` is declared. */
942
+ rotate?: number;
851
943
  /** Spline params (pts in px, shared per layer). Only set for path motion. */
852
944
  path?: {
853
945
  pts: Array<{
@@ -884,7 +976,13 @@ declare function emitEnvelope(u: number): number;
884
976
  declare function alphaAt(e: Entity, t: number): number;
885
977
  /** Analytic size multiplier at time `t` (ms). Pure, clamped to > 0. */
886
978
  declare function sizeAt(e: Entity, t: number): number;
887
- /** Analytic rotation angle at time `t` (ms) in radians. Pure. */
979
+ /**
980
+ * Analytic rotation angle at time `t` (ms) in radians. Pure. A static
981
+ * `rotate` is the base; a spinning entity adds its seeded start angle and
982
+ * speed × t on top. With neither, 0 — and with `spin: [0, 0]` also 0: a zero
983
+ * spin speed never rotates, so its seeded phase is not applied (that is what
984
+ * `rotate` is for).
985
+ */
888
986
  declare function rotationAt(e: Entity, t: number): number;
889
987
  /** Layer lifecycle alpha multiplier at time `t` (ms). Pure. 1 when `life` is unset. */
890
988
  declare function lifeAlphaAt(life: {
@@ -1016,6 +1114,22 @@ declare function revealState(lines: TextBlockLine[], reveal: TextRevealInput, tM
1016
1114
 
1017
1115
  /** Derive a manifest so a compiled spec composes with @idle-screens/capabilities. */
1018
1116
  declare function manifestFor(spec: SaverSpec): SaverManifest;
1117
+ /**
1118
+ * The device's compute tier (`computeTier` from `@idle-screens/capabilities`;
1119
+ * the string union is repeated here so schema does not depend on that
1120
+ * package). Hosts pass it on the mount context; a compiled scene reads it to
1121
+ * step down work that a `basic` / `minimal` device cannot afford.
1122
+ */
1123
+ type CapabilityTier = 'minimal' | 'basic' | 'standard' | 'high';
1124
+ /**
1125
+ * Mount context for a compiled spec. A plain `SaverContext` works — every
1126
+ * field here is optional. `capabilityTier` `basic` / `minimal` halves a
1127
+ * `field` background's raster resolution (48 px short side instead of 96).
1128
+ * Absent ⇒ full resolution.
1129
+ */
1130
+ interface SpecMountContext extends SaverContext {
1131
+ capabilityTier?: CapabilityTier;
1132
+ }
1019
1133
  /**
1020
1134
  * Compile a declarative spec into a runnable SaverPlugin. Throws if the spec is invalid
1021
1135
  * (so an agent-authored spec is validated before it can run). The result is seeded,
@@ -1029,19 +1143,47 @@ declare function compileSaver(spec: unknown): SaverPlugin;
1029
1143
  * clock's elapsed ms (idlescreens.com: `Date.now() − scene.epoch`) so every
1030
1144
  * viewer of that sequence resolves the same segment.
1031
1145
  */
1032
- interface SequenceMountContext extends SaverContext {
1146
+ interface SequenceMountContext extends SpecMountContext {
1033
1147
  /** ms already elapsed on the shared sequence clock. Ignored unless `sync: 'epoch'`. */
1034
1148
  sequenceBaseT?: number;
1035
1149
  /**
1036
- * The device's compute tier (`computeTier` from `@idle-screens/capabilities`;
1037
- * the string union is repeated here so schema does not depend on that
1038
- * package). A `fade` transition keeps two segments live for `dur`, which
1039
- * `basic` (canvas2d only) and `minimal` cannot afford: on those tiers fade
1040
- * renders as `cut`. Absent ⇒ fade enabled.
1150
+ * See `SpecMountContext.capabilityTier`. A `fade` transition keeps two
1151
+ * segments live for `dur`, which `basic` (canvas2d only) and `minimal`
1152
+ * cannot afford: on those tiers fade renders as `cut`. The tier is also
1153
+ * handed to every child and the bed (a `field` background's raster steps
1154
+ * down with it). Absent ⇒ fade enabled, full resolution.
1155
+ */
1156
+ capabilityTier?: CapabilityTier;
1157
+ }
1158
+ /**
1159
+ * What `compileSequence().mount()` returns: a `SaverInstance` that can also
1160
+ * take a republished sequence in place. Hosts feature-detect it with
1161
+ * `hasHotSwapSequence` (an older engine's instance simply lacks the method)
1162
+ * and fall through to a remount when it returns `false`.
1163
+ */
1164
+ interface SequenceSaverInstance extends SaverInstance {
1165
+ renderFrame(t: number, seed: number): void;
1166
+ applyTrack(track: ControlTrack): void;
1167
+ /**
1168
+ * Swap `next` in for the running sequence without remounting, when the
1169
+ * two are `sequenceSwapCompatible`: every live segment child (and the bed)
1170
+ * hot-swaps to its new scene, the clock, the active segment, every
1171
+ * released hold and the retained track stay exactly where they were, and
1172
+ * the next frame resolves to the same `(segment, localT)` as if nothing
1173
+ * had happened — a mid-show caption fix that does not send the room back
1174
+ * to segment 0. Returns `false` and changes nothing when the sequences are
1175
+ * not compatible (a structural or timing edit); the host remounts then.
1041
1176
  */
1042
- capabilityTier?: 'minimal' | 'basic' | 'standard' | 'high';
1177
+ hotSwapSequence(next: IdleSequence): boolean;
1043
1178
  }
1044
- declare function compileSequence(spec: unknown): SaverPlugin;
1179
+ /** `compileSequence`'s plugin: `mount` is typed to the sequence instance and `spec` to the validated sequence. */
1180
+ interface SequenceSaverPlugin extends SaverPlugin {
1181
+ mount(ctx: SequenceMountContext): SequenceSaverInstance;
1182
+ spec: IdleSequence;
1183
+ }
1184
+ /** Feature-detect `hotSwapSequence` on any mounted instance (false for a plain scene, or a sequence from an older engine). */
1185
+ declare function hasHotSwapSequence(inst: SaverInstance): inst is SequenceSaverInstance;
1186
+ declare function compileSequence(spec: unknown): SequenceSaverPlugin;
1045
1187
 
1046
1188
  /**
1047
1189
  * Pure helpers for LIVE steering of compiled specs (setParam / applyTrack).
@@ -1089,7 +1231,7 @@ declare function morphNothingMorphable(a: SaverSpec, b: SaverSpec, opts?: {
1089
1231
  }): boolean;
1090
1232
  /**
1091
1233
  * Enumerate all steerable leaf paths in a (resolved) spec. Returns dot-paths
1092
- * like "layers.0.count", "background.stops.1.color", etc. Metadata fields
1234
+ * like "layers.0.count", "background.stops.1.color", "background.bands.2", etc. Metadata fields
1093
1235
  * (id, label, schemaVersion, seed, units, kind, type, key) are excluded —
1094
1236
  * they describe structure, not tuneable values.
1095
1237
  *
@@ -1126,10 +1268,18 @@ declare function adviseSpec(spec: SaverSpec, viewport?: {
1126
1268
  * describe the same scene its other channels do — without them a caller
1127
1269
  * perceiving at t = 30 s would get `form` for that instant beside an
1128
1270
  * `overlap-seams` advisory computed for a different one.
1271
+ *
1272
+ * `backgroundSeed` is separate from `seed` because a sequence's
1273
+ * `inkOverBed` (the segment's ink layers judged against the bed's
1274
+ * background — see `perceiveSequenceFrame`) mixes two scenes with
1275
+ * potentially different seeds: `seed` drives the ink layers' own entity
1276
+ * stream, `backgroundSeed` samples the bed's field. Defaults to `seed` —
1277
+ * every other caller has one scene and one seed for both.
1129
1278
  */
1130
1279
  opts?: {
1131
1280
  t?: number;
1132
1281
  seed?: number;
1282
+ backgroundSeed?: number;
1133
1283
  }): SpecWarning[];
1134
1284
  /**
1135
1285
  * Non-blocking advisory warnings for a validated sequence envelope.
@@ -1240,10 +1390,12 @@ interface SceneSnapshot {
1240
1390
  warnings: string[];
1241
1391
  }
1242
1392
  interface SceneDescription {
1393
+ /** `finish` is present only when the spec declares one — a print screen the luminance numbers do not include. */
1243
1394
  spec: {
1244
1395
  id: string;
1245
1396
  label: string;
1246
1397
  units: string;
1398
+ finish?: FinishSpec;
1247
1399
  };
1248
1400
  snapshots: SceneSnapshot[];
1249
1401
  }
@@ -1275,8 +1427,15 @@ interface LuminanceGrid {
1275
1427
  rows: number;
1276
1428
  /** Row-major luminance 0..1 including the background. */
1277
1429
  cells: number[];
1278
- /** Per-row background luminance (what an empty scene would be). */
1430
+ /** Per-row background luminance (what an empty scene would be). For a `field` background: each row's mean. */
1279
1431
  background: number[];
1432
+ /**
1433
+ * Per-cell background luminance, present only for a `field` background
1434
+ * (a solid or gradient varies by row alone, and `background` says it all).
1435
+ * Coverage, centroid and the profiles deviate from THIS, so the field's own
1436
+ * contours never count as content — a bright field is ground, not ink.
1437
+ */
1438
+ backgroundCells?: number[];
1280
1439
  meanLuminance: number;
1281
1440
  /** Fraction of cells deviating perceptibly (> 0.03) from the background. */
1282
1441
  coverage: number;
@@ -1512,6 +1671,159 @@ interface PerceiveSequenceOptions extends Omit<LuminanceGridOptions, 't'> {
1512
1671
  */
1513
1672
  declare function perceiveSequenceFrame(seq: IdleSequence, T: number, opts?: PerceiveSequenceOptions): SequenceFramePerception;
1514
1673
 
1674
+ /**
1675
+ * The `field` background sampler — seeded, dependency-free value noise that
1676
+ * is a PURE function of `(u, v, t, config, seed)`.
1677
+ *
1678
+ * Why it is its own module, and why it never touches the entity RNG: a
1679
+ * background must be able to change without re-seating a single entity. The
1680
+ * sampler hashes an integer lattice with its own mixing function, so a spec
1681
+ * that adds (or steers) a field keeps the exact `buildEntities` stream it
1682
+ * had before — `field.test.ts` asserts it. Because the value at any point is
1683
+ * closed-form, the renderer (a low-res raster, upscaled) and the perception
1684
+ * grid (`luminanceGrid`) sample the SAME function and agree by construction;
1685
+ * that is the analytic guarantee FORMAT.md states for the field.
1686
+ *
1687
+ * Coordinates: `u`, `v` are in short-side units — `xPx / min(w, h)`,
1688
+ * `yPx / min(w, h)` — so `scale` reads as "features across the short side"
1689
+ * on every aspect ratio, and a 16:9 frame simply shows more of the same field
1690
+ * than a square one does. Callers on both sides normalise the same way.
1691
+ */
1692
+
1693
+ /** Cache bucket for a drifting field: the raster is recomputed at most this often. */
1694
+ declare const FIELD_BUCKET_MS = 100;
1695
+ /** Raster short side (px) the renderer paints the field at, then upscales. */
1696
+ declare const FIELD_RASTER_SHORT_SIDE = 96;
1697
+ /** Raster short side on the `basic` / `minimal` capability tiers. */
1698
+ declare const FIELD_RASTER_SHORT_SIDE_LOW = 48;
1699
+ /** Default `drift.amount`. */
1700
+ declare const FIELD_DRIFT_DEFAULT_AMOUNT = 0.3;
1701
+ /**
1702
+ * Integer-lattice hash → 0..1. Every product goes through `Math.imul` so the
1703
+ * arithmetic stays in int32 and the result is identical on every platform
1704
+ * (a float multiply of large ints would drift by ULPs across engines).
1705
+ */
1706
+ declare function latticeHash(ix: number, iy: number, seed: number): number;
1707
+ /** The clamped, rounded octave count a config resolves to (steering may glide it through fractions). */
1708
+ declare function fieldOctaves(cfg: Pick<FieldBackground, 'octaves'>): number;
1709
+ /**
1710
+ * The time the field is sampled at for scene time `t`: with `drift` at a
1711
+ * nonzero `amount`, the start of the 100 ms bucket `t` falls in (the renderer
1712
+ * recomputes its raster once per bucket and holds it, so the perception grid
1713
+ * samples the same instant); without `drift`, or with `drift.amount: 0` (the
1714
+ * domain never moves), the field is static and the answer is always 0 — so a
1715
+ * legally-static drift config isn't rebucketed, and re-rastered, ten times a
1716
+ * second for nothing.
1717
+ */
1718
+ declare function fieldSampleTime(cfg: Pick<FieldBackground, 'drift'>, t: number): number;
1719
+ /**
1720
+ * The scalar field at short-side coordinates `(u, v)` and time `t` (ms),
1721
+ * 0..1. `seed` is the spec's seed; `cfg.seed` overrides it. Pure — no state,
1722
+ * no RNG stream, so the same inputs give the same value in every call on
1723
+ * every platform.
1724
+ */
1725
+ declare function fieldAt(u: number, v: number, t: number, cfg: FieldBackground, seed: number): number;
1726
+ /** `#rgb` / `#rrggbb` → integer channels 0..255. Malformed input reads as mid grey. */
1727
+ declare function hexToRgb255(hex: string): [number, number, number];
1728
+ /** Perceptual luma (0..1) of integer RGB — the same weights `hexLuma` uses, so a band's luma matches its hex's. */
1729
+ declare function rgb255Luma(rgb: readonly [number, number, number]): number;
1730
+ /**
1731
+ * The colour a field value paints: `quantize ≥ 2` posterises the value into
1732
+ * that many levels and reads each level off the `bands` ramp (`quantize ===
1733
+ * bands.length`, the default, is exactly one band per level — hard, crisp
1734
+ * contours); `quantize: 0` interpolates the ramp smoothly (Aura, Mist). A
1735
+ * steered `quantize` gliding through fractions rounds to the nearest level
1736
+ * count. Integer channels, so a quantised band is byte-exact its hex.
1737
+ */
1738
+ declare function fieldRgb(value: number, bands: readonly string[], quantize: number | undefined): [number, number, number];
1739
+ /** The painted colour at short-side coordinates `(u, v)`: `fieldRgb(fieldAt(...))`. */
1740
+ declare function fieldRgbAt(u: number, v: number, t: number, cfg: FieldBackground, seed: number): [number, number, number];
1741
+
1742
+ /** Grain tile side (px). Repeats across the frame. */
1743
+ declare const GRAIN_TILE = 256;
1744
+ /** Ordered-dither tile side (px): an 8×8 Bayer matrix. */
1745
+ declare const DITHER_TILE = 8;
1746
+ /** Composite alpha at `grain: 1`. */
1747
+ declare const GRAIN_ALPHA = 0.35;
1748
+ /** Composite alpha at `dither: 1`. */
1749
+ declare const DITHER_ALPHA = 0.25;
1750
+ /** Offset step rate under `animate: true`. */
1751
+ declare const FINISH_ANIMATE_HZ = 12;
1752
+ /** The clamped strengths a spec's finish resolves to (0 when absent). */
1753
+ declare function finishStrength(f: FinishSpec | undefined): {
1754
+ grain: number;
1755
+ dither: number;
1756
+ };
1757
+ /**
1758
+ * RGBA pixels of the grain tile for `seed`: zero-mean grey noise (the sum of
1759
+ * two lattice hashes — a triangular distribution, softer than uniform —
1760
+ * centred on 128), alpha 255. Pure; the same seed gives the same tile on
1761
+ * every platform.
1762
+ */
1763
+ declare function grainTilePixels(seed: number): Uint8ClampedArray;
1764
+ /** The 8×8 Bayer ordered-dither matrix (values 0..63, recursively built). */
1765
+ declare function bayerMatrix(): number[][];
1766
+ /**
1767
+ * RGBA pixels of the dither tile: the Bayer matrix as grey levels
1768
+ * `(v + 0.5) / 64 × 255`, so the tile's mean is mid grey (zero-mean under a
1769
+ * symmetric blend). A stylistic screen — it is composited at low alpha, not
1770
+ * used to threshold the image per pixel.
1771
+ */
1772
+ declare function bayerTilePixels(): Uint8ClampedArray;
1773
+ /**
1774
+ * Where the grain tile sits for scene time `t`: fixed at the origin unless
1775
+ * `animate`, in which case it steps to a seeded offset (0..255 per axis)
1776
+ * once per 1/12 s bucket — a pure function of `(t, seed)`.
1777
+ */
1778
+ declare function grainOffset(t: number, seed: number, animate: boolean): {
1779
+ x: number;
1780
+ y: number;
1781
+ };
1782
+ type ScreenOp = 'overlay' | 'soft-light' | 'multiply';
1783
+ /**
1784
+ * The symmetric blend the finish composites with. `overlay` is what every
1785
+ * shipping canvas2d implements; the readback detect exists for the odd
1786
+ * context that rejects it (an assignment the context does not support leaves
1787
+ * the previous value in place).
1788
+ */
1789
+ declare function pickScreenOp(ctx: {
1790
+ globalCompositeOperation: string;
1791
+ }): ScreenOp;
1792
+ type Canvas2D = CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
1793
+ type AnyCanvas = HTMLCanvasElement | OffscreenCanvas;
1794
+ /**
1795
+ * A presentation pass: the visible canvas and the tiles (as repeat
1796
+ * patterns, created lazily and cached — patterns are bound to the context
1797
+ * that made them). Owned by the instance that presents; the scene itself is
1798
+ * drawn on a separate canvas that this pass copies from.
1799
+ */
1800
+ interface FinishPass {
1801
+ canvas: AnyCanvas;
1802
+ ctx: Canvas2D;
1803
+ op: ScreenOp;
1804
+ grain: CanvasPattern | null;
1805
+ dither: CanvasPattern | null;
1806
+ /** Seed the grain tile was generated for. */
1807
+ seed: number;
1808
+ }
1809
+ /** Open a presentation pass on `visible`; null if it has no 2d context. */
1810
+ declare function createFinishPass(visible: AnyCanvas, seed: number): FinishPass | null;
1811
+ /** A fresh scene canvas for the instance to draw into (never attached to the DOM). */
1812
+ declare function createSceneCanvas(width: number, height: number): AnyCanvas;
1813
+ /**
1814
+ * Copy `scene` onto the pass's canvas and screen the finish over it:
1815
+ * grain at `grain × 0.35` (tile offset per `grainOffset`), then dither at
1816
+ * `dither × 0.25`, both with the pass's blend op and no image smoothing (a
1817
+ * crisp screen). With nothing to apply it is a plain copy. `w`/`h` are the
1818
+ * logical size the context transform maps to the canvas. `seed` is the
1819
+ * effective seed the *current* finish resolves to — a sequence with a
1820
+ * segment-level finish can present a different seed per frame (one segment
1821
+ * to the next); it defaults to the pass's own seed, so a single-scene
1822
+ * caller (whose seed never changes) can omit it. The cached grain pattern
1823
+ * is rebuilt whenever the seed changes.
1824
+ */
1825
+ declare function presentWithFinish(fp: FinishPass, scene: AnyCanvas, w: number, h: number, t: number, finish: FinishSpec | undefined, animateAllowed: boolean, seed?: number): void;
1826
+
1515
1827
  interface ResolvedSegment {
1516
1828
  index: number;
1517
1829
  localT: number;
@@ -1582,6 +1894,53 @@ declare function segmentRenderSeed(seq: IdleSequence, index: number): number | u
1582
1894
  * `SpecInstance`. `undefined` when there's no bed or no derivable seed.
1583
1895
  */
1584
1896
  declare function bedRenderSeed(seq: IdleSequence): number | undefined;
1897
+ /**
1898
+ * Whether any scene in the sequence — the sequence itself or any segment —
1899
+ * declares a `finish`. Mirrors `SequenceInstance`'s construction-time check:
1900
+ * a finish anywhere allocates the presentation pass for the instance's whole
1901
+ * lifetime, so this is also what `sequenceSwapCompatible` must agree on
1902
+ * before and after a swap.
1903
+ */
1904
+ declare function sequenceWantsFinish(seq: IdleSequence): boolean;
1905
+ /**
1906
+ * Whether a republished sequence can be swapped into a live
1907
+ * `SequenceInstance` in place of `prev` without a remount
1908
+ * (`SequenceInstance.hotSwapSequence`). True only when every part of the
1909
+ * running instance that is fixed at mount — or that a running clock is
1910
+ * measured against — is unchanged:
1911
+ *
1912
+ * - the same number of segments;
1913
+ * - every segment's scene is a structural twin (`structuralSignature`) of
1914
+ * its counterpart, so the live child's entities survive a `hotSwapSpec`;
1915
+ * - the same render seed per segment (`segmentRenderSeed`) — a child's seed
1916
+ * is fixed at construction, so a changed seed could only take effect at
1917
+ * the next boundary, leaving the room half-swapped;
1918
+ * - a `bed` on both sides or neither, structural twins with the same seed
1919
+ * when present;
1920
+ * - the same top-level `seq.seed`, compared normalized (like the mount-time
1921
+ * seed itself) so a change of an exact multiple of 2^32 isn't flagged —
1922
+ * even when every segment and the bed resolve their own seed (so
1923
+ * `segmentRenderSeed`/`bedRenderSeed` are unaffected), a sequence-level
1924
+ * `finish` grain resolves against the instance's mount-time seed
1925
+ * (`frameFinishSeed`), which a swap cannot update;
1926
+ * - the same finish presence (`sequenceWantsFinish`) — the presentation
1927
+ * pass is allocated once at construction, so a swap that adds or removes
1928
+ * every `finish` in the sequence cannot (de)allocate it;
1929
+ * - the same `loop` and `sync` (absent `sync` is `'mount'`);
1930
+ * - the same `duration`, `advance` (absent is `'auto'`) and `transition`
1931
+ * (absent is `cut`; `text` on a morph defaults to `'step'`) on every
1932
+ * segment. Durations move segment boundaries under a clock that keeps
1933
+ * running, and `clockOffset`/`releasedBelow` on the instance are measured
1934
+ * in that timeline — so a timing edit is a remount for now.
1935
+ *
1936
+ * Paint stays free: colours, words, `background`, `ghosting`, ids and
1937
+ * labels, segment keys. Layer `alpha` is a per-entity range baked at build
1938
+ * time (like `size`), so it is already covered by the structural twin check
1939
+ * above and is not free — an alpha-only edit correctly falls back to a
1940
+ * remount. Mirrors idle-server's `sequenceSignaturesEqual` precondition and
1941
+ * adds the timing terms.
1942
+ */
1943
+ declare function sequenceSwapCompatible(prev: IdleSequence, next: IdleSequence): boolean;
1585
1944
 
1586
1945
  /**
1587
1946
  * Reproduces the hand-coded Fish Aquarium saver as pure DATA — the discriminating test
@@ -1761,6 +2120,171 @@ declare const PROCESSION_SPEC: SaverSpec;
1761
2120
  */
1762
2121
  declare const WARP_TUNNEL_SPEC: SaverSpec;
1763
2122
 
2123
+ /**
2124
+ * Thermal Field: the `field` background — seeded, domain-warped value noise
2125
+ * posterised into six riso inks (`quantize: 6`, one hard band per level) and
2126
+ * drifting once every forty seconds, so the contours crawl like a weather
2127
+ * chart. The foreground is deliberately almost nothing: a handful of pale
2128
+ * motes screened over the bands, so the ground IS the piece. A house
2129
+ * `thermal` look; the same field with `quantize: 0` is a smooth aura. A
2130
+ * `finish` of grain and dither screens the whole frame like a riso print.
2131
+ */
2132
+ declare const THERMAL_FIELD_SPEC: SaverSpec;
2133
+
2134
+ /**
2135
+ * Sparse Night — emptiness, declared as intent.
2136
+ * A star-grain field (radius under 0.001 — the rung FORMAT.md's ladder calls
2137
+ * texture, never the subject) over near-black, one faint linked pentagon as
2138
+ * the only figure, and a single satellite that flares for three seconds out of
2139
+ * every eighteen. Measured coverage sits around one percent, which `adviseSpec`
2140
+ * would flag as `sparse-scene` at every legal count — the spec's
2141
+ * `density: 'sparse'` declaration is the point of this example: it withholds
2142
+ * that advisory AND opts the scene into `density-mismatch` instead, so the
2143
+ * emptiness is a claim the perception API checks rather than a gap it nags
2144
+ * about. Before `density` shipped, restraint like this could not pass advise
2145
+ * clean; the one-mark house style sat at the suite floor for exactly this
2146
+ * reason.
2147
+ */
2148
+ declare const SPARSE_NIGHT_SPEC: SaverSpec;
2149
+
2150
+ /**
2151
+ * Typed Cue — textBlock.reveal, both moods.
2152
+ * Two self-typing captions over near-black. The upper one types at ten
2153
+ * graphemes a second with a blinking caret (`mode: 'typewriter'` — the classic
2154
+ * terminal reveal; `speed` makes progress a pure function of scene time, so
2155
+ * every viewer types the same line on the same clock from its own mount). The
2156
+ * lower one arrives as a wave of overlapping fades (`mode: 'glyphFade'`,
2157
+ * `fade: 0.4` — each glyph ramps in over 40% of the progress window, which
2158
+ * reads as a soft dissolve rather than typing) and carries no caret. Both
2159
+ * blocks declare `opacity` so the path is steerable, and layout always runs on
2160
+ * the FULL text — reveal only masks glyphs, so lines never reflow mid-type.
2161
+ *
2162
+ * Trap, documented in the playbook and true here too: at t = 0 a self-typing
2163
+ * block has typed nothing, so `previewScene` at the default instant looks
2164
+ * empty — preview at t ≥ 10 s to see the words. To swap the line live, glide
2165
+ * `reveal.progress` to 0, step the `text`, glide back (one `setTrack` call).
2166
+ *
2167
+ * The scan band is the one non-text layer, and it is load-bearing twice over:
2168
+ * a screensaver of two static text blocks is a document, not a screensaver
2169
+ * (`text-heavy`), and the band gives the words something to sit on — a slow
2170
+ * refresh sweep passing under the type. Note the units: `perceiveScene` takes
2171
+ * `t` in MILLISECONDS while the MCP `previewScene` tool takes seconds (the
2172
+ * worker converts); passing seconds to the library reads as a block that
2173
+ * never finishes typing.
2174
+ */
2175
+ declare const TYPED_CUE_SPEC: SaverSpec;
2176
+
2177
+ /**
2178
+ * Signal Board — the instrument panel, laid out by the format.
2179
+ * A 3×3 table of data bars with their labels in a matching table, one
2180
+ * monospace caption, and a phosphor sweep crossing the frame every ~90
2181
+ * seconds. The teaching layer is `layout: { type: 'table', columns: 3 }`:
2182
+ * nine bars and nine labels are TWO layers, not eighteen positioned blocks —
2183
+ * entity i reads `values[i]` / `strings[i]` in reading order, and each bar
2184
+ * draws `length × values[i] / max` from its cell. `values` are paint, so
2185
+ * `setParam("readout.sprite.values", [...])` glides every bar at once, which
2186
+ * is the whole point of the shape: a live readout that never re-publishes.
2187
+ */
2188
+ declare const SIGNAL_BOARD_SPEC: SaverSpec;
2189
+
2190
+ /**
2191
+ * Phase Duet — three layers on one clock.
2192
+ * A grid of sixty dots ripples as a traveling wave (`pulse.wave`), three rings
2193
+ * swell, and one soft heart breathes — and none of them drift apart, because
2194
+ * all three declare the same `clock`. `clock` is the format's answer to
2195
+ * coincidence: without it, `pulse`, `grow` and `cycle` run on per-entity
2196
+ * seeded phases, so two layers with the same period never line up (they are
2197
+ * a room full of people, not a duet). With one shared `clock: { phase: 0,
2198
+ * rate: 1 }` they breathe in step and stay there — period ÷ rate must stay
2199
+ * ≥ 1000 ms, which is why every layer here runs a 6000 ms cycle.
2200
+ */
2201
+ declare const PHASE_DUET_SPEC: SaverSpec;
2202
+
2203
+ /**
2204
+ * Web Work — a randomised graph that fades with distance.
2205
+ * Forty-four drifting nodes wired `mode: 'random'` (a fixed deterministic
2206
+ * wiring, not k-nearest — the difference is topology: nearest gives
2207
+ * crystalline clusters, random gives a web that crosses itself) with
2208
+ * `falloff: true`, so an edge's alpha fades toward `maxDist` instead of
2209
+ * popping in at the cutoff. The second layer is a row of `orient: true`
2210
+ * strokes: the mark's +x axis turns to follow the entity's heading, so each
2211
+ * one reads as a wind tick rather than a stamp. Both are the difference
2212
+ * between "lines between dots" and a drawing.
2213
+ */
2214
+ declare const WEB_WORK_SPEC: SaverSpec;
2215
+
2216
+ /**
2217
+ * Shard Fall — tumbling shapes and a passing shadow.
2218
+ * Two features nothing else here uses. `rotate` is STATIC per-entity rotation
2219
+ * (a range seeded per entity), which is not the same as `spin: [0, 0]` — a
2220
+ * zero spin speed renders the seeded start angle as 0, so "tilted but not
2221
+ * turning" was previously inexpressible; here the shards are thrown at
2222
+ * `rotate: [-180, 180]` AND tumbling on their own `spin` range, so no two
2223
+ * facets agree on an angle. The shadow layer demonstrates `blend:
2224
+ * 'multiply'`: two large soft discs darkening a pale plate as they wander,
2225
+ * which is how the format does shadow and silhouette — `lighter` adds light,
2226
+ * `multiply` removes it, and neither needs a sprite that looks like darkness.
2227
+ * The polygons use `points` (unit coordinates scaled by `radius`), which are
2228
+ * paint, so two shards with the same point count will morph into each other.
2229
+ */
2230
+ declare const SHARD_FALL_SPEC: SaverSpec;
2231
+
2232
+ /**
2233
+ * Murmur — a flock with no birds in it.
2234
+ * Six hundred and twenty entities: two registers of streaks (260 each) on
2235
+ * harmonic `wander` with `coherence` near 1, so each layer folds and wheels as
2236
+ * one body, and a static grain layer behind them. Nothing here
2237
+ * interacts — no entity reads another's position — which is the trick the
2238
+ * format is built on: a shared harmonic set plus a wide speed range reads as
2239
+ * a murmuration while staying a pure function of (seed, t), so it replays
2240
+ * identically and seeks instantly. `density: 'dense'` is doing real work:
2241
+ * past 500 entities `adviseSpec` raises `dense-scene`, and the declaration
2242
+ * says the crowding is the subject rather than a mistake — while still opting
2243
+ * into `density-mismatch` if the scene ever measures as empty instead.
2244
+ */
2245
+ declare const MURMUR_SPEC: SaverSpec;
2246
+
2247
+ /**
2248
+ * Three Movements — one ground, three scenes, two kinds of transition.
2249
+ *
2250
+ * The sequence envelope, exercised end to end:
2251
+ *
2252
+ * - **bed.** A drifting field with a few motes, drawn under every segment on
2253
+ * the SEQUENCE clock — it does not reset at a boundary and a
2254
+ * `sequence.segment` steer cannot rewind it. That is the fix for the
2255
+ * boundary rewind every ambient sequence suffers: put the motion that must
2256
+ * be continuous in the bed, the slide content in the segments. (Segments
2257
+ * deliberately declare no `background`: over a bed it is never painted on
2258
+ * the web, and the validator warns `bed-hides-segment-background` — the
2259
+ * native tvOS player, which ignores the bed, will show them over black
2260
+ * until it learns the field. Prefer a bed the piece can survive without.)
2261
+ * - **morph + text crossfade.** Segments 1 → 2 are structural twins differing
2262
+ * only in colour and words, so the transition can MORPH: every numeric and
2263
+ * hex value glides over 2500 ms, and because the words differ and the
2264
+ * transition declares `text: 'crossfade'`, the outgoing caption fades under
2265
+ * the incoming one instead of switching whole-frame. Without the crossfade
2266
+ * declaration a morph steps strings on its first frame — it reads as a cut
2267
+ * and the validator says `morph-nothing-morphable`.
2268
+ * - **fade.** Segments 2 → 3 have nothing structurally in common, so a morph
2269
+ * is impossible and a `fade` is the honest transition: the outgoing scene
2270
+ * keeps animating on its own canvas for 3000 ms while the incoming one
2271
+ * fades up. The cost is two live segments during the window, which is why
2272
+ * the lowest capability tier degrades a fade to a cut
2273
+ * (`fade-degrades-on-low-tier` — informational, and this sequence keeps the
2274
+ * fade because the web and native players both honour it).
2275
+ * - **finish.** One grain-and-dither pass over the composed frame — bed,
2276
+ * segment and any fade together — the sequence-level screen no segment can
2277
+ * override.
2278
+ *
2279
+ * The final segment carries no duration, so it holds indefinitely; `loop` is
2280
+ * therefore false (`loop: true` is incompatible with a durationless segment).
2281
+ * `sync: 'mount'` is the default and the reason a joiner always sees movement
2282
+ * one: pre-roll semantics. An `epoch` sequence would instead start every
2283
+ * viewer at the room's shared clock — right for a wall of screens, wrong for
2284
+ * a piece that should start when you look at it.
2285
+ */
2286
+ declare const THREE_MOVEMENTS_SEQUENCE: IdleSequence;
2287
+
1764
2288
  /** Catalog entry for a bundled schema example. */
1765
2289
  interface SchemaExample {
1766
2290
  id: string;
@@ -1769,9 +2293,24 @@ interface SchemaExample {
1769
2293
  }
1770
2294
  /** Ordered catalog of bundled schema examples. Add new specs as standalone files, then register here. */
1771
2295
  declare const SCHEMA_EXAMPLES: readonly SchemaExample[];
2296
+ /** Catalog entry for a bundled idle-sequence example. */
2297
+ interface SequenceExample {
2298
+ id: string;
2299
+ label: string;
2300
+ sequence: IdleSequence;
2301
+ }
2302
+ /**
2303
+ * Ordered catalog of bundled idle-sequence examples.
2304
+ *
2305
+ * Deliberately separate from SCHEMA_EXAMPLES: that catalog is typed
2306
+ * `SaverSpec[]` and its consumers (manifests, batch compile, the MCP examples
2307
+ * resource) rely on that. A sequence is a different top-level format, so it
2308
+ * gets its own list rather than widening theirs.
2309
+ */
2310
+ declare const EXAMPLE_SEQUENCES: readonly SequenceExample[];
1772
2311
  /** All example specs in catalog order (tests, batch compile). */
1773
2312
  declare const EXAMPLE_SPECS: SaverSpec[];
1774
2313
  /** Lookup by saver id. */
1775
2314
  declare const EXAMPLE_BY_ID: Readonly<Record<string, SaverSpec>>;
1776
2315
 
1777
- export { AQUARIUM_SPEC, AURORA_SPEC, type BackgroundDrift, type BackgroundSpec, type BandSpec, COHESION_T, COMETS_SPEC, CONSTELLATION_SPEC, type CohesionRead, type CycleSpec, DASHBOARD_SPEC, type DominanceEntry, EXAMPLE_BY_ID, EXAMPLE_SPECS, type Entity, FACETS_SPEC, type GradientStop, HAIKU_SPEC, type IdleSequence, LANTERNS_SPEC, LIMITS, LOBBY_TALK_SPEC, type LayerCohesion, type LayerMotionStats, type LayerSnapshot, type LayerSpec, type LinkEdge, type LuminanceGrid, type LuminanceGridOptions, MATRIX_RAIN_SPEC, type MotionEase, type MotionSpec, NOSTALGHIA_CANDLE_SPEC, ORRERY_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, SNOWFALL_SPEC, type SaverSpec, type SceneDescription, type SceneDiff, type ScenePerception, type SceneSnapshot, type SchemaExample, type SequenceFramePerception, type SequenceMountContext, type SequenceSegment, type SequenceTransition, type SpecError, type SpecWarning, type SpriteSpec, type SteerDelta, type TextBlockAnchor, type TextBlockLine, type TextMetricsClass, type TextRevealInput, type TextRevealSpec, type TextRole, type TextSpriteInfo, type ValidationResult, WARP_MAX_SCALE, WARP_NEAR, WARP_TUNNEL_SPEC, type WanderOsc, type WarningBox, adviseSequence, adviseSpec, alphaAt, applyDeltasToSpec, assertValidSequence, assertValidSpec, bedRenderSeed, breakTextBlock, buildEntities, canMorph, cohesionOf, compileSaver, compileSequence, describeScene, diffScenes, dominanceRanking, easeSmooth, emitEnvelope, emitWindow, graphemeClusters, headingAt, layerCohesion, lerpSpec, lifeAlphaAt, linkEdges, linkPairs, luminanceGrid, manifestFor, morphChainRoot, morphNothingMorphable, motionStats, normalizeSeed, perceiveScene, perceiveSequenceFrame, positionAt, renderBrailleMap, renderDensityMap, resolveSegment, resolveSpecPath, revealState, rotationAt, seamCauseOf, seamsWorthWarning, segmentRenderSeed, segmentStart, sequenceSteerablePaths, sizeAt, spriteIndexAt, spriteVariants, steerablePaths, structuralSignature, textBlockAnchorOffset, textMetricsClassFor, textSprites, textStringsDiffer, textStringsOf, textWidthEm, validateSequence, validateSpec };
2316
+ 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, 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 LayerMotionStats, type LayerSnapshot, type LayerSpec, 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 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 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, cohesionOf, compileSaver, compileSequence, createFinishPass, createSceneCanvas, describeScene, diffScenes, dominanceRanking, easeSmooth, emitEnvelope, emitWindow, fieldAt, fieldOctaves, fieldRgb, fieldRgbAt, fieldSampleTime, finishStrength, grainOffset, grainTilePixels, graphemeClusters, hasHotSwapSequence, headingAt, hexToRgb255, latticeHash, layerCohesion, lerpSpec, lifeAlphaAt, linkEdges, linkPairs, luminanceGrid, manifestFor, morphChainRoot, morphNothingMorphable, motionStats, normalizeSeed, perceiveScene, perceiveSequenceFrame, pickScreenOp, positionAt, presentWithFinish, renderBrailleMap, renderDensityMap, resolveSegment, resolveSpecPath, revealState, rgb255Luma, rotationAt, seamCauseOf, seamsWorthWarning, segmentRenderSeed, segmentStart, sequenceSteerablePaths, sequenceSwapCompatible, sequenceWantsFinish, sizeAt, spriteIndexAt, spriteVariants, steerablePaths, structuralSignature, textBlockAnchorOffset, textMetricsClassFor, textSprites, textStringsDiffer, textStringsOf, textWidthEm, validateSequence, validateSpec };