@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/FORMAT.md +162 -8
- package/dist/index.d.ts +553 -14
- package/dist/index.js +1423 -38
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/saver-spec.schema.json +121 -0
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
|
-
/**
|
|
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
|
|
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
|
-
*
|
|
1037
|
-
*
|
|
1038
|
-
*
|
|
1039
|
-
*
|
|
1040
|
-
*
|
|
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
|
-
|
|
1177
|
+
hotSwapSequence(next: IdleSequence): boolean;
|
|
1043
1178
|
}
|
|
1044
|
-
|
|
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 };
|