@idle-screens/schema 3.16.1 → 3.17.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 +115 -16
- package/README.md +1 -1
- package/dist/index.d.ts +107 -4
- package/dist/index.js +826 -111
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/FORMAT.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
The declarative, agent-authorable screensaver format compiled by
|
|
4
4
|
`@idle-screens/schema`. A spec describes a saver as **data** — a background plus
|
|
5
5
|
layers of moving sprites — which `compileSaver()` turns into a seeded,
|
|
6
|
-
deterministic
|
|
6
|
+
deterministic `SaverPlugin` with bounded motion and work (see Safety
|
|
7
|
+
invariants for what is guaranteed, and what is checked at publish). There is no code in a spec: no
|
|
7
8
|
scripting, no network access, no DOM access.
|
|
8
9
|
|
|
9
10
|
- **Machine-readable schema:** [`saver-spec.schema.json`](./saver-spec.schema.json)
|
|
@@ -58,13 +59,20 @@ live steers), `rock`, `life.stagger`, `reveal.rise`, `textBlock.number`, the
|
|
|
58
59
|
|
|
59
60
|
## Safety invariants
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
Three kinds of promise, kept apart so that none is claimed for more than it
|
|
63
|
+
covers: what the format guarantees by construction, what is checked when a
|
|
64
|
+
scene is published, and what nothing checks.
|
|
65
|
+
|
|
66
|
+
**By construction.** The validator rejects any spec that breaks these, and
|
|
67
|
+
`compileSaver()` refuses an invalid spec:
|
|
62
68
|
|
|
63
69
|
1. **No flash primitive.** The background is static (drift is floored at 10 s
|
|
64
70
|
— for a gradient's stop positions and for a `field`'s domain travel alike,
|
|
65
71
|
the only way either changes over time) and entities are bounded sprites,
|
|
66
|
-
so
|
|
67
|
-
|
|
72
|
+
so no single field switches the whole frame from one frame to the next.
|
|
73
|
+
This is about primitives, not pictures: several timeline keys together,
|
|
74
|
+
or a regular pattern in motion, can still flicker, which is what the
|
|
75
|
+
publish checks below are for.
|
|
68
76
|
2. **Pulse/grow/cycle are bounded.** Breathing amplitude is capped and periods
|
|
69
77
|
floored at 500 ms (2 Hz — under the WCAG 3 Hz flash threshold). Every entity
|
|
70
78
|
gets its own seeded phase — and with `pulse.wave`, a position-derived phase —
|
|
@@ -74,7 +82,8 @@ These hold **by construction** — no spec can violate them:
|
|
|
74
82
|
them. `clock` deliberately removes the per-entity phase, so the validator
|
|
75
83
|
floors a clocked layer's periods at 1000 ms (1 Hz) instead — a layer in
|
|
76
84
|
unison breathes, it never flashes. `emit` events are floored at one per
|
|
77
|
-
second per entity and always ride a smooth envelope, never a cut.
|
|
85
|
+
second per entity and always ride a smooth envelope, never a cut. Distinct
|
|
86
|
+
timeline key times on one path sit ≥ 200 ms apart (see Timeline).
|
|
78
87
|
3. **Motion is bounded.** Speeds are capped (4000 px/sec; warp at 1.5
|
|
79
88
|
depth-units/sec; orbit at 180 deg/sec; path laps at ≥ 2 s).
|
|
80
89
|
4. **Work is bounded.** ≤ 36 layers, ≤ 400 entities per layer, ≤ 800 total,
|
|
@@ -85,6 +94,44 @@ These hold **by construction** — no spec can violate them:
|
|
|
85
94
|
replay a bounded fixed-step warm-up so even the accumulated smear is
|
|
86
95
|
reproducible.
|
|
87
96
|
|
|
97
|
+
**Checked at publish.** Properties of the whole picture that no per-field
|
|
98
|
+
rule can promise. Each is an exported, deterministic check that a host
|
|
99
|
+
runs before it accepts a scene, and refuses on; none is part of
|
|
100
|
+
`validateSpec` or `compileSaver`, which run on every screen. A spec drawn
|
|
101
|
+
without them carries none of these guarantees.
|
|
102
|
+
|
|
103
|
+
6. **Timeline flashes** (`timelineFlashReport`, WCAG 2.3.1): paint keys on
|
|
104
|
+
several paths that combine into more than 3 flashes a second over a
|
|
105
|
+
quarter of the screen. See Timeline for the method and its sample budget.
|
|
106
|
+
7. **Spatial patterns** (`patternHazardReport`, ITU-R BT.1702-3 Annex 1):
|
|
107
|
+
more than five light-dark pairs of regular stripes (parallel, radial,
|
|
108
|
+
curved, or rows of repeated elements such as dots) whose light and dark
|
|
109
|
+
differ by a flash-level 0.1 relative luminance with the darker side under
|
|
110
|
+
0.8, covering more than **40 %** of the frame while still, or more than
|
|
111
|
+
**25 %** while it spins, rocks, orbits, bounces, wanders, follows a path,
|
|
112
|
+
pulses, grows, emits, cycles or is keyed by the timeline. A pattern that
|
|
113
|
+
only flows one way (`drift` without `bob` or `bidirectional`, `rise`
|
|
114
|
+
without `sway`, `warp`) is judged as still, and is refused when the
|
|
115
|
+
stripes crossing a point make more than 3 light-dark cycles a second
|
|
116
|
+
(speed ÷ pitch) over 25 % of the frame. The scene is drawn by the
|
|
117
|
+
analytic perception raster at 216 cells on the short side and sampled at
|
|
118
|
+
t = 5 s and up to five points where timeline keys settle.
|
|
119
|
+
|
|
120
|
+
**Not checked.** Nothing measures these; an author has to:
|
|
121
|
+
|
|
122
|
+
- Fast motion of large shapes other than regular stripes. Speeds are capped,
|
|
123
|
+
but a big, bright shape sweeping across a dark frame is not analysed for
|
|
124
|
+
flashes (the flash check reads timeline paint keys only).
|
|
125
|
+
- Stripes finer than about 8 px at 1080p (1/135 of the short side), which
|
|
126
|
+
the pattern raster averages to grey, and stripes coarser than 0.15 of the
|
|
127
|
+
short side, which it does not count as a pattern. Text (`text`,
|
|
128
|
+
`textBlock`) is left out of the pattern check: its glyphs are not drawn.
|
|
129
|
+
- A pattern that appears only between the sampled times, or only on a
|
|
130
|
+
viewport other than the one checked (1920×1080 unless the host passes
|
|
131
|
+
another), and a pattern formed only where a sequence's bed and segment
|
|
132
|
+
overlap when each is checked on its own.
|
|
133
|
+
- Saturated-red flashes, beyond the validator's approximate red check.
|
|
134
|
+
|
|
88
135
|
## Validation semantics
|
|
89
136
|
|
|
90
137
|
Two validators exist with deliberately different strictness:
|
|
@@ -96,6 +143,13 @@ Two validators exist with deliberately different strictness:
|
|
|
96
143
|
|
|
97
144
|
Author against the JSON Schema; ship through the runtime validator.
|
|
98
145
|
|
|
146
|
+
Two slips have one unambiguous reading, so `validateSpec()` repairs them in
|
|
147
|
+
place instead of ignoring them: a sprite with `colors` but no `color`
|
|
148
|
+
gets `color: colors[0]`, and a `size` written inside an emoji or text `sprite`
|
|
149
|
+
moves up to the layer (with a `misplaced-property` warning; if the layer
|
|
150
|
+
already has a `size`, the layer's value wins and the sprite's is ignored).
|
|
151
|
+
The JSON Schema still rejects `sprite.size`.
|
|
152
|
+
|
|
99
153
|
An unknown field that is another motion tool's vocabulary gets a pointer in
|
|
100
154
|
its `unknown-property` warning: `delay` on a layer names `life.enter`,
|
|
101
155
|
`duration` on a timeline key names `dur`, `fps` or `bpm` at the top level
|
|
@@ -142,6 +196,15 @@ and only `units: "px"` specs get it verbatim. See **Scale** below.
|
|
|
142
196
|
`ghosting` paints each frame over a faded copy of the previous one instead of
|
|
143
197
|
clearing: moving entities leave decaying after-images (Mystify smears, Matrix
|
|
144
198
|
trails, long-exposure light). 0.85–0.95 is the useful range; 0 (default) is off.
|
|
199
|
+
Only the background is faded in: every layer still paints in full each frame on
|
|
200
|
+
top, so wherever an opaque sprite (alpha 1) sits it repaints over the smear —
|
|
201
|
+
a static opaque sprite looks the same with ghosting as without, and only
|
|
202
|
+
motion leaves a trail. A static translucent sprite of alpha *a* over
|
|
203
|
+
bare ground builds up to an effective alpha of *a* / (1 − *g*(1 − *a*)) at
|
|
204
|
+
`ghosting` *g* — 0.5 at *g* = 0.9 reads as 0.91 — so a soft glow brightens
|
|
205
|
+
under ghosting rather than smearing. That figure is the 60 fps steady state,
|
|
206
|
+
the rate seeks and the warm-up replay paint at; a live display fades by
|
|
207
|
+
*g*^(Δt / 16.7 ms) per frame, so at 120 Hz the build-up is a little stronger.
|
|
145
208
|
|
|
146
209
|
`density` declares what the emptiness or the crowding means. `sparse` says
|
|
147
210
|
the scene is *meant* to be almost empty — one faint mark on a dark ground,
|
|
@@ -171,7 +234,7 @@ exceeds 2 %, or a `dense` one that would have tripped `sparse-scene`, gets a
|
|
|
171
234
|
| `scale` | 0.5..8 | — | noise features across the **short side** of the viewport (so a 16:9 frame shows more of the same field than a square one) |
|
|
172
235
|
| `octaves` | int 1..4 | 2 | layers of detail; each halves in amplitude and doubles in frequency |
|
|
173
236
|
| `warp` | 0..1 | 0 | domain warp: 0 round blobs, 1 the folded, smeared contours of a weather chart |
|
|
174
|
-
| `quantize` | 0 or int 2..8 | `bands.length` | posterise into that many levels, one read off the `bands` ramp per level — hard
|
|
237
|
+
| `quantize` | 0 or int 2..8 | `bands.length` | posterise into that many levels, one read off the `bands` ramp per level — hard-edged contours (Terrain, Sonar), stepped at the field raster's resolution (see **Raster and cache**). `bands.length` is exactly one band per level; more levels step through the ramp. `0` interpolates the ramp smoothly (Aura, Mist) |
|
|
175
238
|
| `bands` | 2..8 hex | — | the ramp, low end of the field to high |
|
|
176
239
|
| `drift` | `{ period ≥ 10000, amount 0..1 }` | none | the field's slow **animate**: the sample domain travels a circle of radius `amount` feature units once per `period` ms, so the contours crawl and the field loops exactly. `amount` defaults to 0.3 |
|
|
177
240
|
| `seed` | number | the spec seed | the field's own seed — change it to get a different field without re-seating an entity |
|
|
@@ -190,8 +253,11 @@ exceeds 2 %, or a `dense` one that would have tripped `sparse-scene`, gets a
|
|
|
190
253
|
**Raster and cache.** The renderer samples the field into a low-res raster
|
|
191
254
|
— 96 px on the short side, the long side by aspect; 48 px on the `basic` /
|
|
192
255
|
`minimal` capability tiers (`capabilityTier` on the mount context) — and
|
|
193
|
-
draws it scaled to the canvas
|
|
194
|
-
|
|
256
|
+
draws it scaled to the canvas: nearest-neighbour for quantised bands
|
|
257
|
+
(`quantize` ≥ 2), smoothed for `quantize: 0`. The contours are therefore
|
|
258
|
+
hard but not fine — each raster pixel is about 11 px on a 1080p screen (22 px
|
|
259
|
+
on the low tiers), so a band edge is a staircase of that pitch, not a smooth
|
|
260
|
+
line. Draw a line with a layer when it must be crisp. A static field is
|
|
195
261
|
sampled **once per mount**; a drifting one is resampled only when `t`
|
|
196
262
|
crosses a 100 ms bucket, so ten times a second at most, however high the
|
|
197
263
|
frame rate. Steering any knob recomputes on the next frame.
|
|
@@ -289,7 +355,7 @@ on the sprite tier (the `basic` tier rule).
|
|
|
289
355
|
| `count` | int 1..400 | — | entities in this layer. **At most 800 entities across all layers** (and ≤ 36 layers): the validator rejects a spec whose counts sum past 800, and density scaling never takes a screen past it |
|
|
290
356
|
| `sprite` | SpriteSpec | — | what each entity looks like |
|
|
291
357
|
| `motion` | MotionSpec | — | how entities move |
|
|
292
|
-
| `size` | `[min,max]` > 0 | sprite-dependent | font size for emoji/text; ignored for shaped sprites |
|
|
358
|
+
| `size` | `[min,max]` > 0 | sprite-dependent | font size for emoji/text; ignored for shaped sprites. A **layer** field: it goes next to `count`, never inside `sprite` |
|
|
293
359
|
| `wrap` | boolean | `true` | wrap to the opposite edge when leaving the viewport |
|
|
294
360
|
| `flip` | boolean | `false` | mirror the sprite to face its heading |
|
|
295
361
|
| `alpha` | `[min,max]` 0..1 | `[1,1]` | per-entity opacity range |
|
|
@@ -303,7 +369,7 @@ on the sprite tier (the `basic` tier rule).
|
|
|
303
369
|
| `trail` | `{length ≤ 5000, fade?}` | none | analytic afterglow trail; `length` is **milliseconds** of history (max 5000), `fade` a number 0..1 (not a boolean — `links.falloff` in the next row is the boolean). Echoes of a shaped sprite (`polygon`, `stroke`, `rect`, `ring`, `streak`, `bar`) are the sprite's own shape as it was then, shrinking toward the tail; `circle`, `emoji` and text trail discs. A hard `circle`'s trail is one continuous ribbon tapering behind it, so a quick path draws a line rather than a string of dots (a layer whose ribbons would cover more than a tenth of the screen draws its echo discs instead, to bound the cost); a `soft` circle's echoes are filled in where they would part, within a small per-layer budget |
|
|
304
370
|
| `links` | see below | none | inter-entity lines |
|
|
305
371
|
| `layout` | `{type: "grid", columns?, jitter?}` \| `{type: "list", gap?}` \| `{type: "table", columns, gap?}` (`gap`: a number or `{x?, y?}`) | scatter | `grid`: cells fill `region`, `jitter` scalar or `{x?, y?}` 0..1 per axis. **`list` / `table`: data layouts** — entities in reading order from `position` (any count) or centred in `region`, `gap` apart (viewport units of `min(w,h)`, default 0.06; a number or `{x?, y?}` — a list is one column, so only its `y`, the row pitch, applies). **`gap` is the pitch between cell anchors, not the space between cells** — a label wider than the column pitch runs into its neighbour (the `layout-text-overlap` advisory names the gap that clears it), so size `gap.x` to the widest label. Text/emoji take `strings[i]` / `glyphs[i]` **in order**, so N labels are one layer; a shaped sprite's palette (`colors[i]` on `bar`, `rect`, `circle`…) is read in order too. **Text and emoji have no `colors`**: a `text` layer paints every label in its one `color` (a `colors` on it is an unknown property, ignored with a warning) — use one layer per label colour |
|
|
306
|
-
| `life` | `{enter?, exit?, fade?, stagger?}` ms | always on | act structure: fade the layer in at `enter`, out at `exit`. `stagger` shifts entity *i*'s whole envelope (enter and exit) by *i* × `stagger` in entity order (reading order for `list` / `table` / `grid`), so a row of labels builds one after another; paint, no RNG draws. Density scaling changes the count, so the build takes (count − 1) × `stagger`. **Native:** rendered |
|
|
372
|
+
| `life` | `{enter?, exit?, fade?, stagger?}` ms | always on | act structure: fade the layer in at `enter`, out at `exit`. The times are **scene time from mount** (a sequence segment's own start), not the `timeline` clock: under `timeline.loop` the envelope plays once and does not replay on later laps, so an `exit` stays gone (`adviseSpec` notes it as `life-runs-once`); key `opacity` in the timeline for a build that repeats every lap. `stagger` shifts entity *i*'s whole envelope (enter and exit) by *i* × `stagger` in entity order (reading order for `list` / `table` / `grid`), so a row of labels builds one after another; paint, no RNG draws. Density scaling changes the count, so the build takes (count − 1) × `stagger`. **Native:** rendered |
|
|
307
373
|
| `emit` | `{every ≥ 1000, life ≥ 500, jitter?, grow?}` (`life ≤ every`) | always lit | **sparse events**: each entity is dark except a `life`-ms window every `every` ms, fading in fast and out slow; `jitter` 0 staggers entities evenly (one event at a time while `life ≤ every / count`), 1 scatters the offsets (a fixed sequence, not seeded — declaring `emit` disturbs no other draw); `grow: [from, to]` scales size across the window — expansion rather than travel |
|
|
308
374
|
| `clock` | `{phase?, rate?}` | seeded phases | **phase-lock**: `pulse`, `grow` and `cycle` share one phase (turns, 0..1) and run at `rate` × time; two layers with the same clock breathe in step. Clocked periods must satisfy `period / rate ≥ 1000` |
|
|
309
375
|
| `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`) |
|
|
@@ -324,7 +390,14 @@ for `"nearest"` and `"random"`; a `"chain"` may omit both, except that
|
|
|
324
390
|
### `sprite` (one of)
|
|
325
391
|
|
|
326
392
|
- `{ "kind": "emoji", "glyphs": ["🐟", "🐠"], "cycle": { "period": 800 } }` —
|
|
327
|
-
glyph picked per entity (seeded); optional `cycle` rotates variants over time
|
|
393
|
+
glyph picked per entity (seeded); optional `cycle` rotates variants over time.
|
|
394
|
+
The glyph size is the layer's `size`, beside `count`, not a sprite field:
|
|
395
|
+
|
|
396
|
+
```jsonc
|
|
397
|
+
{ "count": 12, "size": [0.03, 0.06],
|
|
398
|
+
"sprite": { "kind": "emoji", "glyphs": ["🐟"] },
|
|
399
|
+
"motion": { "type": "drift", "speed": [0.02, 0.05], "bidirectional": true } }
|
|
400
|
+
```
|
|
328
401
|
- `{ "kind": "text", "strings": ["HELLO"], "color": "#e6e8ef", "font": "bold monospace", "align": "center", "baseline": "middle", "maxWidth": 300, "cycle": ..., "role": "read" }`
|
|
329
402
|
— a `font` **with** a px size keeps that shorthand, its number rescaled to the
|
|
330
403
|
viewport (verbatim only under `units: "px"`); a family/weight only
|
|
@@ -535,8 +608,12 @@ for `"nearest"` and `"random"`; a `"chain"` may omit both, except that
|
|
|
535
608
|
checking on. Absent, or `"atmosphere"`, the layer is texture — a haiku
|
|
536
609
|
fading in a corner, dim labels on a wall board, the `dev-dashboard`'s
|
|
537
610
|
2.6:1 telemetry — and `adviseSpec` says nothing about how readable it is,
|
|
538
|
-
exactly as before the field existed
|
|
539
|
-
|
|
611
|
+
exactly as before the field existed. `"atmosphere"` goes one step further
|
|
612
|
+
and also opts the layer out of the layout checks — `text-overlap`,
|
|
613
|
+
`layout-text-overlap` and the validator's `list-length-mismatch` — since
|
|
614
|
+
texture words layered over a title or packed into a dense wall are the
|
|
615
|
+
look, not a collision; undeclared text is still checked for layout.
|
|
616
|
+
`"read"` says the words must be read from
|
|
540
617
|
across the room, and opts that layer into two advisories:
|
|
541
618
|
`text-legibility` fires when the text colour's WCAG ratio falls below
|
|
542
619
|
**4.5:1** against the background sampled at the box centre (gradient stops
|
|
@@ -1111,14 +1188,29 @@ moves and scales its positions and sizes (sprite rotation is not modelled).
|
|
|
1111
1188
|
its own × its group's — is 0 in the base scene and at every key boundary of
|
|
1112
1189
|
its `timeline` never shows, so it is not judged and its entities do not count
|
|
1113
1190
|
toward `dense-scene`; coverage is weighed by paint opacity) (also on
|
|
1114
|
-
`perceiveScene().advisories`).
|
|
1191
|
+
`perceiveScene().advisories`). Count-based advisories (`dense-scene`,
|
|
1192
|
+
`full-coherence`) read the **declared** `count`, not the density-scaled
|
|
1193
|
+
build, so a small preview and a 4K screen agree on them. `giant-entity` flags an emoji/text layer
|
|
1194
|
+
whose glyphs come out taller than the viewport's min dimension — usually
|
|
1195
|
+
px numbers in a viewport-unit spec (`size: [20, 40]` is 21,600 px at
|
|
1196
|
+
1080p), which blankets the frame and otherwise reads as healthy coverage.
|
|
1197
|
+
Three of the advisories are **spatial**, for static text:
|
|
1115
1198
|
`text-off-screen` (a text/textBlock box crosses the viewport edge by more
|
|
1116
1199
|
than 1%), `text-overlap` (two static text layers share more than 10% of
|
|
1117
1200
|
the smaller box; it carries both boxes as `boxes: [{x, y, w, h}]` in
|
|
1118
1201
|
viewport fractions so the fix needs no re-derivation) and
|
|
1119
1202
|
`layout-text-overlap` (two cells of one `list`/`table` layer do — its
|
|
1120
1203
|
`gap` pitch is narrower than its labels; the message names the `gap` that
|
|
1121
|
-
clears them, and it carries the two boxes too).
|
|
1204
|
+
clears them, and it carries the two boxes too). A pair `text-overlap` finds
|
|
1205
|
+
at the sample instant is then checked over the piece: at that instant and
|
|
1206
|
+
at every key boundary of a `timeline` that keys the text, a box counts only
|
|
1207
|
+
while it shows (paint opacity in force, an effective `transform` scale above
|
|
1208
|
+
2 % on both axes, a `reveal` that has begun, on screen), and the pair is
|
|
1209
|
+
reported only if both show together while they overlap — a cross-fade
|
|
1210
|
+
between two cards in one place is not a collision (the message then names
|
|
1211
|
+
the time, `both visible at N ms`). Text declared `role: "atmosphere"` is
|
|
1212
|
+
texture: it is left out of `text-overlap` and `layout-text-overlap`, and the
|
|
1213
|
+
validator's `list-length-mismatch` does not fire on it. Boxes come from the
|
|
1122
1214
|
same character-class width table the textBlock line-breaker uses, so they
|
|
1123
1215
|
are estimates of the renderer's own layout — a caption that fails these
|
|
1124
1216
|
will look wrong on the wall; one that passes may still sit a few px off.
|
|
@@ -1143,6 +1235,12 @@ moves and scales its positions and sizes (sprite rotation is not modelled).
|
|
|
1143
1235
|
their densest one-second window, flicker at least 2 times a second over a
|
|
1144
1236
|
quarter of the frame — a full-frame step at the 200 ms key floor is 2.5, which
|
|
1145
1237
|
the publish-time WCAG gate (more than 3) lets through.
|
|
1238
|
+
`life-runs-once` (informational) notes a layer whose `life` envelope sits
|
|
1239
|
+
under a looping timeline: `life` runs on scene time from mount, so it plays
|
|
1240
|
+
once and never replays when the lap wraps.
|
|
1241
|
+
`uniform-motion` fires when every entity of a moving layer travels at
|
|
1242
|
+
nearly the same on-screen speed; an orbit's is its angular speed × its
|
|
1243
|
+
radius, so one angular speed over a spread of radii is not uniform.
|
|
1146
1244
|
`loop-seam` fires when a scene declares a looping timeline and the rest
|
|
1147
1245
|
of the scene repeats too, but on a period the lap does not divide — so
|
|
1148
1246
|
one lap can never come round to its own first frame (it names the period
|
|
@@ -1273,7 +1371,8 @@ change, with the baseline regenerated. **Native:** tvOS lerps the two
|
|
|
1273
1371
|
segments frame by frame on the chain root's seed and honours `text`. Morph requires structurally
|
|
1274
1372
|
identical adjacent segments (same `structuralSignature`); if they differ, the
|
|
1275
1373
|
engine falls back to cut and the validator emits a `morph-structural-mismatch`
|
|
1276
|
-
warning
|
|
1374
|
+
warning that names the fields that differ (e.g. `layers[2].layout.type: grid →
|
|
1375
|
+
list`; the first three, then a count of the rest). When the segments are structural twins whose only differences are
|
|
1277
1376
|
values morph steps (a text-only change), the morph runs but every frame of it
|
|
1278
1377
|
shows the incoming segment — it reads as a cut — and the validator emits a
|
|
1279
1378
|
`morph-nothing-morphable` warning (never an error; stored sequences stay
|
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# @idle-screens/schema
|
|
2
2
|
|
|
3
|
-
Declarative, agent-authorable saver format for idle-screens. Validate a data spec, then compile it into a runnable, seeded,
|
|
3
|
+
Declarative, agent-authorable saver format for idle-screens. Validate a data spec, then compile it into a runnable, seeded SaverPlugin with bounded motion and work (FORMAT.md, "Safety invariants", says what is guaranteed and what is checked at publish).
|
|
4
4
|
|
|
5
5
|
See the [idle-screens repository](https://github.com/shaaaaawn/idle-screens) for full documentation.
|
package/dist/index.d.ts
CHANGED
|
@@ -706,7 +706,9 @@ interface TextNumber {
|
|
|
706
706
|
* words must be readable from across the room and opts the layer into
|
|
707
707
|
* `adviseSpec`'s legibility advisories (`text-legibility`, `text-safe-area`).
|
|
708
708
|
* `atmosphere` says the text is texture (a haiku fading in a corner, dim
|
|
709
|
-
* labels on a wall board)
|
|
709
|
+
* labels on a wall board): silent on legibility, like absent, and also left
|
|
710
|
+
* out of the layout checks (`text-overlap`, `layout-text-overlap`,
|
|
711
|
+
* `list-length-mismatch`) that still judge undeclared text. Changes no
|
|
710
712
|
* pixel anywhere, on any client; never part of the structural signature.
|
|
711
713
|
*/
|
|
712
714
|
type TextRole = 'read' | 'atmosphere';
|
|
@@ -1752,6 +1754,15 @@ declare const STEER_EASES: ReadonlySet<string>;
|
|
|
1752
1754
|
* are baked at build time). Colour/alpha/pulse-only changes redraw in place.
|
|
1753
1755
|
*/
|
|
1754
1756
|
declare function structuralSignature(spec: SaverSpec): string;
|
|
1757
|
+
/**
|
|
1758
|
+
* What makes two scenes differ structurally, named field by field — e.g.
|
|
1759
|
+
* `layers[2].layout.type: grid → list` — in the order `structuralSignature`
|
|
1760
|
+
* reads them. Empty exactly when the signatures agree. A layer whose sprite
|
|
1761
|
+
* kind changed reports the kind alone: every sprite field differs with it.
|
|
1762
|
+
*/
|
|
1763
|
+
declare function structuralDifferences(a: SaverSpec, b: SaverSpec): string[];
|
|
1764
|
+
/** `structuralDifferences` as one phrase for a message: the first few, then a count of the rest. */
|
|
1765
|
+
declare function describeStructuralDifferences(a: SaverSpec, b: SaverSpec, max?: number): string;
|
|
1755
1766
|
|
|
1756
1767
|
/**
|
|
1757
1768
|
* Non-blocking advisory warnings for a valid spec. Does NOT replace validateSpec —
|
|
@@ -1773,11 +1784,15 @@ declare function adviseSpec(spec: SaverSpec, viewport?: {
|
|
|
1773
1784
|
* potentially different seeds: `seed` drives the ink layers' own entity
|
|
1774
1785
|
* stream, `backgroundSeed` samples the bed's field. Defaults to `seed` —
|
|
1775
1786
|
* every other caller has one scene and one seed for both.
|
|
1787
|
+
*
|
|
1788
|
+
* `until` bounds the timeline times the over-time checks sample (a
|
|
1789
|
+
* sequence segment's `duration`): a segment never plays past its end.
|
|
1776
1790
|
*/
|
|
1777
1791
|
opts?: {
|
|
1778
1792
|
t?: number;
|
|
1779
1793
|
seed?: number;
|
|
1780
1794
|
backgroundSeed?: number;
|
|
1795
|
+
until?: number;
|
|
1781
1796
|
}): SpecWarning[];
|
|
1782
1797
|
/**
|
|
1783
1798
|
* Non-blocking advisory warnings for a validated sequence envelope.
|
|
@@ -2002,7 +2017,13 @@ interface LuminanceGridOptions extends PerceiveOptions {
|
|
|
2002
2017
|
* sampled at its rest position. Good enough to perceive composition, focus,
|
|
2003
2018
|
* balance — and now smear.
|
|
2004
2019
|
*/
|
|
2005
|
-
declare function luminanceGrid(spec: SaverSpec, opts?: LuminanceGridOptions
|
|
2020
|
+
declare function luminanceGrid(spec: SaverSpec, opts?: LuminanceGridOptions,
|
|
2021
|
+
/**
|
|
2022
|
+
* @internal The largest grid side. 200 for every public caller (a request
|
|
2023
|
+
* cannot ask for more); the pattern-hazard check raises it to resolve
|
|
2024
|
+
* stripes finer than a 200-cell grid can (see `pattern-hazard.ts`).
|
|
2025
|
+
*/
|
|
2026
|
+
maxSide?: number): LuminanceGrid;
|
|
2006
2027
|
/**
|
|
2007
2028
|
* The luminance-grid cells each layer can paint (same viewport, seed and
|
|
2008
2029
|
* grid as `luminanceGrid` with these options), for the timeline flash gate's
|
|
@@ -2381,6 +2402,88 @@ interface FlashSamplingForecast {
|
|
|
2381
2402
|
*/
|
|
2382
2403
|
declare function flashSamplingForecast(spec: SaverSpec): FlashSamplingForecast;
|
|
2383
2404
|
|
|
2405
|
+
/** Raster cells across the viewport's short side. */
|
|
2406
|
+
declare const PATTERN_GRID_SHORT_SIDE = 216;
|
|
2407
|
+
/** "More than five light-dark pairs": six stripes of one tone, each between stripes of the other. */
|
|
2408
|
+
declare const PATTERN_MIN_PAIRS = 6;
|
|
2409
|
+
/** A still pattern is harmful above this fraction of the screen. */
|
|
2410
|
+
declare const PATTERN_STATIONARY_AREA = 0.4;
|
|
2411
|
+
/** A moving, oscillating, rotating or reversing pattern is harmful above this fraction. */
|
|
2412
|
+
declare const PATTERN_MOVING_AREA = 0.25;
|
|
2413
|
+
/** Forecast flicker above {@link FLASH_MAX_PER_SECOND} a second over this fraction fails. */
|
|
2414
|
+
declare const PATTERN_FLICKER_AREA = 0.25;
|
|
2415
|
+
/** Most sample times one check draws (t = 5 s plus timeline settle points). */
|
|
2416
|
+
declare const PATTERN_MAX_SAMPLE_TIMES = 6;
|
|
2417
|
+
/**
|
|
2418
|
+
* Stripes coarser than this (fraction of the short side) are not counted as
|
|
2419
|
+
* discernible stripes. BT.1702 sets no upper bound on stripe size; this one
|
|
2420
|
+
* is a judgment call, pending review: six stripes this coarse span most of
|
|
2421
|
+
* the frame's width (a row of tiles, a shelf of windows) rather than reading
|
|
2422
|
+
* as a grating. Lowering it to Infinity applies the standard's letter.
|
|
2423
|
+
*/
|
|
2424
|
+
declare const PATTERN_MAX_PITCH = 0.15;
|
|
2425
|
+
/** A patch of runs must reach this many pitches across its stripes to be a pattern (a row of six objects is one pitch deep). */
|
|
2426
|
+
declare const PATTERN_MIN_EXTENT = 2;
|
|
2427
|
+
type PatternHazardKind = 'pattern' | 'moving-pattern' | 'flicker';
|
|
2428
|
+
interface PatternHazardReport {
|
|
2429
|
+
/** Refuse: a pattern over its BT.1702 area limit, or forecast flicker over 25 % of the frame. */
|
|
2430
|
+
fails: boolean;
|
|
2431
|
+
/** Which rule failed (null when it passes). */
|
|
2432
|
+
kind: PatternHazardKind | null;
|
|
2433
|
+
/** Scene time (ms) of the finding, or of the most patterned sample when it passes; null for a scene with no stripes at all. */
|
|
2434
|
+
at: number | null;
|
|
2435
|
+
/** Fraction of the frame covered by discernible stripes (the worst sample). */
|
|
2436
|
+
area: number;
|
|
2437
|
+
/** Of it, the fraction drawn by moving layers (measured only when `area` is between the two limits; else 0). */
|
|
2438
|
+
movingArea: number;
|
|
2439
|
+
/** Fraction of the frame forecast to flicker above 3 Hz (measured only when needed; else 0). */
|
|
2440
|
+
flickerArea: number;
|
|
2441
|
+
/** Fastest forecast flicker in a patterned region, flashes a second (0 when not measured). */
|
|
2442
|
+
flickerHz: number;
|
|
2443
|
+
/** Most stripes of one tone in any single run (a run qualifies at {@link PATTERN_MIN_PAIRS}). */
|
|
2444
|
+
pairs: number;
|
|
2445
|
+
/** That run's pitch, as a fraction of the viewport's short side (null without a qualifying run). */
|
|
2446
|
+
pitch: number | null;
|
|
2447
|
+
/** That run's mean light-dark swing in relative luminance (0..1, linear light). */
|
|
2448
|
+
contrast: number;
|
|
2449
|
+
/** Rasters drawn: the check's cost in units of one perception frame. */
|
|
2450
|
+
rasters: number;
|
|
2451
|
+
/** With `fails`: why, worded for the author, with what would pass. */
|
|
2452
|
+
message?: string;
|
|
2453
|
+
}
|
|
2454
|
+
interface PatternHazardOptions {
|
|
2455
|
+
/** Default 1920×1080. */
|
|
2456
|
+
viewport?: {
|
|
2457
|
+
width: number;
|
|
2458
|
+
height: number;
|
|
2459
|
+
};
|
|
2460
|
+
seed?: number;
|
|
2461
|
+
/** Sample times (ms) instead of t = 5 s plus the timeline's settle points. */
|
|
2462
|
+
times?: number[];
|
|
2463
|
+
}
|
|
2464
|
+
type PatternMotion = 'static' | 'flow' | 'moving';
|
|
2465
|
+
/**
|
|
2466
|
+
* How a layer's drawing changes over time, as BT.1702 sorts it: still,
|
|
2467
|
+
* flowing one way (`drift` without `bob` or `bidirectional`, `rise` without
|
|
2468
|
+
* `sway`, `warp`), or moving in any other way — spin, rock, orbit, bounce,
|
|
2469
|
+
* wander, path, pulse, grow, emit, a sprite `cycle`, or any timeline key on
|
|
2470
|
+
* the layer or its group. Anything this does not recognise is `moving`.
|
|
2471
|
+
*/
|
|
2472
|
+
declare function layerPatternMotion(spec: SaverSpec, index: number): PatternMotion;
|
|
2473
|
+
/**
|
|
2474
|
+
* t = 5 s, plus each time a timeline key settles (where a keyed-in pattern
|
|
2475
|
+
* is at its fullest), within the first lap of a looping timeline. Past
|
|
2476
|
+
* {@link PATTERN_MAX_SAMPLE_TIMES} the settle times are subsampled evenly,
|
|
2477
|
+
* always keeping the last.
|
|
2478
|
+
*/
|
|
2479
|
+
declare function patternSampleTimes(spec: SaverSpec): number[];
|
|
2480
|
+
/**
|
|
2481
|
+
* BT.1702 spatial-pattern check for one scene. Pure and deterministic (same
|
|
2482
|
+
* spec, seed and viewport ⇒ same report). Fails closed nowhere: every
|
|
2483
|
+
* finding is a measurement. See the module comment for what it does not see.
|
|
2484
|
+
*/
|
|
2485
|
+
declare function patternHazardReport(spec: SaverSpec, opts?: PatternHazardOptions): PatternHazardReport;
|
|
2486
|
+
|
|
2384
2487
|
/**
|
|
2385
2488
|
* The `field` background sampler — seeded, dependency-free value noise that
|
|
2386
2489
|
* is a PURE function of `(u, v, t, config, seed)`.
|
|
@@ -3314,5 +3417,5 @@ declare function pathFrame(s: PolygonSprite, r: number, w: number, h: number): {
|
|
|
3314
3417
|
uniform: boolean;
|
|
3315
3418
|
};
|
|
3316
3419
|
|
|
3317
|
-
export { AQUARIUM_SPEC, AURORA_SPEC, COHESION_T, COMETS_SPEC, CONSTELLATION_SPEC, DASHBOARD_SPEC, DEFAULT_KEY_DUR, DITHER_ALPHA, DITHER_TILE, EXAMPLE_BY_ID, EXAMPLE_SEQUENCES, EXAMPLE_SPECS, FACETS_SPEC, FIELD_BUCKET_MS, FIELD_DRIFT_DEFAULT_AMOUNT, FIELD_RASTER_SHORT_SIDE, FIELD_RASTER_SHORT_SIDE_LOW, FINISH_ANIMATE_HZ, GRAIN_ALPHA, GRAIN_TILE, HAIKU_SPEC, LANTERNS_SPEC, LIMITS, LOBBY_TALK_SPEC, MATRIX_RAIN_SPEC, MAX_PATH_POINTS, MAX_SCENE_PERIOD, MOTION_FIELDS, MURMUR_SPEC, NOSTALGHIA_CANDLE_SPEC, ORRERY_SPEC, PHASE_DUET_SPEC, PINGS_SPEC, POLYGONS_SPEC, PROCESSION_SPEC, RAIN_SPEC, RELAY_BOARD_SPEC, SAKURA_SPEC, SCHEMA_EXAMPLES, SCHEMA_VERSION, SEAMLESS_PERIOD_KINDS, SHARD_FALL_SPEC, SIGNAL_BOARD_SPEC, SNOWFALL_SPEC, SPARSE_NIGHT_SPEC, STEER_EASES, THERMAL_FIELD_SPEC, THREE_MOVEMENTS_SEQUENCE, TYPED_CUE_SPEC, WARP_MAX_SCALE, WARP_NEAR, WARP_TUNNEL_SPEC, WEB_WORK_SPEC, adviseSequence, adviseSpec, alphaAt, applyDeltasShared, applyDeltasToSpec, assertValidSequence, assertValidSpec, bayerMatrix, bayerTilePixels, bedRenderSeed, breakTextBlock, buildEntities, buriedAreaOf, canMorph, canWrapMorph, canonicalSpecPath, cohesionOf, compileSaver, compileSequence, createFinishPass, createSceneCanvas, describeScene, diffScenes, dominanceRanking, drawSpan, easeGlide, easeSmooth, emitEnvelope, emitWindow, entityScales, fieldAt, fieldOctaves, fieldRgb, fieldRgbAt, fieldSampleTime, finishStrength, flashSamplingForecast, flattenPath, grainOffset, grainTilePixels, graphemeClusters, hasHotSwapSequence, headingAt, hexToRgb255, ignoredPropertyPaths, isCombiningKana, isFullWidth, isPathPolygon, latticeHash, layerCellFootprints, layerCohesion, lerpPathD, lerpSpec, lerpValue, lifeAlphaAt, lifeStaggerAt, linkEdges, linkMaxDistPx, linkPairs, luminanceGrid, manifestFor, morphChainRoot, morphNothingMorphable, motionStats, nextKeyAfter, normalizeSeed, pathFrame, pathParts, perceiveScene, perceiveSequenceFrame, periodWithoutTimeline, pickScreenOp, positionAt, presentWithFinish, readSpecPath, renderBrailleMap, renderDensityMap, resolveSegment, resolveSpecPath, resolveTimelineAt, revealState, rgb255Luma, rotationAt, sceneColour, scenePeriod, seamCauseOf, seamsWorthWarning, segmentRenderSeed, segmentStart, sequenceSteerablePaths, sequenceSwapCompatible, sequenceWantsFinish, sizeAt, spinSymmetry, spriteIndexAt, spriteVariants, steerablePaths, strokeDrawnSamples, strokeLines, structuralSignature, textBlockAnchorOffset, textBlockText, textMetricsClassFor, textSprites, textStringsDiffer, textStringsOf, textWidthEm, timelineFlashReport, timelineLocalTime, timelineSampleTimes, timelineSampleTimesAfter, timelineTracks, trimLines, trimPolyline, validateSequence, validateSpec, validateSpecPaths, wipeMask, withoutTimeline };
|
|
3318
|
-
export type { BackgroundDrift, BackgroundSpec, BandSpec, CapabilityTier, CohesionRead, CycleSpec, DominanceEntry, Entity, FieldBackground, FieldDrift, FinishPass, FinishSpec, FlashSamplingForecast, FlattenedPath, GlideEase, GradientStop, IdleSequence, LayerCohesion, LayerGroup, LayerMotionStats, LayerSnapshot, LayerSpec, LayerTransform, LinkEdge, LuminanceGrid, LuminanceGridOptions, MotionEase, MotionSpec, PathGradient, PathGradientStop, PathPart, PathStroke, PerceiveOptions, PerceiveSequenceOptions, PeriodKind, Placed, ResolveOptions, ResolvedPart, ResolvedSegment, RevealState, SaverSpec, SceneColour, SceneDescription, SceneDiff, ScenePerception, ScenePeriod, SceneSnapshot, SchemaExample, ScreenOp, SequenceExample, SequenceFramePerception, SequenceMountContext, SequenceSaverInstance, SequenceSaverPlugin, SequenceSegment, SequenceTransition, SpecError, SpecMountContext, SpecWarning, SpriteSpec, SteerDelta, StrokeDraw, TextBlockAnchor, TextBlockLine, TextMetricsClass, TextNumber, TextRevealInput, TextRevealSpec, TextRole, TextSpriteInfo, Timeline, TimelineFlashReport, TimelineKey, ValidationResult, WanderOsc, WarningBox, WipeTransition };
|
|
3420
|
+
export { AQUARIUM_SPEC, AURORA_SPEC, COHESION_T, COMETS_SPEC, CONSTELLATION_SPEC, DASHBOARD_SPEC, DEFAULT_KEY_DUR, DITHER_ALPHA, DITHER_TILE, EXAMPLE_BY_ID, EXAMPLE_SEQUENCES, EXAMPLE_SPECS, FACETS_SPEC, FIELD_BUCKET_MS, FIELD_DRIFT_DEFAULT_AMOUNT, FIELD_RASTER_SHORT_SIDE, FIELD_RASTER_SHORT_SIDE_LOW, FINISH_ANIMATE_HZ, GRAIN_ALPHA, GRAIN_TILE, HAIKU_SPEC, LANTERNS_SPEC, LIMITS, LOBBY_TALK_SPEC, MATRIX_RAIN_SPEC, MAX_PATH_POINTS, MAX_SCENE_PERIOD, MOTION_FIELDS, MURMUR_SPEC, NOSTALGHIA_CANDLE_SPEC, ORRERY_SPEC, PATTERN_FLICKER_AREA, PATTERN_GRID_SHORT_SIDE, PATTERN_MAX_PITCH, PATTERN_MAX_SAMPLE_TIMES, PATTERN_MIN_EXTENT, PATTERN_MIN_PAIRS, PATTERN_MOVING_AREA, PATTERN_STATIONARY_AREA, PHASE_DUET_SPEC, PINGS_SPEC, POLYGONS_SPEC, PROCESSION_SPEC, RAIN_SPEC, RELAY_BOARD_SPEC, SAKURA_SPEC, SCHEMA_EXAMPLES, SCHEMA_VERSION, SEAMLESS_PERIOD_KINDS, SHARD_FALL_SPEC, SIGNAL_BOARD_SPEC, SNOWFALL_SPEC, SPARSE_NIGHT_SPEC, STEER_EASES, THERMAL_FIELD_SPEC, THREE_MOVEMENTS_SEQUENCE, TYPED_CUE_SPEC, WARP_MAX_SCALE, WARP_NEAR, WARP_TUNNEL_SPEC, WEB_WORK_SPEC, adviseSequence, adviseSpec, alphaAt, applyDeltasShared, applyDeltasToSpec, assertValidSequence, assertValidSpec, bayerMatrix, bayerTilePixels, bedRenderSeed, breakTextBlock, buildEntities, buriedAreaOf, canMorph, canWrapMorph, canonicalSpecPath, cohesionOf, compileSaver, compileSequence, createFinishPass, createSceneCanvas, describeScene, describeStructuralDifferences, diffScenes, dominanceRanking, drawSpan, easeGlide, easeSmooth, emitEnvelope, emitWindow, entityScales, fieldAt, fieldOctaves, fieldRgb, fieldRgbAt, fieldSampleTime, finishStrength, flashSamplingForecast, flattenPath, grainOffset, grainTilePixels, graphemeClusters, hasHotSwapSequence, headingAt, hexToRgb255, ignoredPropertyPaths, isCombiningKana, isFullWidth, isPathPolygon, latticeHash, layerCellFootprints, layerCohesion, layerPatternMotion, lerpPathD, lerpSpec, lerpValue, lifeAlphaAt, lifeStaggerAt, linkEdges, linkMaxDistPx, linkPairs, luminanceGrid, manifestFor, morphChainRoot, morphNothingMorphable, motionStats, nextKeyAfter, normalizeSeed, pathFrame, pathParts, patternHazardReport, patternSampleTimes, perceiveScene, perceiveSequenceFrame, periodWithoutTimeline, pickScreenOp, positionAt, presentWithFinish, readSpecPath, renderBrailleMap, renderDensityMap, resolveSegment, resolveSpecPath, resolveTimelineAt, revealState, rgb255Luma, rotationAt, sceneColour, scenePeriod, seamCauseOf, seamsWorthWarning, segmentRenderSeed, segmentStart, sequenceSteerablePaths, sequenceSwapCompatible, sequenceWantsFinish, sizeAt, spinSymmetry, spriteIndexAt, spriteVariants, steerablePaths, strokeDrawnSamples, strokeLines, structuralDifferences, structuralSignature, textBlockAnchorOffset, textBlockText, textMetricsClassFor, textSprites, textStringsDiffer, textStringsOf, textWidthEm, timelineFlashReport, timelineLocalTime, timelineSampleTimes, timelineSampleTimesAfter, timelineTracks, trimLines, trimPolyline, validateSequence, validateSpec, validateSpecPaths, wipeMask, withoutTimeline };
|
|
3421
|
+
export type { BackgroundDrift, BackgroundSpec, BandSpec, CapabilityTier, CohesionRead, CycleSpec, DominanceEntry, Entity, FieldBackground, FieldDrift, FinishPass, FinishSpec, FlashSamplingForecast, FlattenedPath, GlideEase, GradientStop, IdleSequence, LayerCohesion, LayerGroup, LayerMotionStats, LayerSnapshot, LayerSpec, LayerTransform, LinkEdge, LuminanceGrid, LuminanceGridOptions, MotionEase, MotionSpec, PathGradient, PathGradientStop, PathPart, PathStroke, PatternHazardKind, PatternHazardOptions, PatternHazardReport, PatternMotion, PerceiveOptions, PerceiveSequenceOptions, PeriodKind, Placed, ResolveOptions, ResolvedPart, ResolvedSegment, RevealState, SaverSpec, SceneColour, SceneDescription, SceneDiff, ScenePerception, ScenePeriod, SceneSnapshot, SchemaExample, ScreenOp, SequenceExample, SequenceFramePerception, SequenceMountContext, SequenceSaverInstance, SequenceSaverPlugin, SequenceSegment, SequenceTransition, SpecError, SpecMountContext, SpecWarning, SpriteSpec, SteerDelta, StrokeDraw, TextBlockAnchor, TextBlockLine, TextMetricsClass, TextNumber, TextRevealInput, TextRevealSpec, TextRole, TextSpriteInfo, Timeline, TimelineFlashReport, TimelineKey, ValidationResult, WanderOsc, WarningBox, WipeTransition };
|