@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idle-screens/schema",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "Declarative, agent-authorable saver format for idle-screens: validate a data spec, then compile it into a runnable, seeded, flash-safe SaverPlugin.",
5
5
  "license": "MIT",
6
6
  "author": "Shawn Partridge",
@@ -13,6 +13,8 @@
13
13
  "seed": { "type": "number", "description": "Deterministic RNG seed. Same spec + same seed = identical frames. Falls back to the host's seed." },
14
14
  "motionIntensity": { "enum": ["calm", "moderate", "energetic"], "description": "Advisory intensity for capability/reduced-motion gating." },
15
15
  "units": { "enum": ["px", "viewport"], "description": "Dimensional unit system. 'viewport' = all sizes/speeds/distances are fractions of min(w,h)." },
16
+ "referenceViewport": { "type": "number", "minimum": 100, "maximum": 8640, "description": "Resolution the spec was designed at (default 1080). Entity counts density-scale above it." },
17
+ "ghosting": { "type": "number", "minimum": 0, "maximum": 0.95, "description": "Frame persistence 0..0.95: each frame composites over a faded copy of the last, so moving entities smear (Mystify trails, long-exposure light). Flash-safe — only smooths luminance. Seeks stay deterministic via a fixed-step warm-up replay." },
16
18
  "background": { "$ref": "#/definitions/background" },
17
19
  "layers": {
18
20
  "type": "array",
@@ -115,7 +117,7 @@
115
117
  "wrap": { "type": "boolean", "description": "Wrap to the opposite edge when leaving the viewport. Default true." },
116
118
  "flip": { "type": "boolean", "description": "Flip the sprite horizontally to face its heading. Default false." },
117
119
  "alpha": { "$ref": "#/definitions/range01", "description": "Per-entity opacity range. Default [1, 1]." },
118
- "blend": { "const": "lighter", "description": "Additive compositing (glow stacking). Omit for normal source-over." },
120
+ "blend": { "enum": ["lighter", "screen", "multiply"], "description": "Compositing: 'lighter' = additive glow, 'screen' = gentle additive for pale backgrounds, 'multiply' = darkening (shadows). Omit for normal source-over." },
119
121
  "region": {
120
122
  "type": "object",
121
123
  "additionalProperties": false,
@@ -131,11 +133,27 @@
131
133
  "additionalProperties": false,
132
134
  "properties": {
133
135
  "amp": { "type": "number", "exclusiveMinimum": 0, "maximum": 0.5, "description": "Opacity breathing amplitude, capped at 0.5." },
134
- "period": { "type": "number", "minimum": 500, "description": "Breathing period in ms; floor of 500 (max 2 Hz — flash-safety)." }
136
+ "period": { "type": "number", "minimum": 500, "description": "Breathing period in ms; floor of 500 (max 2 Hz — flash-safety)." },
137
+ "wave": {
138
+ "type": "object",
139
+ "required": ["wavelength"],
140
+ "additionalProperties": false,
141
+ "properties": {
142
+ "wavelength": { "type": "number", "exclusiveMinimum": 0, "description": "Spatial wavelength (px or viewport-units)." },
143
+ "angle": { "type": "number", "description": "Propagation direction in degrees (0 = rightward). Default 0." }
144
+ },
145
+ "description": "Derive each entity's phase from its position: breathing becomes a traveling wave across the field (ripples, wind through grass). Neighbors are out of phase by construction — still flash-safe."
146
+ }
135
147
  },
136
148
  "description": "Sinusoidal opacity breathing. Per-entity seeded phase — a layer can never strobe in unison."
137
149
  },
138
- "spin": { "type": "number", "minimum": -360, "maximum": 360, "description": "Per-entity rotation speed in degrees/sec (positive = clockwise). Each entity gets a seeded starting angle." },
150
+ "spin": {
151
+ "description": "Per-entity rotation speed in degrees/sec (positive = clockwise). A scalar applies one speed to all entities; a [min,max] range gives each entity a seeded speed. Each entity gets a seeded starting angle.",
152
+ "oneOf": [
153
+ { "type": "number", "minimum": -360, "maximum": 360 },
154
+ { "type": "array", "items": { "type": "number", "minimum": -360, "maximum": 360 }, "minItems": 2, "maxItems": 2 }
155
+ ]
156
+ },
139
157
  "grow": {
140
158
  "type": "object",
141
159
  "required": ["amp", "period"],
@@ -176,9 +194,46 @@
176
194
  "maxDist": { "type": "number", "exclusiveMinimum": 0, "description": "Maximum link distance (px or viewport-units)." },
177
195
  "color": { "$ref": "#/definitions/hexColor", "description": "Link color. Defaults to per-entity color." },
178
196
  "alpha": { "type": "number", "minimum": 0, "maximum": 1, "description": "Link opacity. Defaults to entity alpha." },
179
- "width": { "type": "number", "exclusiveMinimum": 0, "description": "Stroke width (px or viewport-units). Default 1." }
197
+ "width": { "type": "number", "exclusiveMinimum": 0, "description": "Stroke width (px or viewport-units). Default 1." },
198
+ "mode": { "enum": ["nearest", "chain", "random"], "description": "'nearest' (default) k-nearest within maxDist; 'chain' connects entities in order (Mystify polygons — ignores k/maxDist); 'random' a fixed deterministic wiring filtered by maxDist." },
199
+ "falloff": { "type": "boolean", "description": "Fade link alpha with distance/maxDist — removes pop-in at the cutoff." },
200
+ "closed": { "type": "boolean", "description": "Chain mode: join the last entity back to the first (closed polygon)." }
201
+ },
202
+ "description": "Draw lines between entities. The layer's entity count (count field) must be <= 200 for performance."
203
+ },
204
+ "layout": {
205
+ "type": "object",
206
+ "required": ["type"],
207
+ "additionalProperties": false,
208
+ "properties": {
209
+ "type": { "const": "grid" },
210
+ "columns": { "type": "integer", "minimum": 1, "maximum": 100, "description": "Grid columns. Default: aspect-fit square-ish grid." },
211
+ "jitter": {
212
+ "oneOf": [
213
+ { "type": "number", "minimum": 0, "maximum": 1 },
214
+ {
215
+ "type": "object",
216
+ "additionalProperties": false,
217
+ "properties": {
218
+ "x": { "type": "number", "minimum": 0, "maximum": 1 },
219
+ "y": { "type": "number", "minimum": 0, "maximum": 1 }
220
+ }
221
+ }
222
+ ],
223
+ "description": "Seeded per-entity offset as a fraction of cell size (scalar or per-axis). { y: 1 } keeps columns crisp while scattering vertically (Matrix rain). Default 0."
224
+ }
180
225
  },
181
- "description": "Draw lines to each entity's k nearest neighbors within maxDist. The layer's entity count (count field) must be <= 200 for performance."
226
+ "description": "Place entities on a grid (row-major, within region) instead of random scatter — Matrix columns, LED walls, uniform dot fields."
227
+ },
228
+ "life": {
229
+ "type": "object",
230
+ "additionalProperties": false,
231
+ "properties": {
232
+ "enter": { "type": "number", "minimum": 0, "description": "Layer fades in starting at this time (ms)." },
233
+ "exit": { "type": "number", "minimum": 0, "description": "Layer fades out starting at this time (ms). Must be > enter." },
234
+ "fade": { "type": "number", "minimum": 0, "description": "Fade ramp duration in ms. Default 1000." }
235
+ },
236
+ "description": "Layer lifecycle for act structure (stars first, dancers at t=3000). Pure function of t — deterministic and seekable."
182
237
  }
183
238
  },
184
239
  "if": { "required": ["position"] },
@@ -220,11 +275,61 @@
220
275
  "radius": { "$ref": "#/definitions/positiveRange" },
221
276
  "color": { "$ref": "#/definitions/hexColor" },
222
277
  "soft": { "type": "boolean", "description": "Radial falloff (glow orb) instead of a hard disc." },
223
- "colors": { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/hexColor" }, "description": "Per-entity color palette (seeded pick). Falls back to color." }
278
+ "colors": { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/hexColor" }, "description": "Per-entity color palette (seeded pick). Falls back to color." },
279
+ "colorWeights": { "$ref": "#/definitions/colorWeights" }
224
280
  }
281
+ },
282
+ {
283
+ "type": "object",
284
+ "required": ["kind", "radius", "color"],
285
+ "additionalProperties": false,
286
+ "properties": {
287
+ "kind": { "const": "ring" },
288
+ "radius": { "$ref": "#/definitions/positiveRange" },
289
+ "color": { "$ref": "#/definitions/hexColor" },
290
+ "width": { "type": "number", "exclusiveMinimum": 0, "description": "Stroke width (px or viewport-units). Default ~2px." },
291
+ "colors": { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/hexColor" }, "description": "Per-entity color palette (seeded pick)." },
292
+ "colorWeights": { "$ref": "#/definitions/colorWeights" }
293
+ },
294
+ "description": "Unfilled circle — bubbles, portals, sonar pings."
295
+ },
296
+ {
297
+ "type": "object",
298
+ "required": ["kind", "length", "color"],
299
+ "additionalProperties": false,
300
+ "properties": {
301
+ "kind": { "const": "streak" },
302
+ "length": { "$ref": "#/definitions/positiveRange", "description": "Segment length range (px or viewport-units)." },
303
+ "color": { "$ref": "#/definitions/hexColor" },
304
+ "width": { "type": "number", "exclusiveMinimum": 0, "description": "Stroke width (px or viewport-units). Default ~2px." },
305
+ "colors": { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/hexColor" }, "description": "Per-entity color palette (seeded pick)." },
306
+ "colorWeights": { "$ref": "#/definitions/colorWeights" }
307
+ },
308
+ "description": "A line oriented along the entity's heading with a faded tail — rain that reads as rain, shooting stars, warp stars."
309
+ },
310
+ {
311
+ "type": "object",
312
+ "required": ["kind", "width", "color"],
313
+ "additionalProperties": false,
314
+ "properties": {
315
+ "kind": { "const": "rect" },
316
+ "width": { "$ref": "#/definitions/positiveRange", "description": "Horizontal size range (px or viewport-units)." },
317
+ "aspect": { "$ref": "#/definitions/positiveRange", "description": "Height/width ratio range. Default [1,1] = squares." },
318
+ "color": { "$ref": "#/definitions/hexColor" },
319
+ "colors": { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/hexColor" }, "description": "Per-entity color palette (seeded pick)." },
320
+ "colorWeights": { "$ref": "#/definitions/colorWeights" }
321
+ },
322
+ "description": "Axis-aligned rectangle (rotates with spin) — Mondrian blocks, confetti, city lights."
225
323
  }
226
324
  ]
227
325
  },
326
+ "colorWeights": {
327
+ "type": "array",
328
+ "minItems": 1,
329
+ "items": { "type": "number", "minimum": 0 },
330
+ "contains": { "type": "number", "exclusiveMinimum": 0 },
331
+ "description": "Relative weights for the colors[] pick, same length as colors. At least one weight must be > 0. 'Mostly cool tones, occasional ember' without duplicating entries."
332
+ },
228
333
  "cycle": {
229
334
  "type": "object",
230
335
  "required": ["period"],
@@ -289,6 +394,56 @@
289
394
  "description": "[min, max] angular velocity in degrees/sec, capped at 180."
290
395
  },
291
396
  "radius": { "$ref": "#/definitions/positiveRange", "description": "[min, max] orbit radius in px." },
397
+ "center": {
398
+ "oneOf": [
399
+ {
400
+ "type": "object",
401
+ "required": ["x", "y"],
402
+ "additionalProperties": false,
403
+ "properties": {
404
+ "x": { "type": "number", "minimum": 0, "maximum": 1 },
405
+ "y": { "type": "number", "minimum": 0, "maximum": 1 }
406
+ }
407
+ },
408
+ {
409
+ "type": "object",
410
+ "required": ["layer"],
411
+ "additionalProperties": false,
412
+ "properties": {
413
+ "layer": { "type": "string", "minLength": 1, "pattern": "\\S", "description": "Key of a count-1 parent layer to orbit (moons around a wandering planet). One level deep only — the parent may not itself orbit a layer." }
414
+ }
415
+ }
416
+ ],
417
+ "description": "Orbit center: fractional viewport position (default {0.5, 0.5}) or { layer: key } to orbit a single-entity parent layer."
418
+ }
419
+ }
420
+ },
421
+ {
422
+ "type": "object",
423
+ "required": ["type", "speed"],
424
+ "additionalProperties": false,
425
+ "properties": {
426
+ "type": { "const": "wander" },
427
+ "speed": { "$ref": "#/definitions/speedRange" },
428
+ "angle": { "type": "number", "description": "Base heading in degrees. Omit for a seeded heading per entity." },
429
+ "meander": { "type": "number", "exclusiveMinimum": 0, "description": "Harmonic amplitude (px or viewport-units). Default ~0.05 viewport-units. How far paths curve away from the base line." },
430
+ "coherence": { "type": "number", "minimum": 0, "maximum": 1, "description": "0 = each entity wanders independently; 1 = the whole layer undulates in unison (fake flocking). Default 0." }
431
+ },
432
+ "description": "Organic harmonic drift — base velocity plus 3 seeded sine octaves per axis. Flowing curved paths (Flurry streams, aurora, jellyfish) with zero simulation state."
433
+ },
434
+ {
435
+ "type": "object",
436
+ "required": ["type", "speed"],
437
+ "additionalProperties": false,
438
+ "properties": {
439
+ "type": { "const": "warp" },
440
+ "speed": {
441
+ "type": "array",
442
+ "items": { "type": "number", "minimum": 0, "maximum": 1.5 },
443
+ "minItems": 2,
444
+ "maxItems": 2,
445
+ "description": "[min, max] depth-units/sec (1 = full near-to-far span per second), capped at 1.5."
446
+ },
292
447
  "center": {
293
448
  "type": "object",
294
449
  "required": ["x", "y"],
@@ -297,9 +452,38 @@
297
452
  "x": { "type": "number", "minimum": 0, "maximum": 1 },
298
453
  "y": { "type": "number", "minimum": 0, "maximum": 1 }
299
454
  },
300
- "description": "Orbit center as fractional viewport position. Default {0.5, 0.5}."
455
+ "description": "Vanishing point as fractional viewport position. Default {0.5, 0.5}."
301
456
  }
302
- }
457
+ },
458
+ "description": "Perspective starfield: entities stream toward the viewer along a depth axis, size and alpha scaling with 1/z. Pair with streak sprites for the classic warp tunnel."
459
+ },
460
+ {
461
+ "type": "object",
462
+ "required": ["type", "points", "duration"],
463
+ "additionalProperties": false,
464
+ "properties": {
465
+ "type": { "const": "path" },
466
+ "points": {
467
+ "type": "array",
468
+ "minItems": 2,
469
+ "maxItems": 24,
470
+ "items": {
471
+ "type": "object",
472
+ "required": ["x", "y"],
473
+ "additionalProperties": false,
474
+ "properties": {
475
+ "x": { "type": "number", "minimum": 0, "maximum": 1 },
476
+ "y": { "type": "number", "minimum": 0, "maximum": 1 }
477
+ }
478
+ },
479
+ "description": "Waypoints as fractional viewport positions."
480
+ },
481
+ "duration": { "type": "number", "minimum": 2000, "description": "Time for one full lap in ms (min 2000)." },
482
+ "curve": { "enum": ["linear", "smooth"], "description": "'smooth' (default) = Catmull-Rom spline through the points; 'linear' = straight segments." },
483
+ "closed": { "type": "boolean", "description": "Loop the path (default true). Open paths ping-pong." },
484
+ "scatter": { "type": "number", "exclusiveMinimum": 0, "description": "Seeded per-entity offset (px or viewport-units) so entities sharing a path don't stack." }
485
+ },
486
+ "description": "Choreographed spline motion — figure-eights, sweeping arcs, patrol routes. Each entity gets a seeded phase offset along the path."
303
487
  }
304
488
  ]
305
489
  }