@idle-screens/schema 3.7.0 → 3.9.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 +162 -8
- package/dist/index.d.ts +553 -14
- package/dist/index.js +1423 -38
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/saver-spec.schema.json +121 -0
package/dist/index.js
CHANGED
|
@@ -12,6 +12,8 @@ var LIMITS = {
|
|
|
12
12
|
// ms — caps pulse at 2 Hz (WCAG flash threshold is 3 Hz)
|
|
13
13
|
maxSpin: 360,
|
|
14
14
|
// degrees/sec — one full revolution per second
|
|
15
|
+
maxRotate: 360,
|
|
16
|
+
// degrees — static per-entity rotation, one turn either way
|
|
15
17
|
maxGrowAmp: 0.8,
|
|
16
18
|
// size breathing amplitude cap (fraction of base size)
|
|
17
19
|
maxOrbitSpeed: 180,
|
|
@@ -27,9 +29,19 @@ var LIMITS = {
|
|
|
27
29
|
maxTrailSamples: 24,
|
|
28
30
|
// dots per trail
|
|
29
31
|
minDriftPeriod: 1e4,
|
|
30
|
-
// ms — background drift floor (10 s)
|
|
32
|
+
// ms — background drift floor (10 s), gradient and field alike
|
|
31
33
|
maxDriftAmount: 0.3,
|
|
32
34
|
// fraction of gradient stop shift
|
|
35
|
+
minFieldScale: 0.5,
|
|
36
|
+
// features per short side — below this the field is one flat blob
|
|
37
|
+
maxFieldScale: 8,
|
|
38
|
+
// above this a 96 px raster cell spans more than one feature
|
|
39
|
+
maxFieldOctaves: 4,
|
|
40
|
+
minFieldBands: 2,
|
|
41
|
+
maxFieldBands: 8,
|
|
42
|
+
maxFieldQuantize: 8,
|
|
43
|
+
maxFieldDriftAmount: 1,
|
|
44
|
+
// feature units of domain travel
|
|
33
45
|
maxGhosting: 0.95,
|
|
34
46
|
// frame persistence cap — bounds the seek warm-up replay
|
|
35
47
|
maxGhostReplayFrames: 120,
|
|
@@ -84,16 +96,19 @@ var LIMITS = {
|
|
|
84
96
|
|
|
85
97
|
// src/steer.ts
|
|
86
98
|
var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
87
|
-
var STEERABLE_ROOT_KEYS = /* @__PURE__ */ new Set(["ghosting", "referenceViewport"]);
|
|
99
|
+
var STEERABLE_ROOT_KEYS = /* @__PURE__ */ new Set(["ghosting", "referenceViewport", "finish"]);
|
|
88
100
|
function resolveSpecPath(spec, path) {
|
|
89
101
|
if (!spec || typeof spec !== "object" || !path) return null;
|
|
90
102
|
const parts = path.split(".");
|
|
91
103
|
if (parts.some((p) => UNSAFE_KEYS.has(p))) return null;
|
|
92
104
|
const s = spec;
|
|
93
|
-
if (parts[0] !== "layers" && parts[0] !== "background" &&
|
|
105
|
+
if (parts[0] !== "layers" && parts[0] !== "background" && Array.isArray(s.layers)) {
|
|
94
106
|
const idx = s.layers.findIndex((l) => l && l.key === parts[0]);
|
|
95
|
-
if (idx
|
|
96
|
-
|
|
107
|
+
if (idx !== -1) {
|
|
108
|
+
parts.splice(0, 1, "layers", String(idx));
|
|
109
|
+
} else if (!STEERABLE_ROOT_KEYS.has(parts[0])) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
97
112
|
}
|
|
98
113
|
let node = spec;
|
|
99
114
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
@@ -205,7 +220,11 @@ function morphNothingMorphable(a, b, opts = {}) {
|
|
|
205
220
|
function steerablePaths(spec) {
|
|
206
221
|
if (!spec || typeof spec !== "object") return [];
|
|
207
222
|
const SKIP = /* @__PURE__ */ new Set(["kind", "type", "key", "schemaVersion", "id", "label", "seed", "units", "motionIntensity", "mode", "curve", "layer"]);
|
|
208
|
-
const INDEXED = /* @__PURE__ */ new Set(["layers", "stops"]);
|
|
223
|
+
const INDEXED = /* @__PURE__ */ new Set(["layers", "stops", "bands"]);
|
|
224
|
+
const layers = spec.layers;
|
|
225
|
+
const shadowedRootKeys = new Set(
|
|
226
|
+
Array.isArray(layers) ? layers.map((l) => l && typeof l === "object" ? l.key : void 0).filter((k) => typeof k === "string" && STEERABLE_ROOT_KEYS.has(k)) : []
|
|
227
|
+
);
|
|
209
228
|
const out = [];
|
|
210
229
|
const walk = (node, prefix, key) => {
|
|
211
230
|
if (Array.isArray(node) && INDEXED.has(key)) {
|
|
@@ -219,6 +238,7 @@ function steerablePaths(spec) {
|
|
|
219
238
|
if (node && typeof node === "object") {
|
|
220
239
|
for (const [k, v] of Object.entries(node)) {
|
|
221
240
|
if (SKIP.has(k)) continue;
|
|
241
|
+
if (!prefix && shadowedRootKeys.has(k)) continue;
|
|
222
242
|
walk(v, prefix ? `${prefix}.${k}` : k, k);
|
|
223
243
|
}
|
|
224
244
|
return;
|
|
@@ -241,7 +261,10 @@ function easeSmooth(k) {
|
|
|
241
261
|
function structuralSignature(spec) {
|
|
242
262
|
return JSON.stringify([
|
|
243
263
|
spec.units,
|
|
244
|
-
|
|
264
|
+
// Normalized against the same default every renderer uses — an omitted
|
|
265
|
+
// referenceViewport and an explicit 1080 render identically, so they must
|
|
266
|
+
// hash identically or a same-sizing morph gets rejected as structural.
|
|
267
|
+
spec.referenceViewport ?? LIMITS.referenceViewport,
|
|
245
268
|
spec.layers.map((l) => {
|
|
246
269
|
const s = l.sprite;
|
|
247
270
|
return [
|
|
@@ -267,7 +290,10 @@ function structuralSignature(spec) {
|
|
|
267
290
|
l.sprite.kind === "rect" ? [s.width, s.aspect] : l.sprite.kind === "bar" ? [s.length, s.thickness] : void 0,
|
|
268
291
|
Array.isArray(s.colors) ? s.colors.length : void 0,
|
|
269
292
|
s.colorWeights,
|
|
270
|
-
l.sprite.kind === "emoji" ? [l.sprite.glyphs.length, l.sprite.cycle?.period] : l.sprite.kind === "text" ? [l.sprite.strings.length, l.sprite.cycle?.period] : l.sprite.kind === "textBlock" ? l.sprite.anchor ? [l.sprite.fontSize, l.sprite.anchor] : [l.sprite.fontSize] : void 0
|
|
293
|
+
l.sprite.kind === "emoji" ? [l.sprite.glyphs.length, l.sprite.cycle?.period] : l.sprite.kind === "text" ? [l.sprite.strings.length, l.sprite.cycle?.period] : l.sprite.kind === "textBlock" ? l.sprite.anchor ? [l.sprite.fontSize, l.sprite.anchor] : [l.sprite.fontSize] : void 0,
|
|
294
|
+
// Static rotation is baked into entities (a range draws a seeded
|
|
295
|
+
// angle). Appended only when set — same rule as `anchor` above.
|
|
296
|
+
...l.rotate !== void 0 ? [l.rotate] : []
|
|
271
297
|
];
|
|
272
298
|
})
|
|
273
299
|
]);
|
|
@@ -279,7 +305,8 @@ var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
|
279
305
|
var isStr = (v) => typeof v === "string";
|
|
280
306
|
var isNum = (v) => typeof v === "number" && Number.isFinite(v);
|
|
281
307
|
var isRange = (v) => Array.isArray(v) && v.length === 2 && isNum(v[0]) && isNum(v[1]) && v[0] <= v[1];
|
|
282
|
-
var KNOWN_TOP = /* @__PURE__ */ new Set(["schemaVersion", "id", "label", "seed", "motionIntensity", "density", "units", "referenceViewport", "background", "layers", "ghosting"]);
|
|
308
|
+
var KNOWN_TOP = /* @__PURE__ */ new Set(["schemaVersion", "id", "label", "seed", "motionIntensity", "density", "units", "referenceViewport", "background", "layers", "ghosting", "finish"]);
|
|
309
|
+
var KNOWN_FINISH = /* @__PURE__ */ new Set(["grain", "dither", "animate"]);
|
|
283
310
|
var KNOWN_LAYER = /* @__PURE__ */ new Set([
|
|
284
311
|
"count",
|
|
285
312
|
"sprite",
|
|
@@ -300,7 +327,8 @@ var KNOWN_LAYER = /* @__PURE__ */ new Set([
|
|
|
300
327
|
"layout",
|
|
301
328
|
"life",
|
|
302
329
|
"emit",
|
|
303
|
-
"clock"
|
|
330
|
+
"clock",
|
|
331
|
+
"rotate"
|
|
304
332
|
]);
|
|
305
333
|
var KNOWN_CIRCLE = /* @__PURE__ */ new Set(["kind", "radius", "color", "soft", "colors", "colorWeights"]);
|
|
306
334
|
var KNOWN_RING = /* @__PURE__ */ new Set(["kind", "radius", "color", "width", "colors", "colorWeights"]);
|
|
@@ -327,7 +355,9 @@ var KNOWN_WARP = /* @__PURE__ */ new Set(["type", "speed", "center"]);
|
|
|
327
355
|
var KNOWN_PATH = /* @__PURE__ */ new Set(["type", "points", "duration", "curve", "closed", "scatter"]);
|
|
328
356
|
var KNOWN_BG_SOLID = /* @__PURE__ */ new Set(["type", "color"]);
|
|
329
357
|
var KNOWN_BG_GRADIENT = /* @__PURE__ */ new Set(["type", "stops", "band", "drift"]);
|
|
330
|
-
var
|
|
358
|
+
var KNOWN_BG_FIELD = /* @__PURE__ */ new Set(["type", "scale", "octaves", "warp", "quantize", "bands", "drift", "seed"]);
|
|
359
|
+
var KNOWN_FIELD_DRIFT = /* @__PURE__ */ new Set(["period", "amount"]);
|
|
360
|
+
var LAYER_PROPS_ON_SPRITE = /* @__PURE__ */ new Set(["blend", "trail", "alpha", "pulse", "spin", "grow", "region", "links", "flip", "wrap", "key", "emit", "clock", "life", "layout", "rotate"]);
|
|
331
361
|
function unknownKeys(obj, known) {
|
|
332
362
|
return Object.keys(obj).filter((k) => !known.has(k));
|
|
333
363
|
}
|
|
@@ -383,6 +413,7 @@ function validateSpec(spec) {
|
|
|
383
413
|
warn(k, "unknown-property", `unknown top-level property '${k}' \u2014 will be ignored`);
|
|
384
414
|
}
|
|
385
415
|
if (spec.background !== void 0) validateBackground(spec.background, err, warn);
|
|
416
|
+
if (spec.finish !== void 0) validateFinish(spec.finish, "finish", err, warn);
|
|
386
417
|
if (!Array.isArray(spec.layers) || spec.layers.length === 0) {
|
|
387
418
|
err("layers", "must be a non-empty array");
|
|
388
419
|
} else {
|
|
@@ -399,6 +430,16 @@ function validateSpec(spec) {
|
|
|
399
430
|
function color(v, path, err) {
|
|
400
431
|
if (!isStr(v) || !HEX2.test(v)) err(path, "must be a hex colour like #0a3a52");
|
|
401
432
|
}
|
|
433
|
+
function validateFinish(f, path, err, warn) {
|
|
434
|
+
if (!isObj(f)) return err(path, "must be an object");
|
|
435
|
+
for (const k of ["grain", "dither"]) {
|
|
436
|
+
if (f[k] !== void 0 && (!isNum(f[k]) || f[k] < 0 || f[k] > 1)) err(`${path}.${k}`, "must be a number 0..1");
|
|
437
|
+
}
|
|
438
|
+
if (f.animate !== void 0 && typeof f.animate !== "boolean") err(`${path}.animate`, "must be a boolean");
|
|
439
|
+
for (const k of unknownKeys(f, KNOWN_FINISH)) {
|
|
440
|
+
warn(`${path}.${k}`, "unknown-property", `unknown finish property '${k}' \u2014 will be ignored`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
402
443
|
function validateBackground(bg, err, warn) {
|
|
403
444
|
if (!isObj(bg)) return err("background", "must be an object");
|
|
404
445
|
if (bg.type === "solid") {
|
|
@@ -436,8 +477,47 @@ function validateBackground(bg, err, warn) {
|
|
|
436
477
|
for (const k of unknownKeys(bg, KNOWN_BG_GRADIENT)) {
|
|
437
478
|
warn(`background.${k}`, "unknown-property", `unknown background property '${k}' \u2014 will be ignored`);
|
|
438
479
|
}
|
|
480
|
+
} else if (bg.type === "field") {
|
|
481
|
+
validateFieldBackground(bg, err, warn);
|
|
439
482
|
} else {
|
|
440
|
-
err("background.type", "must be solid | gradient");
|
|
483
|
+
err("background.type", "must be solid | gradient | field");
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function validateFieldBackground(bg, err, warn) {
|
|
487
|
+
if (!isNum(bg.scale) || bg.scale < LIMITS.minFieldScale || bg.scale > LIMITS.maxFieldScale) {
|
|
488
|
+
err("background.scale", `must be a number ${LIMITS.minFieldScale}..${LIMITS.maxFieldScale} (features across the short side)`);
|
|
489
|
+
}
|
|
490
|
+
if (bg.octaves !== void 0 && (!isNum(bg.octaves) || !Number.isInteger(bg.octaves) || bg.octaves < 1 || bg.octaves > LIMITS.maxFieldOctaves)) {
|
|
491
|
+
err("background.octaves", `must be an integer 1..${LIMITS.maxFieldOctaves}`);
|
|
492
|
+
}
|
|
493
|
+
if (bg.warp !== void 0 && (!isNum(bg.warp) || bg.warp < 0 || bg.warp > 1)) {
|
|
494
|
+
err("background.warp", "must be a number 0..1");
|
|
495
|
+
}
|
|
496
|
+
if (bg.quantize !== void 0 && (!isNum(bg.quantize) || !Number.isInteger(bg.quantize) || bg.quantize < 0 || bg.quantize === 1 || bg.quantize > LIMITS.maxFieldQuantize)) {
|
|
497
|
+
err("background.quantize", `must be 0 (smooth) or an integer 2..${LIMITS.maxFieldQuantize}`);
|
|
498
|
+
}
|
|
499
|
+
if (!Array.isArray(bg.bands) || bg.bands.length < LIMITS.minFieldBands || bg.bands.length > LIMITS.maxFieldBands) {
|
|
500
|
+
err("background.bands", `must be ${LIMITS.minFieldBands}..${LIMITS.maxFieldBands} hex colours`);
|
|
501
|
+
} else {
|
|
502
|
+
for (let i = 0; i < bg.bands.length; i++) color(bg.bands[i], `background.bands[${i}]`, err);
|
|
503
|
+
}
|
|
504
|
+
if (bg.drift !== void 0) {
|
|
505
|
+
if (!isObj(bg.drift)) err("background.drift", "must be an object");
|
|
506
|
+
else {
|
|
507
|
+
if (!isNum(bg.drift.period) || bg.drift.period < LIMITS.minDriftPeriod) {
|
|
508
|
+
err("background.drift.period", `must be >= ${LIMITS.minDriftPeriod} ms`);
|
|
509
|
+
}
|
|
510
|
+
if (bg.drift.amount !== void 0 && (!isNum(bg.drift.amount) || bg.drift.amount < 0 || bg.drift.amount > LIMITS.maxFieldDriftAmount)) {
|
|
511
|
+
err("background.drift.amount", `must be a number 0..${LIMITS.maxFieldDriftAmount}`);
|
|
512
|
+
}
|
|
513
|
+
for (const k of unknownKeys(bg.drift, KNOWN_FIELD_DRIFT)) {
|
|
514
|
+
warn(`background.drift.${k}`, "unknown-property", `unknown drift property '${k}' \u2014 will be ignored`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (bg.seed !== void 0 && !isNum(bg.seed)) err("background.seed", "must be a number");
|
|
519
|
+
for (const k of unknownKeys(bg, KNOWN_BG_FIELD)) {
|
|
520
|
+
warn(`background.${k}`, "unknown-property", `unknown background property '${k}' \u2014 will be ignored`);
|
|
441
521
|
}
|
|
442
522
|
}
|
|
443
523
|
function validateLayer(layer, path, err, warn, spec) {
|
|
@@ -526,6 +606,17 @@ function validateLayer(layer, path, err, warn, spec) {
|
|
|
526
606
|
err(`${path}.spin`, `must be within \xB1${LIMITS.maxSpin} deg/sec`);
|
|
527
607
|
}
|
|
528
608
|
}
|
|
609
|
+
if (layer.rotate !== void 0) {
|
|
610
|
+
if (isRange(layer.rotate)) {
|
|
611
|
+
if (Math.abs(layer.rotate[0]) > LIMITS.maxRotate || Math.abs(layer.rotate[1]) > LIMITS.maxRotate) {
|
|
612
|
+
err(`${path}.rotate`, `each end of the range must be within \xB1${LIMITS.maxRotate} degrees`);
|
|
613
|
+
}
|
|
614
|
+
} else if (!isNum(layer.rotate)) {
|
|
615
|
+
err(`${path}.rotate`, "must be a number or a [min,max] range (degrees)");
|
|
616
|
+
} else if (Math.abs(layer.rotate) > LIMITS.maxRotate) {
|
|
617
|
+
err(`${path}.rotate`, `must be within \xB1${LIMITS.maxRotate} degrees`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
529
620
|
if (layer.grow !== void 0) {
|
|
530
621
|
if (!isObj(layer.grow)) err(`${path}.grow`, "must be an object");
|
|
531
622
|
else {
|
|
@@ -1092,6 +1183,7 @@ function validateSequence(seq) {
|
|
|
1092
1183
|
if (seq.seed !== void 0 && !isNum(seq.seed)) err("seed", "must be a number");
|
|
1093
1184
|
if (typeof seq.loop !== "boolean") err("loop", "must be a boolean");
|
|
1094
1185
|
if (seq.sync !== void 0 && seq.sync !== "mount" && seq.sync !== "epoch") err("sync", "must be 'mount' | 'epoch'");
|
|
1186
|
+
if (seq.finish !== void 0) validateFinish(seq.finish, "finish", err, (p, code, message) => void warnings.push({ path: p, code, message }));
|
|
1095
1187
|
if (!Array.isArray(seq.segments) || seq.segments.length === 0) {
|
|
1096
1188
|
err("segments", "must be a non-empty array");
|
|
1097
1189
|
return { valid: false, errors, warnings };
|
|
@@ -1596,7 +1688,10 @@ function buildEntities(layer, rng, w, h, scale = 1, countScale = 1) {
|
|
|
1596
1688
|
} : {},
|
|
1597
1689
|
// Time-structure fields (2026-09): ease draws nothing; emit's one draw
|
|
1598
1690
|
// per entity happens in a pass AFTER this loop (see below).
|
|
1599
|
-
...ease ? { ease: { type: ease.type, tau: ease.tau } } : {}
|
|
1691
|
+
...ease ? { ease: { type: ease.type, tau: ease.tau } } : {},
|
|
1692
|
+
// Static rotation (2026-09-10): a scalar draws nothing; the range form
|
|
1693
|
+
// draws one value here, last, only when declared.
|
|
1694
|
+
...typeof layer.rotate === "number" ? { rotate: layer.rotate * Math.PI / 180 } : Array.isArray(layer.rotate) ? { rotate: rng.range(layer.rotate[0], layer.rotate[1]) * Math.PI / 180 } : {}
|
|
1600
1695
|
});
|
|
1601
1696
|
if (layer.clock) {
|
|
1602
1697
|
const ent = out[out.length - 1];
|
|
@@ -1670,8 +1765,9 @@ function sizeAt(e, t) {
|
|
|
1670
1765
|
return s > 0 ? s : 0.1;
|
|
1671
1766
|
}
|
|
1672
1767
|
function rotationAt(e, t) {
|
|
1673
|
-
|
|
1674
|
-
|
|
1768
|
+
const base = e.rotate ?? 0;
|
|
1769
|
+
if (!e.spinSpeed) return base;
|
|
1770
|
+
return base + e.spinPhase + e.spinSpeed * (t / 1e3);
|
|
1675
1771
|
}
|
|
1676
1772
|
function lifeAlphaAt(life, t) {
|
|
1677
1773
|
if (!life) return 1;
|
|
@@ -2061,6 +2157,254 @@ import {
|
|
|
2061
2157
|
createRng
|
|
2062
2158
|
} from "@idle-screens/core";
|
|
2063
2159
|
|
|
2160
|
+
// src/field.ts
|
|
2161
|
+
var TWO_PI = Math.PI * 2;
|
|
2162
|
+
var FIELD_BUCKET_MS = 100;
|
|
2163
|
+
var FIELD_RASTER_SHORT_SIDE = 96;
|
|
2164
|
+
var FIELD_RASTER_SHORT_SIDE_LOW = 48;
|
|
2165
|
+
var WARP_GAIN = 2;
|
|
2166
|
+
var DRIFT_GAIN = 1;
|
|
2167
|
+
var FIELD_DRIFT_DEFAULT_AMOUNT = 0.3;
|
|
2168
|
+
var CONTRAST = [1, 1.45, 1.8, 1.95, 2];
|
|
2169
|
+
var clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
2170
|
+
function latticeHash(ix, iy, seed) {
|
|
2171
|
+
let h = Math.imul(ix | 0, 668265261) ^ Math.imul(iy | 0, 374761393) ^ Math.imul(seed | 0, 2654435761);
|
|
2172
|
+
h = Math.imul(h ^ h >>> 15, 2246822507);
|
|
2173
|
+
h = Math.imul(h ^ h >>> 13, 3266489909);
|
|
2174
|
+
h ^= h >>> 16;
|
|
2175
|
+
return (h >>> 0) / 4294967296;
|
|
2176
|
+
}
|
|
2177
|
+
var smoothstep2 = (k) => k * k * (3 - 2 * k);
|
|
2178
|
+
function valueNoise(x, y, seed) {
|
|
2179
|
+
const x0 = Math.floor(x);
|
|
2180
|
+
const y0 = Math.floor(y);
|
|
2181
|
+
const fx = smoothstep2(x - x0);
|
|
2182
|
+
const fy = smoothstep2(y - y0);
|
|
2183
|
+
const a = latticeHash(x0, y0, seed);
|
|
2184
|
+
const b = latticeHash(x0 + 1, y0, seed);
|
|
2185
|
+
const c = latticeHash(x0, y0 + 1, seed);
|
|
2186
|
+
const d = latticeHash(x0 + 1, y0 + 1, seed);
|
|
2187
|
+
const top = a + (b - a) * fx;
|
|
2188
|
+
const bottom = c + (d - c) * fx;
|
|
2189
|
+
return top + (bottom - top) * fy;
|
|
2190
|
+
}
|
|
2191
|
+
function fbm(x, y, seed, octaves) {
|
|
2192
|
+
let sum = 0;
|
|
2193
|
+
let amp = 1;
|
|
2194
|
+
let norm = 0;
|
|
2195
|
+
let f = 1;
|
|
2196
|
+
for (let i = 0; i < octaves; i++) {
|
|
2197
|
+
sum += amp * valueNoise(x * f + i * 17.3, y * f + i * 31.7, seed + i * 101);
|
|
2198
|
+
norm += amp;
|
|
2199
|
+
amp *= 0.5;
|
|
2200
|
+
f *= 2;
|
|
2201
|
+
}
|
|
2202
|
+
return sum / norm;
|
|
2203
|
+
}
|
|
2204
|
+
function fieldOctaves(cfg) {
|
|
2205
|
+
const o = Math.round(cfg.octaves ?? 2);
|
|
2206
|
+
return o < 1 ? 1 : o > 4 ? 4 : o;
|
|
2207
|
+
}
|
|
2208
|
+
function fieldSampleTime(cfg, t) {
|
|
2209
|
+
return cfg.drift && cfg.drift.amount !== 0 ? Math.floor(t / FIELD_BUCKET_MS) * FIELD_BUCKET_MS : 0;
|
|
2210
|
+
}
|
|
2211
|
+
function fieldAt(u, v, t, cfg, seed) {
|
|
2212
|
+
const scale = cfg.scale;
|
|
2213
|
+
const octaves = fieldOctaves(cfg);
|
|
2214
|
+
const s = (cfg.seed ?? seed) >>> 0;
|
|
2215
|
+
let x = u * scale;
|
|
2216
|
+
let y = v * scale;
|
|
2217
|
+
const drift = cfg.drift;
|
|
2218
|
+
if (drift) {
|
|
2219
|
+
const phase = TWO_PI * t / drift.period;
|
|
2220
|
+
const r = (drift.amount ?? FIELD_DRIFT_DEFAULT_AMOUNT) * DRIFT_GAIN;
|
|
2221
|
+
x += r * Math.sin(phase);
|
|
2222
|
+
y += r * Math.cos(phase);
|
|
2223
|
+
}
|
|
2224
|
+
const warp = cfg.warp ?? 0;
|
|
2225
|
+
if (warp > 0) {
|
|
2226
|
+
const qx = fbm(x + 5.2, y + 1.3, s + 7919, octaves) - 0.5;
|
|
2227
|
+
const qy = fbm(x + 1.7, y + 9.2, s + 104729, octaves) - 0.5;
|
|
2228
|
+
x += warp * WARP_GAIN * qx;
|
|
2229
|
+
y += warp * WARP_GAIN * qy;
|
|
2230
|
+
}
|
|
2231
|
+
const raw = fbm(x, y, s, octaves);
|
|
2232
|
+
return clamp01(0.5 + (raw - 0.5) * CONTRAST[octaves]);
|
|
2233
|
+
}
|
|
2234
|
+
function hexToRgb255(hex) {
|
|
2235
|
+
const h = hex.length === 4 ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex;
|
|
2236
|
+
const n = /^#[0-9a-f]{6}$/i.test(h) ? parseInt(h.slice(1), 16) : NaN;
|
|
2237
|
+
if (Number.isNaN(n)) return [128, 128, 128];
|
|
2238
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
2239
|
+
}
|
|
2240
|
+
function rgb255Luma(rgb) {
|
|
2241
|
+
return (0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2]) / 255;
|
|
2242
|
+
}
|
|
2243
|
+
function fieldRgb(value, bands, quantize) {
|
|
2244
|
+
const n = bands.length;
|
|
2245
|
+
const q = Math.round(quantize ?? n);
|
|
2246
|
+
let f;
|
|
2247
|
+
if (q >= 2) {
|
|
2248
|
+
const level = Math.min(q - 1, Math.floor(clamp01(value) * q));
|
|
2249
|
+
f = level * (n - 1) / (q - 1);
|
|
2250
|
+
} else {
|
|
2251
|
+
f = clamp01(value) * (n - 1);
|
|
2252
|
+
}
|
|
2253
|
+
const i = Math.min(n - 2, Math.floor(f));
|
|
2254
|
+
const k = f - i;
|
|
2255
|
+
const a = hexToRgb255(bands[i]);
|
|
2256
|
+
if (k === 0) return a;
|
|
2257
|
+
const b = hexToRgb255(bands[i + 1]);
|
|
2258
|
+
return [
|
|
2259
|
+
Math.round(a[0] + (b[0] - a[0]) * k),
|
|
2260
|
+
Math.round(a[1] + (b[1] - a[1]) * k),
|
|
2261
|
+
Math.round(a[2] + (b[2] - a[2]) * k)
|
|
2262
|
+
];
|
|
2263
|
+
}
|
|
2264
|
+
function fieldRgbAt(u, v, t, cfg, seed) {
|
|
2265
|
+
return fieldRgb(fieldAt(u, v, t, cfg, seed), cfg.bands, cfg.quantize);
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
// src/finish.ts
|
|
2269
|
+
var GRAIN_TILE = 256;
|
|
2270
|
+
var DITHER_TILE = 8;
|
|
2271
|
+
var GRAIN_ALPHA = 0.35;
|
|
2272
|
+
var DITHER_ALPHA = 0.25;
|
|
2273
|
+
var FINISH_ANIMATE_HZ = 12;
|
|
2274
|
+
var clamp012 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
2275
|
+
function finishStrength(f) {
|
|
2276
|
+
return { grain: clamp012(f?.grain ?? 0), dither: clamp012(f?.dither ?? 0) };
|
|
2277
|
+
}
|
|
2278
|
+
function grainTilePixels(seed) {
|
|
2279
|
+
const px = new Uint8ClampedArray(GRAIN_TILE * GRAIN_TILE * 4);
|
|
2280
|
+
const s = seed >>> 0;
|
|
2281
|
+
let i = 0;
|
|
2282
|
+
for (let y = 0; y < GRAIN_TILE; y++) {
|
|
2283
|
+
for (let x = 0; x < GRAIN_TILE; x++) {
|
|
2284
|
+
const v = (latticeHash(x, y, s) + latticeHash(x, y, s + 1540483477)) / 2;
|
|
2285
|
+
const g = Math.round(128 + (v - 0.5) * 255);
|
|
2286
|
+
px[i++] = g;
|
|
2287
|
+
px[i++] = g;
|
|
2288
|
+
px[i++] = g;
|
|
2289
|
+
px[i++] = 255;
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
return px;
|
|
2293
|
+
}
|
|
2294
|
+
function bayerMatrix() {
|
|
2295
|
+
let m = [[0]];
|
|
2296
|
+
for (let n = 1; n < DITHER_TILE; n *= 2) {
|
|
2297
|
+
const next = [];
|
|
2298
|
+
for (let y = 0; y < n * 2; y++) {
|
|
2299
|
+
const row = [];
|
|
2300
|
+
for (let x = 0; x < n * 2; x++) {
|
|
2301
|
+
const base = m[y % n][x % n] * 4;
|
|
2302
|
+
const quadrant = (y >= n ? 2 : 0) + (x >= n ? 1 : 0);
|
|
2303
|
+
row.push(base + [0, 2, 3, 1][quadrant]);
|
|
2304
|
+
}
|
|
2305
|
+
next.push(row);
|
|
2306
|
+
}
|
|
2307
|
+
m = next;
|
|
2308
|
+
}
|
|
2309
|
+
return m;
|
|
2310
|
+
}
|
|
2311
|
+
function bayerTilePixels() {
|
|
2312
|
+
const m = bayerMatrix();
|
|
2313
|
+
const px = new Uint8ClampedArray(DITHER_TILE * DITHER_TILE * 4);
|
|
2314
|
+
let i = 0;
|
|
2315
|
+
for (let y = 0; y < DITHER_TILE; y++) {
|
|
2316
|
+
for (let x = 0; x < DITHER_TILE; x++) {
|
|
2317
|
+
const g = Math.round((m[y][x] + 0.5) / (DITHER_TILE * DITHER_TILE) * 255);
|
|
2318
|
+
px[i++] = g;
|
|
2319
|
+
px[i++] = g;
|
|
2320
|
+
px[i++] = g;
|
|
2321
|
+
px[i++] = 255;
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
return px;
|
|
2325
|
+
}
|
|
2326
|
+
function grainOffset(t, seed, animate) {
|
|
2327
|
+
if (!animate) return { x: 0, y: 0 };
|
|
2328
|
+
const bucket = Math.floor(t * FINISH_ANIMATE_HZ / 1e3);
|
|
2329
|
+
const s = seed >>> 0;
|
|
2330
|
+
return {
|
|
2331
|
+
x: Math.floor(latticeHash(bucket, 1, s) * GRAIN_TILE),
|
|
2332
|
+
y: Math.floor(latticeHash(bucket, 2, s) * GRAIN_TILE)
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
function pickScreenOp(ctx) {
|
|
2336
|
+
const prev = ctx.globalCompositeOperation;
|
|
2337
|
+
let op = "multiply";
|
|
2338
|
+
for (const candidate of ["overlay", "soft-light"]) {
|
|
2339
|
+
ctx.globalCompositeOperation = candidate;
|
|
2340
|
+
if (ctx.globalCompositeOperation === candidate) {
|
|
2341
|
+
op = candidate;
|
|
2342
|
+
break;
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
ctx.globalCompositeOperation = prev;
|
|
2346
|
+
return op;
|
|
2347
|
+
}
|
|
2348
|
+
function tileCanvas(size, pixels) {
|
|
2349
|
+
const canvas = typeof document !== "undefined" ? document.createElement("canvas") : new OffscreenCanvas(size, size);
|
|
2350
|
+
canvas.width = size;
|
|
2351
|
+
canvas.height = size;
|
|
2352
|
+
const ctx = canvas.getContext("2d");
|
|
2353
|
+
if (!ctx || typeof ctx.createImageData !== "function") return null;
|
|
2354
|
+
const img = ctx.createImageData(size, size);
|
|
2355
|
+
img.data.set(pixels);
|
|
2356
|
+
ctx.putImageData(img, 0, 0);
|
|
2357
|
+
return canvas;
|
|
2358
|
+
}
|
|
2359
|
+
function createFinishPass(visible, seed) {
|
|
2360
|
+
const ctx = visible.getContext("2d", { alpha: false });
|
|
2361
|
+
if (!ctx) return null;
|
|
2362
|
+
return { canvas: visible, ctx, op: pickScreenOp(ctx), grain: null, dither: null, seed };
|
|
2363
|
+
}
|
|
2364
|
+
function createSceneCanvas(width, height) {
|
|
2365
|
+
if (typeof document === "undefined") return new OffscreenCanvas(Math.max(1, width), Math.max(1, height));
|
|
2366
|
+
const canvas = document.createElement("canvas");
|
|
2367
|
+
canvas.width = Math.max(1, width);
|
|
2368
|
+
canvas.height = Math.max(1, height);
|
|
2369
|
+
return canvas;
|
|
2370
|
+
}
|
|
2371
|
+
function pattern(fp, size, pixels) {
|
|
2372
|
+
const tile = tileCanvas(size, pixels);
|
|
2373
|
+
if (!tile || typeof fp.ctx.createPattern !== "function") return null;
|
|
2374
|
+
return fp.ctx.createPattern(tile, "repeat");
|
|
2375
|
+
}
|
|
2376
|
+
function presentWithFinish(fp, scene, w, h, t, finish, animateAllowed, seed = fp.seed) {
|
|
2377
|
+
const p = fp.ctx;
|
|
2378
|
+
p.globalAlpha = 1;
|
|
2379
|
+
p.globalCompositeOperation = "source-over";
|
|
2380
|
+
p.drawImage(scene, 0, 0, w, h);
|
|
2381
|
+
const normalizedSeed = seed >>> 0 || 1;
|
|
2382
|
+
if (normalizedSeed !== fp.seed) {
|
|
2383
|
+
fp.seed = normalizedSeed;
|
|
2384
|
+
fp.grain = null;
|
|
2385
|
+
}
|
|
2386
|
+
const { grain, dither } = finishStrength(finish);
|
|
2387
|
+
if (grain > 0) {
|
|
2388
|
+
fp.grain ??= pattern(fp, GRAIN_TILE, grainTilePixels(fp.seed));
|
|
2389
|
+
if (fp.grain) screen(fp, fp.grain, grain * GRAIN_ALPHA, grainOffset(t, fp.seed, animateAllowed && finish?.animate === true), w, h);
|
|
2390
|
+
}
|
|
2391
|
+
if (dither > 0) {
|
|
2392
|
+
fp.dither ??= pattern(fp, DITHER_TILE, bayerTilePixels());
|
|
2393
|
+
if (fp.dither) screen(fp, fp.dither, dither * DITHER_ALPHA, { x: 0, y: 0 }, w, h);
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
function screen(fp, pat, alpha, off, w, h) {
|
|
2397
|
+
const p = fp.ctx;
|
|
2398
|
+
p.save();
|
|
2399
|
+
p.globalCompositeOperation = fp.op;
|
|
2400
|
+
p.globalAlpha = alpha;
|
|
2401
|
+
p.imageSmoothingEnabled = false;
|
|
2402
|
+
p.fillStyle = pat;
|
|
2403
|
+
p.translate(-off.x, -off.y);
|
|
2404
|
+
p.fillRect(off.x, off.y, w, h);
|
|
2405
|
+
p.restore();
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2064
2408
|
// src/sequence.ts
|
|
2065
2409
|
function segmentStart(seq, index) {
|
|
2066
2410
|
let t = 0;
|
|
@@ -2131,6 +2475,37 @@ function bedRenderSeed(seq) {
|
|
|
2131
2475
|
const raw = seq.bed.seed ?? (seq.seed !== void 0 ? seq.seed + LIMITS.maxSegments : void 0);
|
|
2132
2476
|
return raw === void 0 ? void 0 : normalizeSeed(raw);
|
|
2133
2477
|
}
|
|
2478
|
+
function sequenceWantsFinish(seq) {
|
|
2479
|
+
return seq.finish !== void 0 || seq.segments.some((s) => s.scene.finish !== void 0);
|
|
2480
|
+
}
|
|
2481
|
+
function sequenceSwapCompatible(prev, next) {
|
|
2482
|
+
if (prev.segments.length !== next.segments.length) return false;
|
|
2483
|
+
if (prev.seed === void 0 !== (next.seed === void 0)) return false;
|
|
2484
|
+
if (prev.seed !== void 0 && next.seed !== void 0 && normalizeSeed(prev.seed) !== normalizeSeed(next.seed)) return false;
|
|
2485
|
+
if (sequenceWantsFinish(prev) !== sequenceWantsFinish(next)) return false;
|
|
2486
|
+
if (prev.loop !== next.loop) return false;
|
|
2487
|
+
if ((prev.sync ?? "mount") !== (next.sync ?? "mount")) return false;
|
|
2488
|
+
if (!!prev.bed !== !!next.bed) return false;
|
|
2489
|
+
if (prev.bed && next.bed) {
|
|
2490
|
+
if (structuralSignature(prev.bed) !== structuralSignature(next.bed)) return false;
|
|
2491
|
+
if (bedRenderSeed(prev) !== bedRenderSeed(next)) return false;
|
|
2492
|
+
}
|
|
2493
|
+
for (let i = 0; i < prev.segments.length; i++) {
|
|
2494
|
+
const a = prev.segments[i];
|
|
2495
|
+
const b = next.segments[i];
|
|
2496
|
+
if (a.duration !== b.duration) return false;
|
|
2497
|
+
if ((a.advance ?? "auto") !== (b.advance ?? "auto")) return false;
|
|
2498
|
+
if (transitionKey(a.transition) !== transitionKey(b.transition)) return false;
|
|
2499
|
+
if (structuralSignature(a.scene) !== structuralSignature(b.scene)) return false;
|
|
2500
|
+
if (segmentRenderSeed(prev, i) !== segmentRenderSeed(next, i)) return false;
|
|
2501
|
+
}
|
|
2502
|
+
return true;
|
|
2503
|
+
}
|
|
2504
|
+
function transitionKey(tr) {
|
|
2505
|
+
if (!tr || tr.type === "cut") return "cut";
|
|
2506
|
+
if (tr.type === "morph") return `morph:${tr.dur}:${tr.text ?? "step"}`;
|
|
2507
|
+
return `fade:${tr.dur}`;
|
|
2508
|
+
}
|
|
2134
2509
|
|
|
2135
2510
|
// src/compile.ts
|
|
2136
2511
|
var DEFAULT_STEER_DUR = 1e3;
|
|
@@ -2185,6 +2560,9 @@ var SpecInstance = class {
|
|
|
2185
2560
|
this.effSpec = spec;
|
|
2186
2561
|
this.saverCtx = ctx;
|
|
2187
2562
|
this.transparent = opts.transparent === true;
|
|
2563
|
+
const tier = opts.capabilityTier;
|
|
2564
|
+
this.fieldShortSide = tier === "basic" || tier === "minimal" ? FIELD_RASTER_SHORT_SIDE_LOW : FIELD_RASTER_SHORT_SIDE;
|
|
2565
|
+
this.animateFinish = tier !== "basic" && tier !== "minimal";
|
|
2188
2566
|
this.seed = normalizeSeed(spec.seed ?? ctx.seed);
|
|
2189
2567
|
let canvas;
|
|
2190
2568
|
if (ctx.surface) {
|
|
@@ -2198,6 +2576,13 @@ var SpecInstance = class {
|
|
|
2198
2576
|
canvas = el;
|
|
2199
2577
|
this.ownsCanvas = true;
|
|
2200
2578
|
}
|
|
2579
|
+
if (spec.finish !== void 0 && !this.transparent && !opts.hostPresents) {
|
|
2580
|
+
const pass = createFinishPass(canvas, this.seed);
|
|
2581
|
+
if (pass) {
|
|
2582
|
+
this.finishPass = pass;
|
|
2583
|
+
canvas = createSceneCanvas(ctx.width, ctx.height);
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2201
2586
|
this.canvas = canvas;
|
|
2202
2587
|
const c2d = canvas.getContext("2d", { alpha: this.transparent });
|
|
2203
2588
|
if (!c2d) throw new Error("schema saver: no 2d context");
|
|
@@ -2238,6 +2623,23 @@ var SpecInstance = class {
|
|
|
2238
2623
|
lastStructural = "";
|
|
2239
2624
|
/** See `LayerPaintOverride`; null (the default, and every plain mount) draws every layer once. */
|
|
2240
2625
|
paintOverrides = null;
|
|
2626
|
+
/** Raster short side for a `field` background — halved on the low tiers. */
|
|
2627
|
+
fieldShortSide;
|
|
2628
|
+
/** The `field` background's cached raster; null until a field is first painted. */
|
|
2629
|
+
field = null;
|
|
2630
|
+
/**
|
|
2631
|
+
* The presentation pass when this instance applies a `finish`: the
|
|
2632
|
+
* visible canvas plus the cached screen tiles. When set, `this.canvas` is
|
|
2633
|
+
* an offscreen SCENE canvas — the surface every layer (and `ghosting`'s
|
|
2634
|
+
* persistence) draws into — and each frame ends by copying it to the
|
|
2635
|
+
* visible canvas and screening the finish over the copy. The finish thus
|
|
2636
|
+
* never enters the persistence loop. Null (every plain mount without a
|
|
2637
|
+
* finish, every sequence child) draws straight onto the visible canvas,
|
|
2638
|
+
* exactly as before the field existed.
|
|
2639
|
+
*/
|
|
2640
|
+
finishPass = null;
|
|
2641
|
+
/** False on the `basic`/`minimal` tiers — `finish.animate` renders as a static tile. */
|
|
2642
|
+
animateFinish;
|
|
2241
2643
|
/** Viewport factor for absolute px sizes — 1 for `units: 'px'` specs. */
|
|
2242
2644
|
fontScale() {
|
|
2243
2645
|
if (this.effSpec.units === "px") return 1;
|
|
@@ -2248,6 +2650,11 @@ var SpecInstance = class {
|
|
|
2248
2650
|
this.canvas.width = Math.max(1, Math.round(this.w * dpr));
|
|
2249
2651
|
this.canvas.height = Math.max(1, Math.round(this.h * dpr));
|
|
2250
2652
|
this.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
2653
|
+
if (this.finishPass) {
|
|
2654
|
+
this.finishPass.canvas.width = this.canvas.width;
|
|
2655
|
+
this.finishPass.canvas.height = this.canvas.height;
|
|
2656
|
+
this.finishPass.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
2657
|
+
}
|
|
2251
2658
|
this.lastRenderT = Number.NEGATIVE_INFINITY;
|
|
2252
2659
|
}
|
|
2253
2660
|
/** (Re)seed and place all entities — deterministic for the seed + viewport. */
|
|
@@ -2289,6 +2696,10 @@ var SpecInstance = class {
|
|
|
2289
2696
|
ctx.fillRect(0, 0, w, h);
|
|
2290
2697
|
return;
|
|
2291
2698
|
}
|
|
2699
|
+
if (bg.type === "field") {
|
|
2700
|
+
this.drawField(bg, t);
|
|
2701
|
+
return;
|
|
2702
|
+
}
|
|
2292
2703
|
const g = ctx.createLinearGradient(0, 0, 0, h);
|
|
2293
2704
|
const drift = bg.drift;
|
|
2294
2705
|
for (let i = 0; i < bg.stops.length; i++) {
|
|
@@ -2309,6 +2720,58 @@ var SpecInstance = class {
|
|
|
2309
2720
|
ctx.fillRect(0, h - bh, w, bh);
|
|
2310
2721
|
}
|
|
2311
2722
|
}
|
|
2723
|
+
/**
|
|
2724
|
+
* Paint a `field` background: sample the field into a low-res raster (short
|
|
2725
|
+
* side `fieldShortSide`, the long side by aspect) and draw it scaled to the
|
|
2726
|
+
* canvas — nearest-neighbour for quantised bands so contours stay crisp,
|
|
2727
|
+
* smoothed for `quantize: 0`. The raster is recomputed only when its key
|
|
2728
|
+
* changes: the config (steering glides it), the canvas size, or — while
|
|
2729
|
+
* drifting — the 100 ms bucket `t` falls in (`fieldSampleTime`); a static
|
|
2730
|
+
* field is sampled exactly once per mount. `luminanceGrid` samples the same
|
|
2731
|
+
* function at the same bucketed time, so perception and paint agree.
|
|
2732
|
+
*/
|
|
2733
|
+
drawField(bg, t) {
|
|
2734
|
+
const { ctx, w, h } = this;
|
|
2735
|
+
const short = Math.max(1, Math.min(w, h));
|
|
2736
|
+
const cols = Math.max(1, Math.round(this.fieldShortSide * w / short));
|
|
2737
|
+
const rows = Math.max(1, Math.round(this.fieldShortSide * h / short));
|
|
2738
|
+
const ft = fieldSampleTime(bg, t);
|
|
2739
|
+
const key = `${JSON.stringify(bg)}|${w}x${h}|${cols}x${rows}|${ft}`;
|
|
2740
|
+
let raster = this.field;
|
|
2741
|
+
if (!raster || raster.key !== key) {
|
|
2742
|
+
if (!raster || raster.cols !== cols || raster.rows !== rows) {
|
|
2743
|
+
const canvas = typeof document !== "undefined" ? document.createElement("canvas") : new OffscreenCanvas(cols, rows);
|
|
2744
|
+
canvas.width = cols;
|
|
2745
|
+
canvas.height = rows;
|
|
2746
|
+
const rctx = canvas.getContext("2d");
|
|
2747
|
+
if (!rctx) return;
|
|
2748
|
+
raster = { canvas, ctx: rctx, cols, rows, key: "" };
|
|
2749
|
+
}
|
|
2750
|
+
const img = raster.ctx.createImageData(cols, rows);
|
|
2751
|
+
const data = img.data;
|
|
2752
|
+
const seed = this.seed;
|
|
2753
|
+
let i = 0;
|
|
2754
|
+
for (let r = 0; r < rows; r++) {
|
|
2755
|
+
const v = (r + 0.5) * h / rows / short;
|
|
2756
|
+
for (let c = 0; c < cols; c++) {
|
|
2757
|
+
const u = (c + 0.5) * w / cols / short;
|
|
2758
|
+
const rgb = fieldRgbAt(u, v, ft, bg, seed);
|
|
2759
|
+
data[i++] = rgb[0];
|
|
2760
|
+
data[i++] = rgb[1];
|
|
2761
|
+
data[i++] = rgb[2];
|
|
2762
|
+
data[i++] = 255;
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
raster.ctx.putImageData(img, 0, 0);
|
|
2766
|
+
raster.key = key;
|
|
2767
|
+
this.field = raster;
|
|
2768
|
+
}
|
|
2769
|
+
const smooth = Math.round(bg.quantize ?? bg.bands.length) < 2;
|
|
2770
|
+
const prev = ctx.imageSmoothingEnabled;
|
|
2771
|
+
ctx.imageSmoothingEnabled = smooth;
|
|
2772
|
+
ctx.drawImage(raster.canvas, 0, 0, w, h);
|
|
2773
|
+
ctx.imageSmoothingEnabled = prev;
|
|
2774
|
+
}
|
|
2312
2775
|
/** Position with parent-orbit resolution: a layer-parented orbit entity's
|
|
2313
2776
|
* positionAt is an offset around (0,0); add the parent's own analytic position. */
|
|
2314
2777
|
entityPos(e, t, parentE) {
|
|
@@ -2394,7 +2857,7 @@ var SpecInstance = class {
|
|
|
2394
2857
|
}
|
|
2395
2858
|
if (sprite.kind === "streak") {
|
|
2396
2859
|
const resolvedColor = sprite.colors?.[e.colorIndex] ?? sprite.color;
|
|
2397
|
-
const heading = headingAt(e, t, this.w, this.h) ?? 0;
|
|
2860
|
+
const heading = (headingAt(e, t, this.w, this.h) ?? 0) + rot;
|
|
2398
2861
|
const tailX = p.x - Math.cos(heading) * sz;
|
|
2399
2862
|
const tailY = p.y - Math.sin(heading) * sz;
|
|
2400
2863
|
const g = ctx.createLinearGradient(tailX, tailY, p.x, p.y);
|
|
@@ -2788,6 +3251,7 @@ var SpecInstance = class {
|
|
|
2788
3251
|
this.paintFrame(t, 1);
|
|
2789
3252
|
}
|
|
2790
3253
|
this.lastRenderT = t;
|
|
3254
|
+
if (this.finishPass) presentWithFinish(this.finishPass, this.canvas, this.w, this.h, t, this.effSpec.finish, this.animateFinish);
|
|
2791
3255
|
}
|
|
2792
3256
|
setPaused(paused) {
|
|
2793
3257
|
this.paused = paused;
|
|
@@ -2836,19 +3300,26 @@ var SpecInstance = class {
|
|
|
2836
3300
|
}
|
|
2837
3301
|
dispose() {
|
|
2838
3302
|
this.stop();
|
|
2839
|
-
|
|
3303
|
+
this.field = null;
|
|
3304
|
+
const visible = this.finishPass?.canvas ?? this.canvas;
|
|
3305
|
+
this.finishPass = null;
|
|
3306
|
+
if (this.ownsCanvas && typeof HTMLCanvasElement !== "undefined" && visible instanceof HTMLCanvasElement) visible.remove();
|
|
2840
3307
|
}
|
|
2841
3308
|
};
|
|
2842
3309
|
function compileSaver(spec) {
|
|
2843
3310
|
const valid = assertValidSpec(spec);
|
|
2844
3311
|
return {
|
|
2845
3312
|
manifest: manifestFor(valid),
|
|
2846
|
-
mount: (ctx) => new SpecInstance(valid, ctx),
|
|
3313
|
+
mount: (ctx) => new SpecInstance(valid, ctx, { capabilityTier: ctx.capabilityTier }),
|
|
2847
3314
|
spec: valid
|
|
2848
3315
|
};
|
|
2849
3316
|
}
|
|
2850
3317
|
var BED_PREFIX = "bed.";
|
|
3318
|
+
function hasHotSwapSequence(inst) {
|
|
3319
|
+
return typeof inst.hotSwapSequence === "function";
|
|
3320
|
+
}
|
|
2851
3321
|
var SequenceInstance = class {
|
|
3322
|
+
/** Replaced wholesale by `hotSwapSequence`; every per-segment lookup (`segmentStart`, `canMorph`, `childScene`) reads it live, so there are no derived tables to refresh. */
|
|
2852
3323
|
seq;
|
|
2853
3324
|
childCtx;
|
|
2854
3325
|
canvas;
|
|
@@ -2899,6 +3370,13 @@ var SequenceInstance = class {
|
|
|
2899
3370
|
* resolve on it (see `applyRetainedDeltas`).
|
|
2900
3371
|
*/
|
|
2901
3372
|
retainedDeltas = /* @__PURE__ */ new Map();
|
|
3373
|
+
/**
|
|
3374
|
+
* Every `bed.<path>` delta (prefix stripped, last wins per path). The bed
|
|
3375
|
+
* is never re-created, so these are applied to it once on arrival — the
|
|
3376
|
+
* map exists only so `hotSwapSequence` can put a steered bed colour back
|
|
3377
|
+
* after the bed takes its republished scene. Absent a swap it is never read.
|
|
3378
|
+
*/
|
|
3379
|
+
retainedBedDeltas = /* @__PURE__ */ new Map();
|
|
2902
3380
|
/** The outgoing segment while a `fade` runs; null otherwise. */
|
|
2903
3381
|
fading = null;
|
|
2904
3382
|
/**
|
|
@@ -2911,6 +3389,19 @@ var SequenceInstance = class {
|
|
|
2911
3389
|
fadingIn = null;
|
|
2912
3390
|
/** False on the `basic`/`minimal` capability tiers — fade renders as cut. */
|
|
2913
3391
|
fadeEnabled;
|
|
3392
|
+
/** The mount's tier, handed to every child and the bed. */
|
|
3393
|
+
capabilityTier;
|
|
3394
|
+
/**
|
|
3395
|
+
* The sequence's presentation pass when it — or any segment — declares a
|
|
3396
|
+
* `finish`: children and the bed draw into an offscreen scene canvas
|
|
3397
|
+
* (`childCtx.surface`), and every frame ends by copying it to the visible
|
|
3398
|
+
* surface with the finish screened over the copy — once per composed
|
|
3399
|
+
* frame, bed + segment + fade together. Null when no finish is declared
|
|
3400
|
+
* anywhere: children draw straight onto the visible surface, as before.
|
|
3401
|
+
*/
|
|
3402
|
+
finishPass = null;
|
|
3403
|
+
/** False on the `basic`/`minimal` tiers — `finish.animate` renders as a static tile. */
|
|
3404
|
+
animateFinish;
|
|
2914
3405
|
/**
|
|
2915
3406
|
* The sequence's `bed`: one scene on the shared surface, drawn first every
|
|
2916
3407
|
* frame at the sequence clock — WITHOUT `clockOffset`, so a
|
|
@@ -2921,6 +3412,7 @@ var SequenceInstance = class {
|
|
|
2921
3412
|
constructor(seq, ctx) {
|
|
2922
3413
|
this.seq = seq;
|
|
2923
3414
|
const { sequenceBaseT, capabilityTier, ...plainCtx } = ctx;
|
|
3415
|
+
this.capabilityTier = capabilityTier;
|
|
2924
3416
|
this.fadeEnabled = capabilityTier !== "basic" && capabilityTier !== "minimal";
|
|
2925
3417
|
if (seq.sync === "epoch" && typeof sequenceBaseT === "number" && Number.isFinite(sequenceBaseT)) {
|
|
2926
3418
|
this.baseT = Math.max(0, sequenceBaseT);
|
|
@@ -2938,10 +3430,19 @@ var SequenceInstance = class {
|
|
|
2938
3430
|
}
|
|
2939
3431
|
this.canvas = canvas;
|
|
2940
3432
|
this.seed = (seq.seed ?? ctx.seed ?? 0) >>> 0 || 1;
|
|
3433
|
+
this.animateFinish = capabilityTier !== "basic" && capabilityTier !== "minimal";
|
|
3434
|
+
if (sequenceWantsFinish(seq) && surface) {
|
|
3435
|
+
const pass = createFinishPass(surface, this.seed);
|
|
3436
|
+
if (pass) {
|
|
3437
|
+
this.finishPass = pass;
|
|
3438
|
+
surface = createSceneCanvas(ctx.width, ctx.height);
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
2941
3441
|
this.childCtx = { ...plainCtx, surface, reducedMotion: true };
|
|
3442
|
+
this.sizeVisible();
|
|
2942
3443
|
this.children = new Array(seq.segments.length).fill(null);
|
|
2943
3444
|
if (seq.bed) {
|
|
2944
|
-
this.bed = new SpecInstance(this.bedScene(seq.bed), this.childCtx);
|
|
3445
|
+
this.bed = new SpecInstance(this.bedScene(seq.bed), this.childCtx, { capabilityTier, hostPresents: true });
|
|
2945
3446
|
this.bed.setPaused(true);
|
|
2946
3447
|
} else {
|
|
2947
3448
|
this.bed = null;
|
|
@@ -3034,7 +3535,7 @@ var SequenceInstance = class {
|
|
|
3034
3535
|
*/
|
|
3035
3536
|
offscreenChild(index, root) {
|
|
3036
3537
|
const canvas = typeof document !== "undefined" ? document.createElement("canvas") : new OffscreenCanvas(Math.max(1, this.childCtx.width), Math.max(1, this.childCtx.height));
|
|
3037
|
-
const child = new SpecInstance(this.childScene(root), { ...this.childCtx, surface: canvas }, { transparent: this.bed !== null });
|
|
3538
|
+
const child = new SpecInstance(this.childScene(root), { ...this.childCtx, surface: canvas }, { transparent: this.bed !== null, capabilityTier: this.capabilityTier, hostPresents: true });
|
|
3038
3539
|
child.setPaused(true);
|
|
3039
3540
|
if (root !== index) child.hotSwapSpec(this.childScene(index));
|
|
3040
3541
|
if (this.retainedDeltas.size > 0) child.applyDeltasNow(this.retainedDeltas.values());
|
|
@@ -3093,7 +3594,7 @@ var SequenceInstance = class {
|
|
|
3093
3594
|
if (index < 0 || index >= this.seq.segments.length) index = 0;
|
|
3094
3595
|
let child = this.children[index];
|
|
3095
3596
|
if (!child) {
|
|
3096
|
-
child = new SpecInstance(rootScene ?? this.childScene(index), this.childCtx, { transparent: this.bed !== null, skipInitialPaint });
|
|
3597
|
+
child = new SpecInstance(rootScene ?? this.childScene(index), this.childCtx, { transparent: this.bed !== null, skipInitialPaint, capabilityTier: this.capabilityTier, hostPresents: true });
|
|
3097
3598
|
this.children[index] = child;
|
|
3098
3599
|
child.setPaused(true);
|
|
3099
3600
|
if (rootScene) child.hotSwapSpec(this.childScene(index));
|
|
@@ -3126,7 +3627,49 @@ var SequenceInstance = class {
|
|
|
3126
3627
|
this.lastT = now - this.startT + this.baseT;
|
|
3127
3628
|
this.renderFrame(this.lastT, this.seed);
|
|
3128
3629
|
}
|
|
3630
|
+
/**
|
|
3631
|
+
* With a presentation pass, size the visible surface like a child sizes
|
|
3632
|
+
* the scene canvas (children never touch the visible surface then).
|
|
3633
|
+
*/
|
|
3634
|
+
sizeVisible() {
|
|
3635
|
+
const fp = this.finishPass;
|
|
3636
|
+
if (!fp) return;
|
|
3637
|
+
const dpr = Math.min(this.childCtx.dpr, 2);
|
|
3638
|
+
fp.canvas.width = Math.max(1, Math.round(this.childCtx.width * dpr));
|
|
3639
|
+
fp.canvas.height = Math.max(1, Math.round(this.childCtx.height * dpr));
|
|
3640
|
+
fp.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
3641
|
+
}
|
|
3642
|
+
/**
|
|
3643
|
+
* The finish this frame applies: the sequence's own overrides; else the
|
|
3644
|
+
* active segment's (the incoming one during a fade) — read through
|
|
3645
|
+
* `steeredScene` so a retained steer to `finish.grain`/`finish.dither`
|
|
3646
|
+
* takes effect immediately instead of the authored value; a bed's is
|
|
3647
|
+
* ignored.
|
|
3648
|
+
*/
|
|
3649
|
+
frameFinish() {
|
|
3650
|
+
if (this.seq.finish !== void 0) return this.seq.finish;
|
|
3651
|
+
if (this.activeIndex < 0) return void 0;
|
|
3652
|
+
return this.steeredScene(this.childScene(this.activeIndex)).finish;
|
|
3653
|
+
}
|
|
3654
|
+
/**
|
|
3655
|
+
* The seed `frameFinish()`'s grain resolves against — the sequence's own
|
|
3656
|
+
* for a sequence-level finish, else the active segment's actual render
|
|
3657
|
+
* seed (`segmentRenderSeed`: chain-root-aware and normalized exactly like
|
|
3658
|
+
* `SpecInstance`, so it agrees with what that segment's entities render
|
|
3659
|
+
* with, morph or not).
|
|
3660
|
+
*/
|
|
3661
|
+
frameFinishSeed() {
|
|
3662
|
+
if (this.seq.finish !== void 0) return this.seed;
|
|
3663
|
+
if (this.activeIndex < 0) return this.seed;
|
|
3664
|
+
return segmentRenderSeed(this.seq, this.activeIndex) ?? this.seed;
|
|
3665
|
+
}
|
|
3129
3666
|
renderFrame(T, seed) {
|
|
3667
|
+
this.renderScene(T, seed);
|
|
3668
|
+
if (this.finishPass) {
|
|
3669
|
+
presentWithFinish(this.finishPass, this.childCtx.surface, this.childCtx.width, this.childCtx.height, T, this.frameFinish(), this.animateFinish, this.frameFinishSeed());
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
renderScene(T, seed) {
|
|
3130
3673
|
this.renderedT = T;
|
|
3131
3674
|
const resolved = resolveSegment(this.seq, T + this.clockOffset, { releasedBelow: this.releasedBelow });
|
|
3132
3675
|
const { index, localT } = resolved;
|
|
@@ -3207,6 +3750,7 @@ var SequenceInstance = class {
|
|
|
3207
3750
|
this.childCtx.width = width;
|
|
3208
3751
|
this.childCtx.height = height;
|
|
3209
3752
|
if (dpr !== void 0) this.childCtx.dpr = dpr;
|
|
3753
|
+
this.sizeVisible();
|
|
3210
3754
|
for (const child of this.children) {
|
|
3211
3755
|
child?.resize(width, height, dpr);
|
|
3212
3756
|
}
|
|
@@ -3218,7 +3762,10 @@ var SequenceInstance = class {
|
|
|
3218
3762
|
const all = track?.deltas ?? [];
|
|
3219
3763
|
const isBedPath = (d) => this.bed !== null && typeof d.path === "string" && d.path.startsWith(BED_PREFIX);
|
|
3220
3764
|
const bedDeltas = all.filter(isBedPath).map((d) => ({ ...d, path: d.path.slice(BED_PREFIX.length) }));
|
|
3221
|
-
if (bedDeltas.length > 0)
|
|
3765
|
+
if (bedDeltas.length > 0) {
|
|
3766
|
+
for (const d of bedDeltas) this.retainedBedDeltas.set(d.path, d);
|
|
3767
|
+
this.bed.applyTrack({ ...track, deltas: bedDeltas });
|
|
3768
|
+
}
|
|
3222
3769
|
const deltas = all.filter((d) => !isBedPath(d));
|
|
3223
3770
|
for (const d of deltas) {
|
|
3224
3771
|
if (d.path !== "sequence.segment" && typeof d.path === "string") this.retainedDeltas.set(d.path, d);
|
|
@@ -3245,6 +3792,34 @@ var SequenceInstance = class {
|
|
|
3245
3792
|
this.fadingIn?.child.applyTrack({ ...track, deltas: childDeltas });
|
|
3246
3793
|
}
|
|
3247
3794
|
}
|
|
3795
|
+
/**
|
|
3796
|
+
* See `SequenceSaverInstance.hotSwapSequence`. Nothing that positions the
|
|
3797
|
+
* show is touched: `clockOffset`, `releasedBelow`, `activeIndex`,
|
|
3798
|
+
* `morphFromIndex`, `pendingWrapFade`, `renderedT` and the retained deltas
|
|
3799
|
+
* all keep their values, and the timing terms `sequenceSwapCompatible`
|
|
3800
|
+
* pins (durations, advance, transitions, loop) are the same on both sides,
|
|
3801
|
+
* so `resolveSegment` at the next frame lands where it would have anyway.
|
|
3802
|
+
* Each live child takes its new scene with the retained track re-applied
|
|
3803
|
+
* (`steeredScene` — the same routing a fresh child gets), so steered paint
|
|
3804
|
+
* survives the swap; `hotSwapSpec` keeps the entities (the signatures
|
|
3805
|
+
* match) and does not reset ghosting contiguity, so a drifting entity is
|
|
3806
|
+
* where it was 1 ms ago. Children are always paused, so none of them has a
|
|
3807
|
+
* glide in flight for the swap to interrupt (`SpecInstance.applyTrack`
|
|
3808
|
+
* snaps when paused). A morph in progress is untouched here: its chain-root
|
|
3809
|
+
* child is re-lerped from the new endpoints on the very next frame.
|
|
3810
|
+
*/
|
|
3811
|
+
hotSwapSequence(next) {
|
|
3812
|
+
if (!sequenceSwapCompatible(this.seq, next)) return false;
|
|
3813
|
+
this.seq = next;
|
|
3814
|
+
if (this.bed && next.bed) this.bed.hotSwapSpec(applyRetainedDeltas(this.bedScene(next.bed), this.retainedBedDeltas.values()));
|
|
3815
|
+
for (let i = 0; i < this.children.length; i++) {
|
|
3816
|
+
this.children[i]?.hotSwapSpec(this.steeredScene(this.childScene(i)));
|
|
3817
|
+
}
|
|
3818
|
+
if (this.fading) this.fading.child.hotSwapSpec(this.steeredScene(this.childScene(this.fading.index)));
|
|
3819
|
+
if (this.fadingIn) this.fadingIn.child.hotSwapSpec(this.steeredScene(this.childScene(this.fadingIn.index)));
|
|
3820
|
+
if (this.paused) this.renderFrame(this.renderedT, this.seed);
|
|
3821
|
+
return true;
|
|
3822
|
+
}
|
|
3248
3823
|
dispose() {
|
|
3249
3824
|
this.stop();
|
|
3250
3825
|
this.releaseFading();
|
|
@@ -3252,6 +3827,10 @@ var SequenceInstance = class {
|
|
|
3252
3827
|
this.releaseChild(i);
|
|
3253
3828
|
}
|
|
3254
3829
|
this.bed?.dispose();
|
|
3830
|
+
if (this.finishPass) {
|
|
3831
|
+
this.finishPass.grain = null;
|
|
3832
|
+
this.finishPass.dither = null;
|
|
3833
|
+
}
|
|
3255
3834
|
if (this.canvas) this.canvas.remove();
|
|
3256
3835
|
}
|
|
3257
3836
|
};
|
|
@@ -3289,6 +3868,14 @@ function compileSequence(spec) {
|
|
|
3289
3868
|
import { createRng as createRng2 } from "@idle-screens/core";
|
|
3290
3869
|
|
|
3291
3870
|
// src/luma.ts
|
|
3871
|
+
function fieldRenderedRgbs(bg) {
|
|
3872
|
+
const n = bg.bands.length;
|
|
3873
|
+
const q = Math.round(bg.quantize ?? n);
|
|
3874
|
+
if (q < 2) return bg.bands.map((hex) => hexToRgb255(hex));
|
|
3875
|
+
const out = [];
|
|
3876
|
+
for (let level = 0; level < q; level++) out.push(fieldRgb((level + 0.5) / q, bg.bands, q));
|
|
3877
|
+
return out;
|
|
3878
|
+
}
|
|
3292
3879
|
function hexLuma(hex) {
|
|
3293
3880
|
const h = hex.length === 4 ? `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}` : hex;
|
|
3294
3881
|
const n = parseInt(h.slice(1), 16);
|
|
@@ -3317,6 +3904,10 @@ function spriteHex(layer, e) {
|
|
|
3317
3904
|
function backgroundLuma(spec) {
|
|
3318
3905
|
const bg = spec.background;
|
|
3319
3906
|
if (!bg || bg.type === "solid") return hexLuma(bg?.color ?? "#05050a");
|
|
3907
|
+
if (bg.type === "field") {
|
|
3908
|
+
const rgbs = fieldRenderedRgbs(bg);
|
|
3909
|
+
return rgbs.reduce((s, c) => s + rgb255Luma(c), 0) / rgbs.length;
|
|
3910
|
+
}
|
|
3320
3911
|
return bg.stops.reduce((s, st) => s + hexLuma(st.color), 0) / bg.stops.length;
|
|
3321
3912
|
}
|
|
3322
3913
|
function hexRgb(hex) {
|
|
@@ -3328,14 +3919,20 @@ function hexRgb(hex) {
|
|
|
3328
3919
|
function backgroundRgb(spec) {
|
|
3329
3920
|
const bg = spec.background;
|
|
3330
3921
|
if (!bg || bg.type === "solid") return hexRgb(bg?.color ?? "#05050a");
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3922
|
+
if (bg.type === "field") {
|
|
3923
|
+
const rgbs = fieldRenderedRgbs(bg);
|
|
3924
|
+
const acc2 = rgbs.reduce((s, c) => ({ r: s.r + c[0], g: s.g + c[1], b: s.b + c[2] }), { r: 0, g: 0, b: 0 });
|
|
3925
|
+
return { r: acc2.r / rgbs.length / 255, g: acc2.g / rgbs.length / 255, b: acc2.b / rgbs.length / 255 };
|
|
3926
|
+
}
|
|
3927
|
+
const colours = bg.stops.map((st) => st.color);
|
|
3928
|
+
const acc = colours.reduce(
|
|
3929
|
+
(s, hex) => {
|
|
3930
|
+
const c = hexRgb(hex);
|
|
3334
3931
|
return { r: s.r + c.r, g: s.g + c.g, b: s.b + c.b };
|
|
3335
3932
|
},
|
|
3336
3933
|
{ r: 0, g: 0, b: 0 }
|
|
3337
3934
|
);
|
|
3338
|
-
const n =
|
|
3935
|
+
const n = colours.length || 1;
|
|
3339
3936
|
return { r: acc.r / n, g: acc.g / n, b: acc.b / n };
|
|
3340
3937
|
}
|
|
3341
3938
|
function colourSeparation(layer, sprite, bg) {
|
|
@@ -3358,9 +3955,16 @@ function legibilityRatio(a, b) {
|
|
|
3358
3955
|
const lo = Math.min(la, lb);
|
|
3359
3956
|
return (hi + 0.05) / (lo + 0.05);
|
|
3360
3957
|
}
|
|
3361
|
-
function backgroundRgbAt(spec, yPx, h, unit) {
|
|
3958
|
+
function backgroundRgbAt(spec, yPx, h, unit, xPx, w, seed, t = 0) {
|
|
3362
3959
|
const bg = spec.background;
|
|
3363
3960
|
if (!bg || bg.type === "solid") return hexRgb(bg?.color ?? "#05050a");
|
|
3961
|
+
if (bg.type === "field") {
|
|
3962
|
+
if (xPx === void 0 || w === void 0) return backgroundRgb(spec);
|
|
3963
|
+
const short = Math.max(1, Math.min(w, h));
|
|
3964
|
+
const ft = fieldSampleTime(bg, t);
|
|
3965
|
+
const c = fieldRgbAt(xPx / short, yPx / short, ft, bg, seed ?? spec.seed ?? 42);
|
|
3966
|
+
return { r: c[0] / 255, g: c[1] / 255, b: c[2] / 255 };
|
|
3967
|
+
}
|
|
3364
3968
|
if (bg.band) {
|
|
3365
3969
|
const bh = bg.band.height * unit;
|
|
3366
3970
|
if (yPx >= h - bh) return hexRgb(bg.band.color);
|
|
@@ -3913,7 +4517,8 @@ function adviseSpec(spec, viewport = { width: 1920, height: 1080 }, opts = {}) {
|
|
|
3913
4517
|
let worst = null;
|
|
3914
4518
|
for (const box of boxes) {
|
|
3915
4519
|
const cy = (box.y0 + box.y1) / 2;
|
|
3916
|
-
const
|
|
4520
|
+
const cx = (box.x0 + box.x1) / 2;
|
|
4521
|
+
const groundRgb = backgroundRgbAt(spec, cy, h, scale, cx, w, opts.backgroundSeed ?? opts.seed, opts.t ?? COHESION_T);
|
|
3917
4522
|
const ground = legibilityRatio(textPlate(groundRgb, box.alpha), groundRgb);
|
|
3918
4523
|
const lit = brightestAdditivePlate(spec, allEntities, groundRgb, box, w, h);
|
|
3919
4524
|
const plate = lit ? legibilityRatio(textPlate(lit.rgb, box.alpha), lit.rgb) : Infinity;
|
|
@@ -4151,7 +4756,7 @@ function describeScene(spec, options = {}) {
|
|
|
4151
4756
|
return { t, viewport: { w, h }, layers: layerSnapshots, warnings };
|
|
4152
4757
|
});
|
|
4153
4758
|
return {
|
|
4154
|
-
spec: { id: spec.id, label: spec.label, units: spec.units ?? "px" },
|
|
4759
|
+
spec: { id: spec.id, label: spec.label, units: spec.units ?? "px", ...spec.finish ? { finish: spec.finish } : {} },
|
|
4155
4760
|
snapshots
|
|
4156
4761
|
};
|
|
4157
4762
|
}
|
|
@@ -4249,8 +4854,23 @@ function luminanceGrid(spec, opts = {}) {
|
|
|
4249
4854
|
const cellH = h / rows;
|
|
4250
4855
|
const bg = spec.background;
|
|
4251
4856
|
const bgRow = new Array(rows).fill(0);
|
|
4857
|
+
let bgCells;
|
|
4252
4858
|
if (!bg || bg.type === "solid") {
|
|
4253
4859
|
bgRow.fill(hexLuma(bg?.color ?? "#05050a"));
|
|
4860
|
+
} else if (bg.type === "field") {
|
|
4861
|
+
bgCells = new Array(cols * rows);
|
|
4862
|
+
const short = Math.max(1, Math.min(w, h));
|
|
4863
|
+
const seed = normalizeSeed(opts.seed ?? spec.seed ?? 42);
|
|
4864
|
+
const ft = fieldSampleTime(bg, t);
|
|
4865
|
+
for (let r = 0; r < rows; r++) {
|
|
4866
|
+
let rowSum = 0;
|
|
4867
|
+
for (let c = 0; c < cols; c++) {
|
|
4868
|
+
const lum = rgb255Luma(fieldRgbAt((c + 0.5) * cellW / short, (r + 0.5) * cellH / short, ft, bg, seed));
|
|
4869
|
+
bgCells[r * cols + c] = lum;
|
|
4870
|
+
rowSum += lum;
|
|
4871
|
+
}
|
|
4872
|
+
bgRow[r] = rowSum / cols;
|
|
4873
|
+
}
|
|
4254
4874
|
} else {
|
|
4255
4875
|
const stops = [...bg.stops].sort((a, b) => a.at - b.at);
|
|
4256
4876
|
for (let r = 0; r < rows; r++) {
|
|
@@ -4275,7 +4895,7 @@ function luminanceGrid(spec, opts = {}) {
|
|
|
4275
4895
|
}
|
|
4276
4896
|
}
|
|
4277
4897
|
const cells = new Array(cols * rows);
|
|
4278
|
-
for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) cells[r * cols + c] = bgRow[r];
|
|
4898
|
+
for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) cells[r * cols + c] = bgCells ? bgCells[r * cols + c] : bgRow[r];
|
|
4279
4899
|
const compose = (idx, lum, a, blend) => {
|
|
4280
4900
|
const cur = cells[idx];
|
|
4281
4901
|
if (blend === "lighter" || blend === "screen") cells[idx] = Math.min(1, cur + lum * a);
|
|
@@ -4366,7 +4986,7 @@ function luminanceGrid(spec, opts = {}) {
|
|
|
4366
4986
|
continue;
|
|
4367
4987
|
}
|
|
4368
4988
|
if (s.kind === "streak") {
|
|
4369
|
-
const heading = headingAt(e, tPass, w, h) ?? 0;
|
|
4989
|
+
const heading = (headingAt(e, tPass, w, h) ?? 0) + rotationAt(e, tPass);
|
|
4370
4990
|
const steps = Math.max(2, Math.ceil(sz / Math.min(cellW, cellH)));
|
|
4371
4991
|
const wgt = Math.min(1, (s.width ?? (scale === 1 ? 2 : 2e-3)) * scale / cellH);
|
|
4372
4992
|
for (let i = 0; i <= steps; i++) {
|
|
@@ -4489,9 +5109,9 @@ function luminanceGrid(spec, opts = {}) {
|
|
|
4489
5109
|
}
|
|
4490
5110
|
}
|
|
4491
5111
|
splatPass(t, 1);
|
|
4492
|
-
return finishGrid(cols, rows, cells, bgRow);
|
|
5112
|
+
return finishGrid(cols, rows, cells, bgRow, bgCells);
|
|
4493
5113
|
}
|
|
4494
|
-
function finishGrid(cols, rows, cells, bgRow) {
|
|
5114
|
+
function finishGrid(cols, rows, cells, bgRow, bgCells) {
|
|
4495
5115
|
let sum = 0;
|
|
4496
5116
|
let covered = 0;
|
|
4497
5117
|
let devSum = 0;
|
|
@@ -4503,7 +5123,7 @@ function finishGrid(cols, rows, cells, bgRow) {
|
|
|
4503
5123
|
for (let c = 0; c < cols; c++) {
|
|
4504
5124
|
const v = cells[r * cols + c];
|
|
4505
5125
|
sum += v;
|
|
4506
|
-
const dev = Math.abs(v - bgRow[r]);
|
|
5126
|
+
const dev = Math.abs(v - (bgCells ? bgCells[r * cols + c] : bgRow[r]));
|
|
4507
5127
|
if (dev > 0.03) covered++;
|
|
4508
5128
|
devSum += dev;
|
|
4509
5129
|
cxAcc += dev * (c + 0.5);
|
|
@@ -4519,6 +5139,7 @@ function finishGrid(cols, rows, cells, bgRow) {
|
|
|
4519
5139
|
rows,
|
|
4520
5140
|
cells,
|
|
4521
5141
|
background: bgRow,
|
|
5142
|
+
...bgCells ? { backgroundCells: bgCells } : {},
|
|
4522
5143
|
meanLuminance: sum / (cols * rows),
|
|
4523
5144
|
coverage: covered / (cols * rows),
|
|
4524
5145
|
centroid: devSum > 1e-6 ? { x: cxAcc / devSum / cols, y: cyAcc / devSum / rows } : null,
|
|
@@ -4742,8 +5363,9 @@ function regionMeans(grid) {
|
|
|
4742
5363
|
const cnt = new Array(9).fill(0);
|
|
4743
5364
|
for (let r = 0; r < grid.rows; r++) {
|
|
4744
5365
|
for (let c = 0; c < grid.cols; c++) {
|
|
5366
|
+
const idx = r * grid.cols + c;
|
|
4745
5367
|
const region = Math.min(2, Math.floor(r / grid.rows * 3)) * 3 + Math.min(2, Math.floor(c / grid.cols * 3));
|
|
4746
|
-
out[region] += Math.abs(grid.cells[
|
|
5368
|
+
out[region] += Math.abs(grid.cells[idx] - (grid.backgroundCells ? grid.backgroundCells[idx] : grid.background[r]));
|
|
4747
5369
|
cnt[region]++;
|
|
4748
5370
|
}
|
|
4749
5371
|
}
|
|
@@ -4819,7 +5441,7 @@ function perceiveSequenceFrame(seq, T, opts = {}) {
|
|
|
4819
5441
|
const bedGrid = luminanceGrid(seq.bed, bedOpts);
|
|
4820
5442
|
const inkGrid = luminanceGrid(ink, segOpts);
|
|
4821
5443
|
const cells = bedGrid.cells.map((v, i) => Math.min(1, v + inkGrid.cells[i]));
|
|
4822
|
-
const grid = finishGrid(bedGrid.cols, bedGrid.rows, cells, bedGrid.background);
|
|
5444
|
+
const grid = finishGrid(bedGrid.cols, bedGrid.rows, cells, bedGrid.background, bedGrid.backgroundCells);
|
|
4823
5445
|
const bedLayers = seq.bed.layers.length;
|
|
4824
5446
|
const dominance = rankDominance([
|
|
4825
5447
|
...rawDominance(seq.bed, bedOpts).map((d) => ({ ...d, key: bedKey(d.key, d.layerIndex) })),
|
|
@@ -4842,7 +5464,16 @@ function perceiveSequenceFrame(seq, T, opts = {}) {
|
|
|
4842
5464
|
text: [...textSprites(seq.bed, bedOpts).map(prefix), ...textSprites(ink, segOpts).map(shift)],
|
|
4843
5465
|
advisories: [
|
|
4844
5466
|
...adviseSpec(seq.bed, viewport, { t: T, seed: bedOpts.seed }).map((w) => ({ ...w, path: `bed.${w.path}` })),
|
|
4845
|
-
|
|
5467
|
+
// inkOverBed's layers are the segment's (seed: segOpts.seed) but its
|
|
5468
|
+
// background is the bed's (seq.bed.background) — a field there must be
|
|
5469
|
+
// sampled with the bed's own seed, or a `role: 'read'` legibility
|
|
5470
|
+
// advisory checks terrain that isn't what's actually behind the ink.
|
|
5471
|
+
// `?? 42`: inkOverBed.seed is the SEGMENT's raw seed field (it's `ink`
|
|
5472
|
+
// spread first), so adviseSpec's own `backgroundSeed ?? seed` fallback
|
|
5473
|
+
// would substitute the segment's seed, not the bed's, if bedOpts.seed
|
|
5474
|
+
// were left undefined — 42 is the same default `luminanceGrid` (and
|
|
5475
|
+
// every other field seed fallback) converges on.
|
|
5476
|
+
...adviseSpec(inkOverBed, viewport, { t: r.localT, seed: segOpts.seed, backgroundSeed: bedOpts.seed ?? 42 })
|
|
4846
5477
|
],
|
|
4847
5478
|
segment,
|
|
4848
5479
|
bed: true
|
|
@@ -5561,6 +6192,613 @@ var DASHBOARD_SPEC = {
|
|
|
5561
6192
|
]
|
|
5562
6193
|
};
|
|
5563
6194
|
|
|
6195
|
+
// src/examples/sparse-night.ts
|
|
6196
|
+
var SPARSE_NIGHT_SPEC = {
|
|
6197
|
+
schemaVersion: 1,
|
|
6198
|
+
id: "sparse-night",
|
|
6199
|
+
label: "Sparse Night",
|
|
6200
|
+
seed: 101,
|
|
6201
|
+
motionIntensity: "calm",
|
|
6202
|
+
density: "sparse",
|
|
6203
|
+
background: { type: "solid", color: "#02030a" },
|
|
6204
|
+
layers: [
|
|
6205
|
+
{
|
|
6206
|
+
key: "grain",
|
|
6207
|
+
count: 110,
|
|
6208
|
+
sprite: {
|
|
6209
|
+
kind: "circle",
|
|
6210
|
+
radius: [3e-4, 9e-4],
|
|
6211
|
+
color: "#c9d4e6"
|
|
6212
|
+
},
|
|
6213
|
+
motion: { type: "static" },
|
|
6214
|
+
alpha: [0.05, 0.3],
|
|
6215
|
+
pulse: { amp: 0.08, period: 7e3 }
|
|
6216
|
+
},
|
|
6217
|
+
{
|
|
6218
|
+
key: "figure",
|
|
6219
|
+
count: 5,
|
|
6220
|
+
sprite: {
|
|
6221
|
+
kind: "circle",
|
|
6222
|
+
radius: [16e-4, 22e-4],
|
|
6223
|
+
color: "#dfe8f2"
|
|
6224
|
+
},
|
|
6225
|
+
motion: { type: "drift", speed: [4e-4, 9e-4] },
|
|
6226
|
+
alpha: [0.4, 0.7],
|
|
6227
|
+
region: { x: [0.36, 0.64], y: [0.32, 0.62] },
|
|
6228
|
+
links: {
|
|
6229
|
+
k: 2,
|
|
6230
|
+
maxDist: 0.09,
|
|
6231
|
+
color: "#7f97b8",
|
|
6232
|
+
alpha: 0.16,
|
|
6233
|
+
width: 4e-4,
|
|
6234
|
+
mode: "chain",
|
|
6235
|
+
closed: true
|
|
6236
|
+
}
|
|
6237
|
+
},
|
|
6238
|
+
{
|
|
6239
|
+
key: "satellite",
|
|
6240
|
+
count: 1,
|
|
6241
|
+
sprite: {
|
|
6242
|
+
kind: "circle",
|
|
6243
|
+
radius: [24e-4, 24e-4],
|
|
6244
|
+
color: "#e8b04a",
|
|
6245
|
+
soft: true
|
|
6246
|
+
},
|
|
6247
|
+
motion: { type: "orbit", speed: [3, 3], radius: [0.34, 0.4] },
|
|
6248
|
+
alpha: [0.85, 0.85],
|
|
6249
|
+
blend: "lighter",
|
|
6250
|
+
emit: { every: 18e3, life: 3e3, jitter: 0, grow: [0.5, 1.6] }
|
|
6251
|
+
}
|
|
6252
|
+
]
|
|
6253
|
+
};
|
|
6254
|
+
|
|
6255
|
+
// src/examples/signal-board.ts
|
|
6256
|
+
var SIGNAL_BOARD_SPEC = {
|
|
6257
|
+
schemaVersion: 1,
|
|
6258
|
+
id: "signal-board",
|
|
6259
|
+
label: "Signal Board",
|
|
6260
|
+
seed: 707,
|
|
6261
|
+
motionIntensity: "calm",
|
|
6262
|
+
background: { type: "solid", color: "#0d0b08" },
|
|
6263
|
+
layers: [
|
|
6264
|
+
{
|
|
6265
|
+
key: "readout",
|
|
6266
|
+
count: 9,
|
|
6267
|
+
sprite: {
|
|
6268
|
+
kind: "bar",
|
|
6269
|
+
values: [62, 38, 84, 21, 55, 91, 44, 70, 29],
|
|
6270
|
+
max: 100,
|
|
6271
|
+
length: 0.24,
|
|
6272
|
+
thickness: 0.013,
|
|
6273
|
+
color: "#ffb000",
|
|
6274
|
+
direction: "right"
|
|
6275
|
+
},
|
|
6276
|
+
motion: { type: "static" },
|
|
6277
|
+
layout: { type: "table", columns: 3, gap: 0.07 },
|
|
6278
|
+
region: { x: [0.1, 0.9], y: [0.34, 0.78] },
|
|
6279
|
+
alpha: [0.5, 0.62]
|
|
6280
|
+
},
|
|
6281
|
+
{
|
|
6282
|
+
key: "labels",
|
|
6283
|
+
count: 9,
|
|
6284
|
+
sprite: {
|
|
6285
|
+
kind: "text",
|
|
6286
|
+
strings: [
|
|
6287
|
+
"A-01",
|
|
6288
|
+
"A-02",
|
|
6289
|
+
"A-03",
|
|
6290
|
+
"B-01",
|
|
6291
|
+
"B-02",
|
|
6292
|
+
"B-03",
|
|
6293
|
+
"C-01",
|
|
6294
|
+
"C-02",
|
|
6295
|
+
"C-03"
|
|
6296
|
+
],
|
|
6297
|
+
color: "#b08a3a",
|
|
6298
|
+
font: "monospace",
|
|
6299
|
+
align: "left"
|
|
6300
|
+
},
|
|
6301
|
+
motion: { type: "static" },
|
|
6302
|
+
size: [0.011, 0.011],
|
|
6303
|
+
layout: { type: "table", columns: 3, gap: 0.07 },
|
|
6304
|
+
region: { x: [0.1, 0.9], y: [0.28, 0.72] },
|
|
6305
|
+
alpha: [0.55, 0.55]
|
|
6306
|
+
},
|
|
6307
|
+
{
|
|
6308
|
+
key: "caption",
|
|
6309
|
+
count: 1,
|
|
6310
|
+
sprite: {
|
|
6311
|
+
kind: "textBlock",
|
|
6312
|
+
text: "STATION 7 \u2014 QUIET HOURS",
|
|
6313
|
+
maxWidth: 0.6,
|
|
6314
|
+
fontSize: 0.022,
|
|
6315
|
+
lineHeight: 1.4,
|
|
6316
|
+
align: "left",
|
|
6317
|
+
anchor: "top-left",
|
|
6318
|
+
font: "bold monospace",
|
|
6319
|
+
color: "#c8862a"
|
|
6320
|
+
},
|
|
6321
|
+
motion: { type: "static" },
|
|
6322
|
+
position: { x: 0.1, y: 0.14 },
|
|
6323
|
+
alpha: [0.8, 0.8]
|
|
6324
|
+
},
|
|
6325
|
+
{
|
|
6326
|
+
key: "sweep",
|
|
6327
|
+
count: 1,
|
|
6328
|
+
sprite: {
|
|
6329
|
+
kind: "stroke",
|
|
6330
|
+
length: [1.4, 1.4],
|
|
6331
|
+
points: [
|
|
6332
|
+
[-1, 0],
|
|
6333
|
+
[1, 0]
|
|
6334
|
+
],
|
|
6335
|
+
width: 16e-4,
|
|
6336
|
+
color: "#33ff66"
|
|
6337
|
+
},
|
|
6338
|
+
motion: { type: "drift", speed: [0.012, 0.012], angle: 90 },
|
|
6339
|
+
alpha: [0.22, 0.22],
|
|
6340
|
+
blend: "screen"
|
|
6341
|
+
}
|
|
6342
|
+
]
|
|
6343
|
+
};
|
|
6344
|
+
|
|
6345
|
+
// src/examples/phase-duet.ts
|
|
6346
|
+
var PHASE_DUET_SPEC = {
|
|
6347
|
+
schemaVersion: 1,
|
|
6348
|
+
id: "phase-duet",
|
|
6349
|
+
label: "Phase Duet",
|
|
6350
|
+
seed: 88,
|
|
6351
|
+
motionIntensity: "calm",
|
|
6352
|
+
background: {
|
|
6353
|
+
type: "gradient",
|
|
6354
|
+
stops: [
|
|
6355
|
+
{ at: 0, color: "#0a0f1c" },
|
|
6356
|
+
{ at: 0.6, color: "#101b2e" },
|
|
6357
|
+
{ at: 1, color: "#07141a" }
|
|
6358
|
+
]
|
|
6359
|
+
},
|
|
6360
|
+
layers: [
|
|
6361
|
+
{
|
|
6362
|
+
key: "ripple",
|
|
6363
|
+
count: 60,
|
|
6364
|
+
sprite: {
|
|
6365
|
+
kind: "circle",
|
|
6366
|
+
radius: [18e-4, 3e-3],
|
|
6367
|
+
color: "#6fd3c7",
|
|
6368
|
+
colors: ["#6fd3c7", "#9fb8d8"],
|
|
6369
|
+
colorWeights: [2, 1]
|
|
6370
|
+
},
|
|
6371
|
+
motion: { type: "static" },
|
|
6372
|
+
layout: { type: "grid", columns: 10, jitter: 0 },
|
|
6373
|
+
alpha: [0.3, 0.55],
|
|
6374
|
+
pulse: { amp: 0.35, period: 6e3, wave: { wavelength: 0.7, angle: 25 } },
|
|
6375
|
+
clock: { phase: 0, rate: 1 }
|
|
6376
|
+
},
|
|
6377
|
+
{
|
|
6378
|
+
key: "rings",
|
|
6379
|
+
count: 3,
|
|
6380
|
+
sprite: {
|
|
6381
|
+
kind: "ring",
|
|
6382
|
+
radius: [0.05, 0.09],
|
|
6383
|
+
width: 12e-4,
|
|
6384
|
+
color: "#6fd3c7"
|
|
6385
|
+
},
|
|
6386
|
+
motion: { type: "static" },
|
|
6387
|
+
alpha: [0.25, 0.35],
|
|
6388
|
+
blend: "lighter",
|
|
6389
|
+
grow: { amp: 0.5, period: 6e3 },
|
|
6390
|
+
clock: { phase: 0, rate: 1 }
|
|
6391
|
+
},
|
|
6392
|
+
{
|
|
6393
|
+
key: "heart",
|
|
6394
|
+
count: 1,
|
|
6395
|
+
sprite: {
|
|
6396
|
+
kind: "circle",
|
|
6397
|
+
radius: [0.11, 0.11],
|
|
6398
|
+
color: "#9fb8d8",
|
|
6399
|
+
soft: true
|
|
6400
|
+
},
|
|
6401
|
+
motion: { type: "static" },
|
|
6402
|
+
position: { x: 0.5, y: 0.5 },
|
|
6403
|
+
alpha: [0.22, 0.22],
|
|
6404
|
+
blend: "lighter",
|
|
6405
|
+
grow: { amp: 0.25, period: 6e3 },
|
|
6406
|
+
clock: { phase: 0, rate: 1 }
|
|
6407
|
+
}
|
|
6408
|
+
]
|
|
6409
|
+
};
|
|
6410
|
+
|
|
6411
|
+
// src/examples/web-work.ts
|
|
6412
|
+
var WEB_WORK_SPEC = {
|
|
6413
|
+
schemaVersion: 1,
|
|
6414
|
+
id: "web-work",
|
|
6415
|
+
label: "Web Work",
|
|
6416
|
+
seed: 31,
|
|
6417
|
+
motionIntensity: "calm",
|
|
6418
|
+
background: { type: "solid", color: "#04070c" },
|
|
6419
|
+
layers: [
|
|
6420
|
+
{
|
|
6421
|
+
key: "nodes",
|
|
6422
|
+
count: 44,
|
|
6423
|
+
sprite: {
|
|
6424
|
+
kind: "circle",
|
|
6425
|
+
radius: [18e-4, 34e-4],
|
|
6426
|
+
color: "#8fb4d8",
|
|
6427
|
+
colors: ["#8fb4d8", "#c9d8e6"],
|
|
6428
|
+
colorWeights: [2, 1]
|
|
6429
|
+
},
|
|
6430
|
+
motion: {
|
|
6431
|
+
type: "drift",
|
|
6432
|
+
speed: [2e-3, 6e-3],
|
|
6433
|
+
angle: 0,
|
|
6434
|
+
bidirectional: true
|
|
6435
|
+
},
|
|
6436
|
+
alpha: [0.45, 0.8],
|
|
6437
|
+
links: {
|
|
6438
|
+
k: 3,
|
|
6439
|
+
maxDist: 0.3,
|
|
6440
|
+
color: "#5f7f9f",
|
|
6441
|
+
alpha: 0.16,
|
|
6442
|
+
width: 5e-4,
|
|
6443
|
+
mode: "random",
|
|
6444
|
+
falloff: true
|
|
6445
|
+
}
|
|
6446
|
+
},
|
|
6447
|
+
{
|
|
6448
|
+
key: "flags",
|
|
6449
|
+
count: 22,
|
|
6450
|
+
sprite: {
|
|
6451
|
+
kind: "stroke",
|
|
6452
|
+
length: [0.02, 0.045],
|
|
6453
|
+
points: [
|
|
6454
|
+
[-1, 0.2],
|
|
6455
|
+
[-0.2, -0.5],
|
|
6456
|
+
[0.6, -0.3],
|
|
6457
|
+
[1, 0.1]
|
|
6458
|
+
],
|
|
6459
|
+
width: 16e-4,
|
|
6460
|
+
taper: true,
|
|
6461
|
+
orient: true,
|
|
6462
|
+
color: "#6f93b8",
|
|
6463
|
+
colors: ["#6f93b8", "#9fb8d8"]
|
|
6464
|
+
},
|
|
6465
|
+
motion: {
|
|
6466
|
+
type: "drift",
|
|
6467
|
+
speed: [0.01, 0.03],
|
|
6468
|
+
angle: 0,
|
|
6469
|
+
bidirectional: true
|
|
6470
|
+
},
|
|
6471
|
+
alpha: [0.3, 0.6]
|
|
6472
|
+
}
|
|
6473
|
+
]
|
|
6474
|
+
};
|
|
6475
|
+
|
|
6476
|
+
// src/examples/shard-fall.ts
|
|
6477
|
+
var SHARD_FALL_SPEC = {
|
|
6478
|
+
schemaVersion: 1,
|
|
6479
|
+
id: "shard-fall",
|
|
6480
|
+
label: "Shard Fall",
|
|
6481
|
+
seed: 214,
|
|
6482
|
+
motionIntensity: "moderate",
|
|
6483
|
+
background: {
|
|
6484
|
+
type: "gradient",
|
|
6485
|
+
stops: [
|
|
6486
|
+
{ at: 0, color: "#efe6d6" },
|
|
6487
|
+
{ at: 0.55, color: "#e6d6bd" },
|
|
6488
|
+
{ at: 1, color: "#d9c39f" }
|
|
6489
|
+
]
|
|
6490
|
+
},
|
|
6491
|
+
layers: [
|
|
6492
|
+
{
|
|
6493
|
+
key: "shards",
|
|
6494
|
+
count: 26,
|
|
6495
|
+
sprite: {
|
|
6496
|
+
kind: "polygon",
|
|
6497
|
+
points: [
|
|
6498
|
+
[-1, 0.35],
|
|
6499
|
+
[-0.25, -1],
|
|
6500
|
+
[0.85, -0.4],
|
|
6501
|
+
[0.4, 0.9]
|
|
6502
|
+
],
|
|
6503
|
+
radius: [0.012, 0.03],
|
|
6504
|
+
color: "#b4593a",
|
|
6505
|
+
colors: ["#b4593a", "#7f8a5e", "#5a6b7d", "#8a5a3c"],
|
|
6506
|
+
colorWeights: [2, 2, 1, 2]
|
|
6507
|
+
},
|
|
6508
|
+
motion: { type: "drift", speed: [0.012, 0.032], angle: 90 },
|
|
6509
|
+
alpha: [0.85, 0.95],
|
|
6510
|
+
rotate: [-180, 180],
|
|
6511
|
+
spin: [-35, 35]
|
|
6512
|
+
},
|
|
6513
|
+
{
|
|
6514
|
+
key: "shadows",
|
|
6515
|
+
count: 2,
|
|
6516
|
+
sprite: {
|
|
6517
|
+
kind: "circle",
|
|
6518
|
+
radius: [0.18, 0.22],
|
|
6519
|
+
color: "#5a4a34",
|
|
6520
|
+
soft: true
|
|
6521
|
+
},
|
|
6522
|
+
motion: {
|
|
6523
|
+
type: "wander",
|
|
6524
|
+
speed: [3e-3, 5e-3],
|
|
6525
|
+
meander: 0.04,
|
|
6526
|
+
coherence: 0.6
|
|
6527
|
+
},
|
|
6528
|
+
alpha: [0.2, 0.28],
|
|
6529
|
+
blend: "multiply"
|
|
6530
|
+
}
|
|
6531
|
+
]
|
|
6532
|
+
};
|
|
6533
|
+
|
|
6534
|
+
// src/examples/murmur.ts
|
|
6535
|
+
var MURMUR_SPEC = {
|
|
6536
|
+
schemaVersion: 1,
|
|
6537
|
+
id: "murmur",
|
|
6538
|
+
label: "Murmur",
|
|
6539
|
+
seed: 512,
|
|
6540
|
+
motionIntensity: "moderate",
|
|
6541
|
+
density: "dense",
|
|
6542
|
+
background: {
|
|
6543
|
+
type: "gradient",
|
|
6544
|
+
stops: [
|
|
6545
|
+
{ at: 0, color: "#151a26" },
|
|
6546
|
+
{ at: 0.5, color: "#232c3d" },
|
|
6547
|
+
{ at: 1, color: "#3a3f52" }
|
|
6548
|
+
]
|
|
6549
|
+
},
|
|
6550
|
+
layers: [
|
|
6551
|
+
{
|
|
6552
|
+
key: "flock-a",
|
|
6553
|
+
count: 260,
|
|
6554
|
+
sprite: {
|
|
6555
|
+
kind: "streak",
|
|
6556
|
+
length: [4e-3, 0.012],
|
|
6557
|
+
width: 12e-4,
|
|
6558
|
+
color: "#c9d4e6",
|
|
6559
|
+
colors: ["#c9d4e6", "#8fa6c4"],
|
|
6560
|
+
colorWeights: [2, 1]
|
|
6561
|
+
},
|
|
6562
|
+
motion: {
|
|
6563
|
+
type: "wander",
|
|
6564
|
+
speed: [0.015, 0.06],
|
|
6565
|
+
meander: 0.05,
|
|
6566
|
+
coherence: 0.95
|
|
6567
|
+
},
|
|
6568
|
+
alpha: [0.25, 0.6],
|
|
6569
|
+
blend: "screen"
|
|
6570
|
+
},
|
|
6571
|
+
{
|
|
6572
|
+
// The cap is 400 per layer, so a flock above it is two registers of the
|
|
6573
|
+
// same body — slightly different speed and length ranges read as depth.
|
|
6574
|
+
key: "flock-b",
|
|
6575
|
+
count: 260,
|
|
6576
|
+
sprite: {
|
|
6577
|
+
kind: "streak",
|
|
6578
|
+
length: [6e-3, 0.014],
|
|
6579
|
+
width: 12e-4,
|
|
6580
|
+
color: "#8fa6c4",
|
|
6581
|
+
colors: ["#8fa6c4", "#c9d4e6"],
|
|
6582
|
+
colorWeights: [2, 1]
|
|
6583
|
+
},
|
|
6584
|
+
motion: {
|
|
6585
|
+
type: "wander",
|
|
6586
|
+
speed: [0.02, 0.05],
|
|
6587
|
+
meander: 0.045,
|
|
6588
|
+
coherence: 0.93
|
|
6589
|
+
},
|
|
6590
|
+
alpha: [0.2, 0.5],
|
|
6591
|
+
blend: "screen"
|
|
6592
|
+
},
|
|
6593
|
+
{
|
|
6594
|
+
key: "grain",
|
|
6595
|
+
count: 100,
|
|
6596
|
+
sprite: { kind: "circle", radius: [5e-4, 12e-4], color: "#c9d4e6" },
|
|
6597
|
+
motion: { type: "static" },
|
|
6598
|
+
alpha: [0.06, 0.2]
|
|
6599
|
+
}
|
|
6600
|
+
]
|
|
6601
|
+
};
|
|
6602
|
+
|
|
6603
|
+
// src/examples/three-movements.ts
|
|
6604
|
+
var THREE_MOVEMENTS_SEQUENCE = {
|
|
6605
|
+
format: "idle-sequence",
|
|
6606
|
+
schemaVersion: 1,
|
|
6607
|
+
id: "three-movements",
|
|
6608
|
+
label: "Three Movements",
|
|
6609
|
+
seed: 404,
|
|
6610
|
+
loop: false,
|
|
6611
|
+
sync: "mount",
|
|
6612
|
+
finish: { grain: 0.3, dither: 0.12 },
|
|
6613
|
+
bed: {
|
|
6614
|
+
schemaVersion: 1,
|
|
6615
|
+
id: "three-movements-bed",
|
|
6616
|
+
label: "Three Movements \u2014 bed",
|
|
6617
|
+
background: {
|
|
6618
|
+
type: "field",
|
|
6619
|
+
scale: 2,
|
|
6620
|
+
octaves: 3,
|
|
6621
|
+
warp: 0.4,
|
|
6622
|
+
quantize: 0,
|
|
6623
|
+
bands: ["#0a0d18", "#141c2e", "#233046"],
|
|
6624
|
+
drift: { period: 6e4, amount: 0.25 }
|
|
6625
|
+
},
|
|
6626
|
+
layers: [
|
|
6627
|
+
{
|
|
6628
|
+
key: "motes",
|
|
6629
|
+
count: 12,
|
|
6630
|
+
sprite: {
|
|
6631
|
+
kind: "circle",
|
|
6632
|
+
radius: [12e-4, 3e-3],
|
|
6633
|
+
color: "#9fb8d8",
|
|
6634
|
+
soft: true
|
|
6635
|
+
},
|
|
6636
|
+
motion: { type: "drift", speed: [2e-3, 6e-3], angle: 90 },
|
|
6637
|
+
alpha: [0.1, 0.25]
|
|
6638
|
+
}
|
|
6639
|
+
]
|
|
6640
|
+
},
|
|
6641
|
+
segments: [
|
|
6642
|
+
{
|
|
6643
|
+
key: "cold-open",
|
|
6644
|
+
duration: 8e3,
|
|
6645
|
+
transition: { type: "morph", dur: 2500, text: "crossfade" },
|
|
6646
|
+
scene: {
|
|
6647
|
+
schemaVersion: 1,
|
|
6648
|
+
id: "three-movements-cold-open",
|
|
6649
|
+
label: "Cold Open",
|
|
6650
|
+
seed: 11,
|
|
6651
|
+
layers: [
|
|
6652
|
+
{
|
|
6653
|
+
key: "caption",
|
|
6654
|
+
count: 1,
|
|
6655
|
+
sprite: {
|
|
6656
|
+
kind: "textBlock",
|
|
6657
|
+
text: "COLD OPEN.\nThe room before anyone arrives.",
|
|
6658
|
+
maxWidth: 0.6,
|
|
6659
|
+
fontSize: 0.028,
|
|
6660
|
+
lineHeight: 1.5,
|
|
6661
|
+
align: "left",
|
|
6662
|
+
anchor: "top-left",
|
|
6663
|
+
font: "monospace",
|
|
6664
|
+
color: "#9fb8d8",
|
|
6665
|
+
opacity: 0.9,
|
|
6666
|
+
reveal: {
|
|
6667
|
+
progress: 1,
|
|
6668
|
+
mode: "typewriter",
|
|
6669
|
+
speed: 12,
|
|
6670
|
+
caret: { blink: 1.1 }
|
|
6671
|
+
}
|
|
6672
|
+
},
|
|
6673
|
+
motion: { type: "static" },
|
|
6674
|
+
position: { x: 0.1, y: 0.4 },
|
|
6675
|
+
alpha: [0.9, 0.9]
|
|
6676
|
+
},
|
|
6677
|
+
{
|
|
6678
|
+
key: "sparks",
|
|
6679
|
+
count: 30,
|
|
6680
|
+
sprite: {
|
|
6681
|
+
kind: "circle",
|
|
6682
|
+
radius: [12e-4, 26e-4],
|
|
6683
|
+
color: "#c9d8e6"
|
|
6684
|
+
},
|
|
6685
|
+
motion: { type: "static" },
|
|
6686
|
+
alpha: [0.15, 0.45],
|
|
6687
|
+
pulse: { amp: 0.12, period: 6e3 }
|
|
6688
|
+
}
|
|
6689
|
+
]
|
|
6690
|
+
}
|
|
6691
|
+
},
|
|
6692
|
+
{
|
|
6693
|
+
key: "kindle",
|
|
6694
|
+
duration: 9e3,
|
|
6695
|
+
transition: { type: "fade", dur: 3e3 },
|
|
6696
|
+
scene: {
|
|
6697
|
+
schemaVersion: 1,
|
|
6698
|
+
id: "three-movements-kindle",
|
|
6699
|
+
label: "Kindle",
|
|
6700
|
+
seed: 12,
|
|
6701
|
+
layers: [
|
|
6702
|
+
{
|
|
6703
|
+
key: "caption",
|
|
6704
|
+
count: 1,
|
|
6705
|
+
sprite: {
|
|
6706
|
+
kind: "textBlock",
|
|
6707
|
+
text: "KINDLE.\nOne lamp, and then another.",
|
|
6708
|
+
maxWidth: 0.6,
|
|
6709
|
+
fontSize: 0.028,
|
|
6710
|
+
lineHeight: 1.5,
|
|
6711
|
+
align: "left",
|
|
6712
|
+
anchor: "top-left",
|
|
6713
|
+
font: "monospace",
|
|
6714
|
+
color: "#e8b878",
|
|
6715
|
+
opacity: 0.9,
|
|
6716
|
+
reveal: {
|
|
6717
|
+
progress: 1,
|
|
6718
|
+
mode: "typewriter",
|
|
6719
|
+
speed: 12,
|
|
6720
|
+
caret: { blink: 1.1 }
|
|
6721
|
+
}
|
|
6722
|
+
},
|
|
6723
|
+
motion: { type: "static" },
|
|
6724
|
+
position: { x: 0.1, y: 0.4 },
|
|
6725
|
+
alpha: [0.9, 0.9]
|
|
6726
|
+
},
|
|
6727
|
+
{
|
|
6728
|
+
key: "sparks",
|
|
6729
|
+
count: 30,
|
|
6730
|
+
sprite: {
|
|
6731
|
+
kind: "circle",
|
|
6732
|
+
radius: [12e-4, 26e-4],
|
|
6733
|
+
color: "#e8b878"
|
|
6734
|
+
},
|
|
6735
|
+
motion: { type: "static" },
|
|
6736
|
+
alpha: [0.15, 0.45],
|
|
6737
|
+
pulse: { amp: 0.12, period: 6e3 }
|
|
6738
|
+
}
|
|
6739
|
+
]
|
|
6740
|
+
}
|
|
6741
|
+
},
|
|
6742
|
+
{
|
|
6743
|
+
key: "dusk",
|
|
6744
|
+
scene: {
|
|
6745
|
+
schemaVersion: 1,
|
|
6746
|
+
id: "three-movements-dusk",
|
|
6747
|
+
label: "Dusk",
|
|
6748
|
+
seed: 13,
|
|
6749
|
+
layers: [
|
|
6750
|
+
{
|
|
6751
|
+
key: "bloom",
|
|
6752
|
+
count: 3,
|
|
6753
|
+
sprite: {
|
|
6754
|
+
kind: "circle",
|
|
6755
|
+
radius: [0.09, 0.14],
|
|
6756
|
+
color: "#7a4a5e",
|
|
6757
|
+
colors: ["#7a4a5e", "#b06a4a"],
|
|
6758
|
+
soft: true
|
|
6759
|
+
},
|
|
6760
|
+
motion: {
|
|
6761
|
+
type: "wander",
|
|
6762
|
+
speed: [1e-3, 2e-3],
|
|
6763
|
+
meander: 0.04,
|
|
6764
|
+
coherence: 0.7
|
|
6765
|
+
},
|
|
6766
|
+
alpha: [0.25, 0.4],
|
|
6767
|
+
blend: "lighter",
|
|
6768
|
+
grow: { amp: 0.3, period: 14e3 }
|
|
6769
|
+
},
|
|
6770
|
+
{
|
|
6771
|
+
key: "strokes",
|
|
6772
|
+
count: 8,
|
|
6773
|
+
sprite: {
|
|
6774
|
+
kind: "stroke",
|
|
6775
|
+
length: [0.1, 0.2],
|
|
6776
|
+
points: [
|
|
6777
|
+
[-1, 0.2],
|
|
6778
|
+
[-0.3, -0.6],
|
|
6779
|
+
[0.5, -0.4],
|
|
6780
|
+
[1, 0.2]
|
|
6781
|
+
],
|
|
6782
|
+
width: 4e-3,
|
|
6783
|
+
taper: true,
|
|
6784
|
+
color: "#c2447a",
|
|
6785
|
+
colors: ["#c2447a", "#8f2c58"]
|
|
6786
|
+
},
|
|
6787
|
+
motion: {
|
|
6788
|
+
type: "drift",
|
|
6789
|
+
speed: [3e-3, 8e-3],
|
|
6790
|
+
angle: 20,
|
|
6791
|
+
bidirectional: true
|
|
6792
|
+
},
|
|
6793
|
+
alpha: [0.3, 0.5],
|
|
6794
|
+
rotate: [-30, 30]
|
|
6795
|
+
}
|
|
6796
|
+
]
|
|
6797
|
+
}
|
|
6798
|
+
}
|
|
6799
|
+
]
|
|
6800
|
+
};
|
|
6801
|
+
|
|
5564
6802
|
// src/examples/facets.ts
|
|
5565
6803
|
var FACETS_SPEC = {
|
|
5566
6804
|
schemaVersion: 1,
|
|
@@ -6467,6 +7705,38 @@ var SNOWFALL_SPEC = {
|
|
|
6467
7705
|
]
|
|
6468
7706
|
};
|
|
6469
7707
|
|
|
7708
|
+
// src/examples/thermal-field.ts
|
|
7709
|
+
var THERMAL_FIELD_SPEC = {
|
|
7710
|
+
schemaVersion: 1,
|
|
7711
|
+
id: "thermal-field",
|
|
7712
|
+
label: "Thermal Field",
|
|
7713
|
+
seed: 71,
|
|
7714
|
+
motionIntensity: "calm",
|
|
7715
|
+
// The riso print: grain at half strength, a light dither screen — the
|
|
7716
|
+
// finish that makes the flat inks read as paper rather than pixels.
|
|
7717
|
+
finish: { grain: 0.5, dither: 0.3 },
|
|
7718
|
+
background: {
|
|
7719
|
+
type: "field",
|
|
7720
|
+
scale: 2.2,
|
|
7721
|
+
octaves: 3,
|
|
7722
|
+
warp: 0.4,
|
|
7723
|
+
quantize: 6,
|
|
7724
|
+
bands: ["#1b1a3a", "#0078bf", "#00a99d", "#ffe800", "#ff6c2f", "#ff48b0"],
|
|
7725
|
+
drift: { period: 4e4, amount: 0.3 }
|
|
7726
|
+
},
|
|
7727
|
+
layers: [
|
|
7728
|
+
{
|
|
7729
|
+
key: "motes",
|
|
7730
|
+
count: 14,
|
|
7731
|
+
sprite: { kind: "circle", radius: [0.012, 0.028], color: "#fff6e0", soft: true },
|
|
7732
|
+
motion: { type: "wander", speed: [3e-3, 8e-3], meander: 0.04, coherence: 0.3 },
|
|
7733
|
+
alpha: [0.18, 0.4],
|
|
7734
|
+
blend: "screen",
|
|
7735
|
+
pulse: { amp: 0.15, period: 9e3 }
|
|
7736
|
+
}
|
|
7737
|
+
]
|
|
7738
|
+
};
|
|
7739
|
+
|
|
6470
7740
|
// src/examples/warp-tunnel.ts
|
|
6471
7741
|
var WARP_TUNNEL_SPEC = {
|
|
6472
7742
|
schemaVersion: 1,
|
|
@@ -6520,6 +7790,71 @@ var WARP_TUNNEL_SPEC = {
|
|
|
6520
7790
|
]
|
|
6521
7791
|
};
|
|
6522
7792
|
|
|
7793
|
+
// src/examples/typed-cue.ts
|
|
7794
|
+
var TYPED_CUE_SPEC = {
|
|
7795
|
+
schemaVersion: 1,
|
|
7796
|
+
id: "typed-cue",
|
|
7797
|
+
label: "Typed Cue",
|
|
7798
|
+
seed: 19,
|
|
7799
|
+
motionIntensity: "calm",
|
|
7800
|
+
background: { type: "solid", color: "#050608" },
|
|
7801
|
+
layers: [
|
|
7802
|
+
{
|
|
7803
|
+
key: "scan",
|
|
7804
|
+
count: 1,
|
|
7805
|
+
sprite: { kind: "rect", width: [1, 1], aspect: [0.02, 0.02], color: "#16241f", feather: 0.9 },
|
|
7806
|
+
motion: { type: "drift", speed: [0.014, 0.014], angle: 90 },
|
|
7807
|
+
alpha: [0.5, 0.5],
|
|
7808
|
+
blend: "screen"
|
|
7809
|
+
},
|
|
7810
|
+
{
|
|
7811
|
+
key: "headline",
|
|
7812
|
+
count: 1,
|
|
7813
|
+
sprite: {
|
|
7814
|
+
kind: "textBlock",
|
|
7815
|
+
text: "THE SCREEN IS AWAKE.\nWORDS ARRIVE ON THEIR OWN CLOCK.",
|
|
7816
|
+
maxWidth: 0.84,
|
|
7817
|
+
fontSize: 0.03,
|
|
7818
|
+
lineHeight: 1.5,
|
|
7819
|
+
align: "left",
|
|
7820
|
+
anchor: "top-left",
|
|
7821
|
+
font: "bold monospace",
|
|
7822
|
+
color: "#e6e8ef",
|
|
7823
|
+
opacity: 0.94,
|
|
7824
|
+
reveal: {
|
|
7825
|
+
progress: 1,
|
|
7826
|
+
mode: "typewriter",
|
|
7827
|
+
speed: 10,
|
|
7828
|
+
caret: { blink: 1.1, color: "#e6e8ef" }
|
|
7829
|
+
}
|
|
7830
|
+
},
|
|
7831
|
+
motion: { type: "static" },
|
|
7832
|
+
position: { x: 0.08, y: 0.16 },
|
|
7833
|
+
alpha: [0.95, 0.95]
|
|
7834
|
+
},
|
|
7835
|
+
{
|
|
7836
|
+
key: "aside",
|
|
7837
|
+
count: 1,
|
|
7838
|
+
sprite: {
|
|
7839
|
+
kind: "textBlock",
|
|
7840
|
+
text: "and leaves the same way \u2014\na dissolve, not a delete.",
|
|
7841
|
+
maxWidth: 0.5,
|
|
7842
|
+
fontSize: 0.022,
|
|
7843
|
+
lineHeight: 1.5,
|
|
7844
|
+
align: "left",
|
|
7845
|
+
anchor: "bottom-right",
|
|
7846
|
+
font: "monospace",
|
|
7847
|
+
color: "#8f96a3",
|
|
7848
|
+
opacity: 0.85,
|
|
7849
|
+
reveal: { progress: 1, mode: "glyphFade", speed: 6, fade: 0.4 }
|
|
7850
|
+
},
|
|
7851
|
+
motion: { type: "static" },
|
|
7852
|
+
position: { x: 0.92, y: 0.86 },
|
|
7853
|
+
alpha: [0.9, 0.9]
|
|
7854
|
+
}
|
|
7855
|
+
]
|
|
7856
|
+
};
|
|
7857
|
+
|
|
6523
7858
|
// src/examples/index.ts
|
|
6524
7859
|
var SCHEMA_EXAMPLES = [
|
|
6525
7860
|
{ id: "aquarium", label: "Aquarium", spec: AQUARIUM_SPEC },
|
|
@@ -6541,7 +7876,18 @@ var SCHEMA_EXAMPLES = [
|
|
|
6541
7876
|
{ id: "pings", label: "Pings", spec: PINGS_SPEC },
|
|
6542
7877
|
{ id: "facets", label: "Facets", spec: FACETS_SPEC },
|
|
6543
7878
|
{ id: "relay-board", label: "Relay Board", spec: RELAY_BOARD_SPEC },
|
|
6544
|
-
{ id: "lobby-talk", label: "Lobby Talk", spec: LOBBY_TALK_SPEC }
|
|
7879
|
+
{ id: "lobby-talk", label: "Lobby Talk", spec: LOBBY_TALK_SPEC },
|
|
7880
|
+
{ id: "thermal-field", label: "Thermal Field", spec: THERMAL_FIELD_SPEC },
|
|
7881
|
+
{ id: "sparse-night", label: "Sparse Night", spec: SPARSE_NIGHT_SPEC },
|
|
7882
|
+
{ id: "typed-cue", label: "Typed Cue", spec: TYPED_CUE_SPEC },
|
|
7883
|
+
{ id: "signal-board", label: "Signal Board", spec: SIGNAL_BOARD_SPEC },
|
|
7884
|
+
{ id: "phase-duet", label: "Phase Duet", spec: PHASE_DUET_SPEC },
|
|
7885
|
+
{ id: "web-work", label: "Web Work", spec: WEB_WORK_SPEC },
|
|
7886
|
+
{ id: "shard-fall", label: "Shard Fall", spec: SHARD_FALL_SPEC },
|
|
7887
|
+
{ id: "murmur", label: "Murmur", spec: MURMUR_SPEC }
|
|
7888
|
+
];
|
|
7889
|
+
var EXAMPLE_SEQUENCES = [
|
|
7890
|
+
{ id: "three-movements", label: "Three Movements", sequence: THREE_MOVEMENTS_SEQUENCE }
|
|
6545
7891
|
];
|
|
6546
7892
|
var EXAMPLE_SPECS = SCHEMA_EXAMPLES.map((e) => e.spec);
|
|
6547
7893
|
var EXAMPLE_BY_ID = Object.fromEntries(
|
|
@@ -6554,16 +7900,28 @@ export {
|
|
|
6554
7900
|
COMETS_SPEC,
|
|
6555
7901
|
CONSTELLATION_SPEC,
|
|
6556
7902
|
DASHBOARD_SPEC,
|
|
7903
|
+
DITHER_ALPHA,
|
|
7904
|
+
DITHER_TILE,
|
|
6557
7905
|
EXAMPLE_BY_ID,
|
|
7906
|
+
EXAMPLE_SEQUENCES,
|
|
6558
7907
|
EXAMPLE_SPECS,
|
|
6559
7908
|
FACETS_SPEC,
|
|
7909
|
+
FIELD_BUCKET_MS,
|
|
7910
|
+
FIELD_DRIFT_DEFAULT_AMOUNT,
|
|
7911
|
+
FIELD_RASTER_SHORT_SIDE,
|
|
7912
|
+
FIELD_RASTER_SHORT_SIDE_LOW,
|
|
7913
|
+
FINISH_ANIMATE_HZ,
|
|
7914
|
+
GRAIN_ALPHA,
|
|
7915
|
+
GRAIN_TILE,
|
|
6560
7916
|
HAIKU_SPEC,
|
|
6561
7917
|
LANTERNS_SPEC,
|
|
6562
7918
|
LIMITS,
|
|
6563
7919
|
LOBBY_TALK_SPEC,
|
|
6564
7920
|
MATRIX_RAIN_SPEC,
|
|
7921
|
+
MURMUR_SPEC,
|
|
6565
7922
|
NOSTALGHIA_CANDLE_SPEC,
|
|
6566
7923
|
ORRERY_SPEC,
|
|
7924
|
+
PHASE_DUET_SPEC,
|
|
6567
7925
|
PINGS_SPEC,
|
|
6568
7926
|
POLYGONS_SPEC,
|
|
6569
7927
|
PROCESSION_SPEC,
|
|
@@ -6572,16 +7930,25 @@ export {
|
|
|
6572
7930
|
SAKURA_SPEC,
|
|
6573
7931
|
SCHEMA_EXAMPLES,
|
|
6574
7932
|
SCHEMA_VERSION,
|
|
7933
|
+
SHARD_FALL_SPEC,
|
|
7934
|
+
SIGNAL_BOARD_SPEC,
|
|
6575
7935
|
SNOWFALL_SPEC,
|
|
7936
|
+
SPARSE_NIGHT_SPEC,
|
|
7937
|
+
THERMAL_FIELD_SPEC,
|
|
7938
|
+
THREE_MOVEMENTS_SEQUENCE,
|
|
7939
|
+
TYPED_CUE_SPEC,
|
|
6576
7940
|
WARP_MAX_SCALE,
|
|
6577
7941
|
WARP_NEAR,
|
|
6578
7942
|
WARP_TUNNEL_SPEC,
|
|
7943
|
+
WEB_WORK_SPEC,
|
|
6579
7944
|
adviseSequence,
|
|
6580
7945
|
adviseSpec,
|
|
6581
7946
|
alphaAt,
|
|
6582
7947
|
applyDeltasToSpec,
|
|
6583
7948
|
assertValidSequence,
|
|
6584
7949
|
assertValidSpec,
|
|
7950
|
+
bayerMatrix,
|
|
7951
|
+
bayerTilePixels,
|
|
6585
7952
|
bedRenderSeed,
|
|
6586
7953
|
breakTextBlock,
|
|
6587
7954
|
buildEntities,
|
|
@@ -6589,14 +7956,27 @@ export {
|
|
|
6589
7956
|
cohesionOf,
|
|
6590
7957
|
compileSaver,
|
|
6591
7958
|
compileSequence,
|
|
7959
|
+
createFinishPass,
|
|
7960
|
+
createSceneCanvas,
|
|
6592
7961
|
describeScene,
|
|
6593
7962
|
diffScenes,
|
|
6594
7963
|
dominanceRanking,
|
|
6595
7964
|
easeSmooth,
|
|
6596
7965
|
emitEnvelope,
|
|
6597
7966
|
emitWindow,
|
|
7967
|
+
fieldAt,
|
|
7968
|
+
fieldOctaves,
|
|
7969
|
+
fieldRgb,
|
|
7970
|
+
fieldRgbAt,
|
|
7971
|
+
fieldSampleTime,
|
|
7972
|
+
finishStrength,
|
|
7973
|
+
grainOffset,
|
|
7974
|
+
grainTilePixels,
|
|
6598
7975
|
graphemeClusters,
|
|
7976
|
+
hasHotSwapSequence,
|
|
6599
7977
|
headingAt,
|
|
7978
|
+
hexToRgb255,
|
|
7979
|
+
latticeHash,
|
|
6600
7980
|
layerCohesion,
|
|
6601
7981
|
lerpSpec,
|
|
6602
7982
|
lifeAlphaAt,
|
|
@@ -6610,18 +7990,23 @@ export {
|
|
|
6610
7990
|
normalizeSeed,
|
|
6611
7991
|
perceiveScene,
|
|
6612
7992
|
perceiveSequenceFrame,
|
|
7993
|
+
pickScreenOp,
|
|
6613
7994
|
positionAt,
|
|
7995
|
+
presentWithFinish,
|
|
6614
7996
|
renderBrailleMap,
|
|
6615
7997
|
renderDensityMap,
|
|
6616
7998
|
resolveSegment,
|
|
6617
7999
|
resolveSpecPath,
|
|
6618
8000
|
revealState,
|
|
8001
|
+
rgb255Luma,
|
|
6619
8002
|
rotationAt,
|
|
6620
8003
|
seamCauseOf,
|
|
6621
8004
|
seamsWorthWarning,
|
|
6622
8005
|
segmentRenderSeed,
|
|
6623
8006
|
segmentStart,
|
|
6624
8007
|
sequenceSteerablePaths,
|
|
8008
|
+
sequenceSwapCompatible,
|
|
8009
|
+
sequenceWantsFinish,
|
|
6625
8010
|
sizeAt,
|
|
6626
8011
|
spriteIndexAt,
|
|
6627
8012
|
spriteVariants,
|