| name | debug-issue |
| description | Systematic Godot debugging decision trees for physics, signals, rendering, navigation, and input issues |
| argument-hint | [symptom description] |
Debug Issue
Systematic debugging using domain-specific decision trees. Start by identifying the symptom category, then follow the corresponding tree.
Category Detection
Map user's symptom to the right tree:
| Symptom keywords | Category |
|---|
| "pass through", "no collision", "overlap", "stuck", "slides off" | Physics |
| "signal not firing", "not connected", "callback not called" | Signals |
| "invisible", "not showing", "behind", "flickering", "wrong color" | Rendering |
| "not moving to target", "path wrong", "stuck on nav", "no path" | Navigation |
| "key not working", "input ignored", "wrong button", "double input" | Input |
Physics Decision Tree
Check in this order -- each step is the most common cause at that point:
-
Collision layer/mask mismatch (most common):
- Check defined layer names:
physics(action="layers")
- Check node properties:
nodes(action="get_property", scene_path="<scene>.tscn", name="<body>", property="collision_layer")
nodes(action="get_property", scene_path="<scene>.tscn", name="<body>", property="collision_mask")
- Rule: Object A detects Object B only if A's
collision_mask has a bit that matches B's collision_layer. Both must be set correctly.
-
Missing CollisionShape2D/3D:
- Inspect the scene tree:
scenes(action="info", scene_path="<scene>.tscn")
- Verify every physics body and Area has a CollisionShape child. A body without a shape = invisible to physics.
-
Wrong body type:
StaticBody2D: immovable (walls, floors)
CharacterBody2D: player/NPC movement via move_and_slide()
RigidBody2D: physics-driven (projectiles, debris)
- Common mistake: using
RigidBody2D for a player character, then fighting the physics engine
-
Script velocity issues:
- Read the script:
scripts(action="read", script_path="res://scripts/<name>.gd")
- Check if
velocity is being set before move_and_slide()
- Check if gravity is applied in
_physics_process (not _process)
- Check if
delta is used for frame-independent movement
Signals Decision Tree
-
Connection exists?
- List all connections:
signals(action="list", scene_path="<scene>.tscn")
- Missing connection = signal silently does nothing
-
Signature match?
- Signal definition parameters must match handler function parameters
signal health_changed(new_hp: int) requires handler func _on_health_changed(new_hp: int)
- Extra or missing parameters = Godot error at runtime
-
Signal emitted?
- Add
print("signal emitted") before emit_signal() / signal.emit()
- If not printed, the emit call is never reached (logic bug)
-
Callable valid?
- If connected via code: target node must exist when
connect() runs
- If target is freed, signal emits to invalid callable = error
Rendering Decision Tree
-
Visibility:
visible property = false? Check node and ALL parents (parent invisible = children invisible)
modulate.a = 0? (fully transparent)
-
Z-index:
- Lower z-index renders behind higher z-index
z_as_relative = true means z_index is relative to parent
- Sprite behind a TileMapLayer? Check z_index values on both
-
Material/shader:
- CanvasItemMaterial or ShaderMaterial overriding appearance?
- Shader compilation errors make object invisible (check Output panel)
-
Viewport issues:
- SubViewport not updating? Check
render_target_update_mode
- Camera2D not active? Only one Camera2D should have
enabled = true per viewport
Navigation Decision Tree
-
NavigationRegion setup:
NavigationRegion2D/3D must exist in scene
- Must have a
NavigationPolygon/NavigationMesh resource assigned
-
Navigation mesh baked?
- Mesh must be baked (either in editor or via
bake_navigation_mesh())
- Unbaked mesh = no paths available
-
NavigationAgent config:
path_desired_distance: how close before moving to next path point
target_desired_distance: how close to target before stopping
- Values too small = agent oscillates; too large = imprecise
-
Path calculation:
- Call
NavigationAgent.set_target_position() then check get_next_path_position()
- If returns current position, no valid path exists (check mesh coverage)
Input Decision Tree
-
Input Map:
- List actions:
input_map(action="list")
- Check
project.godot for [input] section
-
Action name match:
Input.is_action_pressed("jump") -- exact string match required
- Typo in action name = silently returns false (no error!)
-
Event processing:
_input() vs _unhandled_input() -- if another node consumes the event, _unhandled_input never fires
set_process_input(false) disables _input() on that node
-
Conflicts:
- Two actions on same key = both fire
- UI elements (Button, LineEdit) consume input events before game nodes
Diagnostic Steps
For any category:
- Inspect the scene tree:
scenes(action="info", scene_path="<scene>.tscn")
- Read relevant scripts:
scripts(action="read", script_path="res://scripts/<name>.gd")
- Check properties:
nodes(action="get_property", scene_path="<scene>.tscn", name="<node_path>", property="<name>")
- Run the scene:
project(action="run", scene_path="<scene>.tscn") and check output for errors
- Report findings: present root cause and specific fix (property change, missing node, script edit)
When to Use
- Any "it doesn't work" debugging scenario in Godot
- Systematic elimination when the cause is not obvious
- Verifying correct setup of physics, signals, rendering, navigation, or input