@weasel-js/simulation 0.0.0-stage → 1.10.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 orochi235
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,48 @@
1
- # Temporary Holding Version
1
+ # @weasel-js/simulation
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Continuous N-body simulation — force-directed layout, physics-ish motion.
4
+
5
+ Part of [weasel](https://github.com/orochi235/weasel), a domain-agnostic 2D
6
+ scene-graph canvas kit for React. See the
7
+ [API reference](https://orochi235.github.io/weasel/api/).
8
+
9
+ ```sh
10
+ npm install @weasel-js/simulation
11
+ ```
12
+
13
+ `createSimulation` is the root entry and needs no React or DOM. `useSimulation`
14
+ is in `@weasel-js/simulation/react`.
15
+
16
+ `createSimulation` is the velocity-Verlet integrator and nothing else: `tick()`
17
+ is the only thing that moves a node, so a caller wanting the whole relaxation at
18
+ once — a layout pass, a pre-warm — runs it to settle and reads the answer.
19
+ `useSimulation` is that integrator on a frame loop.
20
+
21
+ Forces are pluggable functions matching **d3-force's protocol**, so d3-force's
22
+ bundled forces (`forceManyBody`, `forceLink`, `forceCenter`, …) work here
23
+ without translation. Don't invent a parallel force signature.
24
+
25
+ ## The kit doesn't write to your scene
26
+
27
+ `onTick(nodes)` fires after each integration step and **the consumer decides**
28
+ how, and whether, to write through to scene ops. The simulation holds its own
29
+ node array; it never touches the adapter.
30
+
31
+ That matters for undo: a simulation running at 60fps would otherwise produce 60
32
+ history entries per second. Typical wiring is to let the sim run free and commit
33
+ a single op when it settles (or on user action), not per tick.
34
+
35
+ ## Alpha and settling
36
+
37
+ The usual d3 convention: `alpha` decays each tick by `DEFAULT_ALPHA_DECAY`
38
+ until it drops below `DEFAULT_ALPHA_MIN`, at which point the layout is
39
+ considered settled and the loop can stop. `DEFAULT_VELOCITY_DECAY` is the
40
+ per-tick friction.
41
+
42
+ Reheating (bumping alpha back up) is how you restart motion after a user drags
43
+ a node — otherwise a settled simulation stays settled.
44
+
45
+ ## Related
46
+
47
+ `@weasel-js/d3` is the packaged integration if you want the d3 ecosystem wired
48
+ up for you rather than assembling forces by hand.
@@ -0,0 +1,106 @@
1
+ // src/types.ts
2
+ var DEFAULT_ALPHA_DECAY = 1 - Math.pow(1e-3, 1 / 300);
3
+ var DEFAULT_ALPHA_MIN = 1e-3;
4
+ var DEFAULT_VELOCITY_DECAY = 0.4;
5
+
6
+ // src/createSimulation.ts
7
+ function createSimulation(opts) {
8
+ let nodes = opts.nodes;
9
+ let forces = opts.forces ?? [];
10
+ let alpha = opts.alpha ?? 1;
11
+ let alphaMin = opts.alphaMin ?? DEFAULT_ALPHA_MIN;
12
+ let alphaDecay = opts.alphaDecay ?? DEFAULT_ALPHA_DECAY;
13
+ let alphaTarget = opts.alphaTarget ?? 0;
14
+ let velocityDecay = opts.velocityDecay ?? DEFAULT_VELOCITY_DECAY;
15
+ const random = opts.random ?? Math.random;
16
+ function prime(list) {
17
+ list.forEach((n, i) => {
18
+ n.index = i;
19
+ if (n.vx == null) n.vx = 0;
20
+ if (n.vy == null) n.vy = 0;
21
+ });
22
+ }
23
+ prime(nodes);
24
+ for (const f of forces) f.initialize?.(nodes, random);
25
+ function step() {
26
+ alpha += (alphaTarget - alpha) * alphaDecay;
27
+ for (const f of forces) f(alpha);
28
+ const friction = 1 - velocityDecay;
29
+ for (const n of nodes) {
30
+ if (n.fx == null) {
31
+ n.vx = (n.vx ?? 0) * friction;
32
+ if (!Number.isFinite(n.vx)) n.vx = 0;
33
+ n.x += n.vx;
34
+ if (!Number.isFinite(n.x)) n.x = 0;
35
+ } else {
36
+ n.x = n.fx;
37
+ n.vx = 0;
38
+ }
39
+ if (n.fy == null) {
40
+ n.vy = (n.vy ?? 0) * friction;
41
+ if (!Number.isFinite(n.vy)) n.vy = 0;
42
+ n.y += n.vy;
43
+ if (!Number.isFinite(n.y)) n.y = 0;
44
+ } else {
45
+ n.y = n.fy;
46
+ n.vy = 0;
47
+ }
48
+ }
49
+ }
50
+ const core = {
51
+ get nodes() {
52
+ return nodes;
53
+ },
54
+ setNodes(next) {
55
+ nodes = next;
56
+ prime(nodes);
57
+ for (const f of forces) f.initialize?.(nodes, random);
58
+ return core;
59
+ },
60
+ setForces(next) {
61
+ const old = forces;
62
+ forces = next;
63
+ for (const f of next) {
64
+ if (!old.includes(f)) f.initialize?.(nodes, random);
65
+ }
66
+ return core;
67
+ },
68
+ alpha(value) {
69
+ if (value === void 0) return alpha;
70
+ alpha = value;
71
+ return core;
72
+ },
73
+ alphaTarget(value) {
74
+ if (value === void 0) return alphaTarget;
75
+ alphaTarget = value;
76
+ return core;
77
+ },
78
+ alphaDecay(value) {
79
+ if (value === void 0) return alphaDecay;
80
+ alphaDecay = value;
81
+ return core;
82
+ },
83
+ alphaMin(value) {
84
+ if (value === void 0) return alphaMin;
85
+ alphaMin = value;
86
+ return core;
87
+ },
88
+ velocityDecay(value) {
89
+ if (value === void 0) return velocityDecay;
90
+ velocityDecay = value;
91
+ return core;
92
+ },
93
+ tick(iterations = 1) {
94
+ for (let i = 0; i < iterations; i++) step();
95
+ return core;
96
+ },
97
+ isSettled() {
98
+ return alpha < alphaMin && alphaTarget === 0;
99
+ }
100
+ };
101
+ return core;
102
+ }
103
+
104
+ export { DEFAULT_ALPHA_DECAY, DEFAULT_ALPHA_MIN, DEFAULT_VELOCITY_DECAY, createSimulation };
105
+ //# sourceMappingURL=chunk-RA6U67OH.js.map
106
+ //# sourceMappingURL=chunk-RA6U67OH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/createSimulation.ts"],"names":[],"mappings":"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","file":"chunk-RA6U67OH.js","sourcesContent":["/**\n * Matches d3-force's node shape exactly so d3-force's bundled forces work\n * without translation. Consumers extend this with additional fields.\n */\nexport interface SimulationNode {\n x: number;\n y: number;\n vx?: number;\n vy?: number;\n /** Pin x to this value when non-null. The integrator skips x/vx for the node. */\n fx?: number | null;\n fy?: number | null;\n /** Array index, written by `initialize`. Some forces (e.g. forceLink) rely on it. */\n index?: number;\n}\n\n/**\n * Force protocol — contract-compatible with d3-force. A force is a function\n * called once per tick with the current alpha; it mutates `vx`/`vy` on nodes.\n * Optional `initialize` lets the force compute setup (link indices, etc.).\n */\nexport interface SimulationForce<TNode extends SimulationNode = SimulationNode> {\n (alpha: number): void;\n initialize?(nodes: TNode[], random?: () => number): void;\n}\n\n/** Options for `useSimulation`: everything {@link SimulationOptions} carries,\n * plus the loop's own callbacks and clock. */\nexport interface UseSimulationOptions<TNode extends SimulationNode>\n extends SimulationOptions<TNode> {\n /** Fired after each integration step. */\n onTick?: (nodes: TNode[]) => void;\n /** Fired once when the sim settles (alpha < alphaMin && alphaTarget === 0). */\n onEnd?: () => void;\n\n // Test injection (mirrors useAnimator):\n /** Optional rAF injection. Defaults to window.requestAnimationFrame. */\n requestFrame?: (cb: (t: number) => void) => number;\n cancelFrame?: (handle: number) => void;\n}\n\n/**\n * A force simulation over a set of nodes, with no clock of its own.\n *\n * Each tick applies every force and then integrates velocities, scaled by\n * `alpha` — a temperature that decays toward `alphaTarget` so the layout\n * settles. Raise `alphaTarget` to reheat it.\n *\n * Nodes are mutated in place; the simulation holds the array, not a copy.\n * {@link Simulation} adds the frame loop.\n */\nexport interface SimulationCore<TNode extends SimulationNode> {\n readonly nodes: TNode[];\n\n setNodes(nodes: TNode[]): this;\n setForces(forces: SimulationForce<TNode>[]): this;\n\n alpha(): number;\n alpha(value: number): this;\n alphaTarget(): number;\n alphaTarget(value: number): this;\n alphaDecay(): number;\n alphaDecay(value: number): this;\n alphaMin(): number;\n alphaMin(value: number): this;\n velocityDecay(): number;\n velocityDecay(value: number): this;\n\n /** Run the tick body synchronously `iterations` times. Does NOT fire\n * onTick or onEnd (matches d3-force). */\n tick(iterations?: number): this;\n\n isSettled(): boolean;\n}\n\n/** A {@link SimulationCore} running on a frame loop. */\nexport interface Simulation<TNode extends SimulationNode> extends SimulationCore<TNode> {\n /** Resume the RAF loop. If alpha < alphaMin, reset alpha to 1. */\n restart(): this;\n /** Cancel the RAF loop. Does not change alpha. */\n stop(): this;\n}\n\n/** What {@link SimulationCore} needs: the nodes, the forces acting on them,\n * and the cooling schedule. {@link UseSimulationOptions} adds the loop's. */\nexport interface SimulationOptions<TNode extends SimulationNode> {\n /** Mutable array of nodes. Kit + forces mutate vx/vy/x/y in place. */\n nodes: TNode[];\n /** Forces applied per tick. Each may implement `initialize` for setup. */\n forces?: SimulationForce<TNode>[];\n\n /** Initial alpha. Default 1. */\n alpha?: number;\n /** Settle threshold. When alpha drops below and alphaTarget is 0, onEnd fires. Default 0.001. */\n alphaMin?: number;\n /** Per-tick decay rate toward alphaTarget. Default 1 - 0.001^(1/300) ≈ 0.0228. */\n alphaDecay?: number;\n /** Target alpha. Set above alphaMin to keep sim warm (e.g. during drag). Default 0. */\n alphaTarget?: number;\n /** Friction multiplier on velocity per tick: vx *= (1 - velocityDecay). Default 0.4. */\n velocityDecay?: number;\n /** Handed to each force's `initialize`. Default `Math.random`; pass a seeded\n * source to make a run reproducible. */\n random?: () => number;\n}\n\n/** Default alpha decay: such that alpha drops from 1 to alphaMin (0.001) in 300 ticks. */\nexport const DEFAULT_ALPHA_DECAY = 1 - Math.pow(0.001, 1 / 300);\n/** Alpha below which a cooling simulation is considered settled. */\nexport const DEFAULT_ALPHA_MIN = 0.001;\n/** Fraction of velocity shed each tick — friction. Higher settles sooner and\n * overshoots less. */\nexport const DEFAULT_VELOCITY_DECAY = 0.4;\n","/**\n * The integrator, with no clock attached.\n *\n * Velocity-Verlet over a pluggable force list, contract-compatible with\n * d3-force so d3's own bundled forces drop in untranslated. Nothing here\n * advances time: `tick()` is the only thing that moves a node, and the caller\n * decides how often to call it.\n */\nimport {\n DEFAULT_ALPHA_DECAY,\n DEFAULT_ALPHA_MIN,\n DEFAULT_VELOCITY_DECAY,\n type SimulationCore,\n type SimulationForce,\n type SimulationNode,\n type SimulationOptions,\n} from './types';\n\n/**\n * A d3-force-compatible simulation with no clock: nothing moves until `tick()`.\n * Nodes are mutated in place, and primed with `index`, `vx` and `vy` on creation.\n */\nexport function createSimulation<TNode extends SimulationNode>(\n opts: SimulationOptions<TNode>,\n): SimulationCore<TNode> {\n let nodes = opts.nodes;\n let forces = opts.forces ?? [];\n let alpha = opts.alpha ?? 1;\n let alphaMin = opts.alphaMin ?? DEFAULT_ALPHA_MIN;\n let alphaDecay = opts.alphaDecay ?? DEFAULT_ALPHA_DECAY;\n let alphaTarget = opts.alphaTarget ?? 0;\n let velocityDecay = opts.velocityDecay ?? DEFAULT_VELOCITY_DECAY;\n const random = opts.random ?? Math.random;\n\n /** `index` is what `forceLink` and friends address nodes by, and a d3 force\n * does `n.vx += …` without checking, so both must exist before the first\n * tick or the first tick produces NaN. */\n function prime(list: TNode[]): void {\n list.forEach((n, i) => {\n n.index = i;\n if (n.vx == null) n.vx = 0;\n if (n.vy == null) n.vy = 0;\n });\n }\n\n prime(nodes);\n for (const f of forces) f.initialize?.(nodes, random);\n\n function step(): void {\n alpha += (alphaTarget - alpha) * alphaDecay;\n for (const f of forces) f(alpha);\n\n const friction = 1 - velocityDecay;\n for (const n of nodes) {\n if (n.fx == null) {\n n.vx = (n.vx ?? 0) * friction;\n // A non-finite velocity — force overflow, or two nodes exactly\n // coincident — would reach the renderer as geometry and cost\n // pathological tessellation work without throwing anywhere.\n if (!Number.isFinite(n.vx)) n.vx = 0;\n n.x += n.vx;\n if (!Number.isFinite(n.x)) n.x = 0;\n } else {\n n.x = n.fx;\n n.vx = 0;\n }\n if (n.fy == null) {\n n.vy = (n.vy ?? 0) * friction;\n if (!Number.isFinite(n.vy)) n.vy = 0;\n n.y += n.vy;\n if (!Number.isFinite(n.y)) n.y = 0;\n } else {\n n.y = n.fy;\n n.vy = 0;\n }\n }\n }\n\n /* eslint-disable @typescript-eslint/no-explicit-any */\n const core: any = {\n get nodes() {\n return nodes;\n },\n setNodes(next: TNode[]) {\n nodes = next;\n prime(nodes);\n for (const f of forces) f.initialize?.(nodes, random);\n return core;\n },\n setForces(next: SimulationForce<TNode>[]) {\n const old = forces;\n forces = next;\n for (const f of next) {\n if (!old.includes(f)) f.initialize?.(nodes, random);\n }\n return core;\n },\n alpha(value?: number) {\n if (value === undefined) return alpha;\n alpha = value;\n return core;\n },\n alphaTarget(value?: number) {\n if (value === undefined) return alphaTarget;\n alphaTarget = value;\n return core;\n },\n alphaDecay(value?: number) {\n if (value === undefined) return alphaDecay;\n alphaDecay = value;\n return core;\n },\n alphaMin(value?: number) {\n if (value === undefined) return alphaMin;\n alphaMin = value;\n return core;\n },\n velocityDecay(value?: number) {\n if (value === undefined) return velocityDecay;\n velocityDecay = value;\n return core;\n },\n tick(iterations: number = 1) {\n for (let i = 0; i < iterations; i++) step();\n return core;\n },\n isSettled() {\n return alpha < alphaMin && alphaTarget === 0;\n },\n };\n /* eslint-enable @typescript-eslint/no-explicit-any */\n return core as SimulationCore<TNode>;\n}\n"]}
@@ -0,0 +1,19 @@
1
+ import { S as SimulationNode, a as SimulationOptions, b as SimulationCore } from './types-CmLrFukG.js';
2
+ export { D as DEFAULT_ALPHA_DECAY, c as DEFAULT_ALPHA_MIN, d as DEFAULT_VELOCITY_DECAY, e as Simulation, f as SimulationForce, U as UseSimulationOptions } from './types-CmLrFukG.js';
3
+
4
+ /**
5
+ * The integrator, with no clock attached.
6
+ *
7
+ * Velocity-Verlet over a pluggable force list, contract-compatible with
8
+ * d3-force so d3's own bundled forces drop in untranslated. Nothing here
9
+ * advances time: `tick()` is the only thing that moves a node, and the caller
10
+ * decides how often to call it.
11
+ */
12
+
13
+ /**
14
+ * A d3-force-compatible simulation with no clock: nothing moves until `tick()`.
15
+ * Nodes are mutated in place, and primed with `index`, `vx` and `vy` on creation.
16
+ */
17
+ declare function createSimulation<TNode extends SimulationNode>(opts: SimulationOptions<TNode>): SimulationCore<TNode>;
18
+
19
+ export { SimulationCore, SimulationNode, SimulationOptions, createSimulation };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { DEFAULT_ALPHA_DECAY, DEFAULT_ALPHA_MIN, DEFAULT_VELOCITY_DECAY, createSimulation } from './chunk-RA6U67OH.js';
2
+ //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -0,0 +1,14 @@
1
+ import { S as SimulationNode, U as UseSimulationOptions, e as Simulation } from './types-CmLrFukG.js';
2
+
3
+ /**
4
+ * Continuous N-body simulation primitive: `createSimulation`'s integrator on a
5
+ * frame loop.
6
+ *
7
+ * The kit is adapter-agnostic — `onTick(nodes)` fires after each integration
8
+ * step and the consumer decides how (and whether) to write through to scene
9
+ * state. Per-tick history bypass and settle-commit semantics are consumer
10
+ * concerns; the kit doesn't have an opinion.
11
+ */
12
+ declare function useSimulation<TNode extends SimulationNode>(opts: UseSimulationOptions<TNode>): Simulation<TNode>;
13
+
14
+ export { UseSimulationOptions, useSimulation };
package/dist/react.js ADDED
@@ -0,0 +1,115 @@
1
+ import { createSimulation } from './chunk-RA6U67OH.js';
2
+ import { useRef, useLayoutEffect, useMemo } from 'react';
3
+ import { useLatest, useVisibleRaf } from '@weasel-js/react';
4
+
5
+ function useSimulation(opts) {
6
+ const optsRef = useLatest(opts);
7
+ const coreRef = useRef(null);
8
+ coreRef.current ??= createSimulation(opts);
9
+ const core = coreRef.current;
10
+ const endedRef = useRef(false);
11
+ const mountedRef = useRef(true);
12
+ const frameLoop = useVisibleRaf(
13
+ () => {
14
+ rafTick();
15
+ },
16
+ {
17
+ requestFrame: opts.requestFrame,
18
+ cancelFrame: opts.cancelFrame
19
+ }
20
+ );
21
+ const stopRaf = () => {
22
+ frameLoop.cancel();
23
+ };
24
+ const rafTick = () => {
25
+ if (!mountedRef.current) return;
26
+ core.tick();
27
+ optsRef.current.onTick?.(core.nodes);
28
+ if (core.alpha() < core.alphaMin()) {
29
+ stopRaf();
30
+ if (!endedRef.current) {
31
+ endedRef.current = true;
32
+ optsRef.current.onEnd?.();
33
+ }
34
+ return;
35
+ }
36
+ frameLoop.request();
37
+ };
38
+ const startRaf = () => {
39
+ if (!mountedRef.current) return;
40
+ frameLoop.request();
41
+ };
42
+ useLayoutEffect(() => {
43
+ mountedRef.current = true;
44
+ frameLoop.request();
45
+ return () => {
46
+ mountedRef.current = false;
47
+ frameLoop.cancel();
48
+ };
49
+ }, [frameLoop]);
50
+ const handle = useMemo(() => {
51
+ const h = {
52
+ get nodes() {
53
+ return core.nodes;
54
+ },
55
+ setNodes(newNodes) {
56
+ core.setNodes(newNodes);
57
+ return h;
58
+ },
59
+ setForces(newForces) {
60
+ core.setForces(newForces);
61
+ return h;
62
+ },
63
+ alpha(value) {
64
+ if (value === void 0) return core.alpha();
65
+ core.alpha(value);
66
+ if (value >= core.alphaMin()) endedRef.current = false;
67
+ return h;
68
+ },
69
+ alphaTarget(value) {
70
+ if (value === void 0) return core.alphaTarget();
71
+ core.alphaTarget(value);
72
+ if (value > 0) endedRef.current = false;
73
+ return h;
74
+ },
75
+ alphaDecay(value) {
76
+ if (value === void 0) return core.alphaDecay();
77
+ core.alphaDecay(value);
78
+ return h;
79
+ },
80
+ alphaMin(value) {
81
+ if (value === void 0) return core.alphaMin();
82
+ core.alphaMin(value);
83
+ return h;
84
+ },
85
+ velocityDecay(value) {
86
+ if (value === void 0) return core.velocityDecay();
87
+ core.velocityDecay(value);
88
+ return h;
89
+ },
90
+ restart() {
91
+ if (core.alpha() < core.alphaMin()) core.alpha(1);
92
+ endedRef.current = false;
93
+ startRaf();
94
+ return h;
95
+ },
96
+ stop() {
97
+ stopRaf();
98
+ return h;
99
+ },
100
+ tick(iterations = 1) {
101
+ core.tick(iterations);
102
+ return h;
103
+ },
104
+ isSettled() {
105
+ return core.isSettled();
106
+ }
107
+ };
108
+ return h;
109
+ }, []);
110
+ return handle;
111
+ }
112
+
113
+ export { useSimulation };
114
+ //# sourceMappingURL=react.js.map
115
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/useSimulation.ts"],"names":[],"mappings":"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","file":"react.js","sourcesContent":["import { useLayoutEffect, useMemo, useRef } from 'react';\nimport { useLatest, useVisibleRaf } from '@weasel-js/react';\nimport { createSimulation } from './createSimulation';\nimport {\n type Simulation,\n type SimulationCore,\n type SimulationForce,\n type SimulationNode,\n type UseSimulationOptions,\n} from './types';\n\n/**\n * Continuous N-body simulation primitive: `createSimulation`'s integrator on a\n * frame loop.\n *\n * The kit is adapter-agnostic — `onTick(nodes)` fires after each integration\n * step and the consumer decides how (and whether) to write through to scene\n * state. Per-tick history bypass and settle-commit semantics are consumer\n * concerns; the kit doesn't have an opinion.\n */\nexport function useSimulation<TNode extends SimulationNode>(\n opts: UseSimulationOptions<TNode>,\n): Simulation<TNode> {\n const optsRef = useLatest(opts);\n\n const coreRef = useRef<SimulationCore<TNode> | null>(null);\n coreRef.current ??= createSimulation(opts);\n const core = coreRef.current;\n\n /** Whether onEnd has fired since the last energization (restart / alpha set / target raise). */\n const endedRef = useRef(false);\n const mountedRef = useRef(true);\n\n // The loop runs behind the visibility gate: a settling simulation stops\n // integrating on a page nobody is looking at, and picks up where it left off.\n // Its step is fixed rather than time-based, so there is no clock to rebase.\n // The gate defaults an absent clock to `requestAnimationFrame`, so an\n // injected one passes straight through rather than being defaulted twice.\n const frameLoop = useVisibleRaf(\n () => { rafTick(); },\n {\n requestFrame: opts.requestFrame,\n cancelFrame: opts.cancelFrame,\n },\n );\n\n const stopRaf = (): void => { frameLoop.cancel(); };\n\n const rafTick = (): void => {\n if (!mountedRef.current) return;\n\n core.tick();\n optsRef.current.onTick?.(core.nodes);\n\n if (core.alpha() < core.alphaMin()) {\n stopRaf();\n if (!endedRef.current) {\n endedRef.current = true;\n optsRef.current.onEnd?.();\n }\n return;\n }\n\n frameLoop.request();\n };\n\n const startRaf = (): void => {\n if (!mountedRef.current) return;\n frameLoop.request();\n };\n\n // Starts on every mount, StrictMode's simulated remount included, and in a\n // layout effect so a host's own layout effects can already `stop()` it.\n // `createSimulation` has already primed the nodes.\n useLayoutEffect(() => {\n mountedRef.current = true;\n frameLoop.request();\n return () => {\n mountedRef.current = false;\n frameLoop.cancel();\n };\n }, [frameLoop]);\n\n // Stable handle. All methods read through the core, so a fixed identity is safe.\n const handle = useMemo<Simulation<TNode>>(() => {\n /* eslint-disable @typescript-eslint/no-explicit-any */\n const h: any = {\n get nodes() {\n return core.nodes;\n },\n setNodes(newNodes: TNode[]) {\n core.setNodes(newNodes);\n return h;\n },\n setForces(newForces: SimulationForce<TNode>[]) {\n core.setForces(newForces);\n return h;\n },\n alpha(value?: number) {\n if (value === undefined) return core.alpha();\n core.alpha(value);\n if (value >= core.alphaMin()) endedRef.current = false;\n return h;\n },\n alphaTarget(value?: number) {\n if (value === undefined) return core.alphaTarget();\n core.alphaTarget(value);\n if (value > 0) endedRef.current = false;\n return h;\n },\n alphaDecay(value?: number) {\n if (value === undefined) return core.alphaDecay();\n core.alphaDecay(value);\n return h;\n },\n alphaMin(value?: number) {\n if (value === undefined) return core.alphaMin();\n core.alphaMin(value);\n return h;\n },\n velocityDecay(value?: number) {\n if (value === undefined) return core.velocityDecay();\n core.velocityDecay(value);\n return h;\n },\n restart() {\n if (core.alpha() < core.alphaMin()) core.alpha(1);\n endedRef.current = false;\n startRaf();\n return h;\n },\n stop() {\n stopRaf();\n return h;\n },\n tick(iterations: number = 1) {\n core.tick(iterations);\n return h;\n },\n isSettled() {\n return core.isSettled();\n },\n };\n return h as Simulation<TNode>;\n /* eslint-enable @typescript-eslint/no-explicit-any */\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return handle;\n}\n"]}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Matches d3-force's node shape exactly so d3-force's bundled forces work
3
+ * without translation. Consumers extend this with additional fields.
4
+ */
5
+ interface SimulationNode {
6
+ x: number;
7
+ y: number;
8
+ vx?: number;
9
+ vy?: number;
10
+ /** Pin x to this value when non-null. The integrator skips x/vx for the node. */
11
+ fx?: number | null;
12
+ fy?: number | null;
13
+ /** Array index, written by `initialize`. Some forces (e.g. forceLink) rely on it. */
14
+ index?: number;
15
+ }
16
+ /**
17
+ * Force protocol — contract-compatible with d3-force. A force is a function
18
+ * called once per tick with the current alpha; it mutates `vx`/`vy` on nodes.
19
+ * Optional `initialize` lets the force compute setup (link indices, etc.).
20
+ */
21
+ interface SimulationForce<TNode extends SimulationNode = SimulationNode> {
22
+ (alpha: number): void;
23
+ initialize?(nodes: TNode[], random?: () => number): void;
24
+ }
25
+ /** Options for `useSimulation`: everything {@link SimulationOptions} carries,
26
+ * plus the loop's own callbacks and clock. */
27
+ interface UseSimulationOptions<TNode extends SimulationNode> extends SimulationOptions<TNode> {
28
+ /** Fired after each integration step. */
29
+ onTick?: (nodes: TNode[]) => void;
30
+ /** Fired once when the sim settles (alpha < alphaMin && alphaTarget === 0). */
31
+ onEnd?: () => void;
32
+ /** Optional rAF injection. Defaults to window.requestAnimationFrame. */
33
+ requestFrame?: (cb: (t: number) => void) => number;
34
+ cancelFrame?: (handle: number) => void;
35
+ }
36
+ /**
37
+ * A force simulation over a set of nodes, with no clock of its own.
38
+ *
39
+ * Each tick applies every force and then integrates velocities, scaled by
40
+ * `alpha` — a temperature that decays toward `alphaTarget` so the layout
41
+ * settles. Raise `alphaTarget` to reheat it.
42
+ *
43
+ * Nodes are mutated in place; the simulation holds the array, not a copy.
44
+ * {@link Simulation} adds the frame loop.
45
+ */
46
+ interface SimulationCore<TNode extends SimulationNode> {
47
+ readonly nodes: TNode[];
48
+ setNodes(nodes: TNode[]): this;
49
+ setForces(forces: SimulationForce<TNode>[]): this;
50
+ alpha(): number;
51
+ alpha(value: number): this;
52
+ alphaTarget(): number;
53
+ alphaTarget(value: number): this;
54
+ alphaDecay(): number;
55
+ alphaDecay(value: number): this;
56
+ alphaMin(): number;
57
+ alphaMin(value: number): this;
58
+ velocityDecay(): number;
59
+ velocityDecay(value: number): this;
60
+ /** Run the tick body synchronously `iterations` times. Does NOT fire
61
+ * onTick or onEnd (matches d3-force). */
62
+ tick(iterations?: number): this;
63
+ isSettled(): boolean;
64
+ }
65
+ /** A {@link SimulationCore} running on a frame loop. */
66
+ interface Simulation<TNode extends SimulationNode> extends SimulationCore<TNode> {
67
+ /** Resume the RAF loop. If alpha < alphaMin, reset alpha to 1. */
68
+ restart(): this;
69
+ /** Cancel the RAF loop. Does not change alpha. */
70
+ stop(): this;
71
+ }
72
+ /** What {@link SimulationCore} needs: the nodes, the forces acting on them,
73
+ * and the cooling schedule. {@link UseSimulationOptions} adds the loop's. */
74
+ interface SimulationOptions<TNode extends SimulationNode> {
75
+ /** Mutable array of nodes. Kit + forces mutate vx/vy/x/y in place. */
76
+ nodes: TNode[];
77
+ /** Forces applied per tick. Each may implement `initialize` for setup. */
78
+ forces?: SimulationForce<TNode>[];
79
+ /** Initial alpha. Default 1. */
80
+ alpha?: number;
81
+ /** Settle threshold. When alpha drops below and alphaTarget is 0, onEnd fires. Default 0.001. */
82
+ alphaMin?: number;
83
+ /** Per-tick decay rate toward alphaTarget. Default 1 - 0.001^(1/300) ≈ 0.0228. */
84
+ alphaDecay?: number;
85
+ /** Target alpha. Set above alphaMin to keep sim warm (e.g. during drag). Default 0. */
86
+ alphaTarget?: number;
87
+ /** Friction multiplier on velocity per tick: vx *= (1 - velocityDecay). Default 0.4. */
88
+ velocityDecay?: number;
89
+ /** Handed to each force's `initialize`. Default `Math.random`; pass a seeded
90
+ * source to make a run reproducible. */
91
+ random?: () => number;
92
+ }
93
+ /** Default alpha decay: such that alpha drops from 1 to alphaMin (0.001) in 300 ticks. */
94
+ declare const DEFAULT_ALPHA_DECAY: number;
95
+ /** Alpha below which a cooling simulation is considered settled. */
96
+ declare const DEFAULT_ALPHA_MIN = 0.001;
97
+ /** Fraction of velocity shed each tick — friction. Higher settles sooner and
98
+ * overshoots less. */
99
+ declare const DEFAULT_VELOCITY_DECAY = 0.4;
100
+
101
+ export { DEFAULT_ALPHA_DECAY as D, type SimulationNode as S, type UseSimulationOptions as U, type SimulationOptions as a, type SimulationCore as b, DEFAULT_ALPHA_MIN as c, DEFAULT_VELOCITY_DECAY as d, type Simulation as e, type SimulationForce as f };
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "@weasel-js/simulation",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.10.0",
4
+ "description": "A velocity-Verlet N-body integrator that takes d3-force's forces, and a hook that runs it on a frame loop. The root entry has no React and no DOM.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "main": "./dist/index.js",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ },
16
+ "./react": {
17
+ "import": "./dist/react.js",
18
+ "types": "./dist/react.d.ts"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
22
+ "peerDependencies": {
23
+ "react": ">=18"
24
+ },
25
+ "peerDependenciesMeta": {
26
+ "react": { "optional": true }
27
+ },
28
+ "dependencies": {
29
+ "@weasel-js/react": "1.10.0"
30
+ },
31
+ "author": "orochi235",
32
+ "homepage": "https://orochi235.github.io/weasel/",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/orochi235/weasel.git",
36
+ "directory": "packages/simulation"
37
+ },
38
+ "bugs": {
39
+ "url": "https://github.com/orochi235/weasel/issues"
40
+ },
41
+ "engines": {
42
+ "node": ">=22"
43
+ },
44
+ "files": [
45
+ "dist",
46
+ "README.md",
47
+ "LICENSE"
48
+ ],
49
+ "scripts": {
50
+ "build": "tsup"
51
+ },
52
+ "publishConfig": {
53
+ "access": "public",
54
+ "provenance": true
55
+ }
56
+ }