Replaces click-based navigation with mock_interact(0) on TransitionPiece nodes, adds get_current_room_name() verification pattern, and documents the MCP busy protocol for walk animation timeouts.
11 KiB
model, kill, date_introduced
| model | kill | date_introduced | |
|---|---|---|---|
| auto |
|
2026-04-28 |
Plan: kq4-room-navigator Skill (AI Agent Room Pathfinding via Godot MCP)
Context
The game has ~96 rooms connected by TransitionPiece nodes. Each room's .tscn file defines its exits: node name (destination room), target UID, appear_at_node, label, and polygon coordinates. Room scripts connect the interacted signal to handlers that call default_script() on the transition piece, triggering a 3-step animated sequence (walk to exit → fade/swap scenes → walk to entrance in new room).
The custom MCP server (scripts/mcp_interaction_server.gd) runs as an autoload at TCP port 9090 with ~80 commands including eval (arbitrary GDScript execution), click, screenshot, get_scene_tree, get_property, and wait. No Python client exists yet.
Key constraint: Full runtime discovery via eval is fragile — instantiating .tscn files for graph discovery triggers script execution (_ready()) which references autoload singletons (ActionState, GameScript), causing errors when rooms aren't fully set up. Solution: Use targeted find_nodes_by_class MCP command for the currently-loaded room, and file parsing for global graph construction (leveraging existing scripts/check_transitions.py).
Approach
Build a Python tool (tools/kq4_room_navigator.py) that:
- Parses
.tscnfiles to build complete room adjacency graph (reuse logic fromcheck_transitions.py) - Runs BFS pathfinding between start and destination rooms
- Connects to the MCP server on port 9090 for live navigation
- Uses
evalto compute click coordinates within TransitionPiece polygons in the running game - Executes step-by-step clicks with screenshot verification after each transition
The SKILL.md guides an AI agent through: starting Godot, invoking the tool, and optionally verifying steps manually with MCP commands.
Implementation Plan
1. Extend room graph parser (scripts/build_room_graph.py)
Purpose: Parse all .tscn files to produce a queryable room adjacency graph with BFS pathfinding. Extends/extracts logic from existing scripts/check_transitions.py.
What it does:
- Scan
scenes/kq4_*/kq4_*.tscn(exclude placeholder_template) - Parse each file to extract:
- Scene UID (from header
[gd_scene format=3 uid="uid://XXX"]) - Room name/directory stem (e.g.,
kq4_004_ogres_cottage) - All TransitionPiece nodes → build adjacency list
- Scene UID (from header
- Build bidirectional graph:
{room_name: [(exit_node_name, target_uid, appear_at_node, label, polygon), ...]}
Output: Returns a structure that supports BFS queries between any two rooms.
Interface (callable from Python or CLI):
def build_graph(scenes_dir: Path) -> dict[str, list[TransitionInfo]]:
"""Parse all room .tscn files and return adjacency graph."""
def find_path(graph: dict, start_room: str, end_room: str) -> list[NavigationStep]:
"""BFS from start to end. Returns ordered list of steps or None."""
@dataclass
class NavigationStep:
from_room: str # Current room name (e.g., "kq4_004_ogres_cottage")
exit_node_name: str # TransitionPiece node name (e.g., "kq4_010_forest_path")
to_room: str # Destination room name
label: str # Human-readable label (e.g., "Forest Path")
polygon: list[tuple] # Polygon vertices for click coordinate computation
File: scripts/build_room_graph.py
3. Create the main navigator tool (tools/kq4_room_navigator.py)
Purpose: End-to-end CLI tool that connects to MCP, builds graph, finds path, and navigates.
Workflow:
python tools/kq4_room_navigator.py --from kq4_004_ogres_cottage --to kq4_092_lolottes_throne_room
Implementation flow:
-
Build graph (offline): Calls
build_graph()to parse all rooms -
BFS pathfinding: Finds shortest path from start → destination
-
No path found: Reports unreachable, lists connected components
-
Path found: Prints the step-by-step plan with click coordinates
-
Connect to MCP (runtime): Opens TCP connection to port 9090
-
For each navigation step: a. Verify starting room:
eval("return get_node('/root/Node2D').get_current_room_name()")returns the current room's node name (e.g.,"kq4_003_fountain_pool"). This convenience function on MainGame simplifies verification compared to traversing the scene tree. b.find_nodes_by_class(class_name="TransitionPiece")— discover all transition pieces in current scene c. Match the exit node name from our path → get runtime position + polygon d. Compute click coordinate: centroid of the TransitionPiece'spolygon, transformed to viewport coordinates e.click(x, y)— trigger the transition f. Wait for transition animation (2-3s viawaitcommand or poll-loop) g. Verify arrival:eval("return get_node('/root/Node2D').get_current_room_name()")to confirm we've entered the expected room. Compare against theto_roomfrom our BFS path. Returns empty string if no scene loaded. h. If verification fails, retry up to 2 times with adjusted coordinates h. Optionally take a screenshot viascreenshot()for visual confirmation -
Complete: Print summary of navigation path taken
GDScript eval code used at runtime:
Find clickable position within TransitionPiece polygon:
# Returns {node_name, centroid_x, centroid_y} for matching transition piece
var bg = get_tree().root.get_node_or_null("Node2D/SceneViewport/background")
if not bg: return null
for child in bg.get_children():
if child.has_method("is_class") and child.is_class("TransitionPiece"):
var p = child.position + child.polygon.reduce(func(p, a): return p + a, Vector2(0,0)) / child.polygon.size()
return {"node": child.name, "x": p.x, "y": p.y, "polygon_size": child.polygon.size()}
Verify current room:
var bg = get_tree().root.get_node_or_null("Node2D/SceneViewport/background")
return bg ? bg.name : null
File: tools/kq4_room_navigator.py
4. Write SKILL.md (.opencode/skills/kq4-room-navigator/SKILL.md)
The skill guide documents:
-
When to use: Planning navigation between rooms, verifying room connectivity, debugging transitions
-
Pre-requisites: Godot game running with MCP server active on port 9090
-
Quick start:
python tools/kq4_room_navigator.py --from kq4_XXX --to kq4_YYY -
Room verification helper (
MainGame.get_current_room_name()):The root node (
/root/Node2D, script:MainGame.gd) provides a convenience function for testing:# Via MCP eval — returns node name like "kq4_003_fountain_pool", or "" if no scene loaded return get_node("/root/Node2D").get_current_room_name()Use this after each transition click to verify you arrived at the expected room. Compare against the
to_roomfield from your BFS path. -
Manual MCP workflow (for step-by-step agent control):
- Start Godot:
godot --path .or run exported binary - Check current room:
{"command": "eval", "params": {"code": "return get_node('/root/Node2D').get_current_room_name()"}}→ returns"kq4_003_fountain_pool"style string - Verify connectivity:
{"command": "get_scene_tree"}returns the current scene tree - Discover exits in current room:
{"command": "find_nodes_by_class", "params": {"class_name": "TransitionPiece"}} - Click a transition: compute centroid from polygon data, then
{"command": "click", "params": {"x": px, "y": py}} - Verify current room:
{"command": "eval", "params": {"code": "return get_node('/root/Node2D').get_current_room_name()"}}(returns node name like"kq4_010_forest_path"). Alternatively use screenshot for visual confirmation.
- Start Godot:
-
Coordinate computation math: How to convert TransitionPiece polygon (local-space) to viewport click coordinates:
viewport_x = transition_node.position.x + polygon_centroid.x, accounting for any node scale transforms -
Troubleshooting: Common failures (server busy, node not found mid-transition, wrong room), escape hatches
File: .opencode/skills/kq4-room-navigator/SKILL.md
Task Dependency Graph
[1. build_room_graph.py] ────────┐
├── [2. kq4_room_navigator.py] ─── [3. SKILL.md]
### Using MainGame.get_current_room_name() for Room Verification
The `MainGame.gd` script (attached to `/root/Node2D`) provides a convenience function:
```gdscript
# Returns the node name of the current scene, e.g., "kq4_003_fountain_pool"
func get_current_room_name() -> String:
var scene = get_scene()
if scene:
return scene.name
return ""
Via MCP eval for runtime verification:
curl http://localhost:9090 -d '{"command": "eval", "params": {"code": "return get_node(\"/root/Node2D\").get_current_room_name()"}}'
Returns the room node name string (e.g., "kq4_003_fountain_pool"), or empty string if no scene is loaded. This is more reliable than traversing the scene tree manually since it uses the same $SceneViewport/background reference that MainGame's own logic uses.
When to use:
- After clicking a transition, call to confirm arrival at expected room in
to_room - Before navigation starts, verify you're actually in the intended
from_room - Compare against BFS path nodes during automated step-by-step execution
Tasks run sequentially. Task 3 documents the completed system.
## Verification
- Run `python scripts/build_room_graph.py` → should produce a graph with all rooms and their exits
- Test BFS: path from kq4_001_beach to an adjacent room should be 1 step; path to a far room tests multi-hop correctly
- Connect to running Godot instance via `tools/kq4_room_navigator.py` → verify it can navigate at least one transition end-to-end (from current room to an adjacent room)
- Verify failure handling: requesting a path between disconnected rooms returns "no path found"
- Test `_busy` retry logic by sending rapid commands
## Risks & Mitigations
| Risk | Impact | Mitigation |
|------|--------|------------|
| Room not fully set up — `TransitionPiece._ready()` crashes on eval-instantiation | Can't discover transitions from unloaded rooms via MCP | File parsing for graph discovery is the primary approach; MCP runtime only queries currently-loaded scene |
| Transition animation timing varies | Click at wrong time → missed transition | Poll current room name via eval every 0.5s until it changes, with 10s timeout |
| Polygon coordinates are local to node — need transform to viewport | Click lands outside polygon | Account for node `position` AND `scale` when computing centroid; use `get_node_info` runtime to get actual global polygon positions |
| MCP server `_busy` state blocks commands | Navigation stalls | Retry with exponential backoff (0.1s, 0.2s, 0.4s, ...) and 3-second max wait |
## Out of Scope
- Path optimization beyond BFS shortest path
- Inventory/item-based transitions (e.g., needing a key to enter a room)
- Cutscene-triggered room changes (non-interaction transitions)
- Multiplayer considerations
- Auto-starting Godot from the tool (user starts game manually)