Quick answer: The navigation map is synced on the physics frame, so paths requested in _ready() or the first frame come back empty. Wait one physics frame (or for NavigationServer2D.map_changed) before setting target_position. Then check that a baked NavigationRegion2D exists, its navigation layers overlap the agent's, and you call get_next_path_position() every physics frame.
The agent sits still and get_next_path_position() keeps returning its own position. That means the server found no path — and in most cases it is because it was asked too early or asked about a map the agent cannot use.
Cause 1: asking before the map exists
Regions register with the NavigationServer, and the map is rebuilt on the next physics frame. A target set in _ready() is resolved against an empty map:
func _ready():
actor_setup.call_deferred()
func actor_setup():
await get_tree().physics_frame
$NavigationAgent2D.target_position = target.global_position
Cause 2: no usable navigation polygon
Turn on Debug > Visible Navigation. If no blue polygon covers the area, the region was never baked or its NavigationPolygon has no outline. In Godot 4.2 and later, NavigationPolygon is baked from source geometry; draw an outline that encloses the walkable area and bake it. For TileMaps, tiles need a navigation polygon on a navigation layer in the TileSet.
Cause 3: navigation layers do not match
Both NavigationRegion2D and NavigationAgent2D have Navigation Layers (a bitmask, separate from physics layers). An agent only paths over regions that share a layer. Defaults are layer 1 on both; this breaks after someone edits one side.
Cause 4: the movement loop is wrong
The agent computes the path; your script moves the body:
func _physics_process(_delta):
if $NavigationAgent2D.is_navigation_finished():
return
var next := $NavigationAgent2D.get_next_path_position()
velocity = global_position.direction_to(next) * speed
move_and_slide()
Call get_next_path_position() each physics frame — it advances along the path as you get close to each point. If path_desired_distance is smaller than the distance your body moves per frame, it can overshoot points and oscillate; raise it to roughly your speed times delta.
Cause 5: regions that do not connect
Two NavigationRegion2Ds connect only where their edges line up within the map's edge connection margin. A tiny gap between rooms leaves two islands, and a target on the other island has no path. Bake regions edge to edge or merge them.
Cause 6: target off the mesh
A target outside every polygon is moved to the closest reachable point, which may be the agent's own position if it is on an isolated island. Print NavigationServer2D.map_get_closest_point(get_world_2d().navigation_map, target) to see where the server thinks the target is.