@idle-screens/schema 2.0.0 → 2.2.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
@@ -22,6 +22,8 @@ interface SaverSpec {
22
22
  background?: BackgroundSpec;
23
23
  layers: LayerSpec[];
24
24
  motionIntensity?: 'calm' | 'moderate' | 'energetic';
25
+ /** Dimensional unit system. 'viewport' = all sizes/speeds/distances are fractions of min(w,h). */
26
+ units?: 'px' | 'viewport';
25
27
  }
26
28
  type BackgroundSpec = {
27
29
  type: 'solid';
@@ -30,6 +32,7 @@ type BackgroundSpec = {
30
32
  type: 'gradient';
31
33
  stops: GradientStop[];
32
34
  band?: BandSpec;
35
+ drift?: BackgroundDrift;
33
36
  };
34
37
  /** A vertical gradient stop (`at` 0 = top, 1 = bottom). */
35
38
  interface GradientStop {
@@ -42,6 +45,13 @@ interface BandSpec {
42
45
  /** Band height in px. */
43
46
  height: number;
44
47
  }
48
+ /** Slow oscillation of gradient stop positions — makes the background feel alive. */
49
+ interface BackgroundDrift {
50
+ /** Full cycle period in ms. Floor: LIMITS.minDriftPeriod (10 s). */
51
+ period: number;
52
+ /** How far stops shift (fraction of the 0..1 range). Default 0.15, max 0.3. */
53
+ amount?: number;
54
+ }
45
55
  interface LayerSpec {
46
56
  count: number;
47
57
  sprite: SpriteSpec;
@@ -74,6 +84,20 @@ interface LayerSpec {
74
84
  amp: number;
75
85
  period: number;
76
86
  };
87
+ /**
88
+ * Per-entity rotation speed in degrees/sec (positive = clockwise).
89
+ * Each entity gets a seeded starting angle. Composes with any motion type.
90
+ */
91
+ spin?: number;
92
+ /**
93
+ * Sinusoidal size breathing, parallel to `pulse` for opacity. `amp` is a
94
+ * fraction of base size (0.3 = ±30 %). `period` in ms with the same
95
+ * flash-safety floor as pulse. Per-entity seeded phase.
96
+ */
97
+ grow?: {
98
+ amp: number;
99
+ period: number;
100
+ };
77
101
  /**
78
102
  * Addressable name for this layer. Enables `setParam` to use `key.field` paths
79
103
  * instead of `layers.N.field` indices. Also makes specs self-documenting.
@@ -87,10 +111,30 @@ interface LayerSpec {
87
111
  x: number;
88
112
  y: number;
89
113
  };
114
+ /**
115
+ * Inter-entity links: draw lines to each entity's k nearest neighbors within maxDist.
116
+ * Capped at LIMITS.maxLinksK. Layer count must be <= LIMITS.maxLinkLayerCount when set.
117
+ */
118
+ links?: {
119
+ k: number;
120
+ maxDist: number;
121
+ color?: string;
122
+ alpha?: number;
123
+ width?: number;
124
+ };
125
+ /**
126
+ * Afterglow trail behind moving entities. Samples past positions analytically
127
+ * (no state, fully deterministic). `length` in ms, `fade` 0..1 (default 1 = full fade).
128
+ */
129
+ trail?: {
130
+ length: number;
131
+ fade?: number;
132
+ };
90
133
  }
91
134
  type SpriteSpec = {
92
135
  kind: 'emoji';
93
136
  glyphs: string[];
137
+ cycle?: CycleSpec;
94
138
  } | {
95
139
  kind: 'text';
96
140
  strings: string[];
@@ -99,6 +143,7 @@ type SpriteSpec = {
99
143
  align?: 'left' | 'center' | 'right';
100
144
  baseline?: 'top' | 'middle' | 'bottom';
101
145
  maxWidth?: number;
146
+ cycle?: CycleSpec;
102
147
  }
103
148
  /** `soft` renders a radial falloff (glow orb) instead of a hard disc. */
104
149
  | {
@@ -106,7 +151,12 @@ type SpriteSpec = {
106
151
  radius: [number, number];
107
152
  color: string;
108
153
  soft?: boolean;
154
+ colors?: string[];
109
155
  };
156
+ /** Rotate through sprite variants over time. Each entity offsets by its seeded phase. */
157
+ interface CycleSpec {
158
+ period: number;
159
+ }
110
160
  type MotionSpec =
111
161
  /**
112
162
  * Drift at a per-entity speed picked from `speed` (px/sec) along `angle` degrees
@@ -136,6 +186,20 @@ type MotionSpec =
136
186
  /** Entity stays exactly where placed. No movement. Use with `position` for pinned elements. */
137
187
  | {
138
188
  type: 'static';
189
+ }
190
+ /**
191
+ * Orbit around a center point. Each entity gets a seeded radius from `radius`
192
+ * and a seeded phase. `speed` is angular velocity in degrees/sec. `center` is
193
+ * fractional {x, y} (default {0.5, 0.5} = viewport center).
194
+ */
195
+ | {
196
+ type: 'orbit';
197
+ speed: [number, number];
198
+ radius: [number, number];
199
+ center?: {
200
+ x: number;
201
+ y: number;
202
+ };
139
203
  };
140
204
  /** A validation problem, pointing at a JSON path within the spec. */
141
205
  interface SpecError {
@@ -145,6 +209,14 @@ interface SpecError {
145
209
  interface ValidationResult {
146
210
  valid: boolean;
147
211
  errors: SpecError[];
212
+ /** Non-blocking warnings about unknown/misplaced properties or likely authoring mistakes. */
213
+ warnings?: SpecWarning[];
214
+ }
215
+ /** An advisory warning (non-blocking). Returned by `adviseSpec`. */
216
+ interface SpecWarning {
217
+ path: string;
218
+ code: string;
219
+ message: string;
148
220
  }
149
221
  /** Perf/safety caps enforced by `validateSpec`. */
150
222
  declare const LIMITS: {
@@ -154,11 +226,25 @@ declare const LIMITS: {
154
226
  readonly maxSpeed: 4000;
155
227
  readonly maxPulseAmp: 0.5;
156
228
  readonly minPulsePeriod: 500;
229
+ readonly maxSpin: 360;
230
+ readonly maxGrowAmp: 0.8;
231
+ readonly maxOrbitSpeed: 180;
232
+ readonly maxLinksK: 8;
233
+ readonly maxLinkLayerCount: 200;
234
+ readonly minCyclePeriod: 500;
235
+ readonly referenceViewport: 1080;
236
+ readonly maxTrailLength: 5000;
237
+ readonly maxTrailSamples: 24;
238
+ readonly minDriftPeriod: 10000;
239
+ readonly maxDriftAmount: 0.3;
157
240
  };
158
241
 
159
242
  /**
160
243
  * Validate an untrusted (agent- or JSON-authored) spec structurally + semantically.
161
- * Returns typed errors; never throws. `compileSaver` refuses to run an invalid spec.
244
+ * Returns typed errors and warnings; never throws. `compileSaver` refuses to run an invalid spec.
245
+ *
246
+ * Warnings are non-blocking advisories about unknown/misplaced properties and
247
+ * near-zero speeds. They don't prevent compilation but indicate likely authoring mistakes.
162
248
  */
163
249
  declare function validateSpec(spec: unknown): ValidationResult;
164
250
  /** Narrowing helper: validate + cast. Throws with a joined message when invalid. */
@@ -175,12 +261,22 @@ interface Entity {
175
261
  vx: number;
176
262
  vy: number;
177
263
  bob: number;
178
- motion: 'drift' | 'rise' | 'bounce' | 'static';
264
+ motion: 'drift' | 'rise' | 'bounce' | 'static' | 'orbit';
179
265
  headingLeft: boolean;
180
266
  alpha: number;
181
267
  pulseAmp: number;
182
268
  pulsePeriod: number;
183
269
  pulsePhase: number;
270
+ spinSpeed: number;
271
+ spinPhase: number;
272
+ growAmp: number;
273
+ growPeriod: number;
274
+ growPhase: number;
275
+ orbitR: number;
276
+ orbitCx: number;
277
+ orbitCy: number;
278
+ colorIndex: number;
279
+ cyclePeriod: number;
184
280
  }
185
281
  interface Placed {
186
282
  x: number;
@@ -188,12 +284,26 @@ interface Placed {
188
284
  flip: boolean;
189
285
  }
190
286
  declare function spriteVariants(sprite: SpriteSpec): number;
191
- /** Deterministically place a layer's entities using the seeded RNG (never Math.random). */
192
- declare function buildEntities(layer: LayerSpec, rng: Rng, w: number, h: number): Entity[];
287
+ /** Deterministically place a layer's entities using the seeded RNG (never Math.random).
288
+ * `scale` multiplies every dimensional draw (default 1). Used for viewport units.
289
+ * `countScale` multiplies entity count for density-aware scaling (default 1). */
290
+ declare function buildEntities(layer: LayerSpec, rng: Rng, w: number, h: number, scale?: number, countScale?: number): Entity[];
193
291
  /** Analytic opacity of an entity at logical time `t` (ms). Pure, clamped to 0..1. */
194
292
  declare function alphaAt(e: Entity, t: number): number;
293
+ /** Analytic size multiplier at time `t` (ms). Pure, clamped to > 0. */
294
+ declare function sizeAt(e: Entity, t: number): number;
295
+ /** Analytic rotation angle at time `t` (ms) in radians. Pure. */
296
+ declare function rotationAt(e: Entity, t: number): number;
195
297
  /** Analytic position of an entity at logical time `t` (ms). Pure & deterministic. */
196
298
  declare function positionAt(e: Entity, t: number, w: number, h: number): Placed;
299
+ /** Time-varying sprite index for text/emoji cycling. Returns static index when cyclePeriod is 0. */
300
+ declare function spriteIndexAt(e: Entity, t: number, variants: number): number;
301
+ /** K-nearest-neighbor edge list. Deterministic tie-break: (dist, index).
302
+ * When `doWrap` is true, uses toroidal distance so links don't streak across wrap seams. */
303
+ declare function linkPairs(positions: Array<{
304
+ x: number;
305
+ y: number;
306
+ }>, k: number, maxDist: number, doWrap: boolean, w: number, h: number): Array<[number, number]>;
197
307
 
198
308
  /** Derive a manifest so a compiled spec composes with @idle-screens/capabilities. */
199
309
  declare function manifestFor(spec: SaverSpec): SaverManifest;
@@ -234,6 +344,16 @@ declare function applyDeltasToSpec(spec: SaverSpec, deltas: SteerDelta[]): Saver
234
344
  * target immediately (k > 0). Layer counts round to integers.
235
345
  */
236
346
  declare function lerpSpec(from: SaverSpec, to: SaverSpec, k: number): SaverSpec;
347
+ /**
348
+ * Enumerate all steerable leaf paths in a (resolved) spec. Returns dot-paths
349
+ * like "layers.0.count", "background.stops.1.color", etc. Metadata fields
350
+ * (id, label, schemaVersion, seed, units, kind, type, key) are excluded —
351
+ * they describe structure, not tuneable values.
352
+ *
353
+ * Layer keys are NOT substituted: paths always use numeric indices.
354
+ * Consumers can map to key-based paths via resolveSpecPath if needed.
355
+ */
356
+ declare function steerablePaths(spec: unknown): string[];
237
357
  /** Smooth (ease-in-out) progress curve used for glides. */
238
358
  declare function easeSmooth(k: number): number;
239
359
  /**
@@ -242,6 +362,60 @@ declare function easeSmooth(k: number): number;
242
362
  */
243
363
  declare function structuralSignature(spec: SaverSpec): string;
244
364
 
365
+ /**
366
+ * Non-blocking advisory warnings for a valid spec. Does NOT replace validateSpec —
367
+ * call advise only on specs that have already passed validation.
368
+ */
369
+ declare function adviseSpec(spec: SaverSpec, viewport?: {
370
+ width: number;
371
+ height: number;
372
+ }): SpecWarning[];
373
+
374
+ interface LayerSnapshot {
375
+ key: string | undefined;
376
+ count: number;
377
+ centroid: {
378
+ x: number;
379
+ y: number;
380
+ };
381
+ coverage: number;
382
+ meanAlpha: number;
383
+ linksDrawn: number | null;
384
+ linksExpected: number | null;
385
+ connectedComponents: number | null;
386
+ isolatedNodes: number | null;
387
+ }
388
+ interface SceneSnapshot {
389
+ t: number;
390
+ viewport: {
391
+ w: number;
392
+ h: number;
393
+ };
394
+ layers: LayerSnapshot[];
395
+ warnings: string[];
396
+ }
397
+ interface SceneDescription {
398
+ spec: {
399
+ id: string;
400
+ label: string;
401
+ units: string;
402
+ };
403
+ snapshots: SceneSnapshot[];
404
+ }
405
+ /**
406
+ * Deterministic scene analysis at one or more time values. Reuses `buildEntities`
407
+ * and `linkPairs` — no render needed. Returns structured data a non-vision model
408
+ * can use to understand what a spec looks like when rendered.
409
+ */
410
+ declare function describeScene(spec: SaverSpec, options?: {
411
+ viewport?: {
412
+ width: number;
413
+ height: number;
414
+ };
415
+ seed?: number;
416
+ times?: number[];
417
+ }): SceneDescription;
418
+
245
419
  /**
246
420
  * Reproduces the hand-coded Fish Aquarium saver as pure DATA — the discriminating test
247
421
  * that the schema is actually expressive: two layers (fish drifting both ways at varied
@@ -249,6 +423,20 @@ declare function structuralSignature(spec: SaverSpec): string;
249
423
  */
250
424
  declare const AQUARIUM_SPEC: SaverSpec;
251
425
 
426
+ /**
427
+ * Comet Shower: showcases trails (afterglow behind moving entities) and background
428
+ * drift (gradient stops breathing slowly). Three layers: a static star field, fast
429
+ * comets with long trails, and slow fireflies with short trails and pulse.
430
+ */
431
+ declare const COMETS_SPEC: SaverSpec;
432
+
433
+ /**
434
+ * Constellation: drifting stars connected by faint k-nearest links. Exercises
435
+ * colors[] (multi-hued stars), links (k-nearest neighbor lines), and cycle
436
+ * (emoji asterisms rotate glyphs). Stars are soft circles in pixel units.
437
+ */
438
+ declare const CONSTELLATION_SPEC: SaverSpec;
439
+
252
440
  /** Rain: dense text streaks falling straight down over a near-black sky. */
253
441
  declare const RAIN_SPEC: SaverSpec;
254
442
 
@@ -287,6 +475,14 @@ declare const SAKURA_SPEC: SaverSpec;
287
475
  */
288
476
  declare const DASHBOARD_SPEC: SaverSpec;
289
477
 
478
+ /**
479
+ * Orrery: a mechanical solar system. Exercises orbit motion (concentric rings
480
+ * at different speeds), spin (planets rotate on their axes), and grow (the sun
481
+ * pulses). Soft glow circles with additive blending create the corona and
482
+ * planetary halos.
483
+ */
484
+ declare const ORRERY_SPEC: SaverSpec;
485
+
290
486
  /** Catalog entry for a bundled schema example. */
291
487
  interface SchemaExample {
292
488
  id: string;
@@ -300,4 +496,4 @@ declare const EXAMPLE_SPECS: SaverSpec[];
300
496
  /** Lookup by saver id. */
301
497
  declare const EXAMPLE_BY_ID: Readonly<Record<string, SaverSpec>>;
302
498
 
303
- export { AQUARIUM_SPEC, type BackgroundSpec, type BandSpec, DASHBOARD_SPEC, EXAMPLE_BY_ID, EXAMPLE_SPECS, type Entity, type GradientStop, LANTERNS_SPEC, LIMITS, type LayerSpec, type MotionSpec, type Placed, RAIN_SPEC, SAKURA_SPEC, SCHEMA_EXAMPLES, SCHEMA_VERSION, SNOWFALL_SPEC, type SaverSpec, type SchemaExample, type SpecError, type SpriteSpec, type SteerDelta, type ValidationResult, alphaAt, applyDeltasToSpec, assertValidSpec, buildEntities, compileSaver, easeSmooth, lerpSpec, manifestFor, positionAt, resolveSpecPath, spriteVariants, structuralSignature, validateSpec };
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 };