# Sprint 10a: Agent Efficiency & Quality Assurance — Implementation Plan

## Overview

GodotIQ is an MCP server with 33 tools for AI-assisted Godot 4 development. While functionally complete (an agent built a full Tower Defense 3D game from a single prompt), agent workflows suffer from four critical issues:

1. **Wasted time** (25-30 min instead of 10-15) due to redundant tool calls for editor state
2. **Context blindness** — agent doesn't know which scene is open or if there are errors unless it explicitly polls
3. **False completions** — agent declares "done" without validating results (e.g., disconnected road tiles)
4. **Error invisibility** — Godot console errors are not surfaced to the agent

This plan addresses all four issues through 5 coordinated changes across 4 files.

## Architecture Context

### How GodotIQ Works

GodotIQ has two sides:
- **Python MCP server** (`src/godotiq/`): Receives tool calls from the AI agent, sends WebSocket requests to Godot
- **GDScript addon** (`godot-addon/addons/godotiq/`): Runs inside the Godot editor, handles requests, returns JSON responses

The flow: Agent → MCP tool → Python bridge → WebSocket → GDScript handler → JSON response → back up the chain.

### Current Response Pattern

Every GDScript handler constructs a response dict and calls `send_response(peer_id, id, response)`. Some handlers call `send_response()` directly, while game-forwarded requests (screenshot, input, exec, state_inspect, etc.) go through `_handle_game_forward()` and their responses return via `handle_game_response()`. The `_pending_run` confirmation also calls `send_response()` from `_process()`. Additionally, `_send_error()` uses a separate error response format.

No ambient context is included in any response path.

### Error Capture

The `godotiq_debugger.gd` plugin captures `godotiq:error` messages from the running game via `EngineDebugger`. However, generic Godot console errors (script errors, node-not-found, etc.) are NOT captured or forwarded. The agent is blind to editor-side errors.

---

## Section 1: Editor State in Every Response (GDScript)

### Goal
Inject ambient editor context into every response the server sends, regardless of which handler or response path produces it.

### Design

**Key architectural decision**: Instead of modifying each handler individually, inject `_editor_state` inside `send_response()` itself. This is the cleanest approach — one modification point catches all handlers, game-forwarded responses, the `_pending_run` confirmation, and any future handlers. Also inject in `_send_error()` so error responses include context too.

**New member variable**: `_recent_errors: Array` — a ring buffer holding the last 10 error strings. Populated from the debugger error capture and from error conditions caught in handlers.

**New helper function**: `_get_editor_state() -> Dictionary`
- Reads `EditorInterface.get_edited_scene_root()` for open scene path (returns `scene_file_path` or `""` if null)
- Reads `EditorInterface.is_playing_scene()` for game running state
- Returns last 3 entries from `_recent_errors` (sliced, not the full buffer)
- Returns structured dict

**New method**: `_record_error(msg: String)` — appends to `_recent_errors`, trims to max 10 entries (FIFO eviction).

**Error wiring**: Modify the debugger's `_capture` method (in `godotiq_debugger.gd`) to call `server._record_error(error_string)` when it receives `godotiq:error` messages, in addition to the existing `send_event()` call. Also capture the built-in `"error"` prefix messages if possible.

**Integration in `send_response()`**: Before JSON serialization, merge `_get_editor_state()` into the response dict:
```gdscript
func send_response(peer_id, id, result):
    result["_editor_state"] = _get_editor_state()
    # ... existing serialization and send logic ...
```

**Integration in `_send_error()`**: Similarly attach `_editor_state` to error responses so the agent has context even when operations fail.

### Response Shape

```python
{
    # ... normal tool-specific data ...
    "_editor_state": {
        "open_scene": "res://Main.tscn",  # or "" if no scene open
        "game_running": false,
        "recent_errors": ["Error at tower.gd:40: Invalid node path"]  # last 3, or []
    }
}
```

### Python-side Backward Compatibility

Bridge tool wrappers in Python should default `_editor_state` to `{}` if the key is not present in the GDScript response. This handles the case where a newer Python server connects to an older addon.

The `apply_output_limit()` function must exclude `_editor_state` from truncation — it's a small fixed-size dict that should always be preserved intact.

---

## Section 2: Pre-flight Scene Checks (GDScript)

### Goal
Add precondition validation to `_handle_build_scene` and `_handle_node_ops` so the agent gets an immediate, actionable error instead of a cryptic failure.

### Design

**New helper**: `_preflight_scene_check(params: Dictionary) -> Dictionary`
- Returns `{"ok": true}` if checks pass
- Returns `{"ok": false, "error": "actionable message"}` if checks fail

**Check 1 — Scene is open**:
If `EditorInterface.get_edited_scene_root()` is null, return error:
`"No scene is open in the editor. Call godotiq_exec to open a scene first: EditorInterface.open_scene_from_path('res://YourScene.tscn')"`

**Check 2 — Correct scene** (if params contain a `scene` key):
If the open scene's `scene_file_path` doesn't match the requested scene, return actionable error (do NOT auto-open — that discards unsaved changes):
`"Scene 'res://Current.tscn' is open but you requested 'res://Target.tscn'. Save and switch scenes first: call godotiq_save_scene() then godotiq_exec('EditorInterface.open_scene_from_path(\"res://Target.tscn\")')"`

**Integration**: Call `_preflight_scene_check()` at the top of `_handle_build_scene` and `_handle_node_ops`. If check fails, call `_send_error()` with the actionable message and return early. Since `_send_error()` now includes `_editor_state` (from Section 1), the agent gets full context even on preflight failures.

### Existing Code Check

Some handlers may already have partial scene checks. The pre-flight helper consolidates and standardizes these with actionable error messages. Any existing checks should delegate to the new helper.

---

## Section 3: Grid Connectivity Validation (Python)

### Goal
After `build_scene` processes a `grid` mode request with `overrides`, validate that override tiles form a connected path with no isolated tiles or gaps. This validation runs on the Python side (pure computation, no Godot APIs needed) for easy unit testing.

### Design

**New function in `build_scene.py`**: `validate_grid_connectivity(overrides: dict, spacing: float) -> list[str]`

**Input**:
- `overrides`: Dictionary mapping grid coordinates (e.g., `"2,3"`) to override data
- `spacing`: Distance between adjacent grid cells

**Key format**: Override keys use `"row,col"` format (matching `_expand_build_grid()` which uses `"%d,%d" % [row, col]`).

**Algorithm**:
1. If fewer than 2 overrides, return empty list (single override is a valid use case)
2. Parse override keys to extract grid coordinates as `(row, col)` integer pairs
3. Skip overrides that specify a custom `position` key (their world-space location doesn't match grid layout, so distance checks would be false positives)
4. For each remaining override coordinate, check if it has at least one adjacent override neighbor (up, down, left, right — 4-connectivity)
5. Collect warning strings for isolated tiles (no adjacent override neighbors)

**Output**: List of warning strings, or empty list if all valid.

**Integration in `build_scene.py`**: After the bridge request returns successfully, if mode was "grid" and overrides exist:
- Call `validate_grid_connectivity(overrides, spacing)`
- Add result as `"warnings"` key in the tool result dict
- If no warnings, set `"warnings": []`

### Limitations
- Only checks 4-connectivity (up/down/left/right), not diagonal adjacency
- Does not validate world-space distances (only grid adjacency) — this avoids false positives from custom positions
- Override tiles with custom `position` are excluded from validation

---

## Section 4: MCP Prompt — QA Checklist

### Goal
Add a mandatory 6-step QA checklist to the MCP prompt that the agent must follow before declaring any task "done".

### Location
In `src/godotiq/prompts/godot_development_prompt_v2.md`, after the "Creation Workflow" section and before "Error Recovery".

### Content

The checklist enforces:
1. **Spatial coherence** — call `spatial_audit(detail="brief")`, must have 0 critical/warning
2. **Grid/path connectivity** — check build_scene response warnings, fix with node_ops if needed
3. **Code quality** — call `validate(detail="brief")` on every .gd file created
4. **Signal wiring** — call `signal_map(detail="brief", find="orphans")`, must be 0 orphans
5. **Gameplay test** — run/state_inspect/input/state_inspect/stop sequence
6. **Report to user** — state what was built, QA results, and explicitly ask user to visually verify

### Key Constraint
The agent must NEVER say "I can see the terrain" or "the game looks great." It CANNOT see. It must always ask the user to verify visually.

---

## Section 5: MCP Prompt — Efficiency Rules

### Goal
Add efficiency guidelines to the MCP prompt that prevent the agent from overthinking and making redundant tool calls.

### Location
In `src/godotiq/prompts/godot_development_prompt_v2.md`, after the detail level rules section, near the top of the file.

### Content

Five rules:
1. **Don't overthink — act**: Call the tool immediately when you know what to do. If it fails, you'll know from the response.
2. **Batch operations**: Use build_scene for multiple nodes instead of individual node_ops calls. Use script_ops patch mode for targeted changes instead of rewriting entire files.
3. **Don't repeat tool calls**: If you already called project_summary or asset_registry this session, don't call them again. Keep results from previous calls in your conversation context.
4. **Check _editor_state**: Every bridge tool response includes `_editor_state` with `open_scene`, `game_running`, and `recent_errors`. Read it. If `recent_errors` is not empty, address the errors before continuing. If `open_scene` is wrong or empty, fix it before doing more work.
5. **One script, one validate**: After writing each .gd file, immediately call validate on it. Don't write 5 scripts and then validate them all — errors compound and become harder to debug.

---

## Testing Strategy

### New Tests

All new tests are pure Python unit tests using mocked bridge responses. No live Godot connection required.

**Grid connectivity validation tests** (in `build_scene.py` or new test file):
- Test fully connected L-shaped path (no warnings)
- Test isolated tile among multiple overrides (warning generated)
- Test empty overrides (no warnings, no crash)
- Test single override tile (no warnings — valid use case)
- Test T-shaped and cross-shaped paths (no warnings)
- Test overrides with custom position are excluded from validation
- Test disconnected groups (2 separate clusters of overrides)

**Warning passthrough tests**:
- Test build_scene tool result includes `warnings` key
- Test empty warnings when no connectivity issues

**`apply_output_limit` exclusion test**:
- Test that `_editor_state` is preserved when output limit is applied

### Regression
All existing tests in `tests/` must continue to pass: `pytest tests/ -v`

---

## File Change Summary

| File | Type | Changes |
|------|------|---------|
| `godot-addon/addons/godotiq/godotiq_server.gd` | GDScript | Add `_recent_errors` array, `_get_editor_state()`, `_record_error()`. Modify `send_response()` and `_send_error()` to inject `_editor_state`. Add `_preflight_scene_check()`. Call preflight in `_handle_build_scene` and `_handle_node_ops`. |
| `godot-addon/addons/godotiq/godotiq_debugger.gd` | GDScript | Wire `_record_error()` call in `_capture()` for error messages |
| `src/godotiq/tools/bridge/build_scene.py` | Python | Add `validate_grid_connectivity()` function. Call after bridge request for grid+overrides. Include `warnings` in result. Handle backward compatibility for `_editor_state`. |
| `src/godotiq/prompts/godot_development_prompt_v2.md` | Markdown | Add "Efficiency Rules" section (after detail levels). Add "MANDATORY: Final QA" section (after Creation Workflow, before Error Recovery). |

## Implementation Order

1. **Section 1** first — `_editor_state` injection in `send_response()`/`_send_error()` and error ring buffer. This is the foundation for all other sections.
2. **Section 2** next — pre-flight checks use `_send_error()` which now includes `_editor_state`
3. **Section 3** next — grid validation in Python, independent of GDScript changes
4. **Section 4 & 5** last — prompt changes are independent of code changes

## Risks and Mitigations

| Risk | Impact | Mitigation |
|------|--------|------------|
| `_get_editor_state()` adds latency to every response | Low — cheap API calls (<1ms) | Benchmark: if >5ms, cache with 100ms TTL |
| Error ring buffer grows unbounded | Medium | Cap at 10 entries with FIFO eviction |
| Grid validation false positives for diagonal paths | Medium | Only check 4-connectivity, document limitation |
| Grid validation false positives for custom-positioned overrides | Medium | Skip overrides with custom `position` key |
| Prompt additions make prompt too long | Low | Additions are ~100 lines total |
| Newer Python server connects to older addon without `_editor_state` | Low | Default to `{}` if key missing |
| `apply_output_limit()` strips `_editor_state` | Medium | Exclude `_editor_state` from output limiting |
