Navigation
Usage guide of ai.navigation: layered paths from live collision, the agent, settings and defaults, route and steer, the navigation.plan hook, limits, engines.
Navigation and paths (ai.navigation) #
Paths for characters and units, computed from the live collision of your game: layered floors, agent size, slopes, steps, moving blockers. Readable offline: gripforge docs ai-navigation, or the MCP tool gripforge_docs (topic ai-navigation).
What it is for #
This is the path search of the catalogue: a kit or a game never writes another one. It bakes a layered grid from the collision boxes and triangle meshes registered in the physics port, finds a route between two points for an agent of a given size, and keeps a transient path per actor that it hands out waypoint by waypoint. It never moves anyone: the actor's own collision controller applies the motion, and nothing is teleported.
Who uses it:
npc.core: NPC goals follow its paths (installingai.navigationis what turns path following on).character.top_down: click to move (the player is the actor, around walls and through doors). Without the service, or withclickPathset to straight, the path is the straight line.moba.lanes: withpathfindingset to navigation, lanes ask forworld.navigationpaths; its default stays its map grid, since a bake of a large MOBA map costs seconds per route against milliseconds for the grid.units.commandkeeps its own flow field on purpose: one search from a goal shared by every unit of a group order.
It requires only game.engine. Visual models do not become colliders: a replacement model must bring its real collision through the physics port.
Turn it on #
gripforge_gamekit_install { project_id: "gkp_…", id: "ai.navigation" }
gripforge_gamekit_configure { project_id: "gkp_…", id: "ai.navigation",
config: { "agent": { "radius": 0.5, "height": 2, "maxSlope": 40, "stepHeight": 0.4 } } }
Only the keys that differ from the defaults need to be written. The full reference is gripforge kit api ai.navigation.
Settings and defaults #
| Key | Default | Meaning | |
|---|---|---|---|
agent | radius 0.35, height 1.8, maxSlope 45, stepHeight 0.35 | the default agent: capsule radius and total height in metres, steepest slope in degrees, highest step | |
cellSize | 1 | grid cell in metres (0.25 to 4); reduce it for narrow passages | |
padding | 8 | metres around the requested endpoints baked with them | |
ground | true | include the world.ground height surface (for analytic-ground controllers); false for collision-only interiors or terrain with holes | |
approach | 3 | a goal inside a body (a parked car) or off the floor ends at the nearest reachable floor within this distance; 0 means exact endpoints | |
smooth | true | straighten grid steps into straight segments checked like baked edges | |
retrySeconds | 0.5 | an actor whose route is not ready asks again when its goal or the static world changes, otherwise at most this often | |
maxCells, maxLayers | 16384, 8 | bounds of a bake (floors per cell up to 16) | |
plansPerTick | 4 | routes planned per tick | |
sharedBakeCells | 16384 | a world of at most this many cells is baked once for every agent | |
bakeTile | 16 | the baked region is widened to whole tiles of this size; 0 bakes the region as asked | |
bakeCellsPerTick, bakeBudgetMs | 1024, 4 | how steer() spreads heavy work over ticks while the actor waits | |
runtime | worker | in a browser, where bakes and checks run: worker or sync (URL parameter `navRuntime=sync | worker` overrides) |
layerOverflow | block | Rust: a cell with too many floors is made non-navigable (block) or the bake is rejected (reject) |
Budgets are not moddable.
The service #
route(from, to, agent?) answers at once with ready and the exact endpoints and waypoints, or unreachable, outside, unavailable, budget_exceeded. steer(actorId, from, to, agent?) keeps a path per actor and returns the next waypoint, or waiting while the bake is made (a click-to-move hero walks straight meanwhile; an actor whose goal moved by 2 m at most keeps its route). forget(actorId) drops a path and invalidate() throws the bakes away (needed after ground samplers were changed from outside). On the web, request(from, to, agent) answers a promise.
Bakes are kept until the static world or the agent changes; a request inside an already baked region of the same agent reuses it. Moving bodies never cause a bake: they are prepared once per revision, and the next swept segment of a followed path is checked before it is used. A blocked segment requests a new route, and a door registered as a physics body (closed, removed or moved) is detected by itself. Terrain edits, scene changes and streamed chunks invalidate the bake. Saves keep NPC goals and the paths being followed, so a loaded game takes the same steps.
Hook and event #
Hook navigation.plan { actor, from, to, radius, height, maxSlope, stepHeight, approach } runs before each plan: a tap may move the goal, change the clearance or the approach; cancelled, the route is unreachable with the reason cancelled: <reason>.
{ "id": "giants_wide", "hook": "navigation.plan", "match": { "actor": "troll" }, "set": { "radius": 1.2, "height": 3.5 } }
Event navigation.blocked { actor, status, reason }: once, when a steered actor gets a route that is not ready. An NPC then stops rather than walk through an obstacle. A mod may set cellSize, padding, ground, approach, smooth, retrySeconds and agent.
What it is not #
A layered sampled grid with cardinal connectivity, not Recast, not a polygon union, not a crowd solver and not a jump or climb planner. Each X/Z cell can hold several supported floors (bridges and interiors do not merge with the ground below). Thin walls and too little headroom are rejected by triangle tests; curved primitives are conservative box blockers, not walkable supports; moving bodies block but are not platforms. A square footprint around the capsule makes narrow passages conservative: reduce cellSize. Holes smaller than the cell need an explicit collision blocker. unreachable means no validated path inside the queried region, not proof that a distant detour does not exist. A bake is bounded by cells, layers and 200000 triangles; an oversized query fails explicitly.
Engines #
| Engine | Support |
|---|---|
| Web | the service, with the worker runtime in a browser |
| Rust (native player, libgfkits) | the same service, hook, event and save, checked against the web by parity fixtures |
| Godot, Unity, Unreal | no navigation service of this kit; the collision data contract could feed other adapters |
When it does not do what you expect #
| Symptom | Cause and fix |
|---|---|
| NPCs do not walk to their goal | ai.navigation is not installed, or the models have no collision in the physics port |
unreachable through a narrow gap | cellSize too coarse for the agent; reduce it or reduce agent.radius |
| The hero walks straight at first | waiting while the bake is made; that is the design, the route arrives a tick or two later |
| Paths ignore a changed ground | call invalidate() after changing a ground sampler from outside |
| A goal inside a parked car fails | raise approach, or set it to 0 for exact endpoints and handle the failure |
| Stairs are not climbed | stepHeight is lower than the stair, or the slope is over maxSlope |