@idle-screens/schema 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/FORMAT.md +24 -9
- package/dist/index.d.ts +43 -9
- package/dist/index.js +77 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/saver-spec.schema.json +7 -1
package/FORMAT.md
CHANGED
|
@@ -122,7 +122,7 @@ trails, long-exposure light). 0.85–0.95 is the useful range; 0 (default) is of
|
|
|
122
122
|
| `blend` | `lighter` \| `screen` \| `multiply` | source-over | additive glow / gentle additive / darkening |
|
|
123
123
|
| `region` | `{x?, y?}` ranges 0..1 | full viewport | fractional spawn window (placement only, not travel) |
|
|
124
124
|
| `pulse` | `{amp ≤ 0.5, period ≥ 500, wave?}` | none | opacity breathing; `wave: {wavelength, angle?}` turns it into a traveling wave across the field |
|
|
125
|
-
| `spin` | number ±360 deg/sec | none | per-entity rotation (seeded start angle) |
|
|
125
|
+
| `spin` | number \| `[min,max]` ±360 deg/sec | none | per-entity rotation (seeded start angle); a range gives each entity a seeded speed (confetti, tumbling debris) |
|
|
126
126
|
| `grow` | `{amp ≤ 0.8, period ≥ 500}` | none | size breathing (seeded phase) |
|
|
127
127
|
| `trail` | `{length ≤ 5000, fade?}` | none | analytic afterglow trail (ms of history) |
|
|
128
128
|
| `links` | see below | none | inter-entity lines |
|
|
@@ -225,23 +225,38 @@ zero-dependency, renderer-free analysis tool.
|
|
|
225
225
|
(background gradient + entities + link lines, blend-aware), with coverage,
|
|
226
226
|
visual-mass centroid, and **row/column deviation profiles** (1D transects of
|
|
227
227
|
the composition).
|
|
228
|
-
- `renderBrailleMap(grid)` — the picture: 12 lines × 40 braille chars,
|
|
229
|
-
char a 2×4 dot cell, ordered-dithered with auto-exposure. An agent reads
|
|
230
|
-
whole frame at once instead of reconstructing it entity by entity.
|
|
228
|
+
- `renderBrailleMap(grid)` — the compact picture: 12 lines × 40 braille chars,
|
|
229
|
+
each char a 2×4 dot cell, ordered-dithered with auto-exposure. An agent reads
|
|
230
|
+
the whole frame at once instead of reconstructing it entity by entity.
|
|
231
|
+
- `renderDensityMap(grid)` — a **higher-fidelity** picture: one ASCII density
|
|
232
|
+
char (` .:-=+*#%@`) per grid cell, so it's 1:1 with the grid. Pair with a
|
|
233
|
+
larger `cols`/`rows` (e.g. `{cols: 120, rows: 48}`) to resolve structure the
|
|
234
|
+
braille map smears — concentric rings, grids, text blocks. This is the sharper
|
|
235
|
+
read when composition detail matters.
|
|
231
236
|
- `dominanceRanking(spec, opts)` — layers ranked by estimated visual weight
|
|
232
237
|
(area × alpha × contrast × glow/motion boosts; links count) — *where the eye
|
|
233
|
-
goes*, normalized to shares.
|
|
238
|
+
goes*, normalized to shares. Thin-but-bright structures (rings, streaks, link
|
|
239
|
+
lines) get a line-salience boost so they aren't crushed by filled discs.
|
|
234
240
|
- `motionStats(spec, opts)` — per-layer mean/max on-screen speed from analytic
|
|
235
241
|
displacement — choreography as numbers.
|
|
242
|
+
- `textSprites(spec, opts)` — the literal strings and rendered sizes of every
|
|
243
|
+
text layer. Glyphs are invisible in the luminance maps, so this is the only
|
|
244
|
+
way to confirm *what words* are on screen and how big. (Also on
|
|
245
|
+
`perceiveScene().text`.)
|
|
236
246
|
- `diffScenes(a, b, opts)` — **relative sight**: coverage/luminance deltas,
|
|
237
247
|
visual-balance shift, 3×3 region deltas, dominance-rank movement, and
|
|
238
248
|
advisory codes added/removed. Agents judge "is B better than A" far more
|
|
239
249
|
reliably than "is A good"; this is the edit loop's primary instrument.
|
|
240
250
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
251
|
+
Coverage is **additive-glow-calibrated**: soft circles drawn with
|
|
252
|
+
`blend: lighter`/`screen` bloom past their radius (as they do on the real
|
|
253
|
+
canvas), so `coverage` tracks what the audience sees instead of under-reporting
|
|
254
|
+
glow-heavy scenes by an order of magnitude.
|
|
255
|
+
|
|
256
|
+
Known approximations (deliberate): trails and ghosting are not sampled, text ink
|
|
257
|
+
is estimated from font size × string length. Good enough to perceive
|
|
258
|
+
composition, focus, balance, and motion — not a substitute for a human (or VLM)
|
|
259
|
+
judgement of beauty.
|
|
245
260
|
|
|
246
261
|
## Examples
|
|
247
262
|
|
package/dist/index.d.ts
CHANGED
|
@@ -115,8 +115,12 @@ interface LayerSpec {
|
|
|
115
115
|
/**
|
|
116
116
|
* Per-entity rotation speed in degrees/sec (positive = clockwise).
|
|
117
117
|
* Each entity gets a seeded starting angle. Composes with any motion type.
|
|
118
|
+
* A scalar applies one speed to every entity; a `[min, max]` range gives each
|
|
119
|
+
* entity a seeded speed within the range (like `speed`/`alpha`) — confetti,
|
|
120
|
+
* tumbling debris, foliage. Existing scalar specs are unaffected (the range
|
|
121
|
+
* form draws one extra seeded value, only when present).
|
|
118
122
|
*/
|
|
119
|
-
spin?: number;
|
|
123
|
+
spin?: number | [number, number];
|
|
120
124
|
/**
|
|
121
125
|
* Sinusoidal size breathing, parallel to `pulse` for opacity. `amp` is a
|
|
122
126
|
* fraction of base size (0.3 = ±30 %). `period` in ms with the same
|
|
@@ -703,12 +707,33 @@ declare function luminanceGrid(spec: SaverSpec, opts?: LuminanceGridOptions): Lu
|
|
|
703
707
|
* Encode a luminance grid as a braille "image": each output character is a 2×4
|
|
704
708
|
* pixel cell, ordered-dithered so mid-tones render as dot density. A grid of
|
|
705
709
|
* 80×48 becomes 12 lines of 40 chars — a picture a text model can read whole.
|
|
706
|
-
*
|
|
707
|
-
* Auto-exposure: most savers live in the bottom of the luminance range, so the
|
|
708
|
-
* cells are normalized (min → 98th percentile) before dithering — like a
|
|
709
|
-
* camera exposing for the scene. Stats on the grid itself stay raw.
|
|
710
710
|
*/
|
|
711
711
|
declare function renderBrailleMap(grid: LuminanceGrid): string;
|
|
712
|
+
/**
|
|
713
|
+
* Encode a luminance grid as a one-char-per-cell ASCII density map. Unlike the
|
|
714
|
+
* braille map (compact, 2×4 dots/char) this is 1:1 with the grid, so pairing it
|
|
715
|
+
* with a larger `cols`/`rows` gives a non-vision agent a genuinely higher-detail
|
|
716
|
+
* read: 120×48 resolves concentric rings and text blocks braille smears into a
|
|
717
|
+
* blob. Shares the braille map's auto-exposure so both views agree.
|
|
718
|
+
*/
|
|
719
|
+
declare function renderDensityMap(grid: LuminanceGrid): string;
|
|
720
|
+
interface TextSpriteInfo {
|
|
721
|
+
layerIndex: number;
|
|
722
|
+
key: string | undefined;
|
|
723
|
+
/** The literal strings this layer draws (glyphs are invisible in the maps). */
|
|
724
|
+
strings: string[];
|
|
725
|
+
/** How many entities carry text. */
|
|
726
|
+
count: number;
|
|
727
|
+
/** Representative rendered glyph height in px at the sample viewport. */
|
|
728
|
+
sizePx: number;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* List every text layer's actual strings and rendered size. No grid resolution
|
|
732
|
+
* renders glyphs legibly, so a non-vision agent otherwise can't tell whether the
|
|
733
|
+
* words it wrote are on screen, how big, or whether they'll overlap. This closes
|
|
734
|
+
* that blind spot analytically.
|
|
735
|
+
*/
|
|
736
|
+
declare function textSprites(spec: SaverSpec, opts?: PerceiveOptions): TextSpriteInfo[];
|
|
712
737
|
interface DominanceEntry {
|
|
713
738
|
rank: number;
|
|
714
739
|
layerIndex: number;
|
|
@@ -789,8 +814,14 @@ interface SceneDiff {
|
|
|
789
814
|
declare function diffScenes(a: SaverSpec, b: SaverSpec, opts?: LuminanceGridOptions): SceneDiff;
|
|
790
815
|
interface ScenePerception {
|
|
791
816
|
t: number;
|
|
792
|
-
/** The picture: a braille luminance map (each char = 2×4 pixels). */
|
|
817
|
+
/** The picture: a braille luminance map (each char = 2×4 pixels), compact. */
|
|
793
818
|
braille: string;
|
|
819
|
+
/**
|
|
820
|
+
* A one-char-per-cell ASCII density map at the grid resolution — higher
|
|
821
|
+
* fidelity than braille for structure (rings, grids, text blocks). Pair with
|
|
822
|
+
* a larger `cols`/`rows` for a sharper read.
|
|
823
|
+
*/
|
|
824
|
+
density: string;
|
|
794
825
|
coverage: number;
|
|
795
826
|
meanLuminance: number;
|
|
796
827
|
centroid: {
|
|
@@ -801,12 +832,15 @@ interface ScenePerception {
|
|
|
801
832
|
colProfile: number[];
|
|
802
833
|
dominance: DominanceEntry[];
|
|
803
834
|
motion: LayerMotionStats[];
|
|
835
|
+
/** Literal strings + sizes of any text layers (glyphs don't show in the maps). */
|
|
836
|
+
text: TextSpriteInfo[];
|
|
804
837
|
advisories: ReturnType<typeof adviseSpec>;
|
|
805
838
|
}
|
|
806
839
|
/**
|
|
807
840
|
* Everything a non-vision agent needs to "see" a spec in one deterministic
|
|
808
|
-
* call: the braille picture,
|
|
809
|
-
*
|
|
841
|
+
* call: the braille picture, a higher-res density map, composition transects,
|
|
842
|
+
* dominance ranking, motion stats, the literal text on screen, and advisories.
|
|
843
|
+
* Intended as the payload behind an MCP previewScene.
|
|
810
844
|
*/
|
|
811
845
|
declare function perceiveScene(spec: SaverSpec, opts?: LuminanceGridOptions): ScenePerception;
|
|
812
846
|
|
|
@@ -929,4 +963,4 @@ declare const EXAMPLE_SPECS: SaverSpec[];
|
|
|
929
963
|
/** Lookup by saver id. */
|
|
930
964
|
declare const EXAMPLE_BY_ID: Readonly<Record<string, SaverSpec>>;
|
|
931
965
|
|
|
932
|
-
export { AQUARIUM_SPEC, AURORA_SPEC, type BackgroundDrift, type BackgroundSpec, type BandSpec, COMETS_SPEC, CONSTELLATION_SPEC, type CycleSpec, DASHBOARD_SPEC, type DominanceEntry, EXAMPLE_BY_ID, EXAMPLE_SPECS, type Entity, type GradientStop, LANTERNS_SPEC, LIMITS, type LayerMotionStats, type LayerSnapshot, type LayerSpec, type LinkEdge, type LuminanceGrid, type LuminanceGridOptions, MATRIX_RAIN_SPEC, type MotionSpec, ORRERY_SPEC, POLYGONS_SPEC, PROCESSION_SPEC, type PerceiveOptions, type Placed, RAIN_SPEC, SAKURA_SPEC, SCHEMA_EXAMPLES, SCHEMA_VERSION, SNOWFALL_SPEC, type SaverSpec, type SceneDescription, type SceneDiff, type ScenePerception, type SceneSnapshot, type SchemaExample, type SpecError, type SpecWarning, type SpriteSpec, type SteerDelta, type ValidationResult, WARP_NEAR, WARP_TUNNEL_SPEC, type WanderOsc, adviseSpec, alphaAt, applyDeltasToSpec, assertValidSpec, buildEntities, compileSaver, describeScene, diffScenes, dominanceRanking, easeSmooth, headingAt, lerpSpec, lifeAlphaAt, linkEdges, linkPairs, luminanceGrid, manifestFor, motionStats, perceiveScene, positionAt, renderBrailleMap, resolveSpecPath, rotationAt, sizeAt, spriteIndexAt, spriteVariants, steerablePaths, structuralSignature, validateSpec };
|
|
966
|
+
export { AQUARIUM_SPEC, AURORA_SPEC, type BackgroundDrift, type BackgroundSpec, type BandSpec, COMETS_SPEC, CONSTELLATION_SPEC, type CycleSpec, DASHBOARD_SPEC, type DominanceEntry, EXAMPLE_BY_ID, EXAMPLE_SPECS, type Entity, type GradientStop, LANTERNS_SPEC, LIMITS, type LayerMotionStats, type LayerSnapshot, type LayerSpec, type LinkEdge, type LuminanceGrid, type LuminanceGridOptions, MATRIX_RAIN_SPEC, type MotionSpec, ORRERY_SPEC, POLYGONS_SPEC, PROCESSION_SPEC, type PerceiveOptions, type Placed, RAIN_SPEC, SAKURA_SPEC, SCHEMA_EXAMPLES, SCHEMA_VERSION, SNOWFALL_SPEC, type SaverSpec, type SceneDescription, type SceneDiff, type ScenePerception, type SceneSnapshot, type SchemaExample, type SpecError, type SpecWarning, type SpriteSpec, type SteerDelta, type TextSpriteInfo, type ValidationResult, WARP_NEAR, WARP_TUNNEL_SPEC, type WanderOsc, adviseSpec, alphaAt, applyDeltasToSpec, assertValidSpec, buildEntities, compileSaver, describeScene, diffScenes, dominanceRanking, easeSmooth, headingAt, lerpSpec, lifeAlphaAt, linkEdges, linkPairs, luminanceGrid, manifestFor, motionStats, perceiveScene, positionAt, renderBrailleMap, renderDensityMap, resolveSpecPath, rotationAt, sizeAt, spriteIndexAt, spriteVariants, steerablePaths, structuralSignature, textSprites, validateSpec };
|
package/dist/index.js
CHANGED
|
@@ -250,8 +250,15 @@ function validateLayer(layer, path, err, warn, spec) {
|
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
if (layer.spin !== void 0) {
|
|
253
|
-
if (
|
|
254
|
-
|
|
253
|
+
if (isRange(layer.spin)) {
|
|
254
|
+
if (Math.abs(layer.spin[0]) > LIMITS.maxSpin || Math.abs(layer.spin[1]) > LIMITS.maxSpin) {
|
|
255
|
+
err(`${path}.spin`, `each end of the range must be within \xB1${LIMITS.maxSpin} deg/sec`);
|
|
256
|
+
}
|
|
257
|
+
} else if (!isNum(layer.spin)) {
|
|
258
|
+
err(`${path}.spin`, "must be a number or a [min,max] range (degrees/sec)");
|
|
259
|
+
} else if (Math.abs(layer.spin) > LIMITS.maxSpin) {
|
|
260
|
+
err(`${path}.spin`, `must be within \xB1${LIMITS.maxSpin} deg/sec`);
|
|
261
|
+
}
|
|
255
262
|
}
|
|
256
263
|
if (layer.grow !== void 0) {
|
|
257
264
|
if (!isObj(layer.grow)) err(`${path}.grow`, "must be an object");
|
|
@@ -749,8 +756,10 @@ function buildEntities(layer, rng, w, h, scale = 1, countScale = 1) {
|
|
|
749
756
|
pulsePeriod: layer.pulse?.period ?? 1e3,
|
|
750
757
|
pulsePhase: layer.pulse ? rng.range(0, Math.PI * 2) : 0,
|
|
751
758
|
// New draws AFTER pulsePhase — guarded so existing specs keep identical streams.
|
|
752
|
-
|
|
753
|
-
//
|
|
759
|
+
// Scalar spin: no draw here, one phase draw below (unchanged). Range spin
|
|
760
|
+
// (new): one seeded speed draw here, then the phase draw — array form is new
|
|
761
|
+
// so the extra draw can't perturb any existing spec's entity stream.
|
|
762
|
+
spinSpeed: typeof layer.spin === "number" ? layer.spin * Math.PI / 180 : Array.isArray(layer.spin) ? rng.range(layer.spin[0], layer.spin[1]) * Math.PI / 180 : 0,
|
|
754
763
|
spinPhase: layer.spin ? rng.range(0, Math.PI * 2) : 0,
|
|
755
764
|
growAmp: layer.grow?.amp ?? 0,
|
|
756
765
|
growPeriod: layer.grow?.period ?? 1e3,
|
|
@@ -1875,6 +1884,8 @@ function describeScene(spec, options = {}) {
|
|
|
1875
1884
|
|
|
1876
1885
|
// src/perceive.ts
|
|
1877
1886
|
import { createRng as createRng4 } from "@idle-screens/core";
|
|
1887
|
+
var GLOW_SPREAD = 2.4;
|
|
1888
|
+
var LINE_SALIENCE = 3.5;
|
|
1878
1889
|
function buildScene(spec, opts) {
|
|
1879
1890
|
const { width: w, height: h } = opts.viewport ?? { width: 1920, height: 1080 };
|
|
1880
1891
|
const seed = opts.seed ?? spec.seed ?? 42;
|
|
@@ -2013,12 +2024,16 @@ function luminanceGrid(spec, opts = {}) {
|
|
|
2013
2024
|
halfX = box.halfX;
|
|
2014
2025
|
halfY = box.halfY;
|
|
2015
2026
|
}
|
|
2016
|
-
const c0 = Math.max(0, Math.floor((centerX - halfX) / cellW));
|
|
2017
|
-
const c1 = Math.min(cols - 1, Math.floor((centerX + halfX) / cellW));
|
|
2018
|
-
const r0 = Math.max(0, Math.floor((centerY - halfY) / cellH));
|
|
2019
|
-
const r1 = Math.min(rows - 1, Math.floor((centerY + halfY) / cellH));
|
|
2020
2027
|
const circular = s.kind === "circle" || s.kind === "ring";
|
|
2021
|
-
const soft = s.kind === "circle" && s.soft;
|
|
2028
|
+
const soft = s.kind === "circle" && !!s.soft;
|
|
2029
|
+
const additive = layer.blend === "lighter" || layer.blend === "screen";
|
|
2030
|
+
const glow = soft && additive;
|
|
2031
|
+
const reachX = glow ? halfX * GLOW_SPREAD : halfX;
|
|
2032
|
+
const reachY = glow ? halfY * GLOW_SPREAD : halfY;
|
|
2033
|
+
const c0 = Math.max(0, Math.floor((centerX - reachX) / cellW));
|
|
2034
|
+
const c1 = Math.min(cols - 1, Math.floor((centerX + reachX) / cellW));
|
|
2035
|
+
const r0 = Math.max(0, Math.floor((centerY - reachY) / cellH));
|
|
2036
|
+
const r1 = Math.min(rows - 1, Math.floor((centerY + reachY) / cellH));
|
|
2022
2037
|
const inkWeight = s.kind === "text" || s.kind === "emoji" ? 0.55 : 1;
|
|
2023
2038
|
for (let r = r0; r <= r1; r++) {
|
|
2024
2039
|
for (let c = c0; c <= c1; c++) {
|
|
@@ -2026,12 +2041,23 @@ function luminanceGrid(spec, opts = {}) {
|
|
|
2026
2041
|
const dy = (r + 0.5) * cellH - centerY;
|
|
2027
2042
|
if (circular) {
|
|
2028
2043
|
const d = Math.sqrt(dx * dx + dy * dy);
|
|
2029
|
-
|
|
2044
|
+
const reach = glow ? reachX : halfX;
|
|
2045
|
+
if (d > reach + Math.min(cellW, cellH) / 2) continue;
|
|
2030
2046
|
if (s.kind === "ring") {
|
|
2031
2047
|
const strokeW = (s.width ?? (scale === 1 ? 2 : 2e-3)) * scale;
|
|
2032
2048
|
if (d < halfX - strokeW) continue;
|
|
2033
2049
|
}
|
|
2034
|
-
|
|
2050
|
+
let wgt;
|
|
2051
|
+
if (glow) {
|
|
2052
|
+
const core = halfX;
|
|
2053
|
+
if (d <= core) wgt = 1;
|
|
2054
|
+
else {
|
|
2055
|
+
const f = Math.max(0, 1 - (d - core) / Math.max(reachX - core, 1e-6));
|
|
2056
|
+
wgt = f * f;
|
|
2057
|
+
}
|
|
2058
|
+
} else {
|
|
2059
|
+
wgt = soft ? Math.max(0.1, 1 - d / Math.max(halfX, 1e-6)) : 1;
|
|
2060
|
+
}
|
|
2035
2061
|
compose(r * cols + c, lum, a * wgt, layer.blend);
|
|
2036
2062
|
} else {
|
|
2037
2063
|
compose(r * cols + c, lum, a * inkWeight, layer.blend);
|
|
@@ -2107,11 +2133,14 @@ var DOT_BIT = [
|
|
|
2107
2133
|
[4, 32],
|
|
2108
2134
|
[64, 128]
|
|
2109
2135
|
];
|
|
2110
|
-
function
|
|
2136
|
+
function autoExpose(grid) {
|
|
2111
2137
|
const sorted = [...grid.cells].sort((a, b) => a - b);
|
|
2112
2138
|
const lo = sorted[0];
|
|
2113
2139
|
const hi = Math.max(sorted[Math.floor(sorted.length * 0.98)], lo + 0.08);
|
|
2114
|
-
|
|
2140
|
+
return (v) => Math.pow(Math.max(0, Math.min(1, (v - lo) / (hi - lo))), 0.8);
|
|
2141
|
+
}
|
|
2142
|
+
function renderBrailleMap(grid) {
|
|
2143
|
+
const expose = autoExpose(grid);
|
|
2115
2144
|
const outRows = [];
|
|
2116
2145
|
for (let br = 0; br < Math.floor(grid.rows / 4); br++) {
|
|
2117
2146
|
let line = "";
|
|
@@ -2129,6 +2158,34 @@ function renderBrailleMap(grid) {
|
|
|
2129
2158
|
}
|
|
2130
2159
|
return outRows.join("\n");
|
|
2131
2160
|
}
|
|
2161
|
+
var DENSITY_RAMP = " .:-=+*#%@";
|
|
2162
|
+
function renderDensityMap(grid) {
|
|
2163
|
+
const expose = autoExpose(grid);
|
|
2164
|
+
const ramp = DENSITY_RAMP;
|
|
2165
|
+
const lines = [];
|
|
2166
|
+
for (let r = 0; r < grid.rows; r++) {
|
|
2167
|
+
let line = "";
|
|
2168
|
+
for (let c = 0; c < grid.cols; c++) {
|
|
2169
|
+
const v = expose(grid.cells[r * grid.cols + c]);
|
|
2170
|
+
line += ramp[Math.min(ramp.length - 1, Math.floor(v * ramp.length))];
|
|
2171
|
+
}
|
|
2172
|
+
lines.push(line);
|
|
2173
|
+
}
|
|
2174
|
+
return lines.join("\n");
|
|
2175
|
+
}
|
|
2176
|
+
function textSprites(spec, opts = {}) {
|
|
2177
|
+
const scene = buildScene(spec, opts);
|
|
2178
|
+
const t = opts.t ?? 5e3;
|
|
2179
|
+
const out = [];
|
|
2180
|
+
scene.layers.forEach(({ layer, entities }, layerIndex) => {
|
|
2181
|
+
if (layer.sprite.kind !== "text") return;
|
|
2182
|
+
const s = layer.sprite;
|
|
2183
|
+
const m = s.font ? /(\d+(?:\.\d+)?)px/.exec(s.font) : null;
|
|
2184
|
+
const sizePx = m ? Number(m[1]) : entities[0] ? sizeAt(entities[0], t) : 0;
|
|
2185
|
+
out.push({ layerIndex, key: layer.key, strings: [...s.strings], count: entities.length, sizePx: Math.round(sizePx) });
|
|
2186
|
+
});
|
|
2187
|
+
return out;
|
|
2188
|
+
}
|
|
2132
2189
|
function dominanceRanking(spec, opts = {}) {
|
|
2133
2190
|
const scene = buildScene(spec, opts);
|
|
2134
2191
|
const t = opts.t ?? 5e3;
|
|
@@ -2148,8 +2205,8 @@ function dominanceRanking(spec, opts = {}) {
|
|
|
2148
2205
|
const s = layer.sprite;
|
|
2149
2206
|
let entArea;
|
|
2150
2207
|
if (s.kind === "circle") entArea = Math.PI * (sz / 2) * (sz / 2);
|
|
2151
|
-
else if (s.kind === "ring") entArea = Math.PI * sz * ((s.width ?? (scale === 1 ? 2 : 2e-3)) * scale);
|
|
2152
|
-
else if (s.kind === "streak") entArea = sz * ((s.width ?? (scale === 1 ? 2 : 2e-3)) * scale);
|
|
2208
|
+
else if (s.kind === "ring") entArea = Math.PI * sz * ((s.width ?? (scale === 1 ? 2 : 2e-3)) * scale) * LINE_SALIENCE;
|
|
2209
|
+
else if (s.kind === "streak") entArea = sz * ((s.width ?? (scale === 1 ? 2 : 2e-3)) * scale) * LINE_SALIENCE;
|
|
2153
2210
|
else if (s.kind === "rect") {
|
|
2154
2211
|
const h2 = e.size2 !== void 0 ? e.size2 * (e.size > 0 ? sz / e.size : 1) : sz;
|
|
2155
2212
|
entArea = sz * h2;
|
|
@@ -2166,7 +2223,7 @@ function dominanceRanking(spec, opts = {}) {
|
|
|
2166
2223
|
const edges = linkEdges(layer.links, positions, layer.links.maxDist * scale, layer.wrap !== false && motionWraps, w, h);
|
|
2167
2224
|
const la = (layer.links.alpha ?? 0.6) * lifeA;
|
|
2168
2225
|
const lw = (layer.links.width ?? 1) * scale;
|
|
2169
|
-
for (const edge of edges) area += edge.dist * lw * la;
|
|
2226
|
+
for (const edge of edges) area += edge.dist * lw * la * LINE_SALIENCE;
|
|
2170
2227
|
}
|
|
2171
2228
|
const meanLuma = entities.length ? lumAcc / entities.length : 0;
|
|
2172
2229
|
const contrast = Math.abs(meanLuma - bgMean) + 0.1;
|
|
@@ -2274,6 +2331,7 @@ function perceiveScene(spec, opts = {}) {
|
|
|
2274
2331
|
return {
|
|
2275
2332
|
t: opts.t ?? 5e3,
|
|
2276
2333
|
braille: renderBrailleMap(grid),
|
|
2334
|
+
density: renderDensityMap(grid),
|
|
2277
2335
|
coverage: grid.coverage,
|
|
2278
2336
|
meanLuminance: grid.meanLuminance,
|
|
2279
2337
|
centroid: grid.centroid,
|
|
@@ -2281,6 +2339,7 @@ function perceiveScene(spec, opts = {}) {
|
|
|
2281
2339
|
colProfile: grid.colProfile,
|
|
2282
2340
|
dominance: dominanceRanking(spec, opts),
|
|
2283
2341
|
motion: motionStats(spec, opts),
|
|
2342
|
+
text: textSprites(spec, opts),
|
|
2284
2343
|
advisories: adviseSpec(spec, opts.viewport ?? { width: 1920, height: 1080 })
|
|
2285
2344
|
};
|
|
2286
2345
|
}
|
|
@@ -3616,6 +3675,7 @@ export {
|
|
|
3616
3675
|
perceiveScene,
|
|
3617
3676
|
positionAt,
|
|
3618
3677
|
renderBrailleMap,
|
|
3678
|
+
renderDensityMap,
|
|
3619
3679
|
resolveSpecPath,
|
|
3620
3680
|
rotationAt,
|
|
3621
3681
|
sizeAt,
|
|
@@ -3623,6 +3683,7 @@ export {
|
|
|
3623
3683
|
spriteVariants,
|
|
3624
3684
|
steerablePaths,
|
|
3625
3685
|
structuralSignature,
|
|
3686
|
+
textSprites,
|
|
3626
3687
|
validateSpec
|
|
3627
3688
|
};
|
|
3628
3689
|
//# sourceMappingURL=index.js.map
|