diff --git a/.opencode/plans/kq4-room-navigator.md b/.opencode/plans/kq4-room-navigator.md index dfe4a78..4a235f9 100644 --- a/.opencode/plans/kq4-room-navigator.md +++ b/.opencode/plans/kq4-room-navigator.md @@ -79,13 +79,14 @@ python tools/kq4_room_navigator.py --from kq4_004_ogres_cottage --to kq4_092_lol 5. **Connect to MCP** (runtime): Opens TCP connection to port 9090 6. **For each navigation step**: - a. `find_nodes_by_class(class_name="TransitionPiece")` — discover all transition pieces in current scene - b. Match the exit node name from our path → get runtime position + polygon - c. Compute click coordinate: centroid of the TransitionPiece's `polygon`, transformed to viewport coordinates - d. `click(x, y)` — trigger the transition - e. Wait for transition animation (2-3s via `wait` command or poll-loop) - f. **Verify**: `eval("return get_tree().root.get_node_or_null('Node2D/SceneViewport/background').name")` to confirm we've entered the expected room - g. If verification fails, retry up to 2 times with adjusted coordinates + 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's `polygon`, transformed to viewport coordinates + e. `click(x, y)` — trigger the transition + f. Wait for transition animation (2-3s via `wait` command 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 the `to_room` from 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 via `screenshot()` for visual confirmation 7. **Complete**: Print summary of navigation path taken @@ -118,12 +119,24 @@ 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: + + ```gdscript + # 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_room` field 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}}` - - Wait and verify room change via `eval` or `screenshot` + - 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. - **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 @@ -134,6 +147,31 @@ The skill guide documents: ``` [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**: +```bash +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. diff --git a/.opencode/skills/kq4-room-navigator/SKILL.md b/.opencode/skills/kq4-room-navigator/SKILL.md index c14e86c..405e546 100644 --- a/.opencode/skills/kq4-room-navigator/SKILL.md +++ b/.opencode/skills/kq4-room-navigator/SKILL.md @@ -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 ` 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/ ``` -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 ` 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 |