{
	"article_slug": "why-navigationagent-gets-stuck-in-godot",
	"cases": [
		{
			"case_id": "BAKE_RADIUS_MISMATCH",
			"data": {
				"gap_height": 44.0,
				"large_body_radius": {
					"agent_radius": 24.0,
					"goal_polygon": 1,
					"polygon_count": 2,
					"start_goal_connected": false,
					"start_polygon": 0,
					"vertex_count": 8
				},
				"small_baked_radius": {
					"agent_radius": 8.0,
					"goal_polygon": 0,
					"polygon_count": 7,
					"start_goal_connected": true,
					"start_polygon": 4,
					"vertex_count": 12
				}
			},
			"diagnosis": "Bake NavigationPolygon.agent_radius for the body size; NavigationAgent2D.radius is avoidance-only.",
			"fingerprint": "debug path is legal for the baked mesh, but a fatter body grinds the corridor",
			"passed": true,
			"title": "Path hugs a corner that the body cannot pass"
		},
		{
			"case_id": "DISTANCE_REPATH_LOOP",
			"data": {
				"distance_per_frame": 30.0,
				"overshoots_path_point": true,
				"overshoots_target": true,
				"path_desired_distance": 20.0,
				"physics_ticks": 60.0,
				"speed_px_per_second": 1800.0,
				"target_desired_distance": 10.0
			},
			"diagnosis": "Raise desired distances or reduce per-frame movement so the agent can enter the thresholds.",
			"fingerprint": "path index fails to settle and path_changed can fire repeatedly",
			"passed": true,
			"title": "Distance thresholds are too tight for the movement step"
		},
		{
			"case_id": "AVOIDANCE_PATH_MAX_DISTANCE",
			"data": {
				"exceeds_path_max_distance": true,
				"ideal_path_y": 80.0,
				"max_deviation": 39.0,
				"path_max_distance": 24.0,
				"safe_positions": [
					{
						"x": 126.0,
						"y": 84.0
					},
					{
						"x": 144.0,
						"y": 96.0
					},
					{
						"x": 158.0,
						"y": 112.0
					},
					{
						"x": 174.0,
						"y": 119.0
					}
				]
			},
			"diagnosis": "Treat this as local steering pressure, not a changed route. Tune avoidance or path_max_distance, and keep topology fixes in the navigation data.",
			"fingerprint": "safe velocity diverges from the route and the agent exceeds path_max_distance",
			"passed": true,
			"title": "Avoidance pushes the body away from the ideal path"
		},
		{
			"case_id": "UNREACHABLE_TARGET_FINISHED",
			"data": {
				"final_position": {
					"x": 320.0,
					"y": 80.0
				},
				"final_vs_target_distance": 70.0,
				"is_navigation_finished": true,
				"is_target_reachable": false,
				"target_desired_distance": 10.0,
				"target_position": {
					"x": 390.0,
					"y": 80.0
				}
			},
			"diagnosis": "This is not a stuck agent. Clamp targets or design for walking to the final reachable point.",
			"fingerprint": "is_navigation_finished true, is_target_reachable false, final position differs from target",
			"passed": true,
			"title": "The target is outside the reachable mesh"
		},
		{
			"case_id": "SIGNAL_CALLBACK_PATH_UPDATE",
			"data": {
				"path_update_method": "get_next_path_position",
				"safe_contexts": [
					"_physics_process",
					"call_deferred",
					"CONNECT_DEFERRED"
				],
				"unsafe_context": "waypoint_reached callback"
			},
			"diagnosis": "Call path-updating methods in physics_process or defer them to the end of the frame.",
			"fingerprint": "navigation callback calls a method that can trigger a new path calculation",
			"passed": true,
			"title": "Path-updating methods are called from agent signals"
		}
	],
	"docs_receipts": {
		"avoidance_scope": "Avoidance has no information from navigation meshes or physics collision and does not affect pathfinding.",
		"path_max_distance": "An agent pushed too far from the ideal path recalculates the path.",
		"radius_scope": "NavigationAgent2D.radius is avoidance-only and does not affect normal pathfinding."
	},
	"failed": [],
	"generated_utc": "2026-08-05T07:34:00",
	"godot_version": "4.7.1-stable (official)",
	"passed": true,
	"watchdog": {
		"observed_still_frames": 30,
		"required_frames": 30,
		"sample_count": 30,
		"stuck_epsilon": 1.0,
		"triggered": true
	}
}