Skip to content

Commit 8d0bc2e

Browse files
fix(graph): honor zero Every-node spring stiffness
1 parent d5f4244 commit 8d0bc2e

2 files changed

Lines changed: 35 additions & 1 deletion

File tree

engraphis/dashboard_assets/engraphis-graph-every-worker.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,9 @@
223223
const scaledSpacing = SPACING * MAP_SCALE;
224224
const spring = Number.isFinite(Number(settings.springStiffness))
225225
? Math.max(0, Math.min(100 / 32, Number(settings.springStiffness))) : 1;
226-
const springScale = 0.35 + 0.65 * spring;
226+
// springStiffness is already a normalized multiplier from the dashboard. Preserve its
227+
// zero endpoint so the Link spring control can actually disable pair attraction.
228+
const springScale = spring;
227229
const rest = Math.max(scaledSpacing * 1.9, Number(settings.link) * 1.6 * (MAP_SCALE * 0.55));
228230
for (let edge = 0; edge < model.totalLinks; edge += 1) {
229231
const a = model.sources[edge], b = model.targets[edge];

tests/test_graph_every_asset.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,38 @@ def test_worker_settling_resistance_keeps_high_end_distinct() -> None:
203203
assert samples["14"] != samples["15"]
204204

205205

206+
def test_worker_honors_zero_link_spring_stiffness() -> None:
207+
"""A zero Link spring value must remove pair attraction, not leave a residual floor."""
208+
script = """
209+
const nodes = [
210+
{ id: 'a', community_id: 'c' },
211+
{ id: 'b', community_id: 'c' },
212+
];
213+
send({ type: 'prepare', payload: { nodes, links: [{ source: 'a', target: 'b' }] } });
214+
const waitForFit = (start, callback) => {
215+
const tick = () => {
216+
const fit = messages.slice(start).find(item => item.type === 'layout' && item.fit === true);
217+
if (fit) return callback(fit.positions);
218+
setTimeout(tick, 10);
219+
};
220+
tick();
221+
};
222+
setTimeout(() => {
223+
const seeded = latest('preview').positions.slice();
224+
const start = messages.length;
225+
send({ type: 'settings', settings: {
226+
repel: 0, gravity: 0, springStiffness: 0, damping: 1,
227+
}, relayout: true, fit: true });
228+
waitForFit(start, positions => {
229+
const delta = Math.max(...positions.map((value, index) => Math.abs(value - seeded[index])));
230+
console.log(JSON.stringify({ delta }));
231+
});
232+
}, 50);
233+
"""
234+
report = _run_worker(script)
235+
assert report["delta"] == 0
236+
237+
206238
def test_renderer_is_webgl2_only_without_live_simulation_or_canvas_fallback() -> None:
207239
renderer = RENDERER.read_text(encoding="utf-8")
208240
assert "getContext('webgl2'" in renderer

0 commit comments

Comments
 (0)