Raise MaxVelocity so a graph settles in seconds, not minutes - #365
Merged
Conversation
A graph that opened as a tall narrow column with near-vertical links was
not laid out wrongly - it had not finished. Measured on a twenty node,
twenty edge graph the size of a small class, starting clustered:
600 steps 740 x 1063 aspect 0.70 mean angle 64.8 deg
1800 steps 1050 x 728 aspect 1.44 mean angle 40.5 deg
6000 steps 1448 x 833 aspect 1.74 mean angle 28.8 deg
20000 steps 1447 x 833 aspect 1.74 mean angle 28.8 deg
600 steps is ten seconds at sixty frames a second, which is about as long
as anyone watches. The shape at that point is what looked broken; the
settled one is fine. Starting position makes no difference - scattered,
clustered and coincident all converge to the same shape - so this was not
a local minimum.
MaxVelocity was the bound. A body has to travel several hundred units to
reach its place, and a cap of 50 a second means seconds per body. At 600
steps: v50 gives aspect 0.70 at 64.8 deg, v150 1.17 at 39.0, v250 1.44 at
38.8, v400 1.75 at 33.9. Past that it stops helping - v600 comes back to
1.34 at 34.3 - so there is an overshoot limit and the default now sits
inside it at 250, which reaches its settled shape roughly five times
sooner while settling to much the same place (1.49 at 28.5 deg against
1.74 at 28.8).
Repulsion is still doing its job, which is what prompted looking. Settled,
with it the graph is 1313 x 881 at 28.5 deg; without it 594 x 840 at 72.0
deg. The overlap pass only keeps bodies off one another, it creates no
room beyond that, so without repulsion the graph collapses into the column
the levelling force cannot then recover from.
Two tests cover what nothing did: ASmallGraph_IsReadableWithinTenSeconds
pins the settling speed, and Repulsion_IsWhatSpreadsAGraphOut pins why
repulsion stays.
Step_WithOverlapMarginZero_LeavesTheBodiesOverlapping was asserting a
transient. Two 400 wide bodies belong 420 apart once the ordering bias has
its way, and the pair only stayed overlapping for 120 frames because the
old cap made them too slow to get there. It now turns off the forces that
separate a pair, so it measures the overlap pass rather than how far
anything travelled.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D2KNUKr1xJPTEdDmeF2tQN
|
This was referenced Sep 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



What this actually was
A graph that opens as a tall narrow column with near-vertical links is not laid out wrongly — it has not finished. Measured on a twenty-node, twenty-edge graph the size of a small class, started clustered:
600 steps is ten seconds at sixty frames a second, which is about as long as anyone watches a graph unfold. The shape at that point is what looks broken; the settled one is fine. Starting position makes no difference — scattered, clustered and coincident all converge to the same shape — so this was not a local minimum.
I had guessed the horizontal spacing was pinned at the ordering bias's minimum gap (~150) and proposed making it target the rest length. The measurement says otherwise: horizontal step settles at 196–208 against a rest length of 225. That premise was wrong and no layering change is needed.
The bound is MaxVelocity
A body has to travel several hundred units to reach its place, and a cap of 50/second means seconds per body. At 600 steps:
Past ~400 it stops helping — 600 comes back down — so there is a real overshoot limit and the new default sits inside it. 250 reaches its settled shape roughly five times sooner while settling to much the same place: 1.49 at 28.5° against the old default's 1.74 at 28.8°. 400 is faster still but shifts the settled shape more (aspect 2.10), and 600's non-monotonicity says that is near the edge.
Is repulsion still necessary?
Yes, emphatically. Settled, on the same graph:
The overlap pass only keeps bodies off one another; it creates no room beyond that. Without repulsion the graph collapses into exactly the tall column the levelling force then cannot recover from. Removing it would reintroduce the reported symptom permanently.
Testing
Two tests cover what nothing did — settling speed was never measured, which is why a correct layout could look broken:
ASmallGraph_IsReadableWithinTenSeconds— at 600 steps the graph must be wider than tall with mean angle under 45°.Repulsion_IsWhatSpreadsAGraphOut— with repulsion the settled graph must be both wider and at least 15° flatter.Step_WithOverlapMarginZero_LeavesTheBodiesOverlappingneeded correcting: it was asserting a transient. Two 400-wide bodies belong 420 apart once the ordering bias has its way (halfWidths + 20), and the pair only stayed overlapping for 120 frames because the old cap made them too slow to get there. It now disables the forces that separate a pair, so it measures the overlap pass rather than how far anything travelled.ForceDirectedLayout.Tests39/39,ImGui.NodeEditor.Tests71/71,ForceDirectedLayout.NativeandImGuiAppDemobuild clean in Release.Not verified visually — this container is headless. The numbers are measured, but whether ten seconds is still too long to wait is a judgement worth making in the editor;
MaxVelocityis a setting and the demo exposes it.🤖 Generated with Claude Code
https://claude.ai/code/session_01D2KNUKr1xJPTEdDmeF2tQN
Generated by Claude Code