@aethervtt/shard-noise 0.0.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/kernel.js ADDED
@@ -0,0 +1,263 @@
1
+ // The noise kernel's JavaScript side: instances, memory layout, origin splits, and the point
2
+ // generators for grids and cube-sphere patches. Plain JavaScript with no imports, because worker
3
+ // threads load it directly (no bundler, no TypeScript loader). Types are in kernel.d.ts.
4
+
5
+ /** Points per block: a register's length (matches `program::BLOCK`). */
6
+ export const BLOCK = 256
7
+ /** i32 words per origin record: cell xyzw, then fraction xyzw as f32 bits. */
8
+ export const ORIGIN_WORDS = 8
9
+
10
+ // Skew factors as the f32 values the kernel uses, so the f64 origin split and the f32 local part
11
+ // apply the same matrix.
12
+ const F2 = Math.fround((Math.sqrt(3) - 1) / 2)
13
+ const F4 = Math.fround((Math.sqrt(5) - 1) / 4)
14
+ const TWO_THIRDS = Math.fround(2 / 3)
15
+ const I32_LIMIT = 2 ** 31
16
+
17
+ const bits = new Float32Array(1)
18
+ const bitsI = new Int32Array(bits.buffer)
19
+
20
+ function kernelError(code, message, hint) {
21
+ return Object.assign(new Error(message), { code, hint })
22
+ }
23
+
24
+ /**
25
+ * Writes one origin record per term: the lattice position of `origin` (f64: a × origin + b, then the
26
+ * source's skew) split into an i32 cell and an f32 fraction.
27
+ */
28
+ export function computeOrigins(terms, origin, out) {
29
+ const { a, b, skew } = terms
30
+ const ox = origin[0] ?? 0
31
+ const oy = origin[1] ?? 0
32
+ const oz = origin[2] ?? 0
33
+ const ow = origin[3] ?? 0
34
+ for (let j = 0; j < skew.length; j++) {
35
+ const j4 = j * 4
36
+ let x = a[j4] * ox + b[j4]
37
+ let y = a[j4 + 1] * oy + b[j4 + 1]
38
+ let z = a[j4 + 2] * oz + b[j4 + 2]
39
+ let w = a[j4 + 3] * ow + b[j4 + 3]
40
+ const k = skew[j]
41
+ if (k === 2) {
42
+ const s = (x + y) * F2
43
+ x += s
44
+ y += s
45
+ } else if (k === 3) {
46
+ const r = (x + y + z) * TWO_THIRDS
47
+ x = r - x
48
+ y = r - y
49
+ z = r - z
50
+ } else if (k === 4) {
51
+ const s = (x + y + z + w) * F4
52
+ x += s
53
+ y += s
54
+ z += s
55
+ w += s
56
+ }
57
+ const o = j * ORIGIN_WORDS
58
+ split(x, out, o)
59
+ split(y, out, o + 1)
60
+ split(z, out, o + 2)
61
+ split(w, out, o + 3)
62
+ }
63
+ return out
64
+ }
65
+
66
+ function split(v, out, i) {
67
+ const cell = Math.floor(v)
68
+ if (!(cell < I32_LIMIT && cell >= -I32_LIMIT)) {
69
+ throw kernelError(
70
+ 'noise/frequency-too-high',
71
+ `A lattice coordinate reached ${v.toExponential(2)}, past what an i32 cell holds (2³¹)`,
72
+ 'Lower the graph’s finest frequency, or sample closer to the origin.',
73
+ )
74
+ }
75
+ out[i] = cell | 0
76
+ bits[0] = v - cell
77
+ out[i + 4] = bitsI[0]
78
+ }
79
+
80
+ const states = new WeakMap()
81
+
82
+ /** The instance for a compiled kernel module in this thread (made on first use). */
83
+ export function instantiate(module, instance) {
84
+ let s = states.get(module)
85
+ if (!s) {
86
+ const inst = instance ?? new WebAssembly.Instance(module, {})
87
+ s = {
88
+ x: inst.exports,
89
+ memory: inst.exports.memory,
90
+ base: 0,
91
+ capacity: 0,
92
+ f32: null,
93
+ i32: null,
94
+ // The last output range as a view, reused while calls keep the same size.
95
+ outView: null,
96
+ outAt: -1,
97
+ }
98
+ states.set(module, s)
99
+ }
100
+ return s
101
+ }
102
+
103
+ const align = (n) => (n + 15) & ~15
104
+
105
+ /**
106
+ * Runs `program` at `count` points (`stride` floats each, local to the origin the records were
107
+ * computed for) and writes the results into `out` from `outOffset`. Allocates nothing once the
108
+ * instance's memory is big enough.
109
+ */
110
+ export function evalProgram(state, program, seed, origins, pts, stride, count, out, outOffset = 0) {
111
+ if (count === 0) return
112
+ const codeOff = 0
113
+ const constOff = codeOff + align(program.code.length * 4)
114
+ const originOff = constOff + align(program.consts.length * 4)
115
+ const ptsOff = originOff + align(origins.length * 4)
116
+ const outOff = ptsOff + align(count * stride * 4)
117
+ const regOff = outOff + align(count * 4)
118
+ const total = regOff + program.registers * BLOCK * 4
119
+ if (total > state.capacity) {
120
+ const base = state.x.reserve(total)
121
+ if (base === 0)
122
+ throw kernelError('noise/out-of-memory', `The noise kernel couldn't reserve ${total} bytes`)
123
+ state.base = base
124
+ state.capacity = total
125
+ }
126
+ if (state.f32 === null || state.f32.buffer !== state.memory.buffer) {
127
+ state.f32 = new Float32Array(state.memory.buffer)
128
+ state.i32 = new Int32Array(state.memory.buffer)
129
+ state.outView = null
130
+ }
131
+ const f32 = state.f32
132
+ const i32 = state.i32
133
+ const base = state.base
134
+ i32.set(program.code, (base + codeOff) >> 2)
135
+ f32.set(program.consts, (base + constOff) >> 2)
136
+ i32.set(origins, (base + originOff) >> 2)
137
+ const n = count * stride
138
+ const p = (base + ptsOff) >> 2
139
+ if (pts.length === n) f32.set(pts, p)
140
+ else for (let i = 0; i < n; i++) f32[p + i] = pts[i]
141
+ state.x.eval(
142
+ base + codeOff,
143
+ program.code.length / 12,
144
+ base + constOff,
145
+ base + originOff,
146
+ seed >>> 0,
147
+ base + ptsOff,
148
+ stride,
149
+ count,
150
+ base + outOff,
151
+ base + regOff,
152
+ program.result,
153
+ )
154
+ const o = (base + outOff) >> 2
155
+ if (state.outView === null || state.outAt !== o || state.outView.length !== count) {
156
+ state.outView = f32.subarray(o, o + count)
157
+ state.outAt = o
158
+ }
159
+ out.set(state.outView, outOffset)
160
+ }
161
+
162
+ // --- cube sphere -------------------------------------------------------------------------------
163
+
164
+ /** Per face: normal, right, and up axes (right × up = normal). Faces are +X, −X, +Y, −Y, +Z, −Z. */
165
+ const FACES = [
166
+ [1, 0, 0, 0, 0, -1, 0, 1, 0],
167
+ [-1, 0, 0, 0, 0, 1, 0, 1, 0],
168
+ [0, 1, 0, 1, 0, 0, 0, 0, -1],
169
+ [0, -1, 0, 1, 0, 0, 0, 0, 1],
170
+ [0, 0, 1, 1, 0, 0, 0, 1, 0],
171
+ [0, 0, -1, -1, 0, 0, 0, 1, 0],
172
+ ]
173
+
174
+ const QUARTER_PI = Math.PI / 4
175
+
176
+ /**
177
+ * The unit direction for face coordinates (u, v) in [-1, 1], with the tangent-adjusted mapping
178
+ * (each coordinate goes through tan(π/4 · u)), which keeps cells within 1.4× of each other in area.
179
+ */
180
+ export function faceToDirection(face, u, v, out, offset = 0) {
181
+ const f = FACES[face]
182
+ const tu = Math.tan(QUARTER_PI * u)
183
+ const tv = Math.tan(QUARTER_PI * v)
184
+ const x = f[0] + tu * f[3] + tv * f[6]
185
+ const y = f[1] + tu * f[4] + tv * f[7]
186
+ const z = f[2] + tu * f[5] + tv * f[8]
187
+ const inv = 1 / Math.sqrt(x * x + y * y + z * z)
188
+ out[offset] = x * inv
189
+ out[offset + 1] = y * inv
190
+ out[offset + 2] = z * inv
191
+ return out
192
+ }
193
+
194
+ /** The face a direction lands on, with its face coordinates written to `out` as [u, v]. */
195
+ export function directionToFace(x, y, z, out) {
196
+ const ax = Math.abs(x)
197
+ const ay = Math.abs(y)
198
+ const az = Math.abs(z)
199
+ const face =
200
+ ax >= ay && ax >= az ? (x >= 0 ? 0 : 1) : ay >= az ? (y >= 0 ? 2 : 3) : z >= 0 ? 4 : 5
201
+ const f = FACES[face]
202
+ const n = x * f[0] + y * f[1] + z * f[2]
203
+ out[0] = Math.atan((x * f[3] + y * f[4] + z * f[5]) / n) / QUARTER_PI
204
+ out[1] = Math.atan((x * f[6] + y * f[7] + z * f[8]) / n) / QUARTER_PI
205
+ return face
206
+ }
207
+
208
+ const dir = new Float64Array(3)
209
+
210
+ /** The f64 center of a sphere patch: its middle direction × radius. */
211
+ export function patchOrigin(p, out) {
212
+ const half = p.extent / 2
213
+ faceToDirection(p.face, p.x0 + half, p.y0 + half, dir)
214
+ out[0] = dir[0] * p.radius
215
+ out[1] = dir[1] * p.radius
216
+ out[2] = dir[2] * p.radius
217
+ out[3] = 0
218
+ return out
219
+ }
220
+
221
+ /**
222
+ * Local points (xyz, relative to `origin`) for rows [row0, row1) of a sphere patch: `resolution`
223
+ * vertices per side spanning [x0, x0 + extent] × [y0, y0 + extent] of the face.
224
+ */
225
+ export function patchPoints(p, origin, row0, row1, out) {
226
+ const last = p.resolution - 1
227
+ let k = 0
228
+ for (let j = row0; j < row1; j++) {
229
+ const v = p.y0 + (p.extent * j) / last
230
+ for (let i = 0; i <= last; i++) {
231
+ faceToDirection(p.face, p.x0 + (p.extent * i) / last, v, dir)
232
+ out[k++] = dir[0] * p.radius - origin[0]
233
+ out[k++] = dir[1] * p.radius - origin[1]
234
+ out[k++] = dir[2] * p.radius - origin[2]
235
+ }
236
+ }
237
+ return out
238
+ }
239
+
240
+ /** The f64 center of a grid. */
241
+ export function gridOrigin(g, out) {
242
+ out[0] = g.origin[0] + g.size[0] / 2
243
+ out[1] = g.origin[1] + g.size[1] / 2
244
+ out[2] = g.z ?? 0
245
+ out[3] = 0
246
+ return out
247
+ }
248
+
249
+ /** Local points for rows [row0, row1) of a grid on the z = `g.z` plane. */
250
+ export function gridPoints(g, origin, row0, row1, out) {
251
+ const [nx, ny] = g.resolution
252
+ let k = 0
253
+ for (let j = row0; j < row1; j++) {
254
+ const y = g.origin[1] + (ny > 1 ? (g.size[1] * j) / (ny - 1) : 0)
255
+ for (let i = 0; i < nx; i++) {
256
+ const x = g.origin[0] + (nx > 1 ? (g.size[0] * i) / (nx - 1) : 0)
257
+ out[k++] = x - origin[0]
258
+ out[k++] = y - origin[1]
259
+ out[k++] = 0
260
+ }
261
+ }
262
+ return out
263
+ }
@@ -0,0 +1,25 @@
1
+ import { type KernelState } from './kernel.js';
2
+ export interface NoiseKernel {
3
+ /** The compiled module; workers instantiate it from this (modules are cheap to post). */
4
+ readonly module: WebAssembly.Module;
5
+ /** This thread's instance. */
6
+ readonly state: KernelState;
7
+ /** Whether this is the simd128 build. */
8
+ readonly simd: boolean;
9
+ }
10
+ /** Whether this host runs WebAssembly SIMD (every current browser and Node does). */
11
+ export declare function simdSupported(): boolean;
12
+ /**
13
+ * Loads the WASM noise kernel once per thread: the SIMD build where the host supports it (or when
14
+ * `simd` says so), the scalar build otherwise. The two give bitwise-equal results.
15
+ */
16
+ export declare function loadNoiseKernel(options?: {
17
+ simd?: boolean;
18
+ }): Promise<NoiseKernel>;
19
+ /**
20
+ * Makes a compiled kernel module (`noiseKernel().module`, posted from another thread) this thread's
21
+ * kernel, so a worker samples without fetching the .wasm. Later `loadNoiseKernel()` calls return it.
22
+ */
23
+ export declare function useNoiseKernel(module: WebAssembly.Module, simd?: boolean): NoiseKernel;
24
+ /** The kernel sync sampling uses. Throws `noise/kernel-not-loaded` before `loadNoiseKernel`. */
25
+ export declare function noiseKernel(): NoiseKernel;
package/dist/loader.js ADDED
@@ -0,0 +1,108 @@
1
+ import { ShardError } from "@aethervtt/shard-core";
2
+ import { instantiate } from "./kernel.js";
3
+ const SIMD_PROBE = new Uint8Array([
4
+ 0,
5
+ 97,
6
+ 115,
7
+ 109,
8
+ 1,
9
+ 0,
10
+ 0,
11
+ 0,
12
+ 1,
13
+ 5,
14
+ 1,
15
+ 96,
16
+ 0,
17
+ 1,
18
+ 123,
19
+ 3,
20
+ 2,
21
+ 1,
22
+ 0,
23
+ 10,
24
+ 10,
25
+ 1,
26
+ 8,
27
+ 0,
28
+ 65,
29
+ 0,
30
+ 253,
31
+ 15,
32
+ 253,
33
+ 98,
34
+ 11
35
+ ]);
36
+ function simdSupported() {
37
+ return WebAssembly.validate(SIMD_PROBE);
38
+ }
39
+ const loaded = /* @__PURE__ */ new Map();
40
+ let current;
41
+ async function readWasm(file) {
42
+ const url = new URL(`../wasm/${file}`, import.meta.url);
43
+ if (url.protocol === "file:") {
44
+ const { readFile } = await import("node:fs/promises");
45
+ const data = await readFile(url);
46
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
47
+ }
48
+ const res = await fetch(url);
49
+ if (!res.ok) {
50
+ throw new ShardError(
51
+ "noise/kernel-load-failed",
52
+ `Couldn't fetch the noise kernel (${res.status})`,
53
+ {
54
+ path: url.href,
55
+ hint: "The .wasm files live in packages/noise/wasm; the dev server must serve them."
56
+ }
57
+ );
58
+ }
59
+ return res;
60
+ }
61
+ function loadNoiseKernel(options = {}) {
62
+ if (options.simd === void 0 && current && installed) return Promise.resolve(current);
63
+ const simd = options.simd ?? simdSupported();
64
+ let p = loaded.get(simd);
65
+ if (!p) {
66
+ p = (async () => {
67
+ const source = await readWasm(simd ? "shard_noise_simd.wasm" : "shard_noise.wasm");
68
+ let module;
69
+ try {
70
+ module = source instanceof Uint8Array ? await WebAssembly.compile(source) : await WebAssembly.compileStreaming(source);
71
+ } catch (cause) {
72
+ throw new ShardError("noise/kernel-load-failed", "The noise kernel didn't compile", {
73
+ cause,
74
+ hint: "Rebuild it with `pnpm build:wasm`."
75
+ });
76
+ }
77
+ const instance = await WebAssembly.instantiate(module, {});
78
+ const kernel = { module, state: instantiate(module, instance), simd };
79
+ if (options.simd === void 0 || !current) current = kernel;
80
+ return kernel;
81
+ })();
82
+ loaded.set(simd, p);
83
+ }
84
+ return p;
85
+ }
86
+ let installed = false;
87
+ function useNoiseKernel(module, simd = true) {
88
+ if (current?.module === module) return current;
89
+ const kernel = { module, state: instantiate(module), simd };
90
+ loaded.set(simd, Promise.resolve(kernel));
91
+ current = kernel;
92
+ installed = true;
93
+ return kernel;
94
+ }
95
+ function noiseKernel() {
96
+ if (!current) {
97
+ throw new ShardError("noise/kernel-not-loaded", "The noise kernel is not loaded yet", {
98
+ hint: "await loadNoiseKernel() (or load a NoiseGraph asset, which does) before sampling synchronously."
99
+ });
100
+ }
101
+ return current;
102
+ }
103
+ export {
104
+ loadNoiseKernel,
105
+ noiseKernel,
106
+ simdSupported,
107
+ useNoiseKernel
108
+ };
@@ -0,0 +1,9 @@
1
+ import { type World } from '@aethervtt/shard-core';
2
+ import type { AppMethod } from '@aethervtt/shard-runtime';
3
+ import { NoiseGraph } from './noise-graph.js';
4
+ /** The most points `noise.sample` returns in one call. */
5
+ export declare const MAX_SAMPLE_POINTS = 4096;
6
+ /** A graph from an asset path or guid (loaded if needed), or inline graph JSON to try before saving. */
7
+ export declare function resolveGraph(world: World, graph: unknown): Promise<NoiseGraph>;
8
+ export declare const noiseMethods: AppMethod[];
9
+ export declare const noiseGraphPreview: import("@aethervtt/shard-assets").AssetPreview;
@@ -0,0 +1,119 @@
1
+ import { assetServer, defineAssetPreview } from "@aethervtt/shard-assets";
2
+ import { defineSchema, ShardError, t } from "@aethervtt/shard-core";
3
+ import { loadNoiseKernel } from "./loader.js";
4
+ import { NoiseGraph, NoiseGraphs } from "./noise-graph.js";
5
+ import { sampleNoise, sampleOffset } from "./sample.js";
6
+ import { noiseStats, previewNoise } from "./stats.js";
7
+ const MAX_SAMPLE_POINTS = 4096;
8
+ async function resolveGraph(world, graph) {
9
+ await loadNoiseKernel();
10
+ if (graph && typeof graph === "object") return NoiseGraph.fromJson(graph);
11
+ if (typeof graph !== "string") {
12
+ throw new ShardError("noise/invalid-graph", '"graph" is an asset path, a guid, or graph JSON', {
13
+ hint: 'Pass "noise/planet.noise.json", or { "output": \u2026, "nodes": { \u2026 } } to try a graph unsaved.'
14
+ });
15
+ }
16
+ const server = assetServer(world);
17
+ const ref = server.resolve(graph);
18
+ if (ref?.type !== "NoiseGraph") {
19
+ throw new ShardError("noise/not-a-graph", `"${graph}" isn't a noise graph asset`, {
20
+ hint: "asset.list shows NoiseGraph assets (*.noise.json)."
21
+ });
22
+ }
23
+ await server.load(ref.path ?? graph);
24
+ const loaded = world.resource(NoiseGraphs).get(ref);
25
+ if (!loaded)
26
+ throw new ShardError("noise/not-loaded", `"${graph}" didn't load`, {
27
+ hint: "asset.status shows why."
28
+ });
29
+ return loaded;
30
+ }
31
+ const graphField = t.json({
32
+ required: true,
33
+ description: "A NoiseGraph asset path or guid, or inline graph JSON (tried without saving)."
34
+ });
35
+ const noiseMethods = [
36
+ {
37
+ name: "noise.sample",
38
+ description: `Values of a noise graph at up to ${MAX_SAMPLE_POINTS} points ([x, y, z], or [x, y, z, w] for 4D graphs). With origin, points are offsets from it (precise at any distance). node samples an intermediate node.`,
39
+ params: defineSchema("noise/SampleParams", {
40
+ graph: graphField,
41
+ seed: t.u32({ default: 0 }),
42
+ points: t.json({ required: true, description: "Points: [[x, y, z], \u2026]." }),
43
+ origin: t.json({ description: "f64 origin [x, y, z] the points are relative to." }),
44
+ node: t.string({ description: "Sample this node instead of the output." })
45
+ }),
46
+ handler: async ({ world }, p) => {
47
+ const graph = await resolveGraph(world, p.graph);
48
+ const points = p.points;
49
+ if (!Array.isArray(points) || points.length > MAX_SAMPLE_POINTS) {
50
+ throw new ShardError(
51
+ "noise/too-many-points",
52
+ `points is an array of at most ${MAX_SAMPLE_POINTS} points`,
53
+ {
54
+ hint: "Split the points over several calls, or use noise.stats for a summary of a whole domain."
55
+ }
56
+ );
57
+ }
58
+ const stride = graph.program.dimensions === 4 ? 4 : 3;
59
+ const flat = new Float32Array(points.length * stride);
60
+ points.forEach((pt, i) => {
61
+ for (let k = 0; k < stride; k++) flat[i * stride + k] = pt[k] ?? 0;
62
+ });
63
+ const out = new Float32Array(points.length);
64
+ const node = p.node || void 0;
65
+ if (Array.isArray(p.origin))
66
+ sampleOffset(graph, p.seed, p.origin, flat, out, node);
67
+ else sampleNoise(graph, p.seed, flat, out, node);
68
+ return { values: Array.from(out, (v) => Math.round(v * 1e6) / 1e6) };
69
+ }
70
+ },
71
+ {
72
+ name: "noise.stats",
73
+ description: 'Samples a noise graph over a plane or a sphere and summarizes it: min, max, mean, stdDev, a 16-bin histogram, and the fraction below each threshold (sphere samples weighted by area, so "below 0" is the share of the surface). Check coverage numerically, e.g. how much of a planet is under sea level.',
74
+ params: defineSchema("noise/StatsParams", {
75
+ graph: graphField,
76
+ seed: t.u32({ default: 0 }),
77
+ domain: t.enum(["plane", "sphere"], { default: "plane" }),
78
+ size: t.f64({ description: "Plane side (default: 2 \xD7 extent, or 4)." }),
79
+ radius: t.f64({ description: "Sphere radius (default: extent, or 1)." }),
80
+ resolution: t.u32({ description: "Samples per side (per face for spheres).", max: 1024 }),
81
+ node: t.string({ description: "Summarize this node instead of the output." }),
82
+ thresholds: t.json({ description: "Values to report the fraction below, e.g. [0]." })
83
+ }),
84
+ handler: async ({ world }, p) => {
85
+ const graph = await resolveGraph(world, p.graph);
86
+ const domain = {
87
+ kind: p.domain,
88
+ // Absent numbers arrive as 0: only positive sizes count.
89
+ ...typeof p.size === "number" && p.size > 0 ? { size: p.size } : {},
90
+ ...typeof p.radius === "number" && p.radius > 0 ? { radius: p.radius } : {},
91
+ ...typeof p.resolution === "number" && p.resolution > 0 ? { resolution: p.resolution } : {}
92
+ };
93
+ return noiseStats(graph, p.seed, domain, {
94
+ node: p.node || void 0,
95
+ thresholds: Array.isArray(p.thresholds) ? p.thresholds : []
96
+ });
97
+ }
98
+ }
99
+ ];
100
+ const noiseGraphPreview = defineAssetPreview(
101
+ "NoiseGraph",
102
+ async (world, path, width, height, options) => {
103
+ const graph = await resolveGraph(world, path);
104
+ const o = options ?? {};
105
+ return previewNoise(graph, width, height, {
106
+ ...o.domain === "sphere" ? { domain: "sphere" } : {},
107
+ ...typeof o.seed === "number" ? { seed: o.seed } : {},
108
+ ...typeof o.size === "number" ? { size: Math.max(16, Math.min(2048, o.size)) } : {},
109
+ ...typeof o.node === "string" ? { node: o.node } : {},
110
+ ...typeof o.span === "number" ? { span: o.span } : {}
111
+ });
112
+ }
113
+ );
114
+ export {
115
+ MAX_SAMPLE_POINTS,
116
+ noiseGraphPreview,
117
+ noiseMethods,
118
+ resolveGraph
119
+ };
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The node table. Every node type is one entry here; the validator, the JSON Schema, the agent
3
+ * docs, the compiler, and the WGSL generator all read it. Adding a node is an entry plus its
4
+ * instruction in the Rust kernel (`crates/shard-noise`) and in `wgsl.ts`.
5
+ */
6
+ export type ParamDef = {
7
+ readonly type: 'number';
8
+ readonly default?: number;
9
+ readonly min?: number;
10
+ readonly max?: number;
11
+ readonly positive?: boolean;
12
+ readonly description: string;
13
+ } | {
14
+ readonly type: 'integer';
15
+ readonly default?: number;
16
+ readonly min?: number;
17
+ readonly max?: number;
18
+ readonly description: string;
19
+ } | {
20
+ readonly type: 'boolean';
21
+ readonly default?: boolean;
22
+ readonly description: string;
23
+ } | {
24
+ readonly type: 'enum';
25
+ readonly values: readonly (string | number)[];
26
+ readonly default?: string | number;
27
+ readonly description: string;
28
+ }
29
+ /** A node name, an inline node, or a number. */
30
+ | {
31
+ readonly type: 'input';
32
+ readonly description: string;
33
+ }
34
+ /** `[a, b]` */
35
+ | {
36
+ readonly type: 'pair';
37
+ readonly default?: readonly [number, number];
38
+ readonly description: string;
39
+ }
40
+ /** A number (all axes) or `[x, y, z]` / `[x, y, z, w]`. */
41
+ | {
42
+ readonly type: 'vec';
43
+ readonly default?: number;
44
+ readonly description: string;
45
+ }
46
+ /** `[[x, y], …]`, x increasing. */
47
+ | {
48
+ readonly type: 'points';
49
+ readonly description: string;
50
+ };
51
+ export type NodeCategory = 'source' | 'fractal' | 'combine' | 'shape' | 'domain' | 'constant';
52
+ export interface NodeDef {
53
+ readonly name: string;
54
+ readonly category: NodeCategory;
55
+ readonly description: string;
56
+ /** The output range, for docs. */
57
+ readonly range: string;
58
+ /**
59
+ * `params`: an object of parameters. `list`: an array of two or more inputs. `unary`: one input,
60
+ * written bare or as `{ "input": … }`. `value`: a number.
61
+ */
62
+ readonly form: 'params' | 'list' | 'unary' | 'value';
63
+ readonly params: Readonly<Record<string, ParamDef>>;
64
+ /** A typical use, shown in the schema and the docs. */
65
+ readonly example: unknown;
66
+ }
67
+ export declare const SOURCE_KINDS: readonly ['value', 'perlin', 'simplex', 'cellular'];
68
+ export type SourceKind = (typeof SOURCE_KINDS)[number];
69
+ export declare const CELL_DISTANCES: readonly ['euclidean', 'manhattan', 'chebyshev'];
70
+ export declare const CELL_RETURNS: readonly ['f1', 'f2', 'f2-f1', 'cell'];
71
+ /** Largest octave count a fractal may have (`noise/too-many-octaves`). */
72
+ export declare const MAX_OCTAVES = 16;
73
+ export declare const NODE_DEFS: readonly NodeDef[];
74
+ export declare function nodeDef(name: string): NodeDef | undefined;