Files
ai-game-2/.opencode/plans/kq4-room-navigator.md
Bryce 868b25299a 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.
2026-04-29 15:54:41 -07:00

11 KiB

model, kill, date_introduced
model kill date_introduced
auto
alpha-mask-creator
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:

  1. Parses .tscn files to build complete room adjacency graph (reuse logic from check_transitions.py)
  2. Runs BFS pathfinding between start and destination rooms
  3. Connects to the MCP server on port 9090 for live navigation
  4. Uses eval to compute click coordinates within TransitionPiece polygons in the running game
  5. 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:

  1. Scan scenes/kq4_*/kq4_*.tscn (exclude placeholder_template)
  2. 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
  3. 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:

  1. Build graph (offline): Calls build_graph() to parse all rooms

  2. BFS pathfinding: Finds shortest path from start → destination

  3. No path found: Reports unreachable, lists connected components

  4. Path found: Prints the step-by-step plan with click coordinates

  5. Connect to MCP (runtime): Opens TCP connection to port 9090

  6. 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'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

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_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}}
    • 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

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)