Game AI navigation & pathfinding
Move agents through a level intelligently and per-engine-correctly: pick a world representation,
run the right search, then follow the result with steering + local avoidance. This skill owns the
navigation stack; it stops where physics collision response and high-level behaviour design begin.
Version targets & the API ban-list (read first)
Navigation APIs were renamed hard between engine generations. Emitting an old name compiles into
nothing or silent breakage. Target these versions; never emit the banned column.
| Engine (target) |
NEVER emit (deprecated / removed) |
Use instead |
| Godot 4.x |
Navigation / NavigationMeshInstance / Navigation2D nodes |
NavigationServer2D/3D + NavigationRegion2D/3D |
| Godot 4.x |
agent.get_next_location() |
agent.get_next_path_position() |
| Godot 4.x |
NavigationAgent.set_target_location() |
set the target_position property |
| Godot 4.x |
reading velocity directly when avoidance is on |
feed set_velocity(), read the velocity_computed(safe) signal |
| Unity (AI Navigation pkg) |
the legacy Navigation window static bake, Navigation Static flag, built-in OffMeshLink component |
NavMeshSurface (com.unity.ai.navigation), NavMeshLink, NavMeshModifier |
| Unity |
agent.destination = p then reading a path same frame |
SetDestination(p), then gate on !pathPending && remainingDistance <= stoppingDistance |
| Unreal (UE5) |
hand-rolling A* over your own grid for pawns |
AAIController::MoveTo* over RecastNavMesh; BT MoveTo task |
| Unreal |
expecting a Static navmesh to react to spawned geometry |
set RecastNavMesh Runtime Generation = Dynamic (or Dynamic Modifiers Only) |
The two-layer mental model (center of gravity)
The single most common navigation bug is collapsing two jobs into one. Keep them separate:
- Global pathfinding — where to go. A discrete search (A* / navmesh query) over a static-ish
representation, run occasionally (on new goal, or throttled), returns a corridor of waypoints.
- Local steering + avoidance — how to move this frame. A cheap per-frame vector: follow the
corridor, dodge other agents and dynamic obstacles, respect acceleration. Runs every frame.
Path for the map, steering for the moment. Symptoms of merging them: re-running A* every frame (CPU
melts), or agents that walk the path but pile into each other (no local avoidance). Every engine's
NavMeshAgent / NavigationAgent bundles both — know which layer you are configuring.
Choosing a representation
| Representation |
Fits |
Cost / caveat |
| Uniform grid |
tile/2D games, RTS, roguelikes, destructible terrain |
many nodes; needs path smoothing to avoid staircase paths; JPS accelerates it |
| Waypoint graph |
sparse hand-placed routes, patrol nets, rails, racing lines |
cheap; agents snap to nodes, off-graph space is invisible — brittle for open areas |
| Navmesh |
3D and most open 2D worlds; the default for character movement |
bake step; represents walkable surface as convex polys — fewer nodes, natural paths |
Rule: navmesh for free-roaming characters, grid for tile-locked/destructible worlds, waypoint graph
only for constrained routes. Detail & path-smoothing (funnel algorithm) → references/search-algorithms.md.
Search algorithms (essentials)
- A* — the default. Dijkstra + a heuristic
h(n) that estimates remaining cost. Admissible h
(never over-estimates) ⇒ optimal path; use octile distance on 8-connected grids, Euclidean
on navmesh/any-angle. h must also stay ≤ true edge costs (consistency) to skip re-expansions.
- Dijkstra — A* with
h=0. Use when there is no single goal (nearest of many exits) or you need
the full cost field (see flow fields). Slower than A* to one target.
- Weighted / greedy — inflate
h (f = g + w·h, w>1) for faster, slightly suboptimal paths when
frame budget beats optimality.
- JPS (Jump Point Search) — A* speedup for uniform-cost grids only; skips symmetric paths, often
10×+ fewer expansions. Not for weighted terrain or navmeshes.
- Hierarchical / HPA* — for huge maps: partition into clusters, path cluster-to-cluster, refine
locally. Reach for it when a single flat A* blows the frame budget or you have thousands of tiles.
Always run search off a binary-heap open set and a closed set. Pseudocode, tie-breaking, any-angle
(Theta*), and the funnel string-pull → references/search-algorithms.md.
Navmesh workflow (essentials)
- Bake the walkable surface from level geometry. Key params: agent radius (how far the mesh is
shrunk from walls — the #1 knob), agent height, max step/climb, max slope, cell size.
- One navmesh per agent size. A tank and a rat need different bakes; do not share one mesh and hope.
- Regions / areas + costs — tag surfaces (water, mud, road) with a traversal cost so paths prefer
roads and avoid hazards, rather than deleting the area outright.
- Off-mesh / nav links — explicit edges for jumps, ladders, teleporters, doors: places agents move
but no polygon connects. Godot
NavigationLink, Unity NavMeshLink, Unreal NavLinkProxy.
- Dynamic obstacles — two tools, do not confuse them:
- Carving obstacle (Unity
NavMeshObstacle carving, Godot NavigationObstacle, Unreal
NavModifier): punches a hole so global paths route around a placed prop. Costs a re-carve on move.
- Local avoidance (RVO/ORCA): agents dodge without touching the mesh — for other moving agents.
- Re-baking — full rebake is expensive; prefer tile/partial rebake or carving for runtime
changes. Bake offline for static levels. Full param table + per-engine baking →
references/navmesh-workflow.md.
Steering & local avoidance (essentials)
Steering = a desired-velocity vector combined and clamped to max force/speed. Primitives:
- Seek / Flee — accelerate toward / away from a target point.
- Arrive — seek that ramps speed down inside a slowing radius (no overshoot/jitter at the goal).
- Pursue / Evade — seek/flee the target's predicted future position, not its current one.
- Wander — smoothed random heading for idle/ambient motion.
- Path following — steer toward a look-ahead point along the A*/navmesh corridor, not the far goal.
- Flocking = separation + alignment + cohesion (Reynolds boids) for groups.
Combine either as a weighted sum (simple) or priority/arbitration (avoidance wins over cohesion).
Local avoidance (RVO / ORCA): each agent picks a velocity that is collision-free assuming neighbours
share the burden (reciprocal — hence no oscillating "dance"). This is avoidance, not collision
response: it changes intended velocity before moving; the physics/collision solver is a separate,
last-resort backstop (→ gamedev-physics). For dense crowds use the
engine's crowd/avoidance system.
Flow fields — for many agents → one (or few) goals (RTS swarm, tower-defense creeps): run one
Dijkstra from the goal over the grid to build a cost/integration field, derive a per-cell direction
vector once, then every agent just samples its cell. O(1) per agent vs one A* each. Full derivation,
boids weights, and RVO intuition → references/steering-and-avoidance.md.
Per-engine mapping
Godot 4.x (NavigationAgent3D — 2D is identical with 2D suffix)
extends CharacterBody3D
@onready var agent: NavigationAgent3D = $NavigationAgent3D
@export var speed := 4.0
func _ready() -> void:
agent.avoidance_enabled = true # RVO local avoidance
agent.velocity_computed.connect(_on_velocity_computed)
func set_goal(p: Vector3) -> void:
agent.target_position = p # property, NOT set_target_location()
func _physics_process(_delta: float) -> void:
if agent.is_navigation_finished():
return
var next := agent.get_next_path_position() # 4.x name (was get_next_location)
var desired := global_position.direction_to(next) * speed
agent.set_velocity(desired) # feed RVO; result via signal
func _on_velocity_computed(safe: Vector3) -> void: # only fires while avoidance enabled
velocity = safe
move_and_slide()
One-off query without an agent: NavigationServer3D.map_get_path(map_rid, from, to, true). Rebake a
region at runtime: $NavigationRegion3D.bake_navigation_mesh(). Off-mesh: NavigationLink3D. If
avoidance is off, skip the signal and move_and_slide() with desired directly.
Unity (AI Navigation package + NavMeshAgent)
using UnityEngine;
using UnityEngine.AI;
[RequireComponent(typeof(NavMeshAgent))]
public class Chaser : MonoBehaviour {
NavMeshAgent agent;
void Awake() => agent = GetComponent<NavMeshAgent>();
public void Chase(Transform target) => agent.SetDestination(target.position);
public bool ReachedGoal() =>
!agent.pathPending && agent.remainingDistance <= agent.stoppingDistance
&& (!agent.hasPath || agent.velocity.sqrMagnitude < 0.01f);
}
Bake: add a NavMeshSurface to a scene root and Bake (the legacy Navigation window is gone).
Runtime rebake: surface.BuildNavMesh(). Dynamic blockers: NavMeshObstacle (enable Carving for
stationary props, leave off for moving agents so RVO handles them). Off-mesh: NavMeshLink. Local
avoidance quality: agent.obstacleAvoidanceType. Area costs: agent.SetAreaCost(area, cost).
Unreal (UE5 — RecastNavMesh + AIController + Behavior Tree / EQS)
- Drop a
NavMeshBoundsVolume around playable space → a RecastNavMesh auto-generates. For
runtime changes set its Runtime Generation = Dynamic (or Dynamic Modifiers Only); spawned
geometry must have collision + Can Ever Affect Navigation.
- Possess the pawn with an
AAIController. Movement:
AAIController* AICon = Cast<AAIController>(GetController());
AICon->MoveToActor(TargetActor, /*AcceptanceRadius*/ 50.f); // or MoveToLocation(FVector)
- Behavior Tree + Blackboard for decisions; the
MoveTo task walks the navmesh. EQS
(Environment Query System) picks where to go (cover, flank, nearest item) via scored queries.
- Costs/holes:
NavModifierVolume + NavArea classes. Off-mesh: NavLinkProxy. Crowd
avoidance: enable the Detour Crowd manager (or DetourCrowdAIController) for RVO on many agents.
High-level BT design (states, aggro, difficulty) is game-design; this skill wires the BT's
movement/EQS tasks to navigation, not the decision tree's semantics.
Anti-patterns
| Anti-pattern |
Do instead |
| Running a full A* every frame |
Path on goal-change (or throttled) and steer between frames — per-frame search melts CPU at scale. |
| One navmesh bake shared by every unit |
Radius/height differ; bake per agent size or use agent-type profiles. |
| Baking with a placeholder agent radius |
Bake with the real radius — mismatch is the top "stuck in doorways / clipping walls" cause. |
| Treating agents that clip through each other as a physics bug |
Missing local avoidance; enable RVO/crowd — that is the navigation layer. |
| Expecting RVO to prevent every overlap |
Avoidance ≠ collision. Agents can still overlap under pressure; that is expected, not a physics bug. |
| Expecting a static bake to see runtime-spawned geometry |
Set Dynamic runtime generation / rebake, or add a carving obstacle or nav link. |
| Hand-rolling your own grid A* in Unity/Unreal |
Reinvents the built-in navmesh; use NavMeshAgent / AAIController::MoveTo. |
| 500 zombies each running A* to the player |
That's a flow field: one Dijkstra from the goal, agents sample cells. |
Setting agent.destination and reading the path the same frame |
Path is async (pathPending); gate on it before trusting remainingDistance. |
Inflating h because a bigger heuristic seems smarter |
Over-estimating h breaks A* optimality; keep it admissible (or weight it knowingly). |
| Trusting a path query that came back empty |
Start/end are off the navmesh or in disconnected islands — snap to the nearest poly and check reachability first. |
| Assuming an off-mesh link works both ways |
Links are directional and manual — a jump-down link does not imply a jump-up link. |
| Feeding raw grid paths to the mover |
Smooth them (funnel / string-pull) or agents walk visible zig-zag staircases. |
Related skills
godot — GDScript/scene specifics; this skill owns the navigation subsystem it plugs into.
unity — Unity/C# project setup; here for the NavMesh + AI Navigation package details.
unreal — UE5/Blueprint/C++; here for RecastNavMesh + AIController/BT/EQS wiring.
gamedev-physics — collision response, rigidbodies, character controllers; steering decides intended velocity, physics resolves the contact.
game-design — high-level enemy behaviour, aggro, encounter/difficulty design (what the AI decides, not how it moves).
Checklist
Project grounding (02-DOCS + CLAUDE.md)
When this runs in a project with a 02-DOCS/ layer (the harness wiki), record
the project's navigation decisions in 02-DOCS/wiki/stack/gamedev-pathing.md (indexed from
02-DOCS/wiki/index.md): engine + version, chosen representation, bake settings (agent sizes, cell
size), avoidance/crowd choice, custom links/areas. Read it first on every use; bump its Updated date
when a convention changes. No 02-DOCS/ layer? Skip silently — conventions are recorded, not gated.
1---2name: gamedev-pathing3description: Use when making NPCs, enemies, or units navigate a level — grid vs waypoint vs navmesh, A*/JPS, navmesh baking and agent radius, off-mesh links, steering, RVO crowd avoidance, and flow fields in Godot 4.x, Unity, Unreal. NOT collision response or character controllers (that is gamedev-physics), NOT aggro or difficulty design (that is game-design).4---56# Game AI navigation & pathfinding78Move agents through a level intelligently and per-engine-correctly: pick a world representation,9run the right search, then follow the result with steering + local avoidance. This skill owns the10**navigation stack**; it stops where physics collision response and high-level behaviour design begin.1112## Version targets & the API ban-list (read first)1314Navigation APIs were renamed hard between engine generations. Emitting an old name compiles into15nothing or silent breakage. Target these versions; never emit the banned column.1617| Engine (target) | NEVER emit (deprecated / removed) | Use instead |18| --- | --- | --- |19| **Godot 4.x** | `Navigation` / `NavigationMeshInstance` / `Navigation2D` nodes | `NavigationServer2D/3D` + `NavigationRegion2D/3D` |20| Godot 4.x | `agent.get_next_location()` | `agent.get_next_path_position()` |21| Godot 4.x | `NavigationAgent.set_target_location()` | set the `target_position` property |22| Godot 4.x | reading `velocity` directly when avoidance is on | feed `set_velocity()`, read the `velocity_computed(safe)` signal |23| **Unity (AI Navigation pkg)** | the legacy **Navigation window** static bake, `Navigation Static` flag, built-in `OffMeshLink` component | `NavMeshSurface` (`com.unity.ai.navigation`), `NavMeshLink`, `NavMeshModifier` |24| Unity | `agent.destination = p` then reading a path same frame | `SetDestination(p)`, then gate on `!pathPending && remainingDistance <= stoppingDistance` |25| **Unreal (UE5)** | hand-rolling A* over your own grid for pawns | `AAIController::MoveTo*` over `RecastNavMesh`; BT `MoveTo` task |26| Unreal | expecting a **Static** navmesh to react to spawned geometry | set RecastNavMesh **Runtime Generation = Dynamic** (or Dynamic Modifiers Only) |2728## The two-layer mental model (center of gravity)2930The single most common navigation bug is collapsing two jobs into one. Keep them separate:31321. **Global pathfinding — *where* to go.** A discrete search (A* / navmesh query) over a static-ish33 representation, run *occasionally* (on new goal, or throttled), returns a **corridor of waypoints**.342. **Local steering + avoidance — *how* to move this frame.** A cheap per-frame vector: follow the35 corridor, dodge other agents and dynamic obstacles, respect acceleration. Runs *every frame*.3637Path for the map, steering for the moment. Symptoms of merging them: re-running A* every frame (CPU38melts), or agents that walk the path but pile into each other (no local avoidance). Every engine's39`NavMeshAgent` / `NavigationAgent` bundles both — know which layer you are configuring.4041## Choosing a representation4243| Representation | Fits | Cost / caveat |44| --- | --- | --- |45| **Uniform grid** | tile/2D games, RTS, roguelikes, destructible terrain | many nodes; needs path smoothing to avoid staircase paths; JPS accelerates it |46| **Waypoint graph** | sparse hand-placed routes, patrol nets, rails, racing lines | cheap; agents snap to nodes, off-graph space is invisible — brittle for open areas |47| **Navmesh** | 3D and most open 2D worlds; the default for character movement | bake step; represents *walkable surface* as convex polys — fewer nodes, natural paths |4849Rule: **navmesh for free-roaming characters, grid for tile-locked/destructible worlds, waypoint graph50only for constrained routes.** Detail & path-smoothing (funnel algorithm) → `references/search-algorithms.md`.5152## Search algorithms (essentials)5354- **A\*** — the default. Dijkstra + a heuristic `h(n)` that estimates remaining cost. Admissible `h`55 (never *over*-estimates) ⇒ optimal path; use **octile** distance on 8-connected grids, **Euclidean**56 on navmesh/any-angle. `h` must also stay ≤ true edge costs (consistency) to skip re-expansions.57- **Dijkstra** — A* with `h=0`. Use when there is **no single goal** (nearest of many exits) or you need58 the full cost field (see flow fields). Slower than A* to one target.59- **Weighted / greedy** — inflate `h` (`f = g + w·h`, `w>1`) for faster, slightly suboptimal paths when60 frame budget beats optimality.61- **JPS (Jump Point Search)** — A* speedup for **uniform-cost grids only**; skips symmetric paths, often62 10×+ fewer expansions. Not for weighted terrain or navmeshes.63- **Hierarchical / HPA\*** — for **huge maps**: partition into clusters, path cluster-to-cluster, refine64 locally. Reach for it when a single flat A* blows the frame budget or you have thousands of tiles.6566Always run search off a **binary-heap open set** and a closed set. Pseudocode, tie-breaking, any-angle67(Theta*), and the funnel string-pull → `references/search-algorithms.md`.6869## Navmesh workflow (essentials)70711. **Bake** the walkable surface from level geometry. Key params: **agent radius** (how far the mesh is72 shrunk from walls — the #1 knob), **agent height**, **max step/climb**, **max slope**, cell size.732. **One navmesh per agent size.** A tank and a rat need different bakes; do not share one mesh and hope.743. **Regions / areas + costs** — tag surfaces (water, mud, road) with a traversal cost so paths prefer75 roads and avoid hazards, rather than deleting the area outright.764. **Off-mesh / nav links** — explicit edges for jumps, ladders, teleporters, doors: places agents move77 but no polygon connects. Godot `NavigationLink`, Unity `NavMeshLink`, Unreal `NavLinkProxy`.785. **Dynamic obstacles** — two tools, do not confuse them:79 - **Carving obstacle** (Unity `NavMeshObstacle` carving, Godot `NavigationObstacle`, Unreal80 `NavModifier`): punches a hole so *global* paths route around a placed prop. Costs a re-carve on move.81 - **Local avoidance** (RVO/ORCA): agents dodge *without* touching the mesh — for other moving agents.826. **Re-baking** — full rebake is expensive; prefer **tile/partial** rebake or carving for runtime83 changes. Bake offline for static levels. Full param table + per-engine baking → `references/navmesh-workflow.md`.8485## Steering & local avoidance (essentials)8687Steering = a desired-velocity vector combined and clamped to max force/speed. Primitives:8889- **Seek / Flee** — accelerate toward / away from a target point.90- **Arrive** — seek that ramps speed down inside a slowing radius (no overshoot/jitter at the goal).91- **Pursue / Evade** — seek/flee the target's *predicted future* position, not its current one.92- **Wander** — smoothed random heading for idle/ambient motion.93- **Path following** — steer toward a look-ahead point along the A*/navmesh corridor, not the far goal.94- **Flocking** = separation + alignment + cohesion (Reynolds boids) for groups.9596Combine either as a **weighted sum** (simple) or **priority/arbitration** (avoidance wins over cohesion).9798**Local avoidance (RVO / ORCA):** each agent picks a velocity that is collision-free assuming neighbours99share the burden (*reciprocal* — hence no oscillating "dance"). This is avoidance, **not** collision100*response*: it changes intended velocity *before* moving; the physics/collision solver is a separate,101last-resort backstop (→ [`gamedev-physics`](../gamedev-physics/SKILL.md)). For dense crowds use the102engine's crowd/avoidance system.103104**Flow fields** — for **many agents → one (or few) goals** (RTS swarm, tower-defense creeps): run one105**Dijkstra from the goal** over the grid to build a cost/integration field, derive a per-cell direction106vector once, then every agent just samples its cell. O(1) per agent vs one A* each. Full derivation,107boids weights, and RVO intuition → `references/steering-and-avoidance.md`.108109## Per-engine mapping110111### Godot 4.x (`NavigationAgent3D` — 2D is identical with `2D` suffix)112113```gdscript114extends CharacterBody3D115@onready var agent: NavigationAgent3D = $NavigationAgent3D116@export var speed := 4.0117118func _ready() -> void:119 agent.avoidance_enabled = true # RVO local avoidance120 agent.velocity_computed.connect(_on_velocity_computed)121122func set_goal(p: Vector3) -> void:123 agent.target_position = p # property, NOT set_target_location()124125func _physics_process(_delta: float) -> void:126 if agent.is_navigation_finished():127 return128 var next := agent.get_next_path_position() # 4.x name (was get_next_location)129 var desired := global_position.direction_to(next) * speed130 agent.set_velocity(desired) # feed RVO; result via signal131132func _on_velocity_computed(safe: Vector3) -> void: # only fires while avoidance enabled133 velocity = safe134 move_and_slide()135```136137One-off query without an agent: `NavigationServer3D.map_get_path(map_rid, from, to, true)`. Rebake a138region at runtime: `$NavigationRegion3D.bake_navigation_mesh()`. Off-mesh: `NavigationLink3D`. If139avoidance is **off**, skip the signal and `move_and_slide()` with `desired` directly.140141### Unity (AI Navigation package + `NavMeshAgent`)142143```csharp144using UnityEngine;145using UnityEngine.AI;146147[RequireComponent(typeof(NavMeshAgent))]148public class Chaser : MonoBehaviour {149 NavMeshAgent agent;150 void Awake() => agent = GetComponent<NavMeshAgent>();151152 public void Chase(Transform target) => agent.SetDestination(target.position);153154 public bool ReachedGoal() =>155 !agent.pathPending && agent.remainingDistance <= agent.stoppingDistance156 && (!agent.hasPath || agent.velocity.sqrMagnitude < 0.01f);157}158```159160Bake: add a **`NavMeshSurface`** to a scene root and Bake (the legacy Navigation window is gone).161Runtime rebake: `surface.BuildNavMesh()`. Dynamic blockers: **`NavMeshObstacle`** (enable *Carving* for162stationary props, leave off for moving agents so RVO handles them). Off-mesh: **`NavMeshLink`**. Local163avoidance quality: `agent.obstacleAvoidanceType`. Area costs: `agent.SetAreaCost(area, cost)`.164165### Unreal (UE5 — `RecastNavMesh` + `AIController` + Behavior Tree / EQS)1661671. Drop a **`NavMeshBoundsVolume`** around playable space → a `RecastNavMesh` auto-generates. For168 runtime changes set its **Runtime Generation = Dynamic** (or *Dynamic Modifiers Only*); spawned169 geometry must have collision + **Can Ever Affect Navigation**.1702. Possess the pawn with an **`AAIController`**. Movement:171172```cpp173AAIController* AICon = Cast<AAIController>(GetController());174AICon->MoveToActor(TargetActor, /*AcceptanceRadius*/ 50.f); // or MoveToLocation(FVector)175```1761773. **Behavior Tree + Blackboard** for decisions; the **`MoveTo`** task walks the navmesh. **EQS**178 (Environment Query System) picks *where* to go (cover, flank, nearest item) via scored queries.1794. Costs/holes: **`NavModifierVolume`** + `NavArea` classes. Off-mesh: **`NavLinkProxy`**. Crowd180 avoidance: enable the **Detour Crowd** manager (or `DetourCrowdAIController`) for RVO on many agents.181182> High-level BT *design* (states, aggro, difficulty) is [`game-design`](../game-design/SKILL.md); this skill wires the BT's183> movement/EQS tasks to navigation, not the decision tree's semantics.184185## Anti-patterns186187| Anti-pattern | Do instead |188| --- | --- |189| Running a full A* every frame | Path on goal-change (or throttled) and steer between frames — per-frame search melts CPU at scale. |190| One navmesh bake shared by every unit | Radius/height differ; bake per agent size or use agent-type profiles. |191| Baking with a placeholder agent radius | Bake with the real radius — mismatch is the top "stuck in doorways / clipping walls" cause. |192| Treating agents that clip through each other as a physics bug | Missing **local avoidance**; enable RVO/crowd — that is the navigation layer. |193| Expecting RVO to prevent every overlap | Avoidance ≠ collision. Agents can still overlap under pressure; that is expected, not a physics bug. |194| Expecting a **static** bake to see runtime-spawned geometry | Set Dynamic runtime generation / rebake, or add a carving obstacle or nav link. |195| Hand-rolling your own grid A* in Unity/Unreal | Reinvents the built-in navmesh; use `NavMeshAgent` / `AAIController::MoveTo`. |196| 500 zombies each running A* to the player | That's a **flow field**: one Dijkstra from the goal, agents sample cells. |197| Setting `agent.destination` and reading the path the same frame | Path is async (`pathPending`); gate on it before trusting `remainingDistance`. |198| Inflating `h` because a bigger heuristic seems smarter | Over-estimating `h` breaks A* optimality; keep it admissible (or weight it *knowingly*). |199| Trusting a path query that came back empty | Start/end are **off the navmesh** or in disconnected islands — snap to the nearest poly and check reachability first. |200| Assuming an off-mesh link works both ways | Links are **directional and manual** — a jump-down link does not imply a jump-up link. |201| Feeding raw grid paths to the mover | Smooth them (funnel / string-pull) or agents walk visible zig-zag staircases. |202203## Related skills204205- [`godot`](../godot/SKILL.md) — GDScript/scene specifics; this skill owns the navigation subsystem it plugs into.206- [`unity`](../unity/SKILL.md) — Unity/C# project setup; here for the NavMesh + AI Navigation package details.207- [`unreal`](../unreal/SKILL.md) — UE5/Blueprint/C++; here for RecastNavMesh + AIController/BT/EQS wiring.208- [`gamedev-physics`](../gamedev-physics/SKILL.md) — collision *response*, rigidbodies, character controllers; steering decides intended velocity, physics resolves the contact.209- [`game-design`](../game-design/SKILL.md) — high-level enemy behaviour, aggro, encounter/difficulty design (what the AI *decides*, not how it *moves*).210211## Checklist212213- [ ] Representation chosen deliberately (navmesh / grid / waypoint) and justified for the world type.214- [ ] Global path and local steering are **separate layers**; A* is not per-frame.215- [ ] Navmesh baked with the correct **agent radius/height/step/slope** (per agent size if they differ).216- [ ] Area costs set for hazards/terrain instead of hard-deleting walkable space.217- [ ] Off-mesh/nav links placed for every jump/ladder/teleport gap (correct direction).218- [ ] Dynamic obstacles handled by carving *or* rebake *or* local avoidance — the right one for each case.219- [ ] Local avoidance / crowd enabled if multiple agents share space; flow field used for many→one-goal.220- [ ] Correct **current** engine APIs (no banned names from the version table); paths validated for reachability.221- [ ] Grid paths smoothed (funnel) so movement isn't staircased.222223## Project grounding (02-DOCS + CLAUDE.md)224225When this runs in a project with a `02-DOCS/` layer (the [`harness`](../harness/SKILL.md) wiki), record226the project's navigation decisions in `02-DOCS/wiki/stack/gamedev-pathing.md` (indexed from227`02-DOCS/wiki/index.md`): engine + version, chosen representation, bake settings (agent sizes, cell228size), avoidance/crowd choice, custom links/areas. Read it first on every use; bump its `Updated` date229when a convention changes. No `02-DOCS/` layer? Skip silently — conventions are *recorded, not gated*.