Replace raw eval JSON with godot_game_call_method for checking current room, matching the actual Godot MCP tool interface used in practice.
195 lines
8.4 KiB
Markdown
195 lines
8.4 KiB
Markdown
---
|
|
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".
|
|
---
|
|
|
|
|
|
# KQ4 Room Navigator
|
|
|
|
## Overview
|
|
|
|
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 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
|
|
|
|
- Planning multi-room navigation paths
|
|
- Verifying room connectivity
|
|
- Debugging why a path can't be traversed
|
|
- Automating room transitions during testing
|
|
|
|
## How It Works
|
|
|
|
```
|
|
.scenes/*.tscn files → adjacency graph (96 rooms, 233 exits) → BFS pathfinding → use MCP for execution
|
|
```
|
|
|
|
Room identification at runtime:
|
|
|
|
- Game node tree: `/root/Node2D/SceneViewport/background` (always named "background")
|
|
- 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()`:
|
|
|
|
Use `godot_game_call_method` to call it directly on the node path:
|
|
```json
|
|
Call method get_current_room_name on /root/Node2D
|
|
```
|
|
|
|
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:
|
|
|
|
```bash
|
|
python tools/kq4_room_navigator.py --from kq4_003_fountain_pool --to kq4_011_enchanted_grove
|
|
```
|
|
|
|
Output:
|
|
|
|
```
|
|
Path: kq4_003_fountain_pool → kq4_011_enchanted_grove (3 steps)
|
|
1. Click 'kq4_004_ogres_cottage' in kq4_003_fountain_pool → kq4_004_ogres_cottage [Ogre's Cottage] (click at: 1874, 714)
|
|
2. Click 'kq4_010_forest_path' in kq4_004_ogres_cottage → kq4_010_forest_path [Forest Path] (click at: 1042, 1078)
|
|
3. Click 'kq4_011_enchanted_grove' in kq4_010_forest_path → kq4_011_enchanted_grove [Enchanted Grove] (click at: 1898, 610)
|
|
```
|
|
|
|
The click coordinates are not used — the `exit_node_name` is what matters.
|
|
|
|
### Step 2 — Launch and navigate
|
|
|
|
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
|
|
|
|
## Detailed Navigation Protocol
|
|
|
|
### Identify current room
|
|
|
|
```json
|
|
{"command": "eval", "params": {"code": "get_tree().root.get_node('Node2D').get_current_room_name()"}}
|
|
```
|
|
|
|
Returns `"kq4_0xx_room_name"`, confirming both MCP connectivity and which room you're in.
|
|
|
|
### Discover available exits from current room
|
|
|
|
Use the `set-piece` group to find all interactive polygons (TransitionPieces are automatically added):
|
|
|
|
```json
|
|
{"command": "get_nodes_in_group", "params": {"group": "set-piece"}}
|
|
```
|
|
|
|
Filter results for node names starting with `kq4_0` — these are transition exits.
|
|
|
|
### Trigger a room transition
|
|
|
|
**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>
|
|
```
|
|
|
|
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
|
|
|
|
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)
|
|
- Two additional 36-room components that are disconnected due to UID mismatches
|
|
|
|
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 |
|
|
| --- | --- | --- |
|
|
| `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 module
|
|
|
|
```python
|
|
from pathlib import Path
|
|
from scripts.build_room_graph import build_graph, find_path, NavigationStep
|
|
|
|
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
|
|
# 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 |
|
|
| `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 |
|