Conversation
`astar` returns paths made of graph edges, so on a grid it can only travel in the directions the grid offers and crosses open ground as a staircase. Theta* keeps the same search order but, on reaching a node, first asks whether the node it came from can see the successor directly, and joins them in a straight line when it can. The geometry stays with the caller. The algorithm needs one thing `astar` does not — given two nodes that may not be neighbours, can you travel straight between them and at what cost — and that is a single closure, so `N` remains an opaque hashable value and `C` remains `Zero + Ord + Copy`. Nothing here gains a dependency on coordinates or floating point. Two ways this differs from the rest of the crate, both documented on the function: the path is not optimal, and consecutive nodes of the path need not be neighbours, since the segments between waypoints are straight lines rather than edges. `SmallestCostHolder` is shared with `astar` rather than duplicated; it becomes visible to the module, with no change to the public API. Tests cover the three properties that matter: with sight always blocked the result is exactly what `astar` returns, on open ground the path collapses to its endpoints, and on random maps the path is never longer than `astar`'s while every segment is a clear line whose lengths add up to the cost reported.
tachsin
force-pushed
the
feat/theta-star
branch
from
September 11, 2026 16:33
20f5c1a to
f54a9c0
Compare
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.
Draft for #829 — something concrete to react to, not a finished proposal. If the answer to question 1 over there is "no", close this and nothing is lost.
What it does
astarwith one changed relaxation step. On reaching a node, the search first asks whether the node it came from can see the successor directly; if so the successor is attached to that node by a straight line instead of stepping through the current one. The waypoints stay graph nodes, the segments between them do not.sightgives the cost of a straight line between two nodes that need not be neighbours, orNonewhen it is blocked. That is the only thing Theta* needs thatastardoes not, so line-of-sight and the straight-line metric both stay with the caller and nothing here gains a dependency on coordinates or floating point.150 lines of algorithm, 207 of tests.
The two caveats from the issue, as implemented
Both are documented on the function under their own heading rather than buried:
astarpath, usually close to the true any-angle optimum, but no guarantee — that needs a visibility graph. First inexact search in the crate.path.windows(2)are straight lines, not edges ofsuccessors.There is also a stated requirement on the caller:
sighthas to agree withsuccessorsin the triangle-inequality sense, or the shortcut can make a path worse and the reported cost stops matching it.Testing
Three properties, on 8-connected grids with randomly blocked cells:
sightalways returningNone, the result equalsastarexactly — path and cost, over 40 random maps. The shortcut is the only difference, so refusing every shortcut must reproduceastar.[start, goal]at exactly the straight-line cost.astar, every segment is a clear line, the segment lengths sum to the cost reported, and no waypoint repeats. The test also asserts it is shorter thanastaron most maps, so a version that silently stopped taking shortcuts would fail rather than pass quietly.Plus a walled-off goal returning
None.296 tests pass, clippy and rustfmt clean.
One thing I would flag for review
SmallestCostHolderis shared withastarrather than duplicated — it becomespub(super)withpub(super)fields. No public API change, but it does meanastar.rsis touched by this. Happy to duplicate the 30 lines instead if you would rather the modules stayed independent.Still open from #829: whether
sightshould be one closure or two, whether a line-of-sight helper onGridwould be welcome, and whether Lazy Theta* is worth having alongside. None of those are settled here.