Update room navigator skill with mock_interact and room verification

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.
This commit is contained in:
2026-04-29 15:54:41 -07:00
parent 7a7d9e78db
commit 868b25299a
2 changed files with 141 additions and 80 deletions

View File

@@ -1,6 +1,8 @@
---
name: kq4-room-navigator
description: Navigate between KQ4 rooms using the Godot MCP server. BFS pathfinds through room transition graph, then the agent navigates McpInteractionServer (port 9090). Use when planning navigation between any two rooms or simulating player movement through the game world at runtime. Trigger phrases: "navigate from", "go to room", "path from X to Y", "walk to", "how do I get to room", "room navigation".
---
## name: kq4-room-navigator description: Navigate between KQ4 rooms using the Godot MCP server. BFS pathfinds through room transition graph, then executes step-by-step clicks via McpInteractionServer (port 9090). Use when planning navigation between any two rooms or simulating player movement through the game world at runtime. Trigger phrases: "navigate from", "go to room", "path from X to Y", "walk to", "how do I get to room", "room navigation".
# KQ4 Room Navigator
@@ -8,7 +10,7 @@
This skill finds shortest paths between rooms and can execute them through the running game using Godot's MCP server. It combines offline `.tscn` file parsing for graph construction with runtime interaction via McpInteractionServer (TCP port 9090).
The room transition system uses TransitionPiece nodes: each has an `exit_node_name` (destination), a clickable polygon, and connects rooms bidirectionally. Clicking a TransitionPiece triggers a 3-step animation (walk → fade/swap scenes → walk to entrance in new room).
The room transition system uses TransitionPiece nodes: each has an `exit_node_name` (destination), a polygon, and connects rooms typically bidirectionally. Clicking a TransitionPiece triggers a 3-step animation (walk → fade/swap scenes → walk to entrance in new room).
## When to Use
@@ -16,7 +18,6 @@ The room transition system uses TransitionPiece nodes: each has an `exit_node_na
- Verifying room connectivity
- Debugging why a path can't be traversed
- Automating room transitions during testing
- Generating step-by-step click instructions for agents
## How It Works
@@ -30,9 +31,19 @@ Room identification at runtime:
- Room identity comes from `background.get_script().resource_path` (e.g., `res://scenes/kq4_010_forest_path/kq4_010_forest_path.gd`)
- TransitionPiece children have `.target` UIDs and `.label` strings for cross-referencing
### Getting the current room name at runtime
The canonical way is via `MainGame.get_current_room_name()`:
```json
{"command": "eval", "params": {"code": "get_tree().root.get_node('Node2D').get_current_room_name()"}}
```
Returns a string like `"kq4_010_forest_path"`. The implementation extracts the filename from the scene script's resource path (`script_path.trim_suffix(".gd").get_file()`), NOT the node name (which is always `"background"`).
## Quick Start
### Step 1 Make a plan:
### Step 1 Make a plan:
```bash
python tools/kq4_room_navigator.py --from kq4_003_fountain_pool --to kq4_011_enchanted_grove
@@ -47,73 +58,68 @@ Path: kq4_003_fountain_pool → kq4_011_enchanted_grove (3 steps)
3. Click 'kq4_011_enchanted_grove' in kq4_010_forest_path → kq4_011_enchanted_grove [Enchanted Grove] (click at: 1898, 610)
```
This will tell you which set pieces to click on to get there. Specifically, this is suggesting that you use the walk interaction with kq4_004_ogres_cottage. This can be done using the mcp server for godot, and using the eval tool.
The click coordinates are not used — the `exit_node_name` is what matters.
Here's how to proceed.
### Step 2 — Launch and navigate
1. start the game (godot --path . &)
```bash
# Wait for "McpInteractionServer: Listening on 127.0.0.1:9090" in console
1. Use the Godot MCP to start the game (`godot_run_project`)
2. Poll room name to verify starting room:
```
get_tree().root.get_node('Node2D').get_current_room_name()
```
3. For each step, call **`mock_interact(0)`** on the TransitionPiece node (see exact method name below)
4. Wait and poll until `get_current_room_name()` returns the expected destination
5. Repeat for next step
2. Verify current room matches the starting point
## Detailed Navigation Protocol
3. For each step, find TransitionPiece coordinates at runtime via GDScript eval
4. Use the `func mock_interact(action = 0) -> void` on setpiece using the gdscript eval
5. Poll until room changes to expected destination
6. Verify final arrival
## More detailed
When implementing navigation step by step through MCP commands:
### Starting the game:
```bash
godot --path . &
# Wait for "McpInteractionServer: Listening on 127.0.0.1:9090" in console
```
### Then verify the connection:
Send `{"command": "get_scene_tree"}` via TCP to verify MCP responds. The response will show the full node hierarchy including `Node2D/SceneViewport/background`.
### Discover current room's exits
### Identify current room
```json
{"command": "find_nodes_by_class", "params": {"class_name": "TransitionPiece"}}
{"command": "eval", "params": {"code": "get_tree().root.get_node('Node2D').get_current_room_name()"}}
```
Returns all TransitionPieces in the active scene with `.name`, `.label`, `.target` properties.
Returns `"kq4_0xx_room_name"`, confirming both MCP connectivity and which room you're in.
For rooms without scripts, identify via transition piece labels or target UIDs cross-referenced with `scripts/build_room_graph.py --room <name>` output.
### Discover available exits from current room
### Simulate interactions
Use eval to find the centroid of a TransitionPiece's polygon in viewport space:
Use the `set-piece` group to find all interactive polygons (TransitionPieces are automatically added):
```json
{
"command": "eval",
"params": {
"code": " get_tree().root.get_node_or_null('Node2D/SceneViewport/background/kq4_010_forest_path').mock_interaction(0)"
}
}
{"command": "get_nodes_in_group", "params": {"group": "set-piece"}}
```
Interactions match ActionState.Action (LOOK/WALK/ITEM/TALK)
Filter results for node names starting with `kq4_0` — these are transition exits.
### Waiting after interaction
### Trigger a room transition
```json
{"command": "wait", "params": {"frames": 60}}
**Critical: The method name is `mock_interact`, NOT `mock_interaction`.**
Use `godot_game_call_method` to call it directly on the node path:
```
Call method mock_interact(0) on /root/Node2D/SceneViewport/background/<exit_node_name>
```
Transition animation takes \~1-2 seconds (fade-out + scene swap + fade-in). Wait 30+ frames before checking room change.
Argument `0` = ActionState.Action.WALK (1=LOOK, 2=TOUCH, 3=TALK).
### Waiting — MCP busy protocol
**IMPORTANT**: Walk animations block the McpInteractionServer for ~30 seconds. During this time:
- `eval`, `wait`, `call_method` all fail with "timed out after 30s"
- The server has a built-in `_busy` flag auto-reset after ~30s ("_busy flag stuck for 30.Xs, force-resetting")
**Robust polling pattern**:
1. Call `mock_interact(0)` on the target node (returns immediately, animation starts)
2. Try `wait(frames=30)` then poll room name
3. If both timeout (~30s elapsed), retry the room name poll once more
4. The server will auto-reset and the next poll will succeed
Expected timing per transition: **~45-75 seconds total** (walk ~15s, MCP block ~30s, fade-in ~5s, confirm ~2s)
### Finding transitions by script handler
In room `.gd` files, handlers like `_on_ogres_cottage_interacted()` reference the TransitionPiece node (`$kq4_004_ogres_cottage.default_script(self)`). The handler's parameter name matches the node name you need to call `mock_interact(0)` on.
## Room Graph Structure
@@ -122,49 +128,66 @@ The graph currently has:
- **96 rooms** across 23 connected components
- Largest component: 36 rooms (starting from room 3 Fountain Pool)
- Second largest: 17 rooms (room 1 Beach area)
- Some rooms are fully disconnected (not all transitions wired bidirectionally yet)
- Two additional 36-room components that are disconnected due to UID mismatches
Use `python scripts/build_room_graph.py --room <name>` to check a room's available exits.
Use `python scripts/build_room_graph.py --from A --to B` to find a path. If "no path" is returned but rooms feel like they should connect, the cause is likely a stale UID (see Common Issues).
### UID mismatch example
Room `kq4_018_cemetery` has UID `uid://b3fjmiaribbrl`, but nearby rooms point to it with stale UIDs (`uid://35amqvpjgpf2x`). Runtime navigation works fine (Transitions use file paths), but the graph can't find the connection.
## Common Issues
| Problem | Diagnosis | Fix |
| --- | --- | --- |
| "No path" between expected connected rooms | Room has no matching .uid file or transition pieces not wired bidirectionally | Check `build_room_graph.py` output for which component each room belongs to; verify the target room's `.tscn` has correct UID and exit nodes |
| MCP "Server busy" error during navigation | Previous command hasn't completed (30s timeout on server) | Tool handles automatic retry with exponential backoff. If persistent, restart game. |
| Click lands outside polygon → transition doesn't trigger | Scale transform not applied to coordinates | Use runtime eval (not offline centroid). The `--navigate` flag uses MCP eval which reads actual node transforms. |
| "Room not found in graph" | Room name mismatch — must use exact `.tscn` filename stem | Run `python scripts/build_room_graph.py` to see available room names |
| "Expected X but game reports Y" during --navigate | Game is on different room than specified | Restart at correct room, or adjust --from flag. Check current room name via MCP eval. |
| `mock_interaction` method not found | Wrong method name | Use **`mock_interact(0)`** — no trailing "tion" |
| MCP commands time out during transition | Walk animation blocks server (~30s) | Wait and retry; server auto-resets after ~30s |
| "No path" between adjacent rooms at graph level | Target UID in source `.tscn` doesn't match destination room's `.uid` file | Runtime still works. Fix the target uid in the source tscn or update the destination room's .uid |
| `get_current_room_name()` returns `"background"` | Old implementation used `scene.name` | Updated to use `script_path.trim_suffix(".gd").get_file()` |
## Verified Navigation Example
Successfully tested navigating from kq4_003_fountain_pool to kq4_018_cemetery via 5 transitions:
```
kq4_003_fountain_pool → mock_interact(kq4_004_ogres_cottage) → kq4_004_ogres_cottage ✓
kq4_004_ogres_cottage → mock_interact(kq4_010_forest_path) → kq4_010_forest_path ✓
kq4_010_forest_path → mock_interact(kq4_011_enchanted_grove) → kq4_011_enchanted_grove ✓
kq4_011_enchanted_grove → mock_interact(kq4_017_spooky_house_exterior) → kq4_017_spooky_house_exterior ✓
kq4_017_spooky_house_exterior → mock_interact(kq4_018_cemetery) → kq4_018_cemetery ✓
```
## API Reference
### build_room_graph.py
### build_room_graph.py module
```python
from build_room_graph import build_graph, find_path, NavigationStep
from pathlib import Path
from scripts.build_room_graph import build_graph, find_path, NavigationStep
graph = build_graph(scenes_dir) # RoomInfo dict keyed by room name
steps = find_path(graph, "kq4_003_fountain_pool", "kq4_011_enchanted_grove") # List[NavigationStep] or None
step.from_room # Navigation source room name
step.exit_node_name # TransitionPiece node to click
step.to_room # Destination room name
step.label # Human-readable label
step.viewport_centroid() # (x, y) tuple — polygon center in viewport coords
graph = build_graph(Path('scenes')) # RoomInfo dict keyed by room name
steps = find_path(graph, "kq4_003_fountain_pool", "kq4_018_cemetery") # List[NavigationStep] or None
step.from_room # Source room name
step.exit_node_name # TransitionPiece node to call mock_interact(0) on
step.to_room # Destination room name
step.label # Human-readable label (e.g., "Ogre's Cottage")
```
### kq4_room_navigator.py CLI
```bash
python tools/kq4_room_navigator.py [--from ROOM] [--to ROOM] [summary]
python tools/kq4_room_navigator.py --from ROOM --to ROOM
# For graph summary:
python tools/kq4_room_navigator.py summary
```
## Key Files
| File | Purpose |
| --- | --- |
| `MainGame.gd` | Root game node; `get_current_room_name()` returns room from script path |
| `tools/kq4_room_navigator.py` | CLI combining graph + BFS + MCP navigation |
| `scripts/build_room_graph.py` | Room adjacency graph builder + BFS pathfinding |
| `scripts/check_transitions.py` | Existing transition validation (related) |
| `TransitionPiece.gd` | TransitionPiece node class (class_name TransitionPiece) |
| `TransitionPiece.gd` | Exit nodes; `mock_interact(action)` triggers default_script() |
| `SetPiece_.gd` | Base interactive polygon class (class_name SetPiece) |
| | |
| `.opencode/skills/kq4-room-creator/SKILL.md` | Related: creating new rooms with transitions |