Skip to content

Commit 1a55eb9

Browse files
Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts: # engraphis/classic_assets/dashboard.js # engraphis/dashboard_assets/engraphis-graph-every-worker.js # engraphis/dashboard_assets/engraphis-graph.js # engraphis/dashboard_assets/index.html # engraphis/dashboard_assets/ledger.js # engraphis/static/dashboard.js # tests/e2e/graph-engine.spec.js # tests/e2e/ledger.spec.js # tests/test_graph_engine_asset.py
2 parents 566a041 + c0fd92f commit 1a55eb9

15 files changed

Lines changed: 17422 additions & 15474 deletions

engraphis/classic_assets/dashboard.js

Lines changed: 103 additions & 103 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@
2222
const MAX_CENTROID_GROUPS = 512;
2323

2424
let model = null;
25-
let settings = { repel: 48, link: 16, gravity: 48 };
25+
let settings = {
26+
repel: 48, link: 16, gravity: 48,
27+
gravitationalConstant: 1, blackHoleMass: 1, localGravitationalConstant: 1,
28+
damping: 1, springStiffness: 1,
29+
};
2630
let generation = 0;
2731

2832
function post(message) { self.postMessage(message); }
@@ -217,6 +221,11 @@
217221
springs run weak — they are visual routes between districts, not licence to drag
218222
the districts into one another over the settle passes. */
219223
const scaledSpacing = SPACING * MAP_SCALE;
224+
const spring = Number.isFinite(Number(settings.springStiffness))
225+
? Math.max(0, Math.min(100 / 32, Number(settings.springStiffness))) : 1;
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;
220229
/* Bumped from *1.6 to *2.4 — the link-distance slider now produces 50% more spring
221230
rest-length change per slider unit, so the upper half of the slider is meaningfully
222231
more responsive. */
@@ -227,9 +236,12 @@
227236
const dist = Math.sqrt(ddx * ddx + ddy * ddy) || 0.0001;
228237
const crossCommunity =
229238
model.communities[a] !== model.communities[b] ? 0.02 : 0.07;
230-
const force = (dist - rest) / dist * crossCommunity;
231-
dx[a] -= ddx * force; dy[a] -= ddy * force;
232-
dx[b] += ddx * force; dy[b] += ddy * force;
239+
const force = (dist - rest) / dist * crossCommunity * springScale;
240+
/* `force` is positive when the pair is beyond rest and negative when it overlaps.
241+
Apply equal-and-opposite corrections along a→b so positive force attracts the pair
242+
and negative force separates it. */
243+
dx[a] += ddx * force; dy[a] += ddy * force;
244+
dx[b] -= ddx * force; dy[b] -= ddy * force;
233245
}
234246

235247
/* Local repulsion through a spatial hash with a per-node visit cap keeps each pass O(n)
@@ -246,7 +258,16 @@
246258
const minDist2 = minDist * minDist;
247259
/* Bumped from /48 to /24 — the Every-node engine now produces 100% more repulsion per
248260
slider unit, so the upper half of the repel slider is meaningfully more responsive. */
249-
const push = Number(settings.repel) / 24;
261+
/* The dashboard maps the 0..200 Cluster cohesion slider to localGravitationalConstant
262+
0..4, so clamping at 2 left the entire upper half of the control inert: it is this
263+
worker's only consumer of the setting (PR #177 review thread at this site). Accept
264+
the full emitted range and floor the inverted push coefficient at zero so a high
265+
cohesion cannot turn the collision-style push into an attraction. */
266+
const cohesion = Number.isFinite(Number(settings.localGravitationalConstant))
267+
? Math.max(0, Math.min(4, Number(settings.localGravitationalConstant))) : 1;
268+
/* Cluster cohesion strengthens the attractive spring network above. Invert its influence
269+
on the collision-style push so a higher cohesion setting does not spread clusters apart. */
270+
const push = Number(settings.repel) / 24 * Math.max(0, 1.5 - 0.5 * cohesion);
250271
for (let index = 0; index < count; index += 1) {
251272
const gx = Math.floor(pos[index * 2] / cell), gy = Math.floor(pos[index * 2 + 1] / cell);
252273
let checked = 0;
@@ -320,16 +341,28 @@
320341
}
321342

322343
/* Bumped from 0.0015 to 0.0033 — combined with the base gravity 25% bump and the
323-
linear (no-sqrt) mass path, the Every-node worker now pulls nodes toward the centre
324-
~50% harder at every slider position than the previous 0.0022 calibration. */
325-
const gravity = Number(settings.gravity) / 48 * 0.0033;
344+
linear (no-sqrt) mass path, the Every-node worker pulls nodes toward the centre
345+
~50% harder at every slider position than the previous 0.0022 calibration.
346+
The dashboard emits Core attraction over 0..4 (raw/50) and Core mass up to 4.4;
347+
clamping at 2 left the upper half of those controls inert (PR #177 review thread
348+
at this site), so the full emitted ranges are consumed. */
349+
const coreAttraction = Number.isFinite(Number(settings.gravitationalConstant))
350+
? Math.max(0, Math.min(4, Number(settings.gravitationalConstant))) : 1;
351+
const coreMass = Number.isFinite(Number(settings.blackHoleMass))
352+
? Math.max(0, Math.min(4.4, Number(settings.blackHoleMass))) : 1;
353+
const gravity = Number(settings.gravity) / 48 * 0.0033 * coreAttraction * coreMass;
326354
for (let index = 0; index < count; index += 1) {
327355
dx[index] += (cx - pos[index * 2]) * gravity;
328356
dy[index] += (cy - pos[index * 2 + 1]) * gravity;
329357
}
330358

331359
/* A tight per-pass step cap keeps the settle from smearing district boundaries. */
332-
const damp = 0.8, maxStep = SPACING * MAP_SCALE * 0.7;
360+
const resistance = Number.isFinite(Number(settings.damping))
361+
? Math.max(0, Math.min(15, Number(settings.damping))) : 1;
362+
/* Keep the full 0..15 control range responsive: the reciprocal curve reaches 0.2
363+
at resistance 13, so a 0.2 floor would make the final two slider units inert. */
364+
const damp = Math.max(0.15, Math.min(0.95, 0.8 / (0.75 + 0.25 * resistance)));
365+
const maxStep = SPACING * MAP_SCALE * 0.7;
333366
for (let index = 0; index < count; index += 1) {
334367
let vx = dx[index] * damp, vy = dy[index] * damp;
335368
const speed = Math.sqrt(vx * vx + vy * vy);
@@ -417,6 +450,15 @@
417450
repel: Number.isFinite(Number(next.repel)) ? Number(next.repel) : settings.repel,
418451
link: Number.isFinite(Number(next.link)) ? Number(next.link) : settings.link,
419452
gravity: Number.isFinite(Number(next.gravity)) ? Number(next.gravity) : settings.gravity,
453+
gravitationalConstant: Number.isFinite(Number(next.gravitationalConstant))
454+
? Number(next.gravitationalConstant) : settings.gravitationalConstant,
455+
blackHoleMass: Number.isFinite(Number(next.blackHoleMass))
456+
? Number(next.blackHoleMass) : settings.blackHoleMass,
457+
localGravitationalConstant: Number.isFinite(Number(next.localGravitationalConstant))
458+
? Number(next.localGravitationalConstant) : settings.localGravitationalConstant,
459+
damping: Number.isFinite(Number(next.damping)) ? Number(next.damping) : settings.damping,
460+
springStiffness: Number.isFinite(Number(next.springStiffness))
461+
? Number(next.springStiffness) : settings.springStiffness,
420462
};
421463
if (data.relayout && model) {
422464
generation += 1;

engraphis/dashboard_assets/engraphis-graph-every.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
(function () {
99
'use strict';
1010

11-
const WORKER_URL = '/v2-assets/engraphis-graph-every-worker.js?v=20260823-every-19';
11+
const WORKER_URL = '/v2-assets/engraphis-graph-every-worker.js?v=20260830-spacetime-controls-20';
1212
const MAX_NODES = 20000;
1313
const MAX_LINKS = 200000;
1414
const LABEL_MAX = 220;
@@ -151,7 +151,9 @@
151151
totalLinks: 0, edgeVertexCount: 0,
152152
camera: { x: 0, y: 0, scale: 1 }, baseScale: 1, width: 1, height: 1, dpr: 1,
153153
styleName: opts.style || 'cyber', colorBy: 'community', typeColors: {}, themeColors: {}, palette: 'theme',
154-
settings: { labels: true, flow: false, flowSpeed: 45, frozen: false, mode: 'communities', repel: 48, link: 16, gravity: 48, font: 12, size: 3, linkw: 0.72 },
154+
settings: { labels: true, flow: false, flowSpeed: 45, frozen: false, mode: 'communities', repel: 48, link: 16, gravity: 48, font: 12, size: 3, linkw: 0.72,
155+
gravitationalConstant: 1, blackHoleMass: 1, localGravitationalConstant: 1,
156+
damping: 1, springStiffness: 1 },
155157
sizeBy: 'degree', bridges: true, ghosts: true,
156158
scope: { minDegree: 0, showUnlinked: true, depth: 2 },
157159
collapse: false, collapsed: false,
@@ -1376,7 +1378,10 @@
13761378
const patch = value || {};
13771379
state.settings = { ...state.settings, ...patch };
13781380
state.flowPaintAt = 0;
1379-
const relayout = Object.keys(patch).some(key => ['mode', 'repel', 'link', 'gravity'].includes(key));
1381+
const relayout = Object.keys(patch).some(key => [
1382+
'mode', 'repel', 'link', 'gravity', 'gravitationalConstant', 'blackHoleMass',
1383+
'localGravitationalConstant', 'damping', 'springStiffness',
1384+
].includes(key));
13801385
postSettings(relayout);
13811386
uploadNodeMeta();
13821387
camera();

0 commit comments

Comments
 (0)