Traversal and Pathfinding
Traversal checks if a single move is allowed. Pathfinding finds the best route across multiple moves. Both respect gates and use context (player inventory, game state) to determine accessibility.
How It Works
Traversal asks: "Can I move from A in direction D?" It checks if a connection exists and whether any gate blocks it. You provide context—the player's inventory, discovered passages, and flag store—and get back an allowed/blocked result with a reason.
Pathfinding asks: "What's the best route from A to B?" The library uses Dijkstra's algorithm with weighted costs. Connections have a default cost of 1, but you can make some paths longer (wading through swamp) or shorter (express elevator). Locked gates block paths unless the player has the key.
Start: [Entrance]
Goal: [Treasury]
Path: Entrance → Hall → Treasury
Cost: 1 + 1 = 2
If Hall→Treasury is locked and player lacks key:
Path: null (no valid route)
Basic Usage
import { createSpatialGraph, Direction } from '@motioneffector/spatial'
const graph = createSpatialGraph()
graph.createNode('entrance')
graph.createNode('hall')
graph.createNode('treasury')
graph.connect('entrance', Direction.NORTH, 'hall')
graph.connect('hall', Direction.EAST, 'treasury')
// Check single traversal
const canMove = graph.canTraverse('entrance', Direction.NORTH, {
inventory: ['torch']
})
console.log(canMove.allowed) // true
// Find path between locations
const path = graph.findPath('entrance', 'treasury')
console.log(path) // ['entrance', 'hall', 'treasury']
// Check if destination is reachable
const reachable = graph.canReach('entrance', 'treasury')
console.log(reachable) // true
// Get distance (total cost)
const distance = graph.getDistance('entrance', 'treasury')
console.log(distance) // 2
Key Points
- Traversal returns detailed results —
{ allowed, reason, gateId }tells you exactly why movement failed. - Pathfinding uses weighted costs — Connections default to cost 1. Higher costs make paths less attractive to the algorithm.
- Locked gates block pathfinding — Unless the player has the required key in their context, the path won't route through locked gates.
getReachable()finds all accessible nodes — Useful for fog-of-war, minimap updates, or "where can I go from here?"
Examples
Weighted Paths
Make some routes more expensive to traverse.
// Normal hallway
graph.connect('a', Direction.NORTH, 'b', { cost: 1 })
// Swamp (slow going)
graph.connect('a', Direction.EAST, 'c', { cost: 5 })
// Both lead to 'd'
graph.connect('b', Direction.EAST, 'd', { cost: 1 })
graph.connect('c', Direction.NORTH, 'd', { cost: 1 })
// Pathfinding prefers a→b→d (cost 2) over a→c→d (cost 6)
const path = graph.findPath('a', 'd')
console.log(path) // ['a', 'b', 'd']
Context-Aware Pathfinding
Pass player state to pathfinding.
graph.setGate('hall', Direction.EAST, {
id: 'vault-door',
locked: true,
keyId: 'vault-key'
})
// Without key: can't reach treasury
const noKeyPath = graph.findPath('entrance', 'treasury', {
context: { inventory: [] }
})
console.log(noKeyPath) // null
// With key: path exists
const withKeyPath = graph.findPath('entrance', 'treasury', {
context: { inventory: ['vault-key'] }
})
console.log(withKeyPath) // ['entrance', 'hall', 'treasury']
Finding All Reachable Locations
Discover everywhere the player can currently go.
const reachable = graph.getReachable('entrance', {
context: { inventory: ['brass-key'] }
})
console.log(reachable) // ['entrance', 'hall', 'treasury', 'armory', ...]
Limiting Search Distance
Restrict pathfinding to nearby locations.
// Only find nodes within 3 hops
const nearby = graph.getReachable('entrance', { maxDistance: 3 })
// Limit path length
const shortPath = graph.findPath('entrance', 'treasury', { maxLength: 2 })
// Returns null if path requires more than 2 nodes
Custom Traversal Logic
Override the default gate checking with your own logic.
const graph = createSpatialGraph({
canTraverse: (connection, gate, context) => {
// Custom logic: require specific level
if (context.playerLevel < 10) {
return { allowed: false, reason: 'Level too low' }
}
// Fall through to default behavior for gates
if (gate?.locked && !context.inventory?.includes(gate.keyId)) {
return { allowed: false, reason: 'locked', gateId: gate.id }
}
return { allowed: true }
}
})
Related
- Gates — How gates control access
- Pathfinding Guide — Practical pathfinding examples
- Pathfinding API — Full method reference