@idle-screens/schema 2.2.0 → 2.4.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 +172 -36
- package/dist/index.d.ts +493 -26
- package/dist/index.js +1842 -351
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/saver-spec.schema.json +192 -8
package/dist/index.d.ts
CHANGED
|
@@ -22,8 +22,22 @@ interface SaverSpec {
|
|
|
22
22
|
background?: BackgroundSpec;
|
|
23
23
|
layers: LayerSpec[];
|
|
24
24
|
motionIntensity?: 'calm' | 'moderate' | 'energetic';
|
|
25
|
-
/**
|
|
26
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Frame persistence (0..1, capped at LIMITS.maxGhosting). Instead of fully clearing,
|
|
27
|
+
* each frame the background is painted at reduced alpha so moving entities leave
|
|
28
|
+
* decaying after-images (Mystify smears, long-exposure light). Inherently
|
|
29
|
+
* flash-safe: ghosting can only smooth luminance changes, never sharpen them.
|
|
30
|
+
* `renderFrame(t)` stays deterministic for seeks via a fixed-step warm-up replay.
|
|
31
|
+
*/
|
|
32
|
+
ghosting?: number;
|
|
33
|
+
/** Dimensional unit system. 'viewport' (default) = all sizes/speeds/distances are fractions of min(w,h). */
|
|
34
|
+
units?: 'viewport' | 'px';
|
|
35
|
+
/**
|
|
36
|
+
* Resolution at which this spec was designed. Density scaling kicks in above this
|
|
37
|
+
* threshold: entity counts scale by min(w,h) / referenceViewport. Default: 1080.
|
|
38
|
+
* Set to your design resolution so a spec authored at 4K doesn't over-densify.
|
|
39
|
+
*/
|
|
40
|
+
referenceViewport?: number;
|
|
27
41
|
}
|
|
28
42
|
type BackgroundSpec = {
|
|
29
43
|
type: 'solid';
|
|
@@ -42,7 +56,7 @@ interface GradientStop {
|
|
|
42
56
|
/** An optional solid band at the bottom (e.g. an aquarium seafloor). */
|
|
43
57
|
interface BandSpec {
|
|
44
58
|
color: string;
|
|
45
|
-
/** Band height
|
|
59
|
+
/** Band height — px or a viewport fraction, per the spec's `units`. */
|
|
46
60
|
height: number;
|
|
47
61
|
}
|
|
48
62
|
/** Slow oscillation of gradient stop positions — makes the background feel alive. */
|
|
@@ -64,8 +78,12 @@ interface LayerSpec {
|
|
|
64
78
|
flip?: boolean;
|
|
65
79
|
/** Per-entity opacity range, both 0..1. Default [1,1]. */
|
|
66
80
|
alpha?: [number, number];
|
|
67
|
-
/**
|
|
68
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Compositing for this layer. 'lighter' = additive (glow stacking); 'screen' =
|
|
83
|
+
* gentler additive for pale backgrounds; 'multiply' = darkening (shadows,
|
|
84
|
+
* silhouettes). Default source-over. None of these can strobe.
|
|
85
|
+
*/
|
|
86
|
+
blend?: 'lighter' | 'screen' | 'multiply';
|
|
69
87
|
/**
|
|
70
88
|
* Fractional spawn window (0 = left/top, 1 = right/bottom). Constrains where
|
|
71
89
|
* entities are PLACED, not where they may travel. Default full viewport.
|
|
@@ -79,16 +97,30 @@ interface LayerSpec {
|
|
|
79
97
|
* period has a floor (LIMITS.minPulsePeriod — max 2 Hz), and every entity gets
|
|
80
98
|
* its own seeded phase, so a layer can never strobe in unison. Effective alpha
|
|
81
99
|
* is clamped to 0..1.
|
|
100
|
+
*
|
|
101
|
+
* `wave` derives each entity's phase from its spawn position instead of a seeded
|
|
102
|
+
* draw, turning independent breathing into a traveling wave across the field
|
|
103
|
+
* (ripples, wind through grass). `wavelength` is dimensional (px or viewport
|
|
104
|
+
* units); `angle` is the propagation direction in degrees (0 = rightward,
|
|
105
|
+
* default 0). Still flash-safe: neighbors are out of phase by construction.
|
|
82
106
|
*/
|
|
83
107
|
pulse?: {
|
|
84
108
|
amp: number;
|
|
85
109
|
period: number;
|
|
110
|
+
wave?: {
|
|
111
|
+
wavelength: number;
|
|
112
|
+
angle?: number;
|
|
113
|
+
};
|
|
86
114
|
};
|
|
87
115
|
/**
|
|
88
116
|
* Per-entity rotation speed in degrees/sec (positive = clockwise).
|
|
89
117
|
* Each entity gets a seeded starting angle. Composes with any motion type.
|
|
118
|
+
* A scalar applies one speed to every entity; a `[min, max]` range gives each
|
|
119
|
+
* entity a seeded speed within the range (like `speed`/`alpha`) — confetti,
|
|
120
|
+
* tumbling debris, foliage. Existing scalar specs are unaffected (the range
|
|
121
|
+
* form draws one extra seeded value, only when present).
|
|
90
122
|
*/
|
|
91
|
-
spin?: number;
|
|
123
|
+
spin?: number | [number, number];
|
|
92
124
|
/**
|
|
93
125
|
* Sinusoidal size breathing, parallel to `pulse` for opacity. `amp` is a
|
|
94
126
|
* fraction of base size (0.3 = ±30 %). `period` in ms with the same
|
|
@@ -112,7 +144,13 @@ interface LayerSpec {
|
|
|
112
144
|
y: number;
|
|
113
145
|
};
|
|
114
146
|
/**
|
|
115
|
-
* Inter-entity links
|
|
147
|
+
* Inter-entity links. `mode` picks the wiring:
|
|
148
|
+
* - 'nearest' (default): each entity's k nearest neighbors within maxDist.
|
|
149
|
+
* - 'chain': entities connected in order (0-1-2-…) — Mystify polygons, string
|
|
150
|
+
* art. Ignores k/maxDist; `closed` joins the last entity back to the first.
|
|
151
|
+
* - 'random': a fixed golden-ratio-stride wiring (k partners per entity,
|
|
152
|
+
* deterministic, no RNG draws) — breaks the crystalline k-nearest look.
|
|
153
|
+
* `falloff` fades link alpha with distance/maxDist, killing pop-in at the cutoff.
|
|
116
154
|
* Capped at LIMITS.maxLinksK. Layer count must be <= LIMITS.maxLinkLayerCount when set.
|
|
117
155
|
*/
|
|
118
156
|
links?: {
|
|
@@ -121,6 +159,9 @@ interface LayerSpec {
|
|
|
121
159
|
color?: string;
|
|
122
160
|
alpha?: number;
|
|
123
161
|
width?: number;
|
|
162
|
+
mode?: 'nearest' | 'chain' | 'random';
|
|
163
|
+
falloff?: boolean;
|
|
164
|
+
closed?: boolean;
|
|
124
165
|
};
|
|
125
166
|
/**
|
|
126
167
|
* Afterglow trail behind moving entities. Samples past positions analytically
|
|
@@ -130,6 +171,31 @@ interface LayerSpec {
|
|
|
130
171
|
length: number;
|
|
131
172
|
fade?: number;
|
|
132
173
|
};
|
|
174
|
+
/**
|
|
175
|
+
* Grid placement instead of random scatter: entities fill cells row-major within
|
|
176
|
+
* `region`. `columns` defaults to an aspect-fit square-ish grid; `jitter` (0..1,
|
|
177
|
+
* scalar or per-axis) offsets each entity within its cell by a seeded fraction of
|
|
178
|
+
* the cell size — `{ y: 1 }` keeps columns crisp while scattering vertically
|
|
179
|
+
* (Matrix rain). Unlocks column effects, LED walls, mosaics, uniform dot fields.
|
|
180
|
+
*/
|
|
181
|
+
layout?: {
|
|
182
|
+
type: 'grid';
|
|
183
|
+
columns?: number;
|
|
184
|
+
jitter?: number | {
|
|
185
|
+
x?: number;
|
|
186
|
+
y?: number;
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Layer lifecycle for act structure — a pure function of t, no state. Alpha is 0
|
|
191
|
+
* before `enter` (ms), ramps up over `fade` ms (default 1000), holds at 1, then
|
|
192
|
+
* ramps down starting at `exit`. Entities are skipped entirely while at alpha 0.
|
|
193
|
+
*/
|
|
194
|
+
life?: {
|
|
195
|
+
enter?: number;
|
|
196
|
+
exit?: number;
|
|
197
|
+
fade?: number;
|
|
198
|
+
};
|
|
133
199
|
}
|
|
134
200
|
type SpriteSpec = {
|
|
135
201
|
kind: 'emoji';
|
|
@@ -145,13 +211,52 @@ type SpriteSpec = {
|
|
|
145
211
|
maxWidth?: number;
|
|
146
212
|
cycle?: CycleSpec;
|
|
147
213
|
}
|
|
148
|
-
/** `soft` renders a radial falloff (glow orb) instead of a hard disc.
|
|
214
|
+
/** `soft` renders a radial falloff (glow orb) instead of a hard disc.
|
|
215
|
+
* `colorWeights` (same length as `colors`) biases the seeded per-entity pick —
|
|
216
|
+
* "mostly cool tones, occasional ember" without duplicating entries. */
|
|
149
217
|
| {
|
|
150
218
|
kind: 'circle';
|
|
151
219
|
radius: [number, number];
|
|
152
220
|
color: string;
|
|
153
221
|
soft?: boolean;
|
|
154
222
|
colors?: string[];
|
|
223
|
+
colorWeights?: number[];
|
|
224
|
+
}
|
|
225
|
+
/** Unfilled circle (bubbles, portals, sonar pings). `width` = stroke width. */
|
|
226
|
+
| {
|
|
227
|
+
kind: 'ring';
|
|
228
|
+
radius: [number, number];
|
|
229
|
+
color: string;
|
|
230
|
+
width?: number;
|
|
231
|
+
colors?: string[];
|
|
232
|
+
colorWeights?: number[];
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* A line segment oriented along the entity's instantaneous heading (derived
|
|
236
|
+
* analytically from its motion) with a faded tail — rain that reads as rain,
|
|
237
|
+
* shooting stars, warp stars. `length` is the segment length range; `width` the
|
|
238
|
+
* stroke width.
|
|
239
|
+
*/
|
|
240
|
+
| {
|
|
241
|
+
kind: 'streak';
|
|
242
|
+
length: [number, number];
|
|
243
|
+
color: string;
|
|
244
|
+
width?: number;
|
|
245
|
+
colors?: string[];
|
|
246
|
+
colorWeights?: number[];
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Axis-aligned rectangle (rotates with `spin`). `width` is the horizontal size
|
|
250
|
+
* range; `aspect` the height/width ratio range (default [1,1] = squares).
|
|
251
|
+
* Mondrian blocks, confetti, city lights.
|
|
252
|
+
*/
|
|
253
|
+
| {
|
|
254
|
+
kind: 'rect';
|
|
255
|
+
width: [number, number];
|
|
256
|
+
aspect?: [number, number];
|
|
257
|
+
color: string;
|
|
258
|
+
colors?: string[];
|
|
259
|
+
colorWeights?: number[];
|
|
155
260
|
};
|
|
156
261
|
/** Rotate through sprite variants over time. Each entity offsets by its seeded phase. */
|
|
157
262
|
interface CycleSpec {
|
|
@@ -190,7 +295,10 @@ type MotionSpec =
|
|
|
190
295
|
/**
|
|
191
296
|
* Orbit around a center point. Each entity gets a seeded radius from `radius`
|
|
192
297
|
* and a seeded phase. `speed` is angular velocity in degrees/sec. `center` is
|
|
193
|
-
* fractional {x, y} (default {0.5, 0.5} = viewport center)
|
|
298
|
+
* fractional {x, y} (default {0.5, 0.5} = viewport center) — or
|
|
299
|
+
* `{ layer: key }` to orbit a single-entity parent layer (moons around a
|
|
300
|
+
* wandering planet). Strictly one level deep: the parent may not itself
|
|
301
|
+
* orbit a layer, and must have `count: 1`.
|
|
194
302
|
*/
|
|
195
303
|
| {
|
|
196
304
|
type: 'orbit';
|
|
@@ -199,7 +307,59 @@ type MotionSpec =
|
|
|
199
307
|
center?: {
|
|
200
308
|
x: number;
|
|
201
309
|
y: number;
|
|
310
|
+
} | {
|
|
311
|
+
layer: string;
|
|
202
312
|
};
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Organic harmonic drift — the analytic flow field. Each entity moves at a base
|
|
316
|
+
* velocity from `speed` plus 3 seeded sine octaves per axis, producing flowing
|
|
317
|
+
* curved paths (Flurry streams, aurora, jellyfish) with zero simulation state.
|
|
318
|
+
* `angle` fixes the base heading in degrees (omit for a seeded heading per
|
|
319
|
+
* entity); `meander` scales the harmonic amplitude (dimensional, default 0.05
|
|
320
|
+
* viewport units); `coherence` (0..1) blends every entity's harmonics toward a
|
|
321
|
+
* shared layer-level set — at 1 the field undulates in unison (fake flocking).
|
|
322
|
+
*/
|
|
323
|
+
| {
|
|
324
|
+
type: 'wander';
|
|
325
|
+
speed: [number, number];
|
|
326
|
+
angle?: number;
|
|
327
|
+
meander?: number;
|
|
328
|
+
coherence?: number;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Perspective starfield: entities live on a depth axis and stream toward the
|
|
332
|
+
* viewer, projected as `screen = center + offset / z`. Size and alpha scale
|
|
333
|
+
* with 1/z (small and faint at the far plane, large and fast up close), and z
|
|
334
|
+
* wraps — the honest warp tunnel. `speed` is in depth-units/sec
|
|
335
|
+
* (1 = full near-to-far span per second, capped at LIMITS.maxWarpSpeed).
|
|
336
|
+
*/
|
|
337
|
+
| {
|
|
338
|
+
type: 'warp';
|
|
339
|
+
speed: [number, number];
|
|
340
|
+
center?: {
|
|
341
|
+
x: number;
|
|
342
|
+
y: number;
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Choreographed spline motion: entities traverse `points` (fractional {x,y})
|
|
347
|
+
* over `duration` ms. `curve: 'smooth'` (default) uses Catmull-Rom through the
|
|
348
|
+
* points; 'linear' uses straight segments. `closed` (default true) loops the
|
|
349
|
+
* path; open paths ping-pong. Each entity gets a seeded phase offset along the
|
|
350
|
+
* path, and `scatter` (dimensional) adds a seeded per-entity offset so shared
|
|
351
|
+
* paths don't stack. Figure-eights, sweeping arcs, patrol routes.
|
|
352
|
+
*/
|
|
353
|
+
| {
|
|
354
|
+
type: 'path';
|
|
355
|
+
points: Array<{
|
|
356
|
+
x: number;
|
|
357
|
+
y: number;
|
|
358
|
+
}>;
|
|
359
|
+
duration: number;
|
|
360
|
+
curve?: 'linear' | 'smooth';
|
|
361
|
+
closed?: boolean;
|
|
362
|
+
scatter?: number;
|
|
203
363
|
};
|
|
204
364
|
/** A validation problem, pointing at a JSON path within the spec. */
|
|
205
365
|
interface SpecError {
|
|
@@ -237,6 +397,14 @@ declare const LIMITS: {
|
|
|
237
397
|
readonly maxTrailSamples: 24;
|
|
238
398
|
readonly minDriftPeriod: 10000;
|
|
239
399
|
readonly maxDriftAmount: 0.3;
|
|
400
|
+
readonly maxGhosting: 0.95;
|
|
401
|
+
readonly maxGhostReplayFrames: 120;
|
|
402
|
+
readonly maxMeander: 500;
|
|
403
|
+
readonly maxWarpSpeed: 1.5;
|
|
404
|
+
readonly minPathPoints: 2;
|
|
405
|
+
readonly maxPathPoints: 24;
|
|
406
|
+
readonly minPathDuration: 2000;
|
|
407
|
+
readonly maxGridColumns: 100;
|
|
240
408
|
};
|
|
241
409
|
|
|
242
410
|
/**
|
|
@@ -250,8 +418,21 @@ declare function validateSpec(spec: unknown): ValidationResult;
|
|
|
250
418
|
/** Narrowing helper: validate + cast. Throws with a joined message when invalid. */
|
|
251
419
|
declare function assertValidSpec(spec: unknown): SaverSpec;
|
|
252
420
|
|
|
421
|
+
/** Near plane for warp motion. z lives in [WARP_NEAR, 1]; screen scale is 1/z. */
|
|
422
|
+
declare const WARP_NEAR = 0.08;
|
|
423
|
+
/** One axis-pair of seeded harmonic octaves for wander motion (amps px, freqs rad/ms). */
|
|
424
|
+
interface WanderOsc {
|
|
425
|
+
ax: number[];
|
|
426
|
+
fx: number[];
|
|
427
|
+
phx: number[];
|
|
428
|
+
ay: number[];
|
|
429
|
+
fy: number[];
|
|
430
|
+
phy: number[];
|
|
431
|
+
}
|
|
253
432
|
/** A seeded entity: initial state + resolved velocity. `positionAt` is a pure function
|
|
254
|
-
* of this + time, so the rAF loop and renderFrame(t) produce identical results.
|
|
433
|
+
* of this + time, so the rAF loop and renderFrame(t) produce identical results.
|
|
434
|
+
* IMPORTANT (determinism/compat): fields for newer features are OPTIONAL and only
|
|
435
|
+
* set when the feature is declared, so older specs snapshot byte-identically. */
|
|
255
436
|
interface Entity {
|
|
256
437
|
x0: number;
|
|
257
438
|
y0: number;
|
|
@@ -261,7 +442,7 @@ interface Entity {
|
|
|
261
442
|
vx: number;
|
|
262
443
|
vy: number;
|
|
263
444
|
bob: number;
|
|
264
|
-
motion: 'drift' | 'rise' | 'bounce' | 'static' | 'orbit';
|
|
445
|
+
motion: 'drift' | 'rise' | 'bounce' | 'static' | 'orbit' | 'wander' | 'warp' | 'path';
|
|
265
446
|
headingLeft: boolean;
|
|
266
447
|
alpha: number;
|
|
267
448
|
pulseAmp: number;
|
|
@@ -277,6 +458,39 @@ interface Entity {
|
|
|
277
458
|
orbitCy: number;
|
|
278
459
|
colorIndex: number;
|
|
279
460
|
cyclePeriod: number;
|
|
461
|
+
/** Rect sprite second dimension (height px). Only set for rect sprites. */
|
|
462
|
+
size2?: number;
|
|
463
|
+
/** Orbit parent layer key (motion.center = { layer }). Center resolved at render time. */
|
|
464
|
+
orbitParent?: string;
|
|
465
|
+
/** Harmonic drift params. Only set for wander motion. */
|
|
466
|
+
wander?: {
|
|
467
|
+
own: WanderOsc;
|
|
468
|
+
shared: WanderOsc;
|
|
469
|
+
coherence: number;
|
|
470
|
+
margin: number;
|
|
471
|
+
};
|
|
472
|
+
/** Depth-axis params. Only set for warp motion. */
|
|
473
|
+
warp?: {
|
|
474
|
+
ux: number;
|
|
475
|
+
uy: number;
|
|
476
|
+
z0: number;
|
|
477
|
+
vz: number;
|
|
478
|
+
cx: number;
|
|
479
|
+
cy: number;
|
|
480
|
+
};
|
|
481
|
+
/** Spline params (pts in px, shared per layer). Only set for path motion. */
|
|
482
|
+
path?: {
|
|
483
|
+
pts: Array<{
|
|
484
|
+
x: number;
|
|
485
|
+
y: number;
|
|
486
|
+
}>;
|
|
487
|
+
duration: number;
|
|
488
|
+
closed: boolean;
|
|
489
|
+
smooth: boolean;
|
|
490
|
+
phase: number;
|
|
491
|
+
offX: number;
|
|
492
|
+
offY: number;
|
|
493
|
+
};
|
|
280
494
|
}
|
|
281
495
|
interface Placed {
|
|
282
496
|
x: number;
|
|
@@ -294,10 +508,43 @@ declare function alphaAt(e: Entity, t: number): number;
|
|
|
294
508
|
declare function sizeAt(e: Entity, t: number): number;
|
|
295
509
|
/** Analytic rotation angle at time `t` (ms) in radians. Pure. */
|
|
296
510
|
declare function rotationAt(e: Entity, t: number): number;
|
|
511
|
+
/** Layer lifecycle alpha multiplier at time `t` (ms). Pure. 1 when `life` is unset. */
|
|
512
|
+
declare function lifeAlphaAt(life: {
|
|
513
|
+
enter?: number;
|
|
514
|
+
exit?: number;
|
|
515
|
+
fade?: number;
|
|
516
|
+
} | undefined, t: number): number;
|
|
297
517
|
/** Analytic position of an entity at logical time `t` (ms). Pure & deterministic. */
|
|
298
518
|
declare function positionAt(e: Entity, t: number, w: number, h: number): Placed;
|
|
519
|
+
/**
|
|
520
|
+
* Analytic heading (radians) at time `t`, from a finite difference of positionAt.
|
|
521
|
+
* Used to orient streak sprites. Returns null across a wrap seam (position jump
|
|
522
|
+
* larger than half the viewport) — callers should skip orientation that frame.
|
|
523
|
+
*/
|
|
524
|
+
declare function headingAt(e: Entity, t: number, w: number, h: number): number | null;
|
|
299
525
|
/** Time-varying sprite index for text/emoji cycling. Returns static index when cyclePeriod is 0. */
|
|
300
526
|
declare function spriteIndexAt(e: Entity, t: number, variants: number): number;
|
|
527
|
+
/** An inter-entity link edge with its (wrap-aware) distance, for falloff alpha. */
|
|
528
|
+
interface LinkEdge {
|
|
529
|
+
i: number;
|
|
530
|
+
j: number;
|
|
531
|
+
dist: number;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Edge list for a layer's `links`, dispatching on mode:
|
|
535
|
+
* 'nearest' (default) k-nearest within maxDist; 'chain' sequential order
|
|
536
|
+
* (Mystify — ignores k/maxDist, `closed` joins last to first); 'random' a fixed
|
|
537
|
+
* golden-ratio-stride wiring filtered by maxDist (deterministic, no RNG draws).
|
|
538
|
+
*/
|
|
539
|
+
declare function linkEdges(links: {
|
|
540
|
+
k: number;
|
|
541
|
+
maxDist: number;
|
|
542
|
+
mode?: 'nearest' | 'chain' | 'random';
|
|
543
|
+
closed?: boolean;
|
|
544
|
+
}, positions: Array<{
|
|
545
|
+
x: number;
|
|
546
|
+
y: number;
|
|
547
|
+
}>, maxDistPx: number, doWrap: boolean, w: number, h: number): LinkEdge[];
|
|
301
548
|
/** K-nearest-neighbor edge list. Deterministic tie-break: (dist, index).
|
|
302
549
|
* When `doWrap` is true, uses toroidal distance so links don't streak across wrap seams. */
|
|
303
550
|
declare function linkPairs(positions: Array<{
|
|
@@ -416,6 +663,187 @@ declare function describeScene(spec: SaverSpec, options?: {
|
|
|
416
663
|
times?: number[];
|
|
417
664
|
}): SceneDescription;
|
|
418
665
|
|
|
666
|
+
interface PerceiveOptions {
|
|
667
|
+
viewport?: {
|
|
668
|
+
width: number;
|
|
669
|
+
height: number;
|
|
670
|
+
};
|
|
671
|
+
seed?: number;
|
|
672
|
+
/** Sample time in ms. Default 5000 (past all typical life.enter staging). */
|
|
673
|
+
t?: number;
|
|
674
|
+
}
|
|
675
|
+
interface LuminanceGrid {
|
|
676
|
+
cols: number;
|
|
677
|
+
rows: number;
|
|
678
|
+
/** Row-major luminance 0..1 including the background. */
|
|
679
|
+
cells: number[];
|
|
680
|
+
/** Per-row background luminance (what an empty scene would be). */
|
|
681
|
+
background: number[];
|
|
682
|
+
meanLuminance: number;
|
|
683
|
+
/** Fraction of cells deviating perceptibly (> 0.03) from the background. */
|
|
684
|
+
coverage: number;
|
|
685
|
+
/** Deviation-weighted centre of visual mass as viewport fractions, or null for a flat scene. */
|
|
686
|
+
centroid: {
|
|
687
|
+
x: number;
|
|
688
|
+
y: number;
|
|
689
|
+
} | null;
|
|
690
|
+
/** Mean deviation-from-background per row/column — 1D transects of the composition. */
|
|
691
|
+
rowProfile: number[];
|
|
692
|
+
colProfile: number[];
|
|
693
|
+
}
|
|
694
|
+
interface LuminanceGridOptions extends PerceiveOptions {
|
|
695
|
+
/** Grid resolution. Defaults 80×48 (→ a 40×12 braille map). */
|
|
696
|
+
cols?: number;
|
|
697
|
+
rows?: number;
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Sample the spec into a coarse luminance image — analytically, no renderer.
|
|
701
|
+
* Approximations (documented, deliberate): trails and ghosting are ignored,
|
|
702
|
+
* soft circles use a linear falloff, background drift is sampled at its rest
|
|
703
|
+
* position. Good enough to perceive composition, focus, and balance.
|
|
704
|
+
*/
|
|
705
|
+
declare function luminanceGrid(spec: SaverSpec, opts?: LuminanceGridOptions): LuminanceGrid;
|
|
706
|
+
/**
|
|
707
|
+
* Encode a luminance grid as a braille "image": each output character is a 2×4
|
|
708
|
+
* pixel cell, ordered-dithered so mid-tones render as dot density. A grid of
|
|
709
|
+
* 80×48 becomes 12 lines of 40 chars — a picture a text model can read whole.
|
|
710
|
+
*/
|
|
711
|
+
declare function renderBrailleMap(grid: LuminanceGrid): string;
|
|
712
|
+
/**
|
|
713
|
+
* Encode a luminance grid as a one-char-per-cell ASCII density map. Unlike the
|
|
714
|
+
* braille map (compact, 2×4 dots/char) this is 1:1 with the grid, so pairing it
|
|
715
|
+
* with a larger `cols`/`rows` gives a non-vision agent a genuinely higher-detail
|
|
716
|
+
* read: 120×48 resolves concentric rings and text blocks braille smears into a
|
|
717
|
+
* blob. Shares the braille map's auto-exposure so both views agree.
|
|
718
|
+
*/
|
|
719
|
+
declare function renderDensityMap(grid: LuminanceGrid): string;
|
|
720
|
+
interface TextSpriteInfo {
|
|
721
|
+
layerIndex: number;
|
|
722
|
+
key: string | undefined;
|
|
723
|
+
/** The literal strings this layer draws (glyphs are invisible in the maps). */
|
|
724
|
+
strings: string[];
|
|
725
|
+
/** How many entities carry text. */
|
|
726
|
+
count: number;
|
|
727
|
+
/** Representative rendered glyph height in px at the sample viewport. */
|
|
728
|
+
sizePx: number;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* List every text layer's actual strings and rendered size. No grid resolution
|
|
732
|
+
* renders glyphs legibly, so a non-vision agent otherwise can't tell whether the
|
|
733
|
+
* words it wrote are on screen, how big, or whether they'll overlap. This closes
|
|
734
|
+
* that blind spot analytically.
|
|
735
|
+
*/
|
|
736
|
+
declare function textSprites(spec: SaverSpec, opts?: PerceiveOptions): TextSpriteInfo[];
|
|
737
|
+
interface DominanceEntry {
|
|
738
|
+
rank: number;
|
|
739
|
+
layerIndex: number;
|
|
740
|
+
key: string | undefined;
|
|
741
|
+
/** Normalized share of total visual weight, 0..1 (all entries sum to 1). */
|
|
742
|
+
share: number;
|
|
743
|
+
entityCount: number;
|
|
744
|
+
meanLuma: number;
|
|
745
|
+
factors: {
|
|
746
|
+
area: number;
|
|
747
|
+
contrast: number;
|
|
748
|
+
blendBoost: number;
|
|
749
|
+
motionBoost: number;
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Rank layers by estimated visual weight — where the eye goes. Weight is
|
|
754
|
+
* on-screen area × alpha, scaled by contrast against the background, an
|
|
755
|
+
* additive-glow boost, and a motion boost. Link lines count as area.
|
|
756
|
+
*/
|
|
757
|
+
declare function dominanceRanking(spec: SaverSpec, opts?: PerceiveOptions): DominanceEntry[];
|
|
758
|
+
interface LayerMotionStats {
|
|
759
|
+
layerIndex: number;
|
|
760
|
+
key: string | undefined;
|
|
761
|
+
/** Mean / max on-screen speed in px per second (wrap jumps excluded). */
|
|
762
|
+
meanSpeed: number;
|
|
763
|
+
maxSpeed: number;
|
|
764
|
+
moving: boolean;
|
|
765
|
+
}
|
|
766
|
+
/** Per-layer displacement between t and t+dt — choreography as numbers. */
|
|
767
|
+
declare function motionStats(spec: SaverSpec, opts?: PerceiveOptions & {
|
|
768
|
+
dt?: number;
|
|
769
|
+
}): LayerMotionStats[];
|
|
770
|
+
declare const REGION_NAMES: readonly ["top-left", "top-center", "top-right", "mid-left", "center", "mid-right", "bottom-left", "bottom-center", "bottom-right"];
|
|
771
|
+
interface SceneDiff {
|
|
772
|
+
t: number;
|
|
773
|
+
coverage: {
|
|
774
|
+
a: number;
|
|
775
|
+
b: number;
|
|
776
|
+
delta: number;
|
|
777
|
+
};
|
|
778
|
+
meanLuminance: {
|
|
779
|
+
a: number;
|
|
780
|
+
b: number;
|
|
781
|
+
delta: number;
|
|
782
|
+
};
|
|
783
|
+
/** Shift of the visual-mass centroid from A to B, in viewport fractions. */
|
|
784
|
+
balanceShift: {
|
|
785
|
+
dx: number;
|
|
786
|
+
dy: number;
|
|
787
|
+
} | null;
|
|
788
|
+
/** Mean deviation-from-background per third-by-third region, with deltas. */
|
|
789
|
+
regions: Array<{
|
|
790
|
+
region: (typeof REGION_NAMES)[number];
|
|
791
|
+
a: number;
|
|
792
|
+
b: number;
|
|
793
|
+
delta: number;
|
|
794
|
+
}>;
|
|
795
|
+
/** Dominance changes, matched by layer key (falls back to index). Sorted by |share delta|. */
|
|
796
|
+
dominance: Array<{
|
|
797
|
+
key: string;
|
|
798
|
+
rankA: number | null;
|
|
799
|
+
rankB: number | null;
|
|
800
|
+
shareA: number;
|
|
801
|
+
shareB: number;
|
|
802
|
+
}>;
|
|
803
|
+
advisories: {
|
|
804
|
+
added: string[];
|
|
805
|
+
removed: string[];
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Compare two specs at the same time/viewport — relative perception. A text
|
|
810
|
+
* model is far better at "is B better than A" than "is A good"; this returns
|
|
811
|
+
* the deltas that question needs: coverage, balance, per-region visual mass,
|
|
812
|
+
* dominance-rank movement, and advisory changes.
|
|
813
|
+
*/
|
|
814
|
+
declare function diffScenes(a: SaverSpec, b: SaverSpec, opts?: LuminanceGridOptions): SceneDiff;
|
|
815
|
+
interface ScenePerception {
|
|
816
|
+
t: number;
|
|
817
|
+
/** The picture: a braille luminance map (each char = 2×4 pixels), compact. */
|
|
818
|
+
braille: string;
|
|
819
|
+
/**
|
|
820
|
+
* A one-char-per-cell ASCII density map at the grid resolution — higher
|
|
821
|
+
* fidelity than braille for structure (rings, grids, text blocks). Pair with
|
|
822
|
+
* a larger `cols`/`rows` for a sharper read.
|
|
823
|
+
*/
|
|
824
|
+
density: string;
|
|
825
|
+
coverage: number;
|
|
826
|
+
meanLuminance: number;
|
|
827
|
+
centroid: {
|
|
828
|
+
x: number;
|
|
829
|
+
y: number;
|
|
830
|
+
} | null;
|
|
831
|
+
rowProfile: number[];
|
|
832
|
+
colProfile: number[];
|
|
833
|
+
dominance: DominanceEntry[];
|
|
834
|
+
motion: LayerMotionStats[];
|
|
835
|
+
/** Literal strings + sizes of any text layers (glyphs don't show in the maps). */
|
|
836
|
+
text: TextSpriteInfo[];
|
|
837
|
+
advisories: ReturnType<typeof adviseSpec>;
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Everything a non-vision agent needs to "see" a spec in one deterministic
|
|
841
|
+
* call: the braille picture, a higher-res density map, composition transects,
|
|
842
|
+
* dominance ranking, motion stats, the literal text on screen, and advisories.
|
|
843
|
+
* Intended as the payload behind an MCP previewScene.
|
|
844
|
+
*/
|
|
845
|
+
declare function perceiveScene(spec: SaverSpec, opts?: LuminanceGridOptions): ScenePerception;
|
|
846
|
+
|
|
419
847
|
/**
|
|
420
848
|
* Reproduces the hand-coded Fish Aquarium saver as pure DATA — the discriminating test
|
|
421
849
|
* that the schema is actually expressive: two layers (fish drifting both ways at varied
|
|
@@ -433,7 +861,7 @@ declare const COMETS_SPEC: SaverSpec;
|
|
|
433
861
|
/**
|
|
434
862
|
* Constellation: drifting stars connected by faint k-nearest links. Exercises
|
|
435
863
|
* colors[] (multi-hued stars), links (k-nearest neighbor lines), and cycle
|
|
436
|
-
* (emoji asterisms rotate glyphs). Stars are soft circles in
|
|
864
|
+
* (emoji asterisms rotate glyphs). Stars are soft circles in viewport units.
|
|
437
865
|
*/
|
|
438
866
|
declare const CONSTELLATION_SPEC: SaverSpec;
|
|
439
867
|
|
|
@@ -455,23 +883,24 @@ declare const SNOWFALL_SPEC: SaverSpec;
|
|
|
455
883
|
declare const LANTERNS_SPEC: SaverSpec;
|
|
456
884
|
|
|
457
885
|
/**
|
|
458
|
-
* Sakura Drift: cherry-blossom petals carried on a spring twilight breeze.
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
*
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
* every moving layer; petals differ from snowfall by glyph, colour, palette and the
|
|
466
|
-
* diagonal breeze, and from lanterns by falling rather than rising.
|
|
886
|
+
* Sakura Drift: cherry-blossom petals carried on a spring twilight breeze.
|
|
887
|
+
* Glow-up pass (v1-ceiling features): petals ride `wander` harmonics with shared
|
|
888
|
+
* `coherence` so gusts move the whole fall together, `spin` tumbles the glyphs,
|
|
889
|
+
* faint `streak` wind-lines cross the frame, the blossom-glow backdrop shimmers
|
|
890
|
+
* as a traveling `pulse.wave`, and dusk fireflies wander the lower field. The
|
|
891
|
+
* ground is a warm plum earth blended out of the sunset (no more abrupt dark
|
|
892
|
+
* band), with slow background `drift` keeping the sky alive.
|
|
467
893
|
*/
|
|
468
894
|
declare const SAKURA_SPEC: SaverSpec;
|
|
469
895
|
|
|
470
896
|
/**
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
897
|
+
* Control Center: a general-purpose ambient status HUD (home / ops / anything —
|
|
898
|
+
* not dev-specific). Exercises the static/HUD subset at scale plus the
|
|
899
|
+
* v1-ceiling features: `life` staggers the panels in like a boot sequence, a
|
|
900
|
+
* grid-`layout` LED wall breathes behind everything via a traveling
|
|
901
|
+
* `pulse.wave`, `ring` sprites form the central gauge, and a rect "equalizer"
|
|
902
|
+
* animates with a wave-phased pulse. Every panel is keyed so an agent can
|
|
903
|
+
* steer any reading live (`power-value`, `gauge-value`, `feed-1`, ...).
|
|
475
904
|
*/
|
|
476
905
|
declare const DASHBOARD_SPEC: SaverSpec;
|
|
477
906
|
|
|
@@ -483,6 +912,44 @@ declare const DASHBOARD_SPEC: SaverSpec;
|
|
|
483
912
|
*/
|
|
484
913
|
declare const ORRERY_SPEC: SaverSpec;
|
|
485
914
|
|
|
915
|
+
/**
|
|
916
|
+
* Aurora Borealis: the "fake fluid" recipe — showcases `wander` motion (harmonic
|
|
917
|
+
* drift) with high `coherence` so the curtain undulates as one body, `pulse.wave`
|
|
918
|
+
* (a traveling shimmer across the star field), weighted palettes, additive blend,
|
|
919
|
+
* and `ghosting` smearing the soft glows into continuous curtains of light.
|
|
920
|
+
*/
|
|
921
|
+
declare const AURORA_SPEC: SaverSpec;
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Matrix Rain: showcases `layout: grid` with per-axis jitter ({ y: 1 } keeps the
|
|
925
|
+
* columns crisp while de-syncing them vertically), glyph `cycle`, and `ghosting`
|
|
926
|
+
* doing the classic fading-trail work. Two depths of rain plus rare bright heads.
|
|
927
|
+
*/
|
|
928
|
+
declare const MATRIX_RAIN_SPEC: SaverSpec;
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Polygons: the classic Mystify-style bouncing-polygon saver, expressed as chain links + heavy
|
|
932
|
+
* ghosting. Showcases `links.mode: 'chain'` with `closed` (vertices wired in order
|
|
933
|
+
* into a polygon), `bounce` motion, and high `ghosting` (the decaying after-images
|
|
934
|
+
* ARE the effect — no per-entity trail sampling involved).
|
|
935
|
+
*/
|
|
936
|
+
declare const POLYGONS_SPEC: SaverSpec;
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Night Procession: a choreographed scene. Showcases `path` motion (a smooth
|
|
940
|
+
* figure-eight lantern route with scatter), layer-parented `orbit` (moths circling
|
|
941
|
+
* a wandering lantern-bearer), `life` staging (stars first, the procession enters
|
|
942
|
+
* at 4 s, moths at 7 s), plus `ring` and `rect` sprites.
|
|
943
|
+
*/
|
|
944
|
+
declare const PROCESSION_SPEC: SaverSpec;
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* Warp Tunnel: showcases `warp` motion (perspective depth — size/alpha/speed scale
|
|
948
|
+
* with 1/z), `streak` sprites oriented along the analytic heading, `colorWeights`
|
|
949
|
+
* (mostly white stars, occasional blue/amber), and light `ghosting` for smear.
|
|
950
|
+
*/
|
|
951
|
+
declare const WARP_TUNNEL_SPEC: SaverSpec;
|
|
952
|
+
|
|
486
953
|
/** Catalog entry for a bundled schema example. */
|
|
487
954
|
interface SchemaExample {
|
|
488
955
|
id: string;
|
|
@@ -496,4 +963,4 @@ declare const EXAMPLE_SPECS: SaverSpec[];
|
|
|
496
963
|
/** Lookup by saver id. */
|
|
497
964
|
declare const EXAMPLE_BY_ID: Readonly<Record<string, SaverSpec>>;
|
|
498
965
|
|
|
499
|
-
export { AQUARIUM_SPEC, type BackgroundDrift, type BackgroundSpec, type BandSpec, COMETS_SPEC, CONSTELLATION_SPEC, type CycleSpec, DASHBOARD_SPEC, EXAMPLE_BY_ID, EXAMPLE_SPECS, type Entity, type GradientStop, LANTERNS_SPEC, LIMITS, type LayerSnapshot, type LayerSpec, type MotionSpec, ORRERY_SPEC, type Placed, RAIN_SPEC, SAKURA_SPEC, SCHEMA_EXAMPLES, SCHEMA_VERSION, SNOWFALL_SPEC, type SaverSpec, type SceneDescription, type SceneSnapshot, type SchemaExample, type SpecError, type SpecWarning, type SpriteSpec, type SteerDelta, type ValidationResult, adviseSpec, alphaAt, applyDeltasToSpec, assertValidSpec, buildEntities, compileSaver, describeScene, easeSmooth, lerpSpec, linkPairs, manifestFor, positionAt, resolveSpecPath, rotationAt, sizeAt, spriteIndexAt, spriteVariants, steerablePaths, structuralSignature, validateSpec };
|
|
966
|
+
export { AQUARIUM_SPEC, AURORA_SPEC, type BackgroundDrift, type BackgroundSpec, type BandSpec, COMETS_SPEC, CONSTELLATION_SPEC, type CycleSpec, DASHBOARD_SPEC, type DominanceEntry, EXAMPLE_BY_ID, EXAMPLE_SPECS, type Entity, type GradientStop, LANTERNS_SPEC, LIMITS, type LayerMotionStats, type LayerSnapshot, type LayerSpec, type LinkEdge, type LuminanceGrid, type LuminanceGridOptions, MATRIX_RAIN_SPEC, type MotionSpec, ORRERY_SPEC, POLYGONS_SPEC, PROCESSION_SPEC, type PerceiveOptions, type Placed, RAIN_SPEC, SAKURA_SPEC, SCHEMA_EXAMPLES, SCHEMA_VERSION, SNOWFALL_SPEC, type SaverSpec, type SceneDescription, type SceneDiff, type ScenePerception, type SceneSnapshot, type SchemaExample, type SpecError, type SpecWarning, type SpriteSpec, type SteerDelta, type TextSpriteInfo, type ValidationResult, WARP_NEAR, WARP_TUNNEL_SPEC, type WanderOsc, adviseSpec, alphaAt, applyDeltasToSpec, assertValidSpec, buildEntities, compileSaver, describeScene, diffScenes, dominanceRanking, easeSmooth, headingAt, lerpSpec, lifeAlphaAt, linkEdges, linkPairs, luminanceGrid, manifestFor, motionStats, perceiveScene, positionAt, renderBrailleMap, renderDensityMap, resolveSpecPath, rotationAt, sizeAt, spriteIndexAt, spriteVariants, steerablePaths, structuralSignature, textSprites, validateSpec };
|