Tune the layout defaults against the benchmark corpus - #382
Merged
Conversation
That setting is itself the clamp that keeps the inverse-square law finite where two bodies touch, so setting it to zero removes the only thing standing between the force and a division by zero: touching boxes have exactly no clear space between them, the magnitude comes back infinite, and the integrator carries that into positions that are NaN from then on. The cost is not a bad layout, it is a layout that stops being numbers. It surfaced from a parameter sweep whose candidate list happened to include zero, where every metric taken afterwards read NaN rather than reading badly — a result that cannot even be recognised as a failure. Floored at a value small enough to change nothing for any usable setting, so zero now means "as small as this can safely be". Also extends DirectionalBias's tuning candidates past 4, since a descent that settles on the largest value it was offered has found the edge of the list rather than the edge of the useful range. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018b2bn5CKSzpjRV4nxv3c3y
Six of the fifteen settings moved; the other nine were offered the same
ranges and declined them. Measured by coordinate descent over the corpus,
scored by LayoutScore, at forty-eight starting arrangements per
measurement:
RepulsionStrength 600,000 -> 900,000
MinRepulsionDistance 50 -> 5
LinkSpringStrength 0.5 -> 0.1
RestLinkLength 225 -> 50
DirectionalBias 0.5 -> 4
LinkFlatteningStrength 0.5 -> 3
The corpus score went from 3.25 to 1.19, and held at 1.22-1.28 on three
families of starting arrangements the values were never chosen on,
against a measurement deviation of 0.03. A confirmation pass re-swept
every setting at the tuned point: each either held its value or offered a
gain inside the noise, so this is a genuine local minimum and not a stop
part-way down.
This fixes the defect the corpus was built to expose. A twelve-node chain
settled at 53 degrees off horizontal with two starts in six reading left
to right, because pulling every body towards one centre folds a long
chain into a coil; it now settles at 0.1 degrees, twelve in twelve, and
MixedSizes goes from 50 degrees and 1/12 to 2.1 and 12/12. Gravity is not
what fixed it and was left untouched at 50: a force pulling each edge
towards horizontal simply outcompetes one pulling every body at a point.
The cost is real and is recorded rather than hidden. Links drawn across
an unrelated body get more common as a graph flattens, monotonically with
the setting, roughly doubling on the two-class graph whose cross-class
calls are the long edges. The score's minimum is a shallow basin - 2 and
3 land within half a standard deviation - so that choice is a judgement
call, not a measurement.
Three tests in ForceLayoutTests broke without anything being wrong with
them, and were fixed by naming the inputs they depend on rather than by
moving their thresholds: the flattening splay stopped splaying because
the pair now levels completely and a bound of zero is met by anything,
and the untwist's overlap-pass exemption became unobservable because
flattening decides that geometry outright (held and free agree to six
figures on the defaults; with flattening off they differ by 193
vertically and 426 horizontally). The repulsion floor test now asserts
the real contract, which is the smaller of the law's cap and MaxForce -
under these defaults the latter, so MinRepulsionDistance has no effect at
all below about 13.
Corpus gate thresholds tightened to match, with entries added for the two
graphs that had none.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018b2bn5CKSzpjRV4nxv3c3y
MSTEST0037, on an assertion this branch had already rewritten. The argument order was checked rather than assumed: a backwards conversion still passes on data that satisfies both readings, so it was verified by moving the bound to a value it cannot meet and confirming the failure names 206 against it, which is the value in the second position. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018b2bn5CKSzpjRV4nxv3c3y
|
This was referenced Sep 9, 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.



Uses the harness from #380 to tune
LayoutSettings.Defaultsone setting at a time. Six of the fifteen moved; the other nine were offered the same ranges and declined them.RepulsionStrengthMinRepulsionDistanceLinkSpringStrengthRestLinkLengthDirectionalBiasLinkFlatteningStrengthEvidence
Corpus score 3.25 → 1.19, measured at 48 starting arrangements per point. Validated on three families of arrangements the values were never chosen on:
Against a measurement deviation of 0.032. A confirmation pass re-swept all fifteen settings at the tuned point: each either held its value or offered a gain inside the noise, so this is a genuine local minimum rather than a stop part-way down.
DirectionalBiaswas re-checked past the top of its original list (8 and 16 both score worse) so it is an interior optimum, not a truncation.What it fixes
The defect the corpus was built to expose. Per-graph, at 12 starts:
Pulling every body towards one centre folds a long chain into a coil. Gravity is not what fixed it — weakening
GravityStrengthdid straighten the chain, but at the cost of the one thing gravity is for, and the descent left it at 50 untouched.LinkFlatteningStrengthat six times its old value simply outcompetes the coil: a force pulling each edge towards horizontal beats one pulling every body towards a point, and neither has to be turned off for that to be true.What it costs
Links drawn across an unrelated body get more common as a graph flattens — monotonically with the setting, from 0.10 normalised at flattening 0 to 0.40 at 12 — roughly doubling on
TwoClasses, whose cross-class calls are the long edges that have to cross whatever is parked between them.The score's minimum is a shallow basin it cannot actually resolve:
2 and 3 differ by half a standard deviation, so something other than the score has to choose: 3 settles more reliably, 2 hides fewer links. Going with 3 because it is what the objective picked and its weights are written down and arguable — it is a one-number change if the trade should go the other way.
Two things found on the way
MinRepulsionDistancehas no effect below ≈13.MaxForcecaps the total force at 5,000 while the law's own cap is 36,000, so close-range repulsion is a constant 5,000 and the inverse-square only reappears past 13 units of clear space. Sweeping 0, 2, 5, 10 measures the same layout four times. What the setting really controls is whether that floor is hard or soft — at the old 50 the law's cap was 360, well underMaxForce, so repulsion went soft at close range and let bodies crowd.Zero was never a valid value for it — it is itself the clamp keeping the law finite at contact, so it yielded infinity and then NaN positions. Fixed in the first commit here, with a regression test verified to fail on the unfixed code.
Tests
73/73 in
ForceDirectedLayout.Tests, 98/98 inImGui.NodeEditor.Testsunchanged.Three tests broke on this tuning without anything being wrong with them, and were fixed by naming the inputs they depend on rather than by moving thresholds:
LinkFlattening_..._SplaysApartHorizontally— the pair now levels completely, so the drop goes to zero and a bezier bound of zero is met by any arrangement at all. Its fixture pins the flattening strength and rest length it was written for.TwistedLinks_AreNotHeldApartOnTheAxisTheySwapAlong— flattening decides that geometry outright, so held and free agree to six figures. With flattening and bias off they differ by 193 vertically and 426 horizontally, which is the effect the test is named for; the fixture now isolates it.Repulsion_WithNoClearSpace_PushesAtTheFloor— now asserts the real contract, the smaller of the law's cap andMaxForce.Corpus gate thresholds tightened to match (Chain 60° → 10°, MixedSizes 58° → 15°, Counter 40° → 25°), with entries added for the two graphs that had none.
🤖 Generated with Claude Code
https://claude.ai/code/session_018b2bn5CKSzpjRV4nxv3c3y