@idle-screens/schema 3.9.0 → 3.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +28 -7
- package/dist/index.js +58 -35
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -843,6 +843,13 @@ interface IdleSequence {
|
|
|
843
843
|
}
|
|
844
844
|
|
|
845
845
|
declare function validateSpec(spec: unknown): ValidationResult;
|
|
846
|
+
/**
|
|
847
|
+
* Dot-paths (numeric layer indices, as `steerablePaths` spells them) of every
|
|
848
|
+
* property the validator flags as unknown or misplaced — i.e. fields the
|
|
849
|
+
* renderer never reads, so steering them does nothing. Pure: unlike
|
|
850
|
+
* `validateSpec` it never normalises colours into the caller's spec.
|
|
851
|
+
*/
|
|
852
|
+
declare function ignoredPropertyPaths(spec: unknown): string[];
|
|
846
853
|
/** Narrowing helper: validate + cast. Throws with a joined message when invalid. */
|
|
847
854
|
declare function assertValidSpec(spec: unknown): SaverSpec;
|
|
848
855
|
/**
|
|
@@ -1235,6 +1242,12 @@ declare function morphNothingMorphable(a: SaverSpec, b: SaverSpec, opts?: {
|
|
|
1235
1242
|
* (id, label, schemaVersion, seed, units, kind, type, key) are excluded —
|
|
1236
1243
|
* they describe structure, not tuneable values.
|
|
1237
1244
|
*
|
|
1245
|
+
* Properties the validator flags as unknown or misplaced (e.g. `angle` on a
|
|
1246
|
+
* gradient background, `blend` inside a sprite) are excluded too: the
|
|
1247
|
+
* renderer never reads them, so advertising them invites a steer that
|
|
1248
|
+
* silently does nothing. Same source of truth as validateSpec's
|
|
1249
|
+
* "will be ignored" warnings (`ignoredPropertyPaths`).
|
|
1250
|
+
*
|
|
1238
1251
|
* Layer keys are NOT substituted: paths always use numeric indices.
|
|
1239
1252
|
* Consumers can map to key-based paths via resolveSpecPath if needed.
|
|
1240
1253
|
*/
|
|
@@ -1919,10 +1932,14 @@ declare function sequenceWantsFinish(seq: IdleSequence): boolean;
|
|
|
1919
1932
|
* when present;
|
|
1920
1933
|
* - the same top-level `seq.seed`, compared normalized (like the mount-time
|
|
1921
1934
|
* seed itself) so a change of an exact multiple of 2^32 isn't flagged —
|
|
1922
|
-
*
|
|
1923
|
-
* `
|
|
1924
|
-
*
|
|
1925
|
-
*
|
|
1935
|
+
* but ONLY when either side has a sequence-level `finish`: that's the one
|
|
1936
|
+
* place `seq.seed` reaches rendering on its own (`frameFinishSeed` uses the
|
|
1937
|
+
* instance's mount-time seed for a sequence-level finish's grain, which a
|
|
1938
|
+
* swap cannot update). Without a sequence-level `finish`, every consumer of
|
|
1939
|
+
* `seq.seed` is a `segmentRenderSeed`/`bedRenderSeed` fallback, and those
|
|
1940
|
+
* are already compared per segment and for the bed below — so when every
|
|
1941
|
+
* segment and the bed carry their own seed, `seq.seed` is dead weight and
|
|
1942
|
+
* changing it is free;
|
|
1926
1943
|
* - the same finish presence (`sequenceWantsFinish`) — the presentation
|
|
1927
1944
|
* pass is allocated once at construction, so a swap that adds or removes
|
|
1928
1945
|
* every `finish` in the sequence cannot (de)allocate it;
|
|
@@ -2263,8 +2280,12 @@ declare const MURMUR_SPEC: SaverSpec;
|
|
|
2263
2280
|
* hex value glides over 2500 ms, and because the words differ and the
|
|
2264
2281
|
* transition declares `text: 'crossfade'`, the outgoing caption fades under
|
|
2265
2282
|
* the incoming one instead of switching whole-frame. Without the crossfade
|
|
2266
|
-
* declaration
|
|
2267
|
-
*
|
|
2283
|
+
* declaration the morph would still glide — colour alone gives it a
|
|
2284
|
+
* `hasGlide` — just with the caption stepping on the first frame instead of
|
|
2285
|
+
* fading; the validator only says `morph-nothing-morphable` for a pair
|
|
2286
|
+
* whose differences are all values morph cannot glide — strings
|
|
2287
|
+
* (`textBlock.text` above all), booleans, mismatched arrays — which this
|
|
2288
|
+
* one isn't.
|
|
2268
2289
|
* - **fade.** Segments 2 → 3 have nothing structurally in common, so a morph
|
|
2269
2290
|
* is impossible and a `fade` is the honest transition: the outgoing scene
|
|
2270
2291
|
* keeps animating on its own canvas for 3000 ms while the incoming one
|
|
@@ -2313,4 +2334,4 @@ declare const EXAMPLE_SPECS: SaverSpec[];
|
|
|
2313
2334
|
/** Lookup by saver id. */
|
|
2314
2335
|
declare const EXAMPLE_BY_ID: Readonly<Record<string, SaverSpec>>;
|
|
2315
2336
|
|
|
2316
|
-
export { AQUARIUM_SPEC, AURORA_SPEC, type BackgroundDrift, type BackgroundSpec, type BandSpec, COHESION_T, COMETS_SPEC, CONSTELLATION_SPEC, type CapabilityTier, type CohesionRead, type CycleSpec, DASHBOARD_SPEC, DITHER_ALPHA, DITHER_TILE, type DominanceEntry, EXAMPLE_BY_ID, EXAMPLE_SEQUENCES, EXAMPLE_SPECS, type Entity, FACETS_SPEC, FIELD_BUCKET_MS, FIELD_DRIFT_DEFAULT_AMOUNT, FIELD_RASTER_SHORT_SIDE, FIELD_RASTER_SHORT_SIDE_LOW, FINISH_ANIMATE_HZ, type FieldBackground, type FieldDrift, type FinishPass, type FinishSpec, GRAIN_ALPHA, GRAIN_TILE, type GradientStop, HAIKU_SPEC, type IdleSequence, LANTERNS_SPEC, LIMITS, LOBBY_TALK_SPEC, type LayerCohesion, type LayerMotionStats, type LayerSnapshot, type LayerSpec, type LinkEdge, type LuminanceGrid, type LuminanceGridOptions, MATRIX_RAIN_SPEC, MURMUR_SPEC, type MotionEase, type MotionSpec, NOSTALGHIA_CANDLE_SPEC, ORRERY_SPEC, PHASE_DUET_SPEC, PINGS_SPEC, POLYGONS_SPEC, PROCESSION_SPEC, type PerceiveOptions, type PerceiveSequenceOptions, type Placed, RAIN_SPEC, RELAY_BOARD_SPEC, type ResolveOptions, type ResolvedSegment, type RevealState, SAKURA_SPEC, SCHEMA_EXAMPLES, SCHEMA_VERSION, SHARD_FALL_SPEC, SIGNAL_BOARD_SPEC, SNOWFALL_SPEC, SPARSE_NIGHT_SPEC, type SaverSpec, type SceneDescription, type SceneDiff, type ScenePerception, type SceneSnapshot, type SchemaExample, type ScreenOp, type SequenceExample, type SequenceFramePerception, type SequenceMountContext, type SequenceSaverInstance, type SequenceSaverPlugin, type SequenceSegment, type SequenceTransition, type SpecError, type SpecMountContext, type SpecWarning, type SpriteSpec, type SteerDelta, THERMAL_FIELD_SPEC, THREE_MOVEMENTS_SEQUENCE, TYPED_CUE_SPEC, type TextBlockAnchor, type TextBlockLine, type TextMetricsClass, type TextRevealInput, type TextRevealSpec, type TextRole, type TextSpriteInfo, type ValidationResult, WARP_MAX_SCALE, WARP_NEAR, WARP_TUNNEL_SPEC, WEB_WORK_SPEC, type WanderOsc, type WarningBox, adviseSequence, adviseSpec, alphaAt, applyDeltasToSpec, assertValidSequence, assertValidSpec, bayerMatrix, bayerTilePixels, bedRenderSeed, breakTextBlock, buildEntities, canMorph, cohesionOf, compileSaver, compileSequence, createFinishPass, createSceneCanvas, describeScene, diffScenes, dominanceRanking, easeSmooth, emitEnvelope, emitWindow, fieldAt, fieldOctaves, fieldRgb, fieldRgbAt, fieldSampleTime, finishStrength, grainOffset, grainTilePixels, graphemeClusters, hasHotSwapSequence, headingAt, hexToRgb255, latticeHash, layerCohesion, lerpSpec, lifeAlphaAt, linkEdges, linkPairs, luminanceGrid, manifestFor, morphChainRoot, morphNothingMorphable, motionStats, normalizeSeed, perceiveScene, perceiveSequenceFrame, pickScreenOp, positionAt, presentWithFinish, renderBrailleMap, renderDensityMap, resolveSegment, resolveSpecPath, revealState, rgb255Luma, rotationAt, seamCauseOf, seamsWorthWarning, segmentRenderSeed, segmentStart, sequenceSteerablePaths, sequenceSwapCompatible, sequenceWantsFinish, sizeAt, spriteIndexAt, spriteVariants, steerablePaths, structuralSignature, textBlockAnchorOffset, textMetricsClassFor, textSprites, textStringsDiffer, textStringsOf, textWidthEm, validateSequence, validateSpec };
|
|
2337
|
+
export { AQUARIUM_SPEC, AURORA_SPEC, type BackgroundDrift, type BackgroundSpec, type BandSpec, COHESION_T, COMETS_SPEC, CONSTELLATION_SPEC, type CapabilityTier, type CohesionRead, type CycleSpec, DASHBOARD_SPEC, DITHER_ALPHA, DITHER_TILE, type DominanceEntry, EXAMPLE_BY_ID, EXAMPLE_SEQUENCES, EXAMPLE_SPECS, type Entity, FACETS_SPEC, FIELD_BUCKET_MS, FIELD_DRIFT_DEFAULT_AMOUNT, FIELD_RASTER_SHORT_SIDE, FIELD_RASTER_SHORT_SIDE_LOW, FINISH_ANIMATE_HZ, type FieldBackground, type FieldDrift, type FinishPass, type FinishSpec, GRAIN_ALPHA, GRAIN_TILE, type GradientStop, HAIKU_SPEC, type IdleSequence, LANTERNS_SPEC, LIMITS, LOBBY_TALK_SPEC, type LayerCohesion, type LayerMotionStats, type LayerSnapshot, type LayerSpec, type LinkEdge, type LuminanceGrid, type LuminanceGridOptions, MATRIX_RAIN_SPEC, MURMUR_SPEC, type MotionEase, type MotionSpec, NOSTALGHIA_CANDLE_SPEC, ORRERY_SPEC, PHASE_DUET_SPEC, PINGS_SPEC, POLYGONS_SPEC, PROCESSION_SPEC, type PerceiveOptions, type PerceiveSequenceOptions, type Placed, RAIN_SPEC, RELAY_BOARD_SPEC, type ResolveOptions, type ResolvedSegment, type RevealState, SAKURA_SPEC, SCHEMA_EXAMPLES, SCHEMA_VERSION, SHARD_FALL_SPEC, SIGNAL_BOARD_SPEC, SNOWFALL_SPEC, SPARSE_NIGHT_SPEC, type SaverSpec, type SceneDescription, type SceneDiff, type ScenePerception, type SceneSnapshot, type SchemaExample, type ScreenOp, type SequenceExample, type SequenceFramePerception, type SequenceMountContext, type SequenceSaverInstance, type SequenceSaverPlugin, type SequenceSegment, type SequenceTransition, type SpecError, type SpecMountContext, type SpecWarning, type SpriteSpec, type SteerDelta, THERMAL_FIELD_SPEC, THREE_MOVEMENTS_SEQUENCE, TYPED_CUE_SPEC, type TextBlockAnchor, type TextBlockLine, type TextMetricsClass, type TextRevealInput, type TextRevealSpec, type TextRole, type TextSpriteInfo, type ValidationResult, WARP_MAX_SCALE, WARP_NEAR, WARP_TUNNEL_SPEC, WEB_WORK_SPEC, type WanderOsc, type WarningBox, adviseSequence, adviseSpec, alphaAt, applyDeltasToSpec, assertValidSequence, assertValidSpec, bayerMatrix, bayerTilePixels, bedRenderSeed, breakTextBlock, buildEntities, canMorph, cohesionOf, compileSaver, compileSequence, createFinishPass, createSceneCanvas, describeScene, diffScenes, dominanceRanking, easeSmooth, emitEnvelope, emitWindow, fieldAt, fieldOctaves, fieldRgb, fieldRgbAt, fieldSampleTime, finishStrength, grainOffset, grainTilePixels, graphemeClusters, hasHotSwapSequence, headingAt, hexToRgb255, ignoredPropertyPaths, latticeHash, layerCohesion, lerpSpec, lifeAlphaAt, linkEdges, linkPairs, luminanceGrid, manifestFor, morphChainRoot, morphNothingMorphable, motionStats, normalizeSeed, perceiveScene, perceiveSequenceFrame, pickScreenOp, positionAt, presentWithFinish, renderBrailleMap, renderDensityMap, resolveSegment, resolveSpecPath, revealState, rgb255Luma, rotationAt, seamCauseOf, seamsWorthWarning, segmentRenderSeed, segmentStart, sequenceSteerablePaths, sequenceSwapCompatible, sequenceWantsFinish, sizeAt, spriteIndexAt, spriteVariants, steerablePaths, structuralSignature, textBlockAnchorOffset, textMetricsClassFor, textSprites, textStringsDiffer, textStringsOf, textWidthEm, validateSequence, validateSpec };
|
package/dist/index.js
CHANGED
|
@@ -246,7 +246,9 @@ function steerablePaths(spec) {
|
|
|
246
246
|
if (prefix) out.push(prefix);
|
|
247
247
|
};
|
|
248
248
|
walk(spec, "", "");
|
|
249
|
-
|
|
249
|
+
const ignored = ignoredPropertyPaths(spec);
|
|
250
|
+
if (ignored.length === 0) return out;
|
|
251
|
+
return out.filter((p) => !ignored.some((ig) => p === ig || p.startsWith(`${ig}.`)));
|
|
250
252
|
}
|
|
251
253
|
function sequenceSteerablePaths(seq) {
|
|
252
254
|
const out = /* @__PURE__ */ new Set(["sequence.segment"]);
|
|
@@ -361,6 +363,16 @@ var LAYER_PROPS_ON_SPRITE = /* @__PURE__ */ new Set(["blend", "trail", "alpha",
|
|
|
361
363
|
function unknownKeys(obj, known) {
|
|
362
364
|
return Object.keys(obj).filter((k) => !known.has(k));
|
|
363
365
|
}
|
|
366
|
+
function dimUnit(spec) {
|
|
367
|
+
return isObj(spec) && spec.units === "px" ? "px" : "fractions of min(width, height)";
|
|
368
|
+
}
|
|
369
|
+
function rangeMsg(spec, note) {
|
|
370
|
+
return `must be a [min,max] range of positive ${dimUnit(spec)}${note ? ` (${note})` : ""}`;
|
|
371
|
+
}
|
|
372
|
+
function positiveScalar(v, path, err, msg = "must be > 0") {
|
|
373
|
+
if (Array.isArray(v)) err(path, "must be a single number > 0, not a [min,max] range");
|
|
374
|
+
else if (!isNum(v) || v <= 0) err(path, msg);
|
|
375
|
+
}
|
|
364
376
|
function normalizeColors(spec) {
|
|
365
377
|
if (!isObj(spec) || !Array.isArray(spec.layers)) return;
|
|
366
378
|
for (const layer of spec.layers) {
|
|
@@ -375,6 +387,13 @@ function normalizeColors(spec) {
|
|
|
375
387
|
}
|
|
376
388
|
function validateSpec(spec) {
|
|
377
389
|
normalizeColors(spec);
|
|
390
|
+
return validateSpecCore(spec);
|
|
391
|
+
}
|
|
392
|
+
function ignoredPropertyPaths(spec) {
|
|
393
|
+
if (!isObj(spec)) return [];
|
|
394
|
+
return (validateSpecCore(spec).warnings ?? []).filter((w) => w.code === "unknown-property" || w.code === "misplaced-property").map((w) => w.path.replace(/\[(\d+)\]/g, ".$1"));
|
|
395
|
+
}
|
|
396
|
+
function validateSpecCore(spec) {
|
|
378
397
|
const errors = [];
|
|
379
398
|
const warnings = [];
|
|
380
399
|
const err = (path, message) => void errors.push({ path, message });
|
|
@@ -471,7 +490,7 @@ function validateBackground(bg, err, warn) {
|
|
|
471
490
|
if (!isObj(bg.band)) err("background.band", "must be an object");
|
|
472
491
|
else {
|
|
473
492
|
color(bg.band.color, "background.band.color", err);
|
|
474
|
-
|
|
493
|
+
positiveScalar(bg.band.height, "background.band.height", err);
|
|
475
494
|
}
|
|
476
495
|
}
|
|
477
496
|
for (const k of unknownKeys(bg, KNOWN_BG_GRADIENT)) {
|
|
@@ -537,7 +556,7 @@ function validateLayer(layer, path, err, warn, spec) {
|
|
|
537
556
|
err(`${path}.count`, `at most ${LIMITS.maxPerLayer} per layer`);
|
|
538
557
|
}
|
|
539
558
|
if (layer.size !== void 0 && (!isRange(layer.size) || layer.size[0] <= 0)) {
|
|
540
|
-
err(`${path}.size`,
|
|
559
|
+
err(`${path}.size`, rangeMsg(spec));
|
|
541
560
|
}
|
|
542
561
|
if (layer.wrap !== void 0 && typeof layer.wrap !== "boolean") err(`${path}.wrap`, "must be a boolean");
|
|
543
562
|
if (layer.flip !== void 0 && typeof layer.flip !== "boolean") err(`${path}.flip`, "must be a boolean");
|
|
@@ -634,16 +653,12 @@ function validateLayer(layer, path, err, warn, spec) {
|
|
|
634
653
|
if (!isNum(layer.links.k) || !Number.isInteger(layer.links.k) || layer.links.k < 1 || layer.links.k > LIMITS.maxLinksK) {
|
|
635
654
|
err(`${path}.links.k`, `must be an integer 1..${LIMITS.maxLinksK}`);
|
|
636
655
|
}
|
|
637
|
-
|
|
638
|
-
err(`${path}.links.maxDist`, "must be > 0");
|
|
639
|
-
}
|
|
656
|
+
positiveScalar(layer.links.maxDist, `${path}.links.maxDist`, err);
|
|
640
657
|
if (layer.links.color !== void 0) color(layer.links.color, `${path}.links.color`, err);
|
|
641
658
|
if (layer.links.alpha !== void 0 && (!isNum(layer.links.alpha) || layer.links.alpha < 0 || layer.links.alpha > 1)) {
|
|
642
659
|
err(`${path}.links.alpha`, "must be 0..1");
|
|
643
660
|
}
|
|
644
|
-
if (layer.links.width !== void 0
|
|
645
|
-
err(`${path}.links.width`, "must be > 0");
|
|
646
|
-
}
|
|
661
|
+
if (layer.links.width !== void 0) positiveScalar(layer.links.width, `${path}.links.width`, err);
|
|
647
662
|
if (layer.links.mode !== void 0 && !["nearest", "chain", "random"].includes(layer.links.mode)) {
|
|
648
663
|
err(`${path}.links.mode`, "must be 'nearest' | 'chain' | 'random'");
|
|
649
664
|
}
|
|
@@ -797,10 +812,10 @@ function validateLayer(layer, path, err, warn, spec) {
|
|
|
797
812
|
}
|
|
798
813
|
}
|
|
799
814
|
}
|
|
800
|
-
validateSprite(layer.sprite, `${path}.sprite`, err, warn);
|
|
815
|
+
validateSprite(layer.sprite, `${path}.sprite`, err, warn, spec);
|
|
801
816
|
validateMotion(layer.motion, `${path}.motion`, err, warn, spec);
|
|
802
817
|
}
|
|
803
|
-
function validateSprite(sprite, path, err, warn) {
|
|
818
|
+
function validateSprite(sprite, path, err, warn, spec) {
|
|
804
819
|
if (!isObj(sprite)) return err(path, "must be an object");
|
|
805
820
|
let knownSet;
|
|
806
821
|
if (sprite.kind === "emoji") {
|
|
@@ -824,32 +839,30 @@ function validateSprite(sprite, path, err, warn) {
|
|
|
824
839
|
if (sprite.baseline !== void 0 && !["top", "middle", "bottom"].includes(sprite.baseline)) {
|
|
825
840
|
err(`${path}.baseline`, "must be top | middle | bottom");
|
|
826
841
|
}
|
|
827
|
-
if (sprite.maxWidth !== void 0
|
|
828
|
-
err(`${path}.maxWidth`, "must be a positive number");
|
|
829
|
-
}
|
|
842
|
+
if (sprite.maxWidth !== void 0) positiveScalar(sprite.maxWidth, `${path}.maxWidth`, err, "must be a positive number");
|
|
830
843
|
validateTextRole(sprite, path, err);
|
|
831
844
|
validateCycle(sprite, path, err);
|
|
832
845
|
} else if (sprite.kind === "circle") {
|
|
833
846
|
knownSet = KNOWN_CIRCLE;
|
|
834
|
-
if (!isRange(sprite.radius) || sprite.radius[0] <= 0) err(`${path}.radius`,
|
|
847
|
+
if (!isRange(sprite.radius) || sprite.radius[0] <= 0) err(`${path}.radius`, rangeMsg(spec));
|
|
835
848
|
color(sprite.color, `${path}.color`, err);
|
|
836
849
|
if (sprite.soft !== void 0 && typeof sprite.soft !== "boolean") err(`${path}.soft`, "must be a boolean");
|
|
837
850
|
validatePalette(sprite, path, err);
|
|
838
851
|
} else if (sprite.kind === "ring") {
|
|
839
852
|
knownSet = KNOWN_RING;
|
|
840
|
-
if (!isRange(sprite.radius) || sprite.radius[0] <= 0) err(`${path}.radius`,
|
|
853
|
+
if (!isRange(sprite.radius) || sprite.radius[0] <= 0) err(`${path}.radius`, rangeMsg(spec));
|
|
841
854
|
color(sprite.color, `${path}.color`, err);
|
|
842
|
-
if (sprite.width !== void 0
|
|
855
|
+
if (sprite.width !== void 0) positiveScalar(sprite.width, `${path}.width`, err);
|
|
843
856
|
validatePalette(sprite, path, err);
|
|
844
857
|
} else if (sprite.kind === "streak") {
|
|
845
858
|
knownSet = KNOWN_STREAK;
|
|
846
|
-
if (!isRange(sprite.length) || sprite.length[0] <= 0) err(`${path}.length`,
|
|
859
|
+
if (!isRange(sprite.length) || sprite.length[0] <= 0) err(`${path}.length`, rangeMsg(spec));
|
|
847
860
|
color(sprite.color, `${path}.color`, err);
|
|
848
|
-
if (sprite.width !== void 0
|
|
861
|
+
if (sprite.width !== void 0) positiveScalar(sprite.width, `${path}.width`, err);
|
|
849
862
|
validatePalette(sprite, path, err);
|
|
850
863
|
} else if (sprite.kind === "rect") {
|
|
851
864
|
knownSet = KNOWN_RECT;
|
|
852
|
-
if (!isRange(sprite.width) || sprite.width[0] <= 0) err(`${path}.width`,
|
|
865
|
+
if (!isRange(sprite.width) || sprite.width[0] <= 0) err(`${path}.width`, rangeMsg(spec));
|
|
853
866
|
if (sprite.aspect !== void 0 && (!isRange(sprite.aspect) || sprite.aspect[0] <= 0)) {
|
|
854
867
|
err(`${path}.aspect`, "must be a [min,max] range of positive height/width ratios");
|
|
855
868
|
}
|
|
@@ -863,9 +876,9 @@ function validateSprite(sprite, path, err, warn) {
|
|
|
863
876
|
if (!Array.isArray(sprite.values) || sprite.values.length === 0 || sprite.values.length > LIMITS.maxPerLayer || !sprite.values.every((v) => isNum(v) && v >= 0)) {
|
|
864
877
|
err(`${path}.values`, `must be 1..${LIMITS.maxPerLayer} numbers >= 0`);
|
|
865
878
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
if (sprite.max !== void 0
|
|
879
|
+
positiveScalar(sprite.length, `${path}.length`, err, "must be > 0 (full-scale bar length)");
|
|
880
|
+
positiveScalar(sprite.thickness, `${path}.thickness`, err);
|
|
881
|
+
if (sprite.max !== void 0) positiveScalar(sprite.max, `${path}.max`, err);
|
|
869
882
|
if (sprite.direction !== void 0 && !["right", "left", "up", "down"].includes(sprite.direction)) {
|
|
870
883
|
err(`${path}.direction`, "must be 'right' | 'left' | 'up' | 'down'");
|
|
871
884
|
}
|
|
@@ -873,7 +886,7 @@ function validateSprite(sprite, path, err, warn) {
|
|
|
873
886
|
validatePalette(sprite, path, err);
|
|
874
887
|
} else if (sprite.kind === "polygon") {
|
|
875
888
|
knownSet = KNOWN_POLYGON;
|
|
876
|
-
if (!isRange(sprite.radius) || sprite.radius[0] <= 0) err(`${path}.radius`,
|
|
889
|
+
if (!isRange(sprite.radius) || sprite.radius[0] <= 0) err(`${path}.radius`, rangeMsg(spec, "circumradius"));
|
|
877
890
|
if (sprite.sides !== void 0 && sprite.points !== void 0) {
|
|
878
891
|
err(`${path}.sides`, "use sides (a regular polygon) or points (a custom one), not both");
|
|
879
892
|
}
|
|
@@ -886,9 +899,9 @@ function validateSprite(sprite, path, err, warn) {
|
|
|
886
899
|
validatePalette(sprite, path, err);
|
|
887
900
|
} else if (sprite.kind === "stroke") {
|
|
888
901
|
knownSet = KNOWN_STROKE;
|
|
889
|
-
if (!isRange(sprite.length) || sprite.length[0] <= 0) err(`${path}.length`,
|
|
902
|
+
if (!isRange(sprite.length) || sprite.length[0] <= 0) err(`${path}.length`, rangeMsg(spec, "the mark's bounding size"));
|
|
890
903
|
validateShapePoints(sprite.points, `${path}.points`, 2, err);
|
|
891
|
-
if (sprite.width !== void 0
|
|
904
|
+
if (sprite.width !== void 0) positiveScalar(sprite.width, `${path}.width`, err);
|
|
892
905
|
if (sprite.curve !== void 0 && sprite.curve !== "smooth" && sprite.curve !== "linear") err(`${path}.curve`, "must be 'smooth' | 'linear'");
|
|
893
906
|
if (sprite.taper !== void 0 && typeof sprite.taper !== "boolean") err(`${path}.taper`, "must be a boolean");
|
|
894
907
|
if (sprite.orient !== void 0 && typeof sprite.orient !== "boolean") err(`${path}.orient`, "must be a boolean");
|
|
@@ -901,10 +914,14 @@ function validateSprite(sprite, path, err, warn) {
|
|
|
901
914
|
} else if (sprite.text.length > LIMITS.maxTextBlockLength) {
|
|
902
915
|
err(`${path}.text`, `must be at most ${LIMITS.maxTextBlockLength} characters`);
|
|
903
916
|
}
|
|
904
|
-
if (
|
|
917
|
+
if (Array.isArray(sprite.maxWidth)) {
|
|
918
|
+
err(`${path}.maxWidth`, "must be a single number (viewport fraction), not a [min,max] range");
|
|
919
|
+
} else if (!isNum(sprite.maxWidth) || sprite.maxWidth <= 0 || sprite.maxWidth > LIMITS.maxTextBlockMaxWidth) {
|
|
905
920
|
err(`${path}.maxWidth`, `must be a positive number up to ${LIMITS.maxTextBlockMaxWidth} (viewport fraction)`);
|
|
906
921
|
}
|
|
907
|
-
if (
|
|
922
|
+
if (Array.isArray(sprite.fontSize)) {
|
|
923
|
+
err(`${path}.fontSize`, "must be a single number (viewport fraction), not a [min,max] range");
|
|
924
|
+
} else if (!isNum(sprite.fontSize) || sprite.fontSize < LIMITS.minTextBlockFontSize || sprite.fontSize > LIMITS.maxTextBlockFontSize) {
|
|
908
925
|
err(`${path}.fontSize`, `must be between ${LIMITS.minTextBlockFontSize} and ${LIMITS.maxTextBlockFontSize} (viewport fraction)`);
|
|
909
926
|
}
|
|
910
927
|
if (sprite.lineHeight !== void 0 && (!isNum(sprite.lineHeight) || sprite.lineHeight < 0.5 || sprite.lineHeight > 4)) {
|
|
@@ -1052,7 +1069,7 @@ function validateMotion(motion, path, err, warn, spec) {
|
|
|
1052
1069
|
warn(`${path}.speed`, "near-zero-speed", `orbit speed is near zero \u2014 entities will appear frozen. Typical range: 5\u201360 deg/sec`);
|
|
1053
1070
|
}
|
|
1054
1071
|
}
|
|
1055
|
-
if (!isRange(motion.radius) || motion.radius[0] <= 0) err(`${path}.radius`,
|
|
1072
|
+
if (!isRange(motion.radius) || motion.radius[0] <= 0) err(`${path}.radius`, rangeMsg(spec));
|
|
1056
1073
|
if (motion.center !== void 0) {
|
|
1057
1074
|
if (!isObj(motion.center)) {
|
|
1058
1075
|
err(`${path}.center`, "must be {x, y} (0..1) or { layer: key }");
|
|
@@ -2480,8 +2497,10 @@ function sequenceWantsFinish(seq) {
|
|
|
2480
2497
|
}
|
|
2481
2498
|
function sequenceSwapCompatible(prev, next) {
|
|
2482
2499
|
if (prev.segments.length !== next.segments.length) return false;
|
|
2483
|
-
if (prev.
|
|
2484
|
-
|
|
2500
|
+
if (prev.finish !== void 0 || next.finish !== void 0) {
|
|
2501
|
+
if (prev.seed === void 0 !== (next.seed === void 0)) return false;
|
|
2502
|
+
if (prev.seed !== void 0 && next.seed !== void 0 && normalizeSeed(prev.seed) !== normalizeSeed(next.seed)) return false;
|
|
2503
|
+
}
|
|
2485
2504
|
if (sequenceWantsFinish(prev) !== sequenceWantsFinish(next)) return false;
|
|
2486
2505
|
if (prev.loop !== next.loop) return false;
|
|
2487
2506
|
if ((prev.sync ?? "mount") !== (next.sync ?? "mount")) return false;
|
|
@@ -4768,7 +4787,7 @@ var LINE_SALIENCE = 3.5;
|
|
|
4768
4787
|
var GHOST_TAPS = 12;
|
|
4769
4788
|
function buildScene(spec, opts) {
|
|
4770
4789
|
const { width: w, height: h } = opts.viewport ?? { width: 1920, height: 1080 };
|
|
4771
|
-
const seed = opts.seed ?? spec.seed ?? 42;
|
|
4790
|
+
const seed = normalizeSeed(opts.seed ?? spec.seed ?? 42);
|
|
4772
4791
|
const scale = spec.units === "px" ? 1 : Math.min(w, h);
|
|
4773
4792
|
const refVp = spec.referenceViewport ?? LIMITS.referenceViewport;
|
|
4774
4793
|
let countScale = scale > 1 ? Math.min(w, h) / refVp : 1;
|
|
@@ -4780,7 +4799,7 @@ function buildScene(spec, opts) {
|
|
|
4780
4799
|
const layers = spec.layers.map((layer) => ({ layer, entities: buildEntities(layer, rng, w, h, scale, countScale) }));
|
|
4781
4800
|
const byKey = /* @__PURE__ */ new Map();
|
|
4782
4801
|
for (const { layer, entities } of layers) if (layer.key) byKey.set(layer.key, entities);
|
|
4783
|
-
return { w, h, scale, layers, byKey };
|
|
4802
|
+
return { w, h, scale, seed, layers, byKey };
|
|
4784
4803
|
}
|
|
4785
4804
|
function posOf(scene, e, t) {
|
|
4786
4805
|
const p = positionAt(e, t, scene.w, scene.h);
|
|
@@ -4860,7 +4879,7 @@ function luminanceGrid(spec, opts = {}) {
|
|
|
4860
4879
|
} else if (bg.type === "field") {
|
|
4861
4880
|
bgCells = new Array(cols * rows);
|
|
4862
4881
|
const short = Math.max(1, Math.min(w, h));
|
|
4863
|
-
const seed =
|
|
4882
|
+
const seed = scene.seed;
|
|
4864
4883
|
const ft = fieldSampleTime(bg, t);
|
|
4865
4884
|
for (let r = 0; r < rows; r++) {
|
|
4866
4885
|
let rowSum = 0;
|
|
@@ -6268,7 +6287,10 @@ var SIGNAL_BOARD_SPEC = {
|
|
|
6268
6287
|
kind: "bar",
|
|
6269
6288
|
values: [62, 38, 84, 21, 55, 91, 44, 70, 29],
|
|
6270
6289
|
max: 100,
|
|
6271
|
-
|
|
6290
|
+
// Cell pitch is the table's authored `gap` (0.07), so a full-value
|
|
6291
|
+
// bar must stay under that or neighboring cells' bars overlap.
|
|
6292
|
+
// 0.06 leaves a hairline margin.
|
|
6293
|
+
length: 0.06,
|
|
6272
6294
|
thickness: 0.013,
|
|
6273
6295
|
color: "#ffb000",
|
|
6274
6296
|
direction: "right"
|
|
@@ -7976,6 +7998,7 @@ export {
|
|
|
7976
7998
|
hasHotSwapSequence,
|
|
7977
7999
|
headingAt,
|
|
7978
8000
|
hexToRgb255,
|
|
8001
|
+
ignoredPropertyPaths,
|
|
7979
8002
|
latticeHash,
|
|
7980
8003
|
layerCohesion,
|
|
7981
8004
|
lerpSpec,
|