Optimal network finder (EMST): shortest links joining a set of points - #16
Merged
Conversation
A planning tool for "where should the belts/trains/power actually run".
Reachable only by typing its name in the search bar (a new "tool" catalog
entry, so it costs no permanent UI), it takes a set of points -- clicked
empty map, clicked buildings, the whole rectangle selection in bulk, or
typed X/Y metres -- and draws the shortest set of links joining them.
Two link styles, both exact and both O(n log n) (map/static/map/emst.js):
Straight Euclidean MST. Sits inside the Delaunay triangulation, so a
sweep-hull triangulation cuts the candidate set from n^2/2
edges to under 3n and Kruskal does the rest.
X / Y only Rectilinear (L1) MST -- NOT the straight tree with corners
drawn on: changing the metric changes which points the optimal
tree even connects. Sits inside the octant graph, and inside
one octant the L1 distance collapses to a linear function of
the far endpoint, so four Fenwick sweeps find all eight
nearest neighbours at once.
Optionally one point is a DESTINATION, and a slider trades total network
length against how far every point has to travel through the tree to reach
it (Prim-Dijkstra: Prim with the key alpha * tree-distance + edge weight).
alpha = 0 is Prim, so still the exact MST; alpha = 1 is Dijkstra over a
candidate set augmented with the destination's own edges, so every point
joins it directly -- the exact minimum of summed trip distance. Between the
two it is a heuristic trade, and the panel says so rather than implying an
optimum that is NP-hard to find. On 5,700 real machines, 40% along the
slider costs 9% more network and cuts total travel by 47%.
Everything is computed in map pixels: the world projection is one uniform
scale with no rotation, so an optimal tree there is optimal in metres, and
"along X" on screen is "along X" in the world.
tools/check_emst.js is the gate. A short candidate set still yields a
spanning tree, just a silently longer one, so every result is diffed
against brute-force Prim over grids, circles, collinear runs, duplicates
and clusters -- plus, with a destination, that alpha=0 is still the MST,
that alpha=1 is the exact star, and that the trip distances the panel
reports match a walk of the tree it actually drew. It caught an inverted
in-circle sign on its first run. 100,000 points solve in about a second.
Also here, both found by using the tool:
- selection.js: selecting a PIN (vehicles, collectables, players, crash
sites) marked the bare coordinate, which is where the tail points -- the
pin itself, floating above it, looked untouched. Now ringed, with the
same geometry the hover highlight already uses.
- The same object can no longer be added twice: points carry the object's
save id, so clicking a 100m foundation's far corner counts as the same
object rather than a second point. Clicking it again removes it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Search-bar-only planning tool: give it a set of points and it draws the
shortest set of links joining them all. For working out where belts, trains
or power lines should actually run.
No permanent UI — it is reached by typing "Optimal network finder (EMST)"
(or just "emst") in the search bar, via a new
toolkind in the searchcatalog. Points come from four places, all landing in the same list: clicking
empty map, clicking a building (which takes that building's exact position and
name), the whole rectangle selection in bulk, or typed X/Y metres.
The maths (
map/static/map/emst.js)Both link styles are exact and both O(n log n). 100,000 points solve in
about a second.
X/Y-only is not the straight tree with corners drawn on: changing the
metric changes which points the optimal tree even connects, so it is solved
from scratch.
Everything is computed in map pixels. The world → map projection is a single
uniform scale with no rotation, so a tree that is optimal there is optimal in
metres, and "along X" on screen is "along X" in the world.
Destination (optional)
Mark one point as the destination and a slider trades total network length
against how far every point has to travel through the tree to reach it.
That is the Prim–Dijkstra tradeoff: Prim's algorithm with the key changed to
alpha * (tree distance from the destination) + edge weight.alpha = 0is literally Prim → the same exact MST as before.alpha = 1is literally Dijkstra over a candidate set augmented with thedestination's own edges → every point joins it directly, which is the exact
minimum of summed trip distance.
trips sum to at most X" is NP-hard. The panel wording says so rather than
implying an optimum.
Measured on 5,700 machines from a real save: 40% along the slider costs 9%
more network and cuts total travel by 47% (16.4 → 17.9 km of links,
5,496 → 2,923 km of travel).
With no destination set, nothing changes from the plain shortest-network
behaviour — no slider, no extra readout.
Correctness gate (
tools/check_emst.js)The thing that makes both algorithms fast is that they only ever hand Kruskal
a small candidate set that provably contains the MST. If either set is ever
short of an edge, the result is still a spanning tree — just a silently longer
one. Nothing about the output looks wrong, so it is diffed against a
brute-force O(n²) Prim over the complete graph:
Point sets are chosen to break naive geometry: grids (endlessly co-circular),
points on a circle, collinear runs, exact and near duplicates, tight clusters,
and the map's real coordinate range. With a destination it additionally checks
that
alpha=0is still the exact MST, thatalpha=1is the exact star, andthat the trip distances the panel reports match a walk of the tree it actually
drew. It earned its keep by catching an inverted in-circle sign on its first
run.
Two fixes found while using it
selection.js). A pin — vehicles,collectables, players, crash sites — is drawn as a circle floating above its
coordinate with a tail pointing down at it, so marking the bare coordinate
marked the tail's tip and left the pin itself looking untouched. It is now
ringed as well, using the same geometry
_paintPinand the hover highlightalready use.
id, so clicking a 100 m foundation's far corner counts as the same object
rather than a second point 100 m away. Clicking it again removes it.
Verified
Driven end to end in the real app with Playwright: search → panel → map clicks
→ object clicks → typed coordinates → compute → mode switch → per-link hover
tooltips → bulk add of a 29,000-object selection → destination and slider →
copy → close/reopen. No console errors, at three window sizes.
Scale: the panel lists at most 200 rows (with an explicit "and N more"), so a
29,000-point network adds in 0.1 s and draws in 2.4 s while the tree itself
takes 77 ms.
🤖 Generated with Claude Code