Files
ai-game-2/.opencode/plans/kq4-room-navigator.md
2026-04-29 13:35:31 -07:00

8.8 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. 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 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
  • Manual MCP workflow (for step-by-step agent control):
    • Start Godot: godot --path . or run exported binary
    • 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
  • 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]

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)