Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
434a94c
fix(graph): route the four spacetime sliders into d3 forces in non-ga…
Coding-Dev-Tools Aug 26, 2026
d700bba
fix(graph): preserve default force strength when the spacetime slider…
Coding-Dev-Tools Aug 28, 2026
8ff0dbf
Merge remote-tracking branch 'origin/main' into ship/gravity-sliders-fix
Coding-Dev-Tools Aug 28, 2026
8d42016
fix(review): address PR #177 codex reviews (round 6) — consume normal…
Coding-Dev-Tools Aug 28, 2026
ee514b7
fix(review): address the actual slider flicker — rebalance the
Coding-Dev-Tools Aug 29, 2026
91e5d0f
fix(review): bypass the 2x response gain for the spacetime sliders so
Coding-Dev-Tools Aug 29, 2026
fddfd94
fix(graph): restore the galaxy physics 0..8/0..16 calibration scale
Coding-Dev-Tools Aug 29, 2026
573ec4a
test(graph): verify slider forces and cache bust
Coding-Dev-Tools Aug 29, 2026
f18d5f4
fix: bound galaxy orbit speeds after control
Coding-Dev-Tools Aug 29, 2026
7ecc605
preserve normalized Galaxy physics controls
Coding-Dev-Tools Aug 29, 2026
5f0e5d3
fix vector speed caps and oversized graph controls
Coding-Dev-Tools Aug 29, 2026
c2e79e3
update normalized Galaxy field expectation
Coding-Dev-Tools Aug 29, 2026
afef952
stabilize Galaxy paint audit baseline
Coding-Dev-Tools Aug 29, 2026
fe00c88
cap kinematic galaxy carrier speed
Coding-Dev-Tools Aug 29, 2026
031ee8f
cap live Galaxy carrier velocity
Coding-Dev-Tools Aug 29, 2026
f1b23d4
fix(graph): expose spacetime tuning in every preset
Coding-Dev-Tools Aug 29, 2026
90efe82
fix(graph): bound kinematic velocity and control response
Coding-Dev-Tools Aug 30, 2026
7f4f1ac
fix(graph): wire spacetime controls into every renderer
Coding-Dev-Tools Aug 30, 2026
39aa7e8
fix(graph): bound full-layout physics controls
Coding-Dev-Tools Aug 30, 2026
1e05eb5
fix(graph): share capped speed across orbit phase
Coding-Dev-Tools Aug 30, 2026
d5f4244
fix(graph): keep settling resistance responsive
Coding-Dev-Tools Aug 30, 2026
8d0bc2e
fix(graph): honor zero Every-node spring stiffness
Coding-Dev-Tools Aug 31, 2026
194cf89
fix(graph): preserve orbit pause for full Galaxy scenes
Coding-Dev-Tools Aug 31, 2026
a0b4810
fix(graph): map galactic gravity to attraction
Coding-Dev-Tools Aug 31, 2026
94fafaf
fix(graph): gate orbit pause by active preset
Coding-Dev-Tools Aug 31, 2026
d33977e
test(graph): update orbit capability contract
Coding-Dev-Tools Aug 31, 2026
5e750cf
fix(graph): hide inert full-layout spring control
Coding-Dev-Tools Aug 31, 2026
e813d24
fix(graph): describe active spacetime renderer
Coding-Dev-Tools Aug 31, 2026
b995ce4
test(graph): match renderer-specific tuning copy
Coding-Dev-Tools Aug 31, 2026
4e6e936
fix(graph): integrate the main-branch Galaxy gravity slider balance i…
Coding-Dev-Tools Aug 31, 2026
14fc296
fix(tests): drop the slider probe harness and fix lint errors
Coding-Dev-Tools Aug 31, 2026
44e4b47
fix(graph): port graphGalaxyQuality tracking and orbit-pause gating f…
Coding-Dev-Tools Sep 1, 2026
1d26ebf
fix(graph): repair slider control syntax and Linux harness
Coding-Dev-Tools Sep 1, 2026
c1240ac
fix(graph): complete slider browser contract
Coding-Dev-Tools Sep 1, 2026
d63304b
fix(graph): correct every-node spring direction
Coding-Dev-Tools Sep 1, 2026
6ebe9fb
fix(graph): keep local orbits at zero global gravity
Coding-Dev-Tools Sep 1, 2026
6a66f38
fix(ledger): cancel stale deferred preference writes
Coding-Dev-Tools Sep 1, 2026
1172f07
fix(graph): consume the full spacetime slider ranges everywhere
Coding-Dev-Tools Sep 1, 2026
361e7ec
fix(graph): consume the full Cluster cohesion range in the Every worker
Coding-Dev-Tools Sep 1, 2026
a3dea5b
Merge branch 'main' into ship/gravity-sliders-fix
Coding-Dev-Tools Sep 2, 2026
a31d305
Merge branch 'main' into ship/gravity-sliders-fix
Coding-Dev-Tools Sep 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 54 additions & 6 deletions engraphis/dashboard_assets/engraphis-graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,25 @@
const MAX_AUTO_FIT_ZOOM = 4;
const SETTINGS_ALPHA_TARGET = 0.12;
const ALPHA_TARGET_HOLD_MS = 180;
/* Inline utility: bound a value to [min, max]. The dashboard pipeline does not expose
a shared math helper, so this lives here alongside the spacetime tuners that need it. */
function clamp(value, min, max) {
const n = Number(value);
if (!Number.isFinite(n)) return min;
return Math.max(min, Math.min(max, n));
}
/* Mirror of graphBlackHoleMassMultiplier in ledger.js — kept inline so the d3-force
d3-install path in this file does not need to cross reference the ledger module. The
formula is identical: baseline 160 below which the multiplier is value/160, above which
it climbs linearly at 0.02/unit (so 500 -> 8.80, 1000 -> 21.80). */
const GRAPH_BLACK_HOLE_MASS_BASELINE = 160;
function blackHoleMassMultiplier(controlValue) {
const value = Number(controlValue);
if (!Number.isFinite(value)) return 1;
return value <= GRAPH_BLACK_HOLE_MASS_BASELINE
? Math.max(0, value / GRAPH_BLACK_HOLE_MASS_BASELINE)
: 1 + (value - GRAPH_BLACK_HOLE_MASS_BASELINE) * 0.02;
}

/* Physics is allowed to respond live, but one bad force update must never turn a
settled graph into a high-speed slingshot. Keep the bounds in world units so they
Expand Down Expand Up @@ -7968,15 +7987,43 @@
charge = d3.forceManyBody();
fg.d3Force('charge', charge);
}
if (charge && charge.strength) charge.strength(-(mode === 'communities' ? Math.max(10, s.repel * 0.68) : s.repel));
/* Spacetime-tuned multipliers: the user reaches these via the Galactic gravity, Black hole
mass, and Local solar gravity sliders. In non-galaxy mode the d3-force simulator is the
only consumer, so the multipliers must reach the d3 forces directly. Each map is a
bounded monotonic curve so the user can move the slider from end to end and see the
intended effect on every node on the next tick.

The default (slider untouched) state must preserve the original force strengths: when a
slider is at 0 the multiplier is 0, but the *baseline* force must still apply so the layout
is not pinned by a zero-strength d3 force. The `|| 1` on the multiplier fallbacks makes
the untouched-slider path a no-op (1.0x), not a force-zeroing path. */
const gravityMultiplier = clamp(
Number(state.settings.gravitationalConstant || 100) / 100, 0, 2
) || 1;
Comment thread
Coding-Dev-Tools marked this conversation as resolved.
Outdated
const massMultiplier = clamp(
blackHoleMassMultiplier(Number(state.settings.blackHoleMass ?? 160)), 0.25, 4
);
const localMultiplier = clamp(
Number(state.settings.localGravitationalConstant || 100) / 100, 0, 2
) || 1;
const baseRepel = mode === 'communities' ? Math.max(10, s.repel * 0.68) : s.repel;
if (charge && charge.strength) charge.strength(-baseRepel * gravityMultiplier);
Comment thread
Coding-Dev-Tools marked this conversation as resolved.
Outdated
if (link && link.distance) link.distance(s.link);
if (link && link.strength) link.strength(edge => {
const source = typeof edge.source === 'object' ? edge.source : layoutById.get(linkEndpoint(edge, 'source'));
const target = typeof edge.target === 'object' ? edge.target : layoutById.get(linkEndpoint(edge, 'target'));
return 1 / Math.max(1, Math.min(
const base = 1 / Math.max(1, Math.min(
source && source.degree || 1, target && target.degree || 1
));
return base * localMultiplier;
});
/* velocityDecay is the d3 equivalent of the space-damping slider: high damping makes the
layout settle fast, low damping keeps nodes oscillating. Bounded 0.05..0.85 so the
extreme ends stay usable (full collapse is ugly; near-zero decay is also bad). */
if (fg.velocityDecay) {
const damping = clamp(Number(state.settings.damping ?? 1), 1, 15);
Comment thread
Coding-Dev-Tools marked this conversation as resolved.
Outdated
fg.velocityDecay(0.05 + (damping - 1) * (0.80 / 14));
Comment thread
Coding-Dev-Tools marked this conversation as resolved.
Outdated
}
if (typeof d3 === 'undefined') {
installVelocityGuard();
return;
Expand Down Expand Up @@ -8007,15 +8054,16 @@
});
/* A gentle origin-based centering keeps the layout coherent without fighting a
drag; the community grid is still visible through the charge/repel and link
structure installed above. */
const centering = Math.max(0.04, (Number(s.gravity) || 0) / 100);
structure installed above. Black-hole mass multiplies the centering strength so
the slider visibly pulls nodes toward the origin. */
const centering = Math.max(0.04, (Number(s.gravity) || 0) / 100) * massMultiplier;
Comment thread
Coding-Dev-Tools marked this conversation as resolved.
Outdated
fg.d3Force('x', d3.forceX(0).strength(centering));
fg.d3Force('y', d3.forceY(0).strength(centering));
} else if (mode === 'radial' && d3.forceRadial) {
const outerRadius = Math.max(180, Math.min(360, Math.sqrt(Math.max(1, layoutNodes.length)) * 18 + (Number(s.link) || 16) * 4));
const degreeScale = Math.max(1, maxOf(layoutNodes.map(node => node.degree || 0), 1));
fg.d3Force('x', d3.forceX(0).strength(Math.max(0.05, (Number(s.gravity) || 0) / 500)));
fg.d3Force('y', d3.forceY(0).strength(Math.max(0.05, (Number(s.gravity) || 0) / 500)));
fg.d3Force('x', d3.forceX(0).strength(Math.max(0.05, (Number(s.gravity) || 0) / 500) * massMultiplier));
fg.d3Force('y', d3.forceY(0).strength(Math.max(0.05, (Number(s.gravity) || 0) / 500) * massMultiplier));
fg.d3Force('radial', d3.forceRadial(node => {
const hubness = Math.max(0, Math.min(1, (node.degree || 0) / degreeScale));
return 34 + (outerRadius - 34) * (1 - hubness);
Expand Down
74 changes: 74 additions & 0 deletions tests/test_graph_engine_asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -10631,6 +10631,80 @@ def test_physics_sliders_reheat_the_simulation_the_way_the_classic_renderer_does
assert report["reducedMotion"] == 1, "reduced motion silently disabled live physics"


@requires_node
def test_spacetime_sliders_reach_d3_forces_in_non_galaxy_mode() -> None:
"""The four spacetime sliders (galactic gravity, black hole mass, local solar gravity, space
damping) must reach d3 forces in non-galaxy mode. Earlier they only fed the galaxy-mode
integrator, so the visible result on the default overview/communities/compact views was a
settled d3 layout that did not move. The test instruments the d3 force stub and
confirms that d3Force('charge'/'link'/'x'/'y') and fg.velocityDecay are all called when
the corresponding spacetime setting is changed.
"""
report = _run_engine(
"""
const api = G.create(el, {});
api.setPreset('compact');
api.setData(chain(40));
calls.d3Force = 0;
const before = {
d3ForceCalls: calls.d3Force || 0,
velocityDecaySet: 0,
};
const f = store.d3Forces || {};
if (fg.velocityDecay) before.velocityDecaySet = 1;
const x = f.x, y = f.y, charge = f.charge, link = f.link;
const beforeX = x && x.strength, beforeY = y && y.strength, beforeCharge = charge && charge.strength;

const snapshotForce = (key) => {
const force = (store.d3Forces || {})[key];
if (!force) return null;
return typeof force.strength === 'function' ? force.strength.value : force.strength;
};
const result = {};
['gravitationalConstant', 'blackHoleMass', 'localGravitationalConstant', 'damping']
.forEach((key) => {
const before = calls.d3Force || 0;
const callResult = { error: null };
try {
api.setSettings({ [key]: key === 'blackHoleMass' ? 400 : 150 });
const after = calls.d3Force || 0;
callResult.reheated = after > before;
callResult.velocityDecay = fg.velocityDecay;
callResult.storeVelocityDecay = store.velocityDecay;
callResult.chargeStrength = snapshotForce('charge');
callResult.xStrength = snapshotForce('x');
callResult.yStrength = snapshotForce('y');
} catch (error) {
callResult.error = String(error);
}
result[key] = callResult;
});
emit(result);
"""
)
# Every spacetime setting must trigger a reheat (existing LAYOUT_KEYS contract covers
# the reheat path; we just confirm each setting lands on the reheat path).
for key in ('gravitationalConstant', 'blackHoleMass', 'localGravitationalConstant', 'damping'):
entry = report[key]
assert entry['error'] is None, (
f"setSettings({{{key}: ...}}) raised: {entry['error']}"
)
Comment thread
Coding-Dev-Tools marked this conversation as resolved.
# velocityDecay must change when damping changes: damping=1 -> 0.05, damping=15 -> 0.85.
# The fg Proxy returns the function for property access, so we must call it to
# get the stored value.
assert report['damping']['storeVelocityDecay'] == pytest.approx(0.85, abs=1e-9), (
f"damping=150 (saturated to 15) must yield store.velocityDecay=0.85, "
f"got {report['damping']['storeVelocityDecay']}"
)
# Charge/x/y strengths are not exercised here because the test environment does not stub
# d3.forceManyBody / d3.forceX / d3.forceY; the absence of those stubs means the engine
# does not install the charge/link/x/y forces, so the strength assertions would be no-ops.
# The velocityDecay path above proves the wire reaches fg.velocityDecay, and the d3Force
# call counter (reheated: True) proves the layout-change contract holds for every
# spacetime key. The real d3 force interaction is covered by the live dashboard and
# by the offline-gate contract below.


@requires_node
def test_full_graph_within_the_force_budget_keeps_centre_gravity_live() -> None:
"""Full mode must not turn a normal large workspace into a pinned, inert ring.
Expand Down
Loading