Commit c0fd92f
authored
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
File tree
- engraphis
- classic_assets
- dashboard_assets
- static
- tests
- e2e
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 49 additions & 7 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
25 | | - | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
26 | 30 | | |
27 | 31 | | |
28 | 32 | | |
| |||
217 | 221 | | |
218 | 222 | | |
219 | 223 | | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
220 | 229 | | |
221 | 230 | | |
222 | 231 | | |
223 | 232 | | |
224 | 233 | | |
225 | 234 | | |
226 | 235 | | |
227 | | - | |
228 | | - | |
229 | | - | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
230 | 242 | | |
231 | 243 | | |
232 | 244 | | |
| |||
241 | 253 | | |
242 | 254 | | |
243 | 255 | | |
244 | | - | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
245 | 266 | | |
246 | 267 | | |
247 | 268 | | |
| |||
314 | 335 | | |
315 | 336 | | |
316 | 337 | | |
317 | | - | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
318 | 346 | | |
319 | 347 | | |
320 | 348 | | |
321 | 349 | | |
322 | 350 | | |
323 | 351 | | |
324 | | - | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
325 | 358 | | |
326 | 359 | | |
327 | 360 | | |
| |||
409 | 442 | | |
410 | 443 | | |
411 | 444 | | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
412 | 454 | | |
413 | 455 | | |
414 | 456 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
| 11 | + | |
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| |||
151 | 151 | | |
152 | 152 | | |
153 | 153 | | |
154 | | - | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
155 | 157 | | |
156 | 158 | | |
157 | 159 | | |
| |||
1376 | 1378 | | |
1377 | 1379 | | |
1378 | 1380 | | |
1379 | | - | |
| 1381 | + | |
| 1382 | + | |
| 1383 | + | |
| 1384 | + | |
1380 | 1385 | | |
1381 | 1386 | | |
1382 | 1387 | | |
| |||
0 commit comments