@idle-screens/schema 3.10.0 → 3.11.1

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 CHANGED
@@ -45,7 +45,8 @@ the `fade` transition, `role` on text sprites, and `text: 'crossfade'` on
45
45
  sequence-level) (2026-09-10 — the print look); `timeline` (keyframes on the
46
46
  scene's own clock), paint-level layer `opacity` and `transform`,
47
47
  `position.dx` / `dy`, `wrapMorph` on the sequence envelope and
48
- `text: 'dip'` on `morph` (2026-09-25 — timed scenes).
48
+ `text: 'dip'` on `morph` (2026-09-25 — timed scenes); layer `groups` and
49
+ `transform.origin` (2026-09-26 — groups and origin).
49
50
 
50
51
  ## Safety invariants
51
52
 
@@ -111,7 +112,8 @@ must exist, have `count: 1`, and not themselves orbit a layer.
111
112
  "finish": { "grain": 0.5, "dither": 0.3 }, // optional; print screen over the finished frame
112
113
  "background": { ... }, // optional; defaults to black
113
114
  "layers": [ { ... }, ... ], // 1..36, rendered back-to-front
114
- "timeline": { ... } // optional; authored keyframes — see **Timeline**
115
+ "timeline": { ... }, // optional; authored keyframes — see **Timeline**
116
+ "groups": { "moon": { ... } } // optional; named paint groups — see **Groups**
115
117
  }
116
118
  ```
117
119
 
@@ -289,7 +291,8 @@ own `finish` is not steerable; steer a segment's, or republish.
289
291
  | `key` | string | none | addressable name → `setParam("key.count", …)` for a layer field, `setParam("key.sprite.color", …)` for a sprite field (the path mirrors the JSON: sprite fields sit under `sprite`) |
290
292
  | `position` | `{x, y, dx?, dy?}` — `x`/`y` 0..1, `dx`/`dy` ±2 (±17280 px under `units: "px"`) | none | exact placement; **requires `count: 1`** — except with a `list`/`table` layout, where it anchors the block's top-left; overrides `region`. `dx`/`dy` offset the point in **`min(w, h)` units** (px under `units: "px"`): `x`/`y` are fractions of width and height while every size is a fraction of `min(w, h)`, so parts placed at computed fractions only register at the aspect they were computed for — anchor a compound form's parts on one shared fraction (the centre is the only aspect-safe one) and offset each with `dx`/`dy`. Placement (structural). **Native:** tvOS ignores the offset |
291
293
  | `opacity` | number 0..1 | `1` | **paint-level** layer opacity: multiplies every entity's `alpha` and the `life` envelope at draw time. Unlike `alpha` (a range baked into each entity), it is outside the structural signature, so `setParam`, a `timeline` key or a `morph` **glides** it instead of re-seeding the layer. `0` skips the layer. **Native:** ignored ⇒ 1 |
292
- | `transform` | `{x?, y?, scale?, scaleX?, rotate?}` | identity | **paint-level** transform of the whole layer about the viewport centre: translate `x`/`y` (±2 in `min(w, h)` units; px under `units: "px"`, ±17280), `rotate` (degrees, ±3600), `scale` (0..8) and `scaleX` (±8, × `scale` horizontally — toward 0 reads as a turn about the vertical axis, negative mirrors). Outside the structural signature: a camera for a timed piece, a slow drift for an ambient one, and it glides under steering. Sprites are not re-rasterised, so a large `scale` enlarges pixels of soft sprites, not detail. **Native:** ignored ⇒ identity |
294
+ | `transform` | `{x?, y?, scale?, scaleX?, rotate?, origin?}` | identity | **paint-level** transform of the whole layer, about the viewport centre — or, with `origin: "anchor"`, about the layer's own anchor (its `position` point with `dx`/`dy`; a `list`/`table` block's anchor; without `position`, the centre of its `region`), so a word scales **in place** on every aspect (`origin` is an enum: not a steer or key target, and it steps across a morph — keep one origin per layer): translate `x`/`y` (±2 in `min(w, h)` units; px under `units: "px"`, ±17280), `rotate` (degrees, ±3600), `scale` (0..8) and `scaleX` (±8, × `scale` horizontally — toward 0 reads as a turn about the vertical axis, negative mirrors). Outside the structural signature: a camera for a timed piece, a slow drift for an ambient one, and it glides under steering. Sprites are not re-rasterised, so a large `scale` enlarges pixels of soft sprites, not detail. **Native:** ignored ⇒ identity |
295
+ | `group` | string | none | the name of a `groups` entry this layer paints through — see **Groups**. **Native:** ignored |
293
296
 
294
297
  `links`: `{ k: 1..8, maxDist, color?, alpha?, width?, mode?, falloff?, closed? }`.
295
298
  `mode: "nearest"` (default) wires each entity to its k nearest neighbors within
@@ -754,6 +757,34 @@ it genuinely cuts to a different set of layers.
754
757
  base as the piece's rest state** (its end card), and a timed piece degrades
755
758
  to a valid ambient scene.
756
759
 
760
+ ## Groups — one handle for a multi-layer subject
761
+
762
+ A subject drawn from several layers (a character's body, eyes and mouth; a
763
+ record's grooves and label) can be moved, scaled and faded as one thing:
764
+
765
+ ```jsonc
766
+ "groups": { "moon": { "transform": { "scale": 1, "y": 0 }, "opacity": 1 } },
767
+ "layers": [ { "key": "face", "group": "moon", ... }, { "key": "eyes", "group": "moon", ... } ]
768
+ ```
769
+
770
+ - A member paints through its group: the group's `transform` wraps the
771
+ layer's own (a point lands at *group(layer(p))*), and the group's `opacity`
772
+ multiplies the layer's. A group's transform always turns about the viewport
773
+ centre — it is a camera; `origin: "anchor"` is for a layer's own transform.
774
+ - Up to 16 groups; names are 1–32 letters, digits, `-` or `_`, starting with a
775
+ letter. A layer's `group` must name one of them.
776
+ - **Paint**, outside the structural signature: `setParam("groups.moon.opacity", 0)`,
777
+ a `timeline` key on `groups.moon.transform.scale`, or a sequence `morph`
778
+ glides the whole subject — one path instead of one per layer. A group present
779
+ on only one end of a morph glides from the identity (no transform, opacity 1).
780
+ - Membership is fixed per scene: `layers.N.group` is not a steer or `timeline`
781
+ target. A sequence `morph` where a layer joins or leaves a group glides (the
782
+ end without the group paints it at the identity); moving a layer from one
783
+ group to another steps on the morph's first frame.
784
+
785
+ **Native:** ignored ⇒ identity transform, opacity 1 — author the base so the
786
+ subject reads without the group's paint.
787
+
757
788
  ## Seeing without eyes — the perception API
758
789
 
759
790
  `src/perceive.ts` translates a spec into modalities a **non-vision agent** can
@@ -818,7 +849,10 @@ moves and scales its positions and sizes (sprite rotation is not modelled).
818
849
  text layer. Glyphs are invisible in the luminance maps, so this is the only
819
850
  way to confirm *what words* are on screen and how big. (Also on
820
851
  `perceiveScene().text`.)
821
- - `adviseSpec(spec)` — non-blocking advisories (also on
852
+ - `adviseSpec(spec)` — non-blocking advisories (a layer whose paint opacity —
853
+ its own × its group's — is 0 in the base scene and at every key boundary of
854
+ its `timeline` never shows, so it is not judged and its entities do not count
855
+ toward `dense-scene`; coverage is weighed by paint opacity) (also on
822
856
  `perceiveScene().advisories`). Two of them are **spatial**, for static text:
823
857
  `text-off-screen` (a text/textBlock box crosses the viewport edge by more
824
858
  than 1%) and `text-overlap` (two static text layers share more than 10% of
package/dist/index.d.ts CHANGED
@@ -52,6 +52,8 @@ interface SaverSpec {
52
52
  * ignored ⇒ the base spec (author it as the piece's rest state).
53
53
  */
54
54
  timeline?: Timeline;
55
+ /** Named layer groups — see `LayerGroup`. Absent ⇒ no layer is grouped (today's behaviour). */
56
+ groups?: Record<string, LayerGroup>;
55
57
  /** Dimensional unit system. 'viewport' (default) = all sizes/speeds/distances are fractions of min(w,h). */
56
58
  units?: 'viewport' | 'px';
57
59
  /**
@@ -161,6 +163,28 @@ interface LayerTransform {
161
163
  scale?: number;
162
164
  scaleX?: number;
163
165
  rotate?: number;
166
+ /**
167
+ * What `scale` / `scaleX` / `rotate` turn about. `'viewport'` (default) is
168
+ * the viewport centre — a camera move. `'anchor'` is the layer's own
169
+ * `position` point (with `dx`/`dy`; for a `list`/`table` layout, the block's
170
+ * anchor; without `position`, the centre of its `region`) — a slam word
171
+ * punches in place, on every aspect. Group transforms always turn about the
172
+ * viewport centre. **Native:** ignored with the rest of `transform`.
173
+ */
174
+ origin?: 'viewport' | 'anchor';
175
+ }
176
+ /**
177
+ * A named set of layers painted through one shared `transform` and
178
+ * `opacity` (`SaverSpec.groups`, joined by `LayerSpec.group`): the group's
179
+ * transform wraps each member's own (it turns about the viewport centre, a
180
+ * camera), and its opacity multiplies theirs. Paint — outside the structural
181
+ * signature — so a steer (`groups.<name>.transform.scale`), a `timeline` key
182
+ * or a `morph` glides a whole multi-layer subject as one thing. **Native:**
183
+ * ignored ⇒ identity, opacity 1.
184
+ */
185
+ interface LayerGroup {
186
+ transform?: LayerTransform;
187
+ opacity?: number;
164
188
  }
165
189
  /** One authored key of a `Timeline`: at scene time `t` (ms), glide `path` to `value` over `dur` ms. */
166
190
  interface TimelineKey {
@@ -300,6 +324,8 @@ interface LayerSpec {
300
324
  opacity?: number;
301
325
  /** Paint-level transform of the whole layer — see `LayerTransform`. */
302
326
  transform?: LayerTransform;
327
+ /** The name of a `SaverSpec.groups` entry this layer is painted through (see `LayerGroup`). */
328
+ group?: string;
303
329
  /**
304
330
  * Inter-entity links. `mode` picks the wiring:
305
331
  * - 'nearest' (default): each entity's k nearest neighbors within maxDist.
@@ -835,6 +861,7 @@ declare const LIMITS: {
835
861
  readonly maxLayerTransformOffsetPx: 17280;
836
862
  readonly maxLayerTransformScale: 8;
837
863
  readonly maxLayerTransformRotate: 3600;
864
+ readonly maxGroups: 16;
838
865
  readonly maxEmitEvery: 600000;
839
866
  readonly minEmitLife: 500;
840
867
  readonly maxEmitGrow: 8;
@@ -2541,4 +2568,4 @@ declare const EXAMPLE_SPECS: SaverSpec[];
2541
2568
  /** Lookup by saver id. */
2542
2569
  declare const EXAMPLE_BY_ID: Readonly<Record<string, SaverSpec>>;
2543
2570
 
2544
- 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, DEFAULT_KEY_DUR, 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 LayerTransform, 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 Timeline, type TimelineKey, 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, canWrapMorph, canonicalSpecPath, cohesionOf, compileSaver, compileSequence, createFinishPass, createSceneCanvas, describeScene, diffScenes, dominanceRanking, easeSmooth, emitEnvelope, emitWindow, fieldAt, fieldOctaves, fieldRgb, fieldRgbAt, fieldSampleTime, finishStrength, grainOffset, grainTilePixels, graphemeClusters, hasHotSwapSequence, headingAt, hexToRgb255, ignoredPropertyPaths, latticeHash, layerCohesion, lerpSpec, lerpValue, lifeAlphaAt, linkEdges, linkPairs, luminanceGrid, manifestFor, morphChainRoot, morphNothingMorphable, motionStats, nextKeyAfter, normalizeSeed, perceiveScene, perceiveSequenceFrame, pickScreenOp, positionAt, presentWithFinish, readSpecPath, renderBrailleMap, renderDensityMap, resolveSegment, resolveSpecPath, resolveTimelineAt, revealState, rgb255Luma, rotationAt, seamCauseOf, seamsWorthWarning, segmentRenderSeed, segmentStart, sequenceSteerablePaths, sequenceSwapCompatible, sequenceWantsFinish, sizeAt, spriteIndexAt, spriteVariants, steerablePaths, structuralSignature, textBlockAnchorOffset, textMetricsClassFor, textSprites, textStringsDiffer, textStringsOf, textWidthEm, timelineLocalTime, timelineSampleTimes, timelineSampleTimesAfter, timelineTracks, validateSequence, validateSpec, validateSpecPaths, withoutTimeline };
2571
+ 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, DEFAULT_KEY_DUR, 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 LayerGroup, type LayerMotionStats, type LayerSnapshot, type LayerSpec, type LayerTransform, 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 Timeline, type TimelineKey, 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, canWrapMorph, canonicalSpecPath, cohesionOf, compileSaver, compileSequence, createFinishPass, createSceneCanvas, describeScene, diffScenes, dominanceRanking, easeSmooth, emitEnvelope, emitWindow, fieldAt, fieldOctaves, fieldRgb, fieldRgbAt, fieldSampleTime, finishStrength, grainOffset, grainTilePixels, graphemeClusters, hasHotSwapSequence, headingAt, hexToRgb255, ignoredPropertyPaths, latticeHash, layerCohesion, lerpSpec, lerpValue, lifeAlphaAt, linkEdges, linkPairs, luminanceGrid, manifestFor, morphChainRoot, morphNothingMorphable, motionStats, nextKeyAfter, normalizeSeed, perceiveScene, perceiveSequenceFrame, pickScreenOp, positionAt, presentWithFinish, readSpecPath, renderBrailleMap, renderDensityMap, resolveSegment, resolveSpecPath, resolveTimelineAt, revealState, rgb255Luma, rotationAt, seamCauseOf, seamsWorthWarning, segmentRenderSeed, segmentStart, sequenceSteerablePaths, sequenceSwapCompatible, sequenceWantsFinish, sizeAt, spriteIndexAt, spriteVariants, steerablePaths, structuralSignature, textBlockAnchorOffset, textMetricsClassFor, textSprites, textStringsDiffer, textStringsOf, textWidthEm, timelineLocalTime, timelineSampleTimes, timelineSampleTimesAfter, timelineTracks, validateSequence, validateSpec, validateSpecPaths, withoutTimeline };