# Sprint 4d Work Plan — Navigation, Watch, Undo History

**Status**: PENDING APPROVAL
**Sprint**: 4d (FINAL Phase 2)
**Baseline**: 608 tests, 12 bridge tools, 24 total MCP tools

---

## Sections (execution order)

### Section 1: Fix existing nav_result debugger bug + add watch_result capture

**Files**: `godot-addon/addons/godotiq/godotiq_debugger.gd`
**Risk**: HIGH — latent bug will break nav_query if not fixed first

The existing `_capture` handler for `"godotiq:nav_result"` calls:
```gdscript
server.handle_game_response("godotiq:nav", data)
```
But `_forward_to_game` will store `method = "godotiq:query_nav"`. The match
`"godotiq:query_nav" == "godotiq:nav"` is FALSE. Fix to:
```gdscript
server.handle_game_response("godotiq:query_nav", data)
```

Also add new capture for watch responses:
```gdscript
"godotiq:watch_result":
    server.handle_game_response("godotiq:watch", data)
    return true
```

Remove diagnostic prints added during debugging (cleanup from bug fix session).

**Verification**: Read file, confirm all `handle_game_response` calls match their
corresponding `_forward_to_game` method names.

---

### Section 2: nav_query — GDScript runtime handler

**Files**: `godot-addon/addons/godotiq/godotiq_runtime.gd`

Add `"query_nav"` to `_on_debugger_message` match block.
Implement `_handle_nav_query(params)`:
- Resolve from/to via node name or [x,y,z] position
- `NavigationServer3D.map_get_closest_point()` for navmesh snap
- `NavigationServer3D.map_get_path()` for pathfinding
- Calculate total distance, direct distance, efficiency ratio
- Serialize path points (cap at 50 for token efficiency)
- Send result via `EngineDebugger.send_message("godotiq:nav_result", [json])`

Data parsing: existing pattern — JSON string in `data[0]`, parse to Dictionary.

**GDScript traps to avoid**:
- Use `snapped()` not `snapf()`
- Use `while` loops with explicit index, not Python-style `for i in range()`
- No ternary expressions
- `float()` casts for position array elements

**Verification**: No syntax errors when Godot loads the file.

---

### Section 3: nav_query — Server dispatch

**Files**: `godot-addon/addons/godotiq/godotiq_server.gd`

Add to `_dispatch()`:
```gdscript
"nav_query":
    _handle_game_forward(peer_id, id, "godotiq:query_nav", params, 10000)
```

**Verification**: Dispatch table is complete with all Sprint 4a-4d methods.

---

### Section 4: nav_query — Python MCP tool + tests

**Files (create)**:
- `src/godotiq/tools/bridge/nav_query.py`
- `tests/test_bridge/test_nav_query.py`

Python tool: validate from/to params, call `bridge.request("nav_query", ...)`.
Tests: 5 cases — missing from, missing to, node-to-node, position-to-position, optimize default.

**Verification**: `pytest tests/test_bridge/test_nav_query.py -v` — all pass.

---

### Section 5: watch — GDScript runtime handler

**Files**: `godot-addon/addons/godotiq/godotiq_runtime.gd`

Add state variables at top:
- `_watches: Dictionary`, `_watch_events: Array`, `_watch_sample_timer: float`
- `_watch_sample_interval: float = 0.5`, `_watch_active: bool = false`

Add `_process(delta)` for periodic sampling.

Add `"watch"` to `_on_debugger_message` match block.

Implement:
- `_handle_watch(params)` — start/stop/read/clear actions
- `_sample_watched_nodes()` — diff-based change detection
- `_get_watch_value(node, prop)` — handles sub-paths, method calls
- `_serialize_watch_value(val)` — JSON-safe conversion

Event cap at 1000 entries (trim to 500 oldest).

**GDScript traps**:
- `_get_watch_value` uses `## comment` docstring style — GDScript uses `##` for
  doc comments but only on class/method level. Use regular `#` for inline.
- String comparison for change detection: `str(current) != str(last)`
- `is_instance_valid(node)` check before accessing freed nodes

**Verification**: No syntax errors when Godot loads the file.

---

### Section 6: watch — Server dispatch

**Files**: `godot-addon/addons/godotiq/godotiq_server.gd`

Add to `_dispatch()`:
```gdscript
"watch":
    _handle_game_forward(peer_id, id, "godotiq:watch", params, 5000)
```

**Verification**: Dispatch entry added.

---

### Section 7: watch — Python MCP tool + tests

**Files (create)**:
- `src/godotiq/tools/bridge/watch.py`
- `tests/test_bridge/test_watch.py`

Python tool: validate action, clamp interval, call `bridge.request("watch", ...)`.
Tests: 6 cases — start no watches, start with watches, read, stop, min interval clamp, clear.

**Verification**: `pytest tests/test_bridge/test_watch.py -v` — all pass.

---

### Section 8: undo_history — GDScript editor handler

**Files**: `godot-addon/addons/godotiq/godotiq_server.gd`

Add `var _godotiq_action_history: Array = []` and `const MAX_HISTORY_SIZE: int = 50`.

Add to `_dispatch()`:
```gdscript
"undo_history":
    _handle_undo_history(peer_id, id, params)
```

Implement `_handle_undo_history()`:
- Use the existing `undo_redo` var (already wired from plugin)
- `undo_redo.has_undo()`, `undo_redo.has_redo()`, `undo_redo.get_current_action_name()`
- Return state + tracked GodotIQ actions

Note: `EditorInterface.get_editor_undo_redo()` is NOT the same as the instance
`undo_redo` var. The server already has `undo_redo` set by plugin — use that.
BUT: `has_undo()`, `has_redo()`, `get_current_action_name()` are on `UndoRedo`,
not `EditorUndoRedoManager`. Need to check which API the wired instance supports.
If `EditorUndoRedoManager`, it has `get_history_undo_redo(id)` to get an `UndoRedo`.

Update `_handle_node_ops()` to append to `_godotiq_action_history` after commit.

**Verification**: No syntax errors, undo_history responds via WebSocket.

---

### Section 9: undo_history — Python MCP tool + tests

**Files (create)**:
- `src/godotiq/tools/bridge/undo_history.py`
- `tests/test_bridge/test_undo_history.py`

Python tool: simple `bridge.request("undo_history", ...)`.
Tests: 3 cases — basic response, empty history, timeout value.

**Verification**: `pytest tests/test_bridge/test_undo_history.py -v` — all pass.

---

### Section 10: MCP tool registration

**Files**: `src/godotiq/tools/bridge/__init__.py`

Add 3 new `@mcp.tool()` wrappers following existing pattern:
- `godotiq_nav_query` — readOnly, openWorld
- `godotiq_watch` — destructive (start/stop changes state), openWorld
- `godotiq_undo_history` — readOnly, openWorld

Import from new modules. Standard bridge error handling.

**Verification**: `python -c "from godotiq.tools.bridge import *; print('OK')"`

---

### Section 11: Full test suite + GDScript audit

**Verification steps**:
1. `pytest tests/ -v` — ALL tests pass (608 + ~14 new = ~622)
2. GDScript audit: grep for `snapf(`, Python ternaries, `f"`, `isinstance(` → ZERO matches
3. Import smoke test for all 3 new modules
4. Verify `_on_debugger_message` match block has: screenshot, query_perf, input, exec, query_state, query_nav, watch
5. Verify `_dispatch` match block has: ping, editor_context, run, stop, screenshot, perf_snapshot, scene_tree, node_ops, input, exec, state_inspect, nav_query, watch, undo_history
6. Verify `_capture` match block has results for all game-side methods + watch_update push event

---

## Critical risks and mitigations

| Risk | Impact | Mitigation |
|------|--------|------------|
| `snapf`/ternary syntax errors in new GDScript | Runtime won't load, ALL game tools break | Automated grep audit in Section 11 |
| nav_result response mismatch (existing bug) | nav_query always times out | Fixed first in Section 1 |
| `EditorUndoRedoManager` API mismatch | undo_history crashes | Check API in Section 8, fallback to basic response |
| Watch `_process` overhead | Game FPS drop | Guard with `_watch_active` flag, cap events at 1000 |
| NavigationServer3D not initialized | nav_query fails on scenes without navmesh | Return error with `reachable: false` |

---

## Deliverables summary

| # | Action | Path |
|---|--------|------|
| 1 | MODIFY | `godot-addon/addons/godotiq/godotiq_debugger.gd` |
| 2 | MODIFY | `godot-addon/addons/godotiq/godotiq_runtime.gd` |
| 3 | MODIFY | `godot-addon/addons/godotiq/godotiq_server.gd` |
| 4 | CREATE | `src/godotiq/tools/bridge/nav_query.py` |
| 5 | CREATE | `src/godotiq/tools/bridge/watch.py` |
| 6 | CREATE | `src/godotiq/tools/bridge/undo_history.py` |
| 7 | CREATE | `tests/test_bridge/test_nav_query.py` |
| 8 | CREATE | `tests/test_bridge/test_watch.py` |
| 9 | CREATE | `tests/test_bridge/test_undo_history.py` |
| 10 | MODIFY | `src/godotiq/tools/bridge/__init__.py` |

**After completion**: 27 MCP tools total. Phase 2 complete.
