Add GetReachable movement-range query to Pathfinder#22
Merged
Conversation
Add Pathfinder.GetReachable(IAgent, Coordinate from, float budget), a uniform-cost (Dijkstra) flood fill that returns every cell whose cheapest path cost from the origin is within a movement budget. Useful for tactics/ strategy "where can this unit move?" mechanics. It reuses the same movement rules as GetPath (diagonal movement, corner-cutting, occupied cells, agent clearance) and the same pooled, reusable cell buffer. Per-step cost is StraightMovementMultiplier / DiagonalMovementMultiplier, multiplied by the destination cell's Weight when cell weighting is enabled. Results are returned as a poolable, IDisposable RangeResult of ReachableCell (Coordinate + Cost), mirroring PathResult. Supporting changes: - UnsafePriorityQueue.UpdatePriority: a real decrease-key (cascade up/down) used by the bounded Dijkstra search. - Fix Utils.CellsComparison to sort X-major then Y-minor so the Cell[] (CellsArray) constructor's sorted layout matches GetCell's x*Height+y indexing — consistent with the matrix/holder init modes. The previous (Y, X) ordering transposed the grid and scrambled neighbor adjacency; it was masked in existing GetPath tests because they use symmetric (x==y) coordinates. Tests: 13 new reachability tests (budget bounds, diagonals, walls, weights, agent clearance, asymmetric-origin orientation, disposal, reuse). Full suite 78/78 green. Docs: new api/RangeResult, api/ReachableCell, guides/movement-range; updated Pathfinder API doc, README, and CLAUDE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The reachability flood fill computed step cost as `travel * Weight`, while the main A* pathfinder folds weight in additively. Cell.Weight defaults to 0 and IsCellWeightEnabled defaults to true, so for any grid that doesn't set weights the multiplicative model made every step cost `travel * 0 = 0` — the budget never bit and GetReachable returned the entire walkable board. GetPath was unaffected because its weight term is additive (`+ 0`). Change GetReachableStepCost to `travel + GetCellWeightMultiplier(neighbor)`, reusing the same helper as the main pathfinder so the two cost models treat weight consistently. With a default Weight of 0 the step cost is now just the travel multiplier, and budgets bound the search as intended. Tests: add a regression test that reproduces the client's scenario (default Weight 0 + weighting enabled) and asserts the result stays within budget (13-cell diamond, not the full 100-cell board) — verified to fail on the old multiplicative formula. Update the existing weight test (weight is now additive: a normal neighbour costs travel+weight = 2) and bump a reuse test's budget above the new minimum step cost. Docs/CLAUDE.md updated to describe weight as additive. Full suite 79/79 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
migus88
added a commit
that referenced
this pull request
Jun 24, 2026
Resolve the MazeBenchmarkRunner.cs conflict by keeping the restructured Suites/ version with the competitor benchmarks enabled (master had commented them out in the now-relocated file). Brings in GetReachable, so ReachabilityBenchmarkRunner compiles and the whole solution builds again. Co-Authored-By: Claude Opus 4.8 (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.
What
Adds
Pathfinder.GetReachable(IAgent agent, Coordinate from, float budget)— a movement-range query that returns every cell whose cheapest path cost from the origin is within a budget. This is the core mechanic behind tactics/strategy games that highlight where a unit can move.It is a bounded uniform-cost (Dijkstra) flood fill that reuses the existing infrastructure:
GetPath— diagonal movement, corner-cutting, occupied cells, and agent clearance (Size > 1).Cell[]buffer andunsafehot path.Per-step cost is
StraightMovementMultiplier(cardinal) /DiagonalMovementMultiplier(diagonal), multiplied by the destination cell'sWeightwhenIsCellWeightEnabledis set. The origin is always included with cost0.Results come back as a poolable,
IDisposableRangeResultofReachableCell(Coordinate+Cost), mirroringPathResult.Contains/TryGetCostconvenience lookups are provided.Why
GetPathanswers "how do I get from A to B?". Range/reachability ("where can I go?") is the other half of grid movement and is commonly requested for turn-based and tactics games. It can't be expressed withGetPath(no single destination), so it needs its own search.Supporting changes
UnsafePriorityQueue.UpdatePriority— a real decrease-key (re-heapify via cascade up/down) used by the bounded Dijkstra search. The A* hot path is unchanged.Utils.CellsComparisonto sort X-major then Y-minor so theCell[](CellsArray) constructor's sorted layout matchesGetCell'sx * Height + yindexing — consistent with the matrix/holder init modes which write cells atCoordinate.X * Height + Coordinate.Y. The previous(Y, X)ordering transposed the grid and scrambled neighbor adjacency. This was a latent bug masked in existingGetPathtests because they use symmetric (x == y) start/destination coordinates; it surfaced immediately for the range query. A dedicated asymmetric-origin test now guards against regressing it.Verification
dotnet build src/mpath-source/Migs.MPath.sln— succeeds, 0 errors.dotnet test src/mpath-source/Migs.MPath.Tests/Migs.MPath.Tests.csproj— 78/78 passing, including 13 new reachability tests (zero budget, negative budget, Manhattan diamond, diagonals, walls, cell weights, larger-agent clearance, asymmetric-origin orientation, invalid origin, null agent, disposal, instance reuse) and all pre-existing tests (confirming theCellsComparisonfix introduced no regressions).Docs
docs/api/RangeResult.mdanddocs/api/ReachableCell.md; updateddocs/api/Pathfinder.mdand the API index.docs/guides/movement-range.md(linked from the docs index).README.md(feature list + quick-start snippet) andCLAUDE.md(architecture notes for the new partial, data types, queue decrease-key, and theCellsComparisonordering invariant).🤖 Generated with Claude Code