From 297e1f579e56955d5e381007d67ff50dcd9737a9 Mon Sep 17 00:00:00 2001 From: Arkadiusz Sygulski Date: Wed, 3 Jun 2026 21:32:17 +0200 Subject: [PATCH] Fix AStar overflowing the priority queue on twisted paths (#4149) Resolves pathfinding issue: https://discord.com/channels/1359946986937258015/1458870041964445706 ## Description: BucketQueue requires `maxF` to be defined. Estimating it is much more complicated than the code assumed. This caused the bucket to overflow on certain paths, mostly (a) twisty paths - defined as one which must traverse both ways along the same axis, (b) maps where height > width, because we used `width ** 2` to estimate size instead of `width * height` (iirc height was not easily accessible). This PR replcaes BucketQueue with already existing MinHeap. `AStar.AbstractGraph` is already specialized in traversing `AbstractGraph`, so I have dropped the `useMinHeap` option and instead made it the default path. Otherwise we'd be leaving dead code. The max priority was also fixed to account for duplicate connections, abstract graph is already very small so it should not affect (and in my testing does not) affect performance. **Before** image **After** image ## Please complete the following: - [ ] I have added screenshots for all UI updates - [ ] I process any text displayed to the user through translateText() and I've added it to the en.json file - [ ] I have added relevant tests to the test directory ## Please put your Discord username so you can be contacted if a bug or regression is found: moleole --- .../algorithms/AStar.AbstractGraph.ts | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/core/pathfinding/algorithms/AStar.AbstractGraph.ts b/src/core/pathfinding/algorithms/AStar.AbstractGraph.ts index 36f4c958a..7671e64f9 100644 --- a/src/core/pathfinding/algorithms/AStar.AbstractGraph.ts +++ b/src/core/pathfinding/algorithms/AStar.AbstractGraph.ts @@ -1,11 +1,10 @@ import { PathFinder } from "../types"; import { AbstractGraph } from "./AbstractGraph"; -import { BucketQueue, MinHeap, PriorityQueue } from "./PriorityQueue"; +import { MinHeap, PriorityQueue } from "./PriorityQueue"; export interface AbstractGraphAStarConfig { heuristicWeight?: number; maxIterations?: number; - useMinHeap?: boolean; // Use MinHeap instead of BucketQueue (better for variable costs) } export class AbstractGraphAStar implements PathFinder { @@ -34,17 +33,10 @@ export class AbstractGraphAStar implements PathFinder { this.cameFrom = new Int32Array(numNodes); this.startNode = new Int32Array(numNodes); - // For abstract graphs with variable costs, MinHeap may be better - // BucketQueue is O(1) but requires integer priorities - if (config?.useMinHeap) { - this.queue = new MinHeap(numNodes); - } else { - // Estimate max priority: weight * (mapWidth + mapHeight) - // Use cluster size * clusters as approximation - const maxDist = graph.clusterSize * Math.max(graph.clustersX, 10) * 2; - const maxF = this.heuristicWeight * maxDist; - this.queue = new BucketQueue(maxF); - } + // MinHeap: abstract edge costs are variable and long routes accumulate f-values beyond any cheap bucket-range estimate. + // A* also pushes lazy duplicates (a node re-enters the queue each time its gScore improves), so live entries can exceed + // numNodes; size for the worst case — one push per directed edge relaxation — to avoid the heap's resize-with-error path. + this.queue = new MinHeap(numNodes + graph.edgeCount * 2); } findPath(start: number | number[], goal: number): number[] | null {