Skip to content

Commit c0fd92f

Browse files
fix(graph): route the four spacetime sliders into d3 forces in non-galaxy mode (#177)
* fix(graph): route the four spacetime sliders into d3 forces in non-galaxy mode The Galactic gravity, Black hole mass, Local solar gravity, and Space damping sliders previously only fed the galaxy-mode integrator. In the default overview/communities/compact views a settled d3 layout had already cooled, so a force-only re-render was invisible and the user-facing effect of the sliders was "nothing happens when I drag it". This change wires each spacetime slider into the d3-force installation so the layout visibly responds in every non-galaxy mode: - gravitationalConstant (0..200) scales the charge (node repulsion) strength. Default 100 -> 1.0x; max 200 -> 2.0x; min 0 -> 0x. - blackHoleMass (0..500) scales the existing gravity-driven centering strength via the same multiplier used by the galaxy-mode integrator (linear above the 160 baseline, value/160 below). Default 160 -> 1.0x; 500 -> 7.8x; 80 -> 0.5x. - localGravitationalConstant (0..200) scales the link spring strength. The existing d3 path used 1/(min degree) as the base; we now multiply by the same scalar so the slider tightens or loosens the visible link force. - damping (1..15) maps to fg.velocityDecay. At 1 the layout is bouncy (decay 0.05); at 15 it settles quickly (decay 0.85). Bounded 0.05..0.85 so the extreme ends stay usable. Two small helpers (clamp, blackHoleMassMultiplier) are inlined next to the d3-force install path; the existing helper in ledger.js is unchanged. A new regression test test_spacetime_sliders_reach_d3_forces_in_non_galaxy_mode instruments fg.d3Force / fg.velocityDecay to confirm each spacetime setting lands on the d3 wire. Fixes the user-reported "Galactic gravity / Black hole mass / Local solar gravity / Space damping sliders STILL NOT WORKING CORRECTLY" complaint. * fix(graph): preserve default force strength when the spacetime sliders are untouched The PR #177 commit 434a94c introduced gravityMultiplier (gravitational constant / 100) and localMultiplier (local gravitational constant / 100) and applied them as multipliers on the d3 charge and link strengths. At the default (untouched-slider) state, both sliders read 0, so both multipliers read 0, and the d3 charge + link forces were zeroed. The fix: the multiplier fallbacks default to 100 (the slider no-op center) instead of 0, and the `|| 1` after clamp() collapses a clamped-0 into a no-op 1.0x multiplier, preserving the original force strength when the slider is untouched. Moving the slider to either end still produces the bounded 0.0x..2.0x range intended by the original commit. Galaxy mode is unaffected: it has its own d3Force install path that reads the four settings separately and is not subject to the non-galaxy applyForces block. Verified locally: pytest tests/test_graph_engine_asset.py = 227/227. ruff clean. The Playwright accessibility smoke regression should clear on the next CI run for this branch. * fix(review): address PR #177 codex reviews (round 6) — consume normalized multipliers, preserve zero endpoints, size-aware damping, d3VelocityDecay, black-hole mass in every non-galaxy preset Six open codex review threads addressed in this commit. P1 "Consume the normalized spacetime multipliers directly" (engraphis-graph.js:8000) ledger.js::graphSpacetimeEngineSettings() already normalizes visible 100 / 160 / 100 to 2.0 / 1.0 / 2.0 at the engine. The previous d700bba intermediate fix divided those by 100 and fell back to 1, which collapsed the default gravity to 0.02x and silently overrode user-set zeros. Consume the normalized values directly as the multipliers and use Number.isFinite fallbacks so a user-set 0 stays 0 while a *missing* value still falls back to 1.0x to keep the layout alive when the engine is constructed without the dashboard wiring. P1 "Apply black-hole mass to every non-galaxy preset" (engraphis-graph.js:8095, 8103, 8081) massMultiplier was only applied in the `communities` and `radial` branches. The `compact`, `original`, and `constellation` branches ignored the slider, so three of the five non-galaxy presets left the black-hole mass slider inert. Multiply the centering in `compact`/ `original` and the x/y anchor strength in `constellation` by massMultiplier. The full mode test that asserted the old gravity-only centering is updated to the new contract. P2 "Preserve the zero-friction end of the damping control" (engraphis-graph.js:8026) The previous clamp(damping, 1, 15) mapped every value from 0 to 1 to the same d3 velocityDecay, so moving the slider from 1 down to 0 was inert. Use the full 0..15 range and linearly interpolate between the 0.05 floor, the size-aware baseline at the default (1), and the 0.85 ceiling at 15. The full range is now meaningful; the manual slider harness confirms damping=0 reaches the 0.05 floor and damping=15 reaches the 0.85 ceiling. P1 "Retain size-aware decay when applying damping" (engraphis-graph.js:9388) state.settings.damping is always a finite value, so the slider path replaced the size-aware 0.38/0.45 baseline every render — the test_simulation_time_is_bounded_on_a_large_graph contract was silently violated. The slider is now a *multiplier* on the size-aware baseline, so the default (1) keeps the original settling behaviour and the 0.38/0.45 large-vs-small distinction survives. The fallback path in render() now only fires when the dashboard never supplied a damping value, so the user-set value is never clobbered. P1 "Use the actual d3VelocityDecay accessor" (engraphis-graph.js:8026) force-graph exposes velocityDecay through `fg.d3VelocityDecay`, not `fg.velocityDecay`. The previous code's `if (fg.velocityDecay)` check was always false on the real dashboard (the vendored force-graph.min.js has no velocityDecay method) and the slider mapping never executed. Switch to fg.d3VelocityDecay. The test_spacetime_sliders_reach_d3_forces_in_non_galaxy_mode test is updated to read store.d3VelocityDecay (the real API) instead of store.velocityDecay, and to assert the 0..15 range reaches both endpoints (0.05 and 0.85). P2 "Preserve the zero endpoints of both gravity controls" (engraphis-graph.js:8005, 8015) The d700bba `|| 1` fallback replaced a user-set 0 with the neutral 1.0x multiplier, so dragging the slider to its HTML-supported minimum of 0 was indistinguishable from the baseline. The new `Number.isFinite` guard treats only missing/non-finite values as fallback, not the legitimate user-set 0. The gravityMultiplier and localMultiplier now follow the same nullish semantics as blackHoleMass. Local verification - 227/227 tests/test_graph_engine_asset.py pass - The manual_slider_test.js harness reports 8 alive, 0 dead, 0 skipped - All 8 sliders produce a non-zero centroid shift and the engine settings differ between the low and high probe values * fix(review): address the actual slider flicker — rebalance the spacetime multiplier response so the visible default is a true 1.0x no-op and the full slider range produces a useful 0..2 multiplier The previous round-6 fix consumed the ledger.js normalization directly but did not correct the underlying normalization. ledger.js was dividing the visible slider value by 50 for the two gravity sliders, which sent 2.0 to the engine at the visible default of 100 and clamped the entire upper half of the slider (visible 100..200) to the 2.0x ceiling. The user-visible symptom: the slider felt "alive" only at the extremes; the upper quarter was indistinguishable from the default and the lower quarter collapsed the force to zero. This commit fixes the normalization so the engine receives a clean 0..2 range with the default at 1.0x. ledger.js::graphSpacetimeEngineSettings() (line 2515) - Change `gravitationalConstant: controls.gravitationalConstant / 50` to `gravitationalConstant: controls.gravitationalConstant / 100`. At the visible default 100 the engine now receives 1.0 (was 2.0); at visible 50 it receives 0.5 (was 0.0); at visible 200 it receives 2.0 (was 6.0, clamped to 2.0 by the engine). - Same change for `localGravitationalConstant`. ledger.js::graphBlackHoleMassMultiplier() (line 2592) - The previous formula `value/160` for the lower half and `1 + (value-160)/100` for the upper half sent 0.125 at the slider's HTML minimum (20) and 4.4 at its maximum (500) — a 35x range that made the slider feel "alive" only at the extremes. Replace with a piecewise linear that maps visible 20..500 to 0.0..2.0 with the default (160) at 1.0. engraphis-graph.js::applyForces() (line 8006) - The engine was calling `blackHoleMassMultiplier(bhmRaw)` again, which was designed for the old 0..500 range and always clamped the new normalized 0..2 value to the 0.25 floor. Use `bhmRaw` directly as the multiplier (clamped to 0..2) so the dashboard's normalization is the single source of truth. engraphis/dashboard_assets/index.html (line 711) - Bump the ledger.js cache-bust to force a fresh load. engraphis/dashboard_assets/ledger.js (line 460) - Bump the engraphis-graph.js cache-bust to force a fresh load. tests/test_graph_engine_asset.py - Update the full-mode centering assertion: with the new normalization the engine receives massMultiplier=1.0 at the visible default, so the centering is the full 0.98 unchanged from the pre-multiplier era. Local verification - 227/227 tests/test_graph_engine_asset.py pass - manual_slider_test.js reports 8 alive, 0 dead, 0 skipped - The engine now receives gravitationalConstant 0..2 (was 0..8), localGravitationalConstant 0..2 (was 0..8), and blackHoleMass 0..2 (was 0.125..7.8) across the visible slider range - The visible default (100 / 160) produces a 1.0x multiplier at the engine, so the untouched-slider state is a true no-op - Centroid shifts are non-zero for all three spacetime sliders * fix(review): bypass the 2x response gain for the spacetime sliders so the visible slider position maps linearly to the engine value The previous round-7 fix corrected the /100 vs /50 normalization so the engine receives a clean 0..2 range, but the `graphSliderResponseValue` function in ledger.js still applied a 2x response gain centred on the slider's fallback. The 2x gain maps: visible 0 -> engine 0 (clipped at min) visible 25 -> engine 0 (clipped at min) visible 50 -> engine 0 (clipped at min — expanded = 0) visible 75 -> engine 0.5 visible 100 -> engine 1.0 (default) visible 125 -> engine 1.5 visible 150 -> engine 2.0 (clipped at max) visible 200 -> engine 2.0 (clipped at max) So the lower quarter of the slider (0..50) all maps to 0, and the upper quarter (150..200) all maps to 2.0. The user couldn't tell the difference between slider=30 and slider=50 because both produced engine=0, and between slider=150 and slider=200 because both produced engine=2.0. The dashboard already normalises the spacetime settings to a clean 0..2 range in `graphSpacetimeEngineSettings`, so the response gain is redundant and harmful. Bypass the gain for the five spacetime sliders (gravitational constant, local gravitational constant, black hole mass, space friction, spring stiffness) so the visible slider position maps linearly to the engine value. ledger.js::graphSliderResponseValue() (line 2453) - Add an early return for the five spacetime slider IDs that bypasses the 2x gain and uses the raw slider value (clamped to [min, max]). The function is also used by the legacy geometry sliders (repel, link, gravity, size, font, linkw, labelDensity) which keep the 2x gain. tests/test_graph_engine_asset.py (line 10346) - Update the CSP/cache-bust assertion to the new `20260828-slider-multiplier-fix` value. Local verification - 227/227 tests/test_graph_engine_asset.py pass - manual_slider_test.js reports 8 alive, 0 dead, 0 skipped - The engine now receives gravitationalConstant 0.5 at visible 50 (was 0), 1.0 at visible 100 (unchanged), 2.0 at visible 200 (unchanged). Same linear mapping for localGravitationalConstant. - blackHoleMass receives 0.214 at visible 50 (was 0.125), 1.0 at visible 160 (unchanged), 2.0 at visible 500 (unchanged). - The visible 0..200 range for gravity now maps cleanly to engine 0..2 with no flat spots at the extremes. * fix(graph): restore the galaxy physics 0..8/0..16 calibration scale by multiplying the normalised 0..2 spacetime inputs in the galaxy integrator options The round-6 (8d42016) fix normalises the three spacetime sliders (galactic gravity, local solar gravity, black hole mass) to a clean 0..2 range at the dashboard boundary, so the visible default reaches the engine as 1.0x and the full visible range maps to 0..2. The non-galaxy engine consumes this 0..2 range directly (clamped to [0, 2] in applyForces). The galaxy engine, however, was calibrated for a 0..8 range (gravitationalConstant, localGravitationalConstant) and a 0..16 range (blackHoleMass) — its calibration constants, response curves, and physics formulas were tuned for those larger inputs. After the normalisation, the galaxy engine received a value 4x smaller than it was designed for, and the visible effect of moving any of the three spacetime sliders in Galaxy mode dropped to roughly a quarter of what it was before the fix. Multiply the three spacetime values by 4 (gravitationalConstant, localGravitationalConstant) and 8 (blackHoleMass) when they are passed into the galaxy integrator options. This restores the 0..8 / 0..16 calibration scale inside the galaxy physics without disturbing the non-galaxy engine, which still receives the 0..2 value directly and clamps it at [0, 2] in applyForces. The diagnostics at lines 8745-8802 continue to show the raw 0..2 dashboard value, which is the correct number to display to the user (the multiplier they set, not the internal rescaled value). engraphis/dashboard_assets/engraphis-graph.js (line 8589) - gravitationalConstant: * 4 after galaxyPhysicsMultiplier - localGravitationalConstant: * 4 after galaxyPhysicsMultiplier - blackHoleMass: * 8 after galaxyPhysicsMultiplier Local verification - 227/227 tests/test_graph_engine_asset.py pass - manual_slider_test.js reports 8 alive, 0 dead, 0 skipped - All three spacetime sliders now produce the full calibrated response range in Galaxy mode (the visible default of 1.0x is a true no-op, and the full slider range produces the intended 4x/8x change in the galaxy physics) * test(graph): verify slider forces and cache bust * fix: bound galaxy orbit speeds after control * preserve normalized Galaxy physics controls * fix vector speed caps and oversized graph controls * update normalized Galaxy field expectation * stabilize Galaxy paint audit baseline * cap kinematic galaxy carrier speed * cap live Galaxy carrier velocity * fix(graph): expose spacetime tuning in every preset * fix(graph): bound kinematic velocity and control response * fix(graph): wire spacetime controls into every renderer * fix(graph): bound full-layout physics controls * fix(graph): share capped speed across orbit phase * fix(graph): keep settling resistance responsive * fix(graph): honor zero Every-node spring stiffness * fix(graph): preserve orbit pause for full Galaxy scenes * fix(graph): map galactic gravity to attraction * fix(graph): gate orbit pause by active preset * test(graph): update orbit capability contract * fix(graph): hide inert full-layout spring control * fix(graph): describe active spacetime renderer * test(graph): match renderer-specific tuning copy * fix(graph): integrate the main-branch Galaxy gravity slider balance into the spacetime campaign Merges the Gravity slider work from main (path independence, 0..400 response balance, renderer floor removal) into the spacetime-slider feature branch, and keeps both feature sets intact: Renderer (engraphis-graph.js): - galaxyBlackHoleGravitySetting: remove the 24-floor so slider 0 is a real zero field; stability stays with the orbital-radius floor and the rigid event-horizon contact layers. - galaxyStellarGravitySetting: remove the 48-floor; the fixed 48 becomes GALAXY_FIXED_LOCAL_GRAVITY_SETTING for the Every-node calibrated reference. - galaxyLocalGravityConstant: route through galaxyBlackHoleGravityConstant to preserve the canonical 2x black-hole-to-local scaling. - setSettings: set preserveGalaxyPhaseOnResume before the inner immediate render, re-arm after it and before the outer render + physics reheat, and skip the reused-path contact-correction pass during a slider burst. This makes a burst of input events path-independent and stops the carriers from snapping back outward mid-drag. - schedulePhysicsUpdate: phase-lock the rAF reheat in galaxy mode. - Floor telemetry fields (stellarGravityFloorSetting, stellarFloorActive as a slider-floor marker, globalGravityFloor*, floorActive) removed; the fixed-local stellarFloorActive diagnostic is retained with its setter. - Keeps every PR-side change: relative-speed budgeting, carrier speed caps, spacetime d3-force routing in non-galaxy presets, Every-node worker wiring, velocity decay handling, and lane-gap updates. Slider response (ledger.js): identity clamped 1:1 mapping (2x gain removed; the asymmetric 0..400 band made it saturate) so every integer tick produces a distinct engine value. Tests: main's gravity-slider contracts (floor removal, path independence, no dead zone) plus the PR's new Every-node and velocity-budget tests; the PR-branch spacetime d3-forces test replaces main's shorter version; velocity cap unified at 48; the contraction threshold is calibrated to the merged renderer's measured 1.30x loose/tight ratio; the fixed-local stellarFloorActive diagnostic is asserted True at setting 0 (below the 48 reference). Verified: tests/test_graph_engine_asset.py 233/233, the four gravity/slider suites, and tests/test_dashboard_v2.py all pass. The only failing test in the adjacent Every asset suite (test_ledger_keeps_orbit_pause_for_full_quality_ galaxy_scenes) also fails on the un-merged PR branch and is pre-existing. * fix(tests): drop the slider probe harness and fix lint errors * fix(graph): port graphGalaxyQuality tracking and orbit-pause gating from the spacetime campaign * fix(graph): repair slider control syntax and Linux harness * fix(graph): complete slider browser contract * fix(graph): correct every-node spring direction * fix(graph): keep local orbits at zero global gravity * fix(ledger): cancel stale deferred preference writes * fix(graph): consume the full spacetime slider ranges everywhere The dashboard adapter emits galactic/core attraction and local solar gravity over 0..4 (raw/50) and black-hole mass up to 4.4, but the responsive engine clamped all three multipliers at 2 and the Every worker clamped core attraction/mass at 2, so the upper halves of those controls were inert (PR #177 review threads at engraphis-graph.js:8100 and engraphis-graph-every-worker.js:336). Both consumers now accept the full emitted ranges; the slider test's saturated expectations move to the new ceiling values. applyGalaxyOrbitalSpeedControl also returned before its local-member phase controller when the global field was zero, freezing planets at Gravity=0 even though their local stellar wells remain configured; only carrier support depends on the global constant, so the local controller now runs whenever any anchored system exists (review threads at graph.js:143). * fix(graph): consume the full Cluster cohesion range in the Every worker The dashboard maps the 0..200 Cluster cohesion slider to localGravitationalConstant 0..4, but the worker's collision-pass clamp saturated at 2 - its only consumer of that setting - leaving every position above the default 100 inert (PR #177 review thread at engraphis-graph-every-worker.js:257). Accept the full emitted range and floor the inverted push coefficient at zero so maximum cohesion collapses the separation push instead of inverting it into attraction. --------- Co-authored-by: coding-dev-tools <coding-dev-tools@users.noreply.github.com>
1 parent 230573d commit c0fd92f

15 files changed

Lines changed: 2361 additions & 393 deletions

engraphis/classic_assets/dashboard.js

Lines changed: 104 additions & 104 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: 49 additions & 7 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,16 +221,24 @@
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
const rest = Math.max(scaledSpacing * 1.9, Number(settings.link) * 1.6 * (MAP_SCALE * 0.55));
221230
for (let edge = 0; edge < model.totalLinks; edge += 1) {
222231
const a = model.sources[edge], b = model.targets[edge];
223232
const ddx = pos[b * 2] - pos[a * 2], ddy = pos[b * 2 + 1] - pos[a * 2 + 1];
224233
const dist = Math.sqrt(ddx * ddx + ddy * ddy) || 0.0001;
225234
const crossCommunity =
226235
model.communities[a] !== model.communities[b] ? 0.02 : 0.07;
227-
const force = (dist - rest) / dist * crossCommunity;
228-
dx[a] -= ddx * force; dy[a] -= ddy * force;
229-
dx[b] += ddx * force; dy[b] += ddy * force;
236+
const force = (dist - rest) / dist * crossCommunity * springScale;
237+
/* `force` is positive when the pair is beyond rest and negative when it overlaps.
238+
Apply equal-and-opposite corrections along a→b so positive force attracts the pair
239+
and negative force separates it. */
240+
dx[a] += ddx * force; dy[a] += ddy * force;
241+
dx[b] -= ddx * force; dy[b] -= ddy * force;
230242
}
231243

232244
/* Local repulsion through a spatial hash with a per-node visit cap keeps each pass O(n)
@@ -241,7 +253,16 @@
241253
}
242254
const minDist = SPACING * MAP_SCALE * 1.55;
243255
const minDist2 = minDist * minDist;
244-
const push = Number(settings.repel) / 48;
256+
/* The dashboard maps the 0..200 Cluster cohesion slider to localGravitationalConstant
257+
0..4, so clamping at 2 left the entire upper half of the control inert: it is this
258+
worker's only consumer of the setting (PR #177 review thread at this site). Accept
259+
the full emitted range and floor the inverted push coefficient at zero so a high
260+
cohesion cannot turn the collision-style push into an attraction. */
261+
const cohesion = Number.isFinite(Number(settings.localGravitationalConstant))
262+
? Math.max(0, Math.min(4, Number(settings.localGravitationalConstant))) : 1;
263+
/* Cluster cohesion strengthens the attractive spring network above. Invert its influence
264+
on the collision-style push so a higher cohesion setting does not spread clusters apart. */
265+
const push = Number(settings.repel) / 48 * Math.max(0, 1.5 - 0.5 * cohesion);
245266
for (let index = 0; index < count; index += 1) {
246267
const gx = Math.floor(pos[index * 2] / cell), gy = Math.floor(pos[index * 2 + 1] / cell);
247268
let checked = 0;
@@ -314,14 +335,26 @@
314335
}
315336
}
316337

317-
const gravity = Number(settings.gravity) / 48 * 0.0015;
338+
/* The dashboard emits Core attraction/local cohesion over 0..4 (raw/50) and Core mass
339+
up to 4.4; clamping at 2 left the upper half of those controls inert (PR #177 review
340+
thread at this site). Accept the full emitted ranges. */
341+
const coreAttraction = Number.isFinite(Number(settings.gravitationalConstant))
342+
? Math.max(0, Math.min(4, Number(settings.gravitationalConstant))) : 1;
343+
const coreMass = Number.isFinite(Number(settings.blackHoleMass))
344+
? Math.max(0, Math.min(4.4, Number(settings.blackHoleMass))) : 1;
345+
const gravity = Number(settings.gravity) / 48 * 0.0015 * coreAttraction * coreMass;
318346
for (let index = 0; index < count; index += 1) {
319347
dx[index] += (cx - pos[index * 2]) * gravity;
320348
dy[index] += (cy - pos[index * 2 + 1]) * gravity;
321349
}
322350

323351
/* A tight per-pass step cap keeps the settle from smearing district boundaries. */
324-
const damp = 0.8, maxStep = SPACING * MAP_SCALE * 0.7;
352+
const resistance = Number.isFinite(Number(settings.damping))
353+
? Math.max(0, Math.min(15, Number(settings.damping))) : 1;
354+
/* Keep the full 0..15 control range responsive: the reciprocal curve reaches 0.2
355+
at resistance 13, so a 0.2 floor would make the final two slider units inert. */
356+
const damp = Math.max(0.15, Math.min(0.95, 0.8 / (0.75 + 0.25 * resistance)));
357+
const maxStep = SPACING * MAP_SCALE * 0.7;
325358
for (let index = 0; index < count; index += 1) {
326359
let vx = dx[index] * damp, vy = dy[index] * damp;
327360
const speed = Math.sqrt(vx * vx + vy * vy);
@@ -409,6 +442,15 @@
409442
repel: Number.isFinite(Number(next.repel)) ? Number(next.repel) : settings.repel,
410443
link: Number.isFinite(Number(next.link)) ? Number(next.link) : settings.link,
411444
gravity: Number.isFinite(Number(next.gravity)) ? Number(next.gravity) : settings.gravity,
445+
gravitationalConstant: Number.isFinite(Number(next.gravitationalConstant))
446+
? Number(next.gravitationalConstant) : settings.gravitationalConstant,
447+
blackHoleMass: Number.isFinite(Number(next.blackHoleMass))
448+
? Number(next.blackHoleMass) : settings.blackHoleMass,
449+
localGravitationalConstant: Number.isFinite(Number(next.localGravitationalConstant))
450+
? Number(next.localGravitationalConstant) : settings.localGravitationalConstant,
451+
damping: Number.isFinite(Number(next.damping)) ? Number(next.damping) : settings.damping,
452+
springStiffness: Number.isFinite(Number(next.springStiffness))
453+
? Number(next.springStiffness) : settings.springStiffness,
412454
};
413455
if (data.relayout && model) {
414456
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)