diff --git a/Tools/Scripts/MapGenerator.cs b/Tools/Scripts/MapGenerator.cs
index adc69a5..54c3c73 100644
--- a/Tools/Scripts/MapGenerator.cs
+++ b/Tools/Scripts/MapGenerator.cs
@@ -625,7 +625,9 @@ public partial class MapGenerator : TextureRect
GD.Print($"[A*] Starting Logistics Pathfinding for {MapSize}x{MapSize} world...");
_highwayPaths.Clear(); _branchPaths.Clear(); _ruggedPaths.Clear(); _trailPaths.Clear();
- AStarGrid2D astar = new AStarGrid2D();
+ // RoadPathGrid is a stock AStarGrid2D with a scaled-up distance estimate, so the
+ // search stops flood-filling the island for every road. See the class for why.
+ AStarGrid2D astar = new RoadPathGrid();
astar.Region = new Rect2I(0, 0, MapSize, MapSize);
astar.CellSize = new Vector2I(1, 1);
astar.DiagonalMode = AStarGrid2D.DiagonalModeEnum.OnlyIfNoObstacles;
@@ -836,6 +838,23 @@ public partial class MapGenerator : TextureRect
}
}
+ ///
+ /// How much extra a step costs near an already-drawn road. This is what stops new
+ /// roads from lying on top of old ones — it is SEPARATION, not terrain avoidance,
+ /// so it is unrelated to the mountain/beach/wasteland weights.
+ ///
+ /// It used to be 10,000, which was far past "discourage" — a normal step costs 1,
+ /// so it made the ground near a road effectively infinite. That wrecked the search:
+ /// every later road had to weigh routes costing tens of thousands, which is exactly
+ /// the situation A* handles by giving up on being clever and searching everything.
+ /// Worse, it compounded — each road drawn made the next one slower.
+ ///
+ /// 250 still means "strongly prefer not to run alongside an existing road" (250x a
+ /// normal step) without dwarfing the search. Raise it if parallel roads crowd
+ /// together; lower it if roads take silly detours to avoid each other.
+ ///
+ private const float ROAD_REPULSION_PENALTY = 250f;
+
private void PenalizePath(AStarGrid2D astar, Vector2[] path, int radius)
{
int step = Mathf.Max(1, radius / 2);
@@ -847,8 +866,7 @@ public partial class MapGenerator : TextureRect
int ny = Mathf.Clamp((int)p.Y + dy, 0, MapSize - 1);
var cell = new Vector2I(nx, ny);
if (!astar.IsPointSolid(cell)) {
- // The true Iron Curtain
- astar.SetPointWeightScale(cell, astar.GetPointWeightScale(cell) + 10000f);
+ astar.SetPointWeightScale(cell, astar.GetPointWeightScale(cell) + ROAD_REPULSION_PENALTY);
}
}
}
@@ -950,6 +968,44 @@ public partial class MapGenerator : TextureRect
}
// Place this at the very bottom of the file!
+///
+/// The road pathfinder's grid, with one change from stock: a scaled-up estimate.
+///
+/// WHY THIS EXISTS. A* explores outward from the start until the cheapest route it
+/// has found is provably the best. It decides how far to keep looking by comparing
+/// what a route has cost so far against an ESTIMATE of what remains. Godot's stock
+/// estimate is plain straight-line distance — it assumes every step costs 1.
+///
+/// Our steps don't cost 1. A step through mountains costs up to 401, and a step near
+/// an existing road used to cost 10,001. So the estimate was wildly optimistic about
+/// the remaining journey, and A* responded the only way it can: by refusing to rule
+/// anything out and searching almost the whole island for every single road.
+///
+/// Multiplying the estimate makes the search commit to a direction sooner. It's a
+/// well-known trade ("weighted A*"): paths are allowed to be up to HEURISTIC_WEIGHT
+/// times more expensive than the theoretical best, in exchange for exploring far less.
+/// We want believable roads, not provably-optimal ones, so that's a good trade.
+///
+/// It does NOT make roads climb mountains. Crossing a ridge costs hundreds of times
+/// more than walking around it — a 3x bias nowhere near pays for that.
+///
+public partial class RoadPathGrid : AStarGrid2D
+{
+ ///
+ /// How strongly the search commits toward the goal.
+ /// 1.0 = stock behaviour: guaranteed-shortest, explores enormously.
+ /// 3.0 = default: still routes sensibly around terrain, explores far less.
+ /// higher = faster still, but paths get progressively less considered.
+ /// Raise it if generation is still slow; lower it if roads start looking careless.
+ ///
+ public const float HEURISTIC_WEIGHT = 3.0f;
+
+ public override float _EstimateCost(Vector2I fromId, Vector2I toId)
+ {
+ return fromId.DistanceTo(toId) * HEURISTIC_WEIGHT;
+ }
+}
+
public partial class MapDrawProxy : Control
{
public MapGenerator Source;