Skip to content

Fix AStar overflowing the priority queue on twisted paths#4149

Merged
evanpelle merged 1 commit into
openfrontio:mainfrom
Aareksio:fix/pathfinding-2026-06-03
Jun 3, 2026
Merged

Fix AStar overflowing the priority queue on twisted paths#4149
evanpelle merged 1 commit into
openfrontio:mainfrom
Aareksio:fix/pathfinding-2026-06-03

Conversation

@Aareksio
Copy link
Copy Markdown
Contributor

@Aareksio Aareksio commented Jun 3, 2026

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 3, 2026

Review Change Stack

Walkthrough

The A* pathfinding algorithm removes the configurable useMinHeap option from its public configuration interface. The constructor now always instantiates MinHeap for the open set, eliminating conditional queue selection logic and removing the unused BucketQueue import.

Changes

A Queue Implementation Simplification*

Layer / File(s) Summary
Remove useMinHeap config and hardcode MinHeap
src/core/pathfinding/algorithms/AStar.AbstractGraph.ts
AbstractGraphAStarConfig interface loses the useMinHeap field, and the A* constructor removes the conditional branching that selected between BucketQueue and MinHeap. The algorithm now always uses MinHeap with size calculated from numNodes + graph.edgeCount * 2. The BucketQueue import is removed.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

A* walks a clearer path today, ✨
No branching choice to stray—
MinHeap alone, forever true,
One queue to rule them all, bright and new. 🎯

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: replacing BucketQueue with MinHeap to fix overflow issues on twisted paths, which is the core objective of the PR.
Description check ✅ Passed The description is related to the changeset, providing detailed context about the pathfinding issue, the root cause, and the solution implemented in the code changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the small-fix Small fix (≤ 50 lines) — auto-applied by PR gate label Jun 3, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/pathfinding/algorithms/AStar.AbstractGraph.ts`:
- Around line 5-8: Write a regression test that constructs an AbstractGraph
instance and invokes AbstractGraphAStar.findPath with a config that forces many
lazy duplicate pushes (enough re-entries to exceed the previous numNodes-sized
heap) so the MinHeap must be sized correctly; mock/spy on console.error and
assert it is never called with the string "MinHeap capacity exceeded", and also
assert the returned path is correct/valid. Use the AbstractGraphAStar.findPath
entrypoint and supply a graph topology (e.g., a grid or crafted node re-entry
pattern) and A* config (heuristicWeight/maxIterations) to trigger duplicate
re-insertions, and verify no capacity error and that path length/contents match
expected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 616753df-1d3d-42db-bd19-74b95f0aeb4b

📥 Commits

Reviewing files that changed from the base of the PR and between 48609fa and cce83e5.

📒 Files selected for processing (1)
  • src/core/pathfinding/algorithms/AStar.AbstractGraph.ts

Comment on lines 5 to 8
export interface AbstractGraphAStarConfig {
heuristicWeight?: number;
maxIterations?: number;
useMinHeap?: boolean; // Use MinHeap instead of BucketQueue (better for variable costs)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find any lingering callers passing the removed option, and existing tests for this file.
rg -nP '\buseMinHeap\b' -C2 --type=ts
rg -nP 'AbstractGraphAStar' -C2 --glob '**/*test*' --glob '**/*spec*'

Repository: openfrontio/OpenFrontIO

Length of output: 49


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Re-check remaining callers for `useMinHeap`
echo "=== useMinHeap occurrences (TypeScript) ==="
rg -n --no-heading '\buseMinHeap\b' --type=ts -S .

# 2) Find tests/specs that mention AStar / pathfinding
echo
echo "=== AStar-related tests/specs ==="
rg -n --no-heading 'AStar|AbstractGraphAStar|pathfinding|PriorityQueue|MinHeap|useMinHeap' \
  --glob '**/*test*.*' --glob '**/*spec*.*' .

# 3) Inspect the AStar file for queue/resize/numNodes overflow logic hints
echo
echo "=== Key queue/overflow keywords in AStar.AbstractGraph.ts ==="
rg -n --no-heading 'queue|PriorityQueue|numNodes|resize|overflow|twisty|height|width|maxIterations|heuristicWeight' \
  src/core/pathfinding/algorithms/AStar.AbstractGraph.ts

# 4) Show a small window around interface and config usage
echo
echo "=== Interface + config usage around AbstractGraphAStarConfig ==="
rg -n --no-heading 'AbstractGraphAStarConfig|heuristicWeight|maxIterations' \
  -n src/core/pathfinding/algorithms/AStar.AbstractGraph.ts

Repository: openfrontio/OpenFrontIO

Length of output: 111


🏁 Script executed:

#!/bin/bash
set +euo pipefail

echo "=== useMinHeap occurrences (TypeScript) ==="
rg -n --no-heading '\buseMinHeap\b' --type=ts -S . || true

echo
echo "=== AStar-related tests/specs ==="
rg -n --no-heading 'AStar|AbstractGraphAStar|pathfinding|PriorityQueue|MinHeap|useMinHeap' \
  --glob '**/*test*.*' --glob '**/*spec*.*' . || true

echo
echo "=== Key queue/overflow keywords in AStar.AbstractGraph.ts ==="
rg -n --no-heading 'queue|PriorityQueue|numNodes|resize|overflow|twisty|height|width|maxIterations|heuristicWeight' \
  src/core/pathfinding/algorithms/AStar.AbstractGraph.ts || true

echo
echo "=== Interface + config usage around AbstractGraphAStarConfig ==="
rg -n --no-heading 'AbstractGraphAStarConfig|heuristicWeight|maxIterations' \
  src/core/pathfinding/algorithms/AStar.AbstractGraph.ts || true

Repository: openfrontio/OpenFrontIO

Length of output: 5383


🏁 Script executed:

#!/bin/bash
set +euo pipefail

# Inspect the relevant AStar file sections
echo "=== AStar.AbstractGraph.ts (relevant sections) ==="
sed -n '1,260p' src/core/pathfinding/algorithms/AStar.AbstractGraph.ts

# Look for direct test coverage references
echo
echo "=== Test files referencing AStar.AbstractGraph / MinHeap / PriorityQueue ==="
rg -n --no-heading 'AStar\.AbstractGraph|AbstractGraphAStarConfig|new (MinHeap|PriorityQueue)|MinHeap|PriorityQueue' \
  --glob '**/*test*.*' --glob '**/*spec*.*' tests . || true

# Look for useMinHeap in the whole repo (non-type limited)
echo
echo "=== useMinHeap occurrences (whole repo) ==="
rg -n --no-heading '\buseMinHeap\b' . || true

Repository: openfrontio/OpenFrontIO

Length of output: 7563


🏁 Script executed:

#!/bin/bash
set +euo pipefail

echo "=== AStar.AbstractGraph.ts (relevant sections) ==="
sed -n '1,260p' src/core/pathfinding/algorithms/AStar.AbstractGraph.ts

echo
echo "=== Test files referencing AStar.AbstractGraph / AbstractGraphAStarConfig / MinHeap / PriorityQueue ==="
rg -n --no-heading 'AStar\.AbstractGraph|AbstractGraphAStarConfig|new (MinHeap|PriorityQueue)|MinHeap|PriorityQueue' \
  --glob '**/*test*.*' --glob '**/*spec*.*' tests . || true

echo
echo "=== useMinHeap occurrences (whole repo) ==="
rg -n --no-heading '\buseMinHeap\b' . || true

Repository: openfrontio/OpenFrontIO

Length of output: 7590


🏁 Script executed:

#!/bin/bash
set +euo pipefail

echo "=== Where AbstractGraphAStar is used (src + tests) ==="
rg -n --no-heading '\bAbstractGraphAStar\b' src tests . || true

echo
echo "=== Where MinHeap/PriorityQueue are used ==="
rg -n --no-heading '\bMinHeap\b|\bPriorityQueue\b' src/core/pathfinding . || true

echo
echo "=== Check AStar wrapper/pathfinder implementations that might hit AbstractGraphAStar ==="
rg -n --no-heading 'UniversalPathFinding|PathFinderStepper|ConnectedComponents|PathFinding\.(Air|Rail|Water)|PathFinder\.' \
  src/core/pathfinding tests/core/pathfinding || true

Repository: openfrontio/OpenFrontIO

Length of output: 12836


🏁 Script executed:

rg -n --no-heading 'WaterHierarchical|AbstractGraphAStar' src/core/pathfinding src/core/pathfinding/algorithms/ AStar.WaterHierarchical.ts

Repository: openfrontio/OpenFrontIO

Length of output: 2433


🏁 Script executed:

#!/bin/bash
set +euo pipefail

# Show MinHeap overflow/resize logic
echo "=== PriorityQueue.ts (MinHeap capacity/resize checks) ==="
rg -n --no-heading 'capacity|exceeded|resize|grow|push\(' src/core/pathfinding/algorithms/PriorityQueue.ts
echo
sed -n '1,220p' src/core/pathfinding/algorithms/PriorityQueue.ts

# Show WaterHierarchical algorithm using AbstractGraphAStar
echo
echo "=== AStar.WaterHierarchical.ts ==="
sed -n '1,220p' src/core/pathfinding/algorithms/AStar.WaterHierarchical.ts

# Show PathFinding.Water test coverage context
echo
echo "=== tests/core/pathfinding/PathFinding.Water.test.ts ==="
sed -n '1,240p' tests/core/pathfinding/PathFinding.Water.test.ts

Repository: openfrontio/OpenFrontIO

Length of output: 19250


🏁 Script executed:

#!/bin/bash
set +euo pipefail

# Show MinHeap overflow/resize logic
echo "=== PriorityQueue.ts (MinHeap capacity/resize checks) ==="
rg -n --no-heading 'capacity|exceeded|resize|grow|push\(' src/core/pathfinding/algorithms/PriorityQueue.ts
echo
sed -n '1,220p' src/core/pathfinding/algorithms/PriorityQueue.ts

# Show WaterHierarchical algorithm using AbstractGraphAStar
echo
echo "=== AStar.WaterHierarchical.ts ==="
sed -n '1,220p' src/core/pathfinding/algorithms/AStar.WaterHierarchical.ts

# Show PathFinding.Water test coverage context
echo
echo "=== tests/core/pathfinding/PathFinding.Water.test.ts ==="
sed -n '1,240p' tests/core/pathfinding/PathFinding.Water.test.ts

Repository: openfrontio/OpenFrontIO

Length of output: 19250


🏁 Script executed:

#!/bin/bash
set +euo pipefail

echo "=== Search tests for MinHeap overflow warning text ==="
rg -n --no-heading 'MinHeap capacity exceeded' tests src . || true

echo
echo "=== Search tests for PriorityQueue/MinHeap resize-related spies ==="
rg -n --no-heading 'capacity exceeded|Resizing, but this indicates a bug|MinHeap|PriorityQueue' tests . || true

echo
echo "=== Search tests that spy on console.error ==="
rg -n --no-heading 'spyOn\(console,\s*"error"\)|console\.error' tests . || true

Repository: openfrontio/OpenFrontIO

Length of output: 21191


🏁 Script executed:

#!/bin/bash
set +euo pipefail

echo "=== Search tests for MinHeap overflow warning text ==="
rg -n --no-heading 'MinHeap capacity exceeded' tests src . || true

echo
echo "=== Search tests for PriorityQueue/MinHeap resize-related spies ==="
rg -n --no-heading 'capacity exceeded|Resizing, but this indicates a bug|MinHeap|PriorityQueue' tests . || true

echo
echo "=== Search tests that spy on console.error ==="
rg -n --no-heading 'spyOn\(console,\s*"error"\)|console\.error' tests . || true

Repository: openfrontio/OpenFrontIO

Length of output: 21191


🏁 Script executed:

#!/bin/bash
set +euo pipefail

echo "=== PathFinder.Water wiring ==="
rg -n --no-heading 'Water\(game\)|PathFinding\.Water|new AStarWaterHierarchical|AStarWaterHierarchical|WaterHierarchical' src/core/pathfinding/PathFinder.ts src/core/pathfinding/PathFinderBuilder.ts src/core/pathfinding/PathFinder.*.ts src/core/pathfinding/*.ts src/core/pathfinding/**.ts || true

# Show the Water factory implementation if found in PathFinder.ts
rg -n --no-heading 'PathFinding\.Water|function Water|static Water|Water\(game\)' src/core/pathfinding/PathFinder.ts || true
sed -n '1,240p' src/core/pathfinding/PathFinder.ts

Repository: openfrontio/OpenFrontIO

Length of output: 8147


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== PathFinding.Water wiring (which A* gets used) ==="
rg -n --no-heading 'PathFinding\.Water|Water\(game\)|AStar\.WaterHierarchical|AStarWaterHierarchical|WaterHierarchical' src/core/pathfinding/PathFinder.ts src/core/pathfinding/PathFinder.*.ts src/core/pathfinding/**.ts || true

echo
echo "=== PathFinder.ts (where Water is built) ==="
sed -n '1,260p' src/core/pathfinding/PathFinder.ts

Repository: openfrontio/OpenFrontIO

Length of output: 7857


🏁 Script executed:

#!/bin/bash
set +euo pipefail

echo "=== Find miniWaterHPA/miniWaterGraph methods ==="
rg -n --no-heading 'miniWaterHPA\(|miniWaterGraph\(' src/core/game src/core -S || true

echo
echo "=== Inspect Setup util to see how test worlds are initialized ==="
rg -n --no-heading 'setup\("' tests/util/Setup.* tests -S || true
fd -t f Setup tests/util -x echo {} | head -n 20 || true
ls -la tests/util || true

Repository: openfrontio/OpenFrontIO

Length of output: 5681


🏁 Script executed:

#!/bin/bash
set +euo pipefail

echo "=== Find miniWaterHPA / miniWaterGraph ==="
rg -n --no-heading 'miniWaterHPA\(|miniWaterGraph\(' src/core/game src/core -S || true

echo
echo "=== Locate tests/util/Setup ==="
ls -la tests/util || true
fd -t f 'Setup.*' tests/util -a || true
fd -t f 'Setup.*' tests -a || true

echo
echo "=== Find how `setup('ocean_and_land')` is configured ==="
rg -n --no-heading 'setup\("ocean_and_land"|setup\("world"' tests -S || true

echo
echo "=== Open tests/util/Setup.ts (or closest match) ==="
setup_file=$(fd -t f 'Setup.*\.(ts|js|tsx)$' tests/util tests -a | head -n 1)
echo "Using: ${setup_file:-<not found>}"
if [[ -n "${setup_file}" ]]; then
  sed -n '1,240p' "$setup_file"
fi

Repository: openfrontio/OpenFrontIO

Length of output: 5835


Add a regression test for AbstractGraphAStar MinHeap overflow behavior

AStar.AbstractGraph.ts now sizes the MinHeap to avoid the “capacity exceeded”/resize path caused by lazy duplicate pushes.

  • Add a regression test that runs AbstractGraphAStar.findPath on an AbstractGraph setup that forces enough re-entries (lazy duplicates) to exceed the old numNodes-sized heap.
  • Assert console.error is not called with MinHeap capacity exceeded (and keep asserting the returned path is still valid).

There are no remaining useMinHeap usages in the repo, so caller updates are already handled by the type changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/pathfinding/algorithms/AStar.AbstractGraph.ts` around lines 5 - 8,
Write a regression test that constructs an AbstractGraph instance and invokes
AbstractGraphAStar.findPath with a config that forces many lazy duplicate pushes
(enough re-entries to exceed the previous numNodes-sized heap) so the MinHeap
must be sized correctly; mock/spy on console.error and assert it is never called
with the string "MinHeap capacity exceeded", and also assert the returned path
is correct/valid. Use the AbstractGraphAStar.findPath entrypoint and supply a
graph topology (e.g., a grid or crafted node re-entry pattern) and A* config
(heuristicWeight/maxIterations) to trigger duplicate re-insertions, and verify
no capacity error and that path length/contents match expected.

@github-project-automation github-project-automation Bot moved this from Triage to Development in OpenFront Release Management Jun 3, 2026
@evanpelle evanpelle added this to the v32 milestone Jun 3, 2026
Copy link
Copy Markdown
Collaborator

@evanpelle evanpelle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@evanpelle evanpelle merged commit 297e1f5 into openfrontio:main Jun 3, 2026
11 of 14 checks passed
@github-project-automation github-project-automation Bot moved this from Development to Complete in OpenFront Release Management Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

small-fix Small fix (≤ 50 lines) — auto-applied by PR gate

Projects

Status: Complete

Development

Successfully merging this pull request may close these issues.

2 participants