Sounds like the algorithm is working out pretty well. You should go over a checklist of the various situations and corner cases you might need to handle. Here's what I can think of at the moment:
- Does it support flying units? You'll probably need to look for interesting nodes on the walls and ceilings of caverns.
At present, no. That will be resolved in the next stage, when I add support for diagonal vertical movement. This will also ease support for hills (in effect, making them uninteresting). Aquatic- and magma-pathers will be done after flying, as they'll be functionally identical, just using a different set of interesting points.
- Does it update efficiently when the environment is altered? Moving liquids can cause a lot of modifications, so updating has to be fast.
Based on Toady's posts, static water---lakes, rivers which flow without drainage except at edge-of-map---cause no particular constraints, as internally little if anything gets modified. Last I checked, the only tiles which might be considered truly in flux are the ones at the drain end of a river, but they'll typically be static with respect to aquatic and ground passability. However, bridges, floods and the like
are strongly volatile, but I've not yet begun to incorporate mutation in the map. I can already see a fairly clean way to handle volatile restrictions to movement---like doors---by dynamically toggling an interesting node's passability and/or interesting status without modifying routing tables. Long-term mutations---digging, and the like---will warrant permanent routing table changes.
More to the point, calculating if an interesting node is a constant-time operation. Calculating the region(s) if belongs to... that I have no idea how long it will take, as I'm currently not over-pleased with my current region system (its around O(n^3) slow, but for typically small n). Calculating its neighbors is O(ri), where r is the average number of regions an interesting point belongs to, and i is the average number of interesting points in a region.
- Does it handle all possible terrain including mountains, cliffs, caves and any other configuration you can imagine?
So far, yes: Cliffs simply have a point where neighbor nodes stop, and there's thus no line-of-sight past that point. Hilly/mountainous regions are handles at present, but are currently no more efficient than the current A* algorithm until I get diagonal vertical pathing implemented.
- Dwarves slow down when they pass each other on the same tile. Does your algorithm allow them to path around each other?
Both possibilities are eligible---but that's more a concern for the local movement AI. The global pathing AI would hand out the route (calculated in full when requested), the local one would be responsible for minor course corrections as its being executed---up to, and including, requesting a new path if the current one becomes invalid.
- Does it allow for different types of movement rules? For example, amphibious creatures can path through water and pets cannot path through certain doors.
The system will need to track 3-4 sets of interesting nodes:
- Ground-nodes.
- Aquatic nodes.
- Magma nodes.
- (Possibly) Arial nodes. These may not be necessary, as a flier will use the ground nodes when on the ground, and will not need any nodes if its pathing to a z-level above terrain (natural or artificial).
Pets will require a somewhat special case, in that when generating a global path, door restrictions will need to be respected. I recommend this be implemented as a completely separate pathing routine, because despite its similarity, you
don't want a conditional nested so deeply in a fast-spinning loop (like A*) unless it really is required.
- How does the worst case processing time compare to A*?
Worst case occurs on a 3-tile path that does not have line-of-sight. Run time is approx 2x, but the time-to-compute for both A* & mine are so small, its hard to get a good figure.
- How does the worst case memory usage compare to A*?
This I've not tested, yet. Worst case for static allocation (needed at all times, and thus saved in the map itself) would be ~O(nm), where n is the number of interesting nodes, and m is the average number of interesting nodes connected to. If you're caching paths between interesting nodes (which I advise), worst case for memory is nearer ~O(nmp), where n & m are as above, and p is the average # of nodes between n & m.
- Are the paths found always near optimal?
As optimal as A* with the same heuristic function.
Seems like you have most of those questions handled. I'm still hoping that you can get a bit more than a 10x speedup. It seems like it should be possible, given how many nodes can be eliminated over A*.
I traced most of this down to being a result of an inefficiency that I accidentally carried over from version 1 of my code, and realized while I was writing a response:
My algorithm first checks to see if the nodes share a line-of-sight. Which currently, I do not have optimized to use the regions, so it currently takes O(p), where p is the number of nodes on the line-of-sight path from start to finish---whereas, I could instead check if the nodes belong to a common region, a small O(r), where r is the average number of regions a node belongs to, usually < 3. Win.
One open question I have, for Toady: what are the costs for cardinal (NESW), diagonal and vertical moves?