# Sprint 5c Research — Editor Screenshot + Camera Control

## Codebase Research

### Screenshot Tool Architecture

**File**: `src/godotiq/tools/bridge/screenshot.py`
- Uses `bridge.request_with_retry()` with `SCREENSHOT_MAX_RETRIES=3` and `SCREENSHOT_RETRY_DELAY=0.5`
- Parameters: `viewport`, `scale`, `format`
- Already has `viewport` parameter that's forwarded to the bridge

**MCP Registration** in `bridge/__init__.py`:
- `@mcp.tool(name="godotiq_screenshot", annotations={readOnlyHint: True, ...})`
- Standard error handling: BridgeConnectionError, BridgeTimeoutError, BridgeError, Exception
- Accepts `ctx: Context = None` parameter

### Server Dispatch

**godotiq_server.gd dispatch** (lines 124-160):
- All current editor-side handlers are synchronous
- `_handle_editor_screenshot` will be the FIRST async handler using `await`
- Game screenshot forwards via `_handle_game_forward()` to EngineDebugger

### Game Screenshot Encoding Pattern (runtime.gd)

```gdscript
var img := tex.get_image()
var scale: float = clampf(params.get("scale", 0.5), 0.1, 1.0)
if scale < 1.0:
    img.resize(int(w * scale), int(h * scale))
var buffer: PackedByteArray
match fmt:
    "png": buffer = img.save_png_to_buffer()
    "jpg": buffer = img.save_jpg_to_buffer(0.85)
    _: buffer = img.save_webp_to_buffer()
var b64 := Marshalls.raw_to_base64(buffer)
```

### Node Search Functions (reusable for camera)

- `_find_node_by_name_or_path(name_or_path, scene_root)` — tries NodePath first, falls back to recursive
- `_find_by_name_recursive(node, target_name)` — recursive name search

### Testing Pattern

```python
mock_bridge = AsyncMock()
mock_bridge.request_with_retry.return_value = {...}
with patch("godotiq.tools.bridge.screenshot.get_bridge", return_value=mock_bridge):
    result = await screenshot()
mock_bridge.request_with_retry.assert_called_once_with(...)
```

## Web Research

### Editor Viewport API — CONFIRMED

- `EditorInterface.get_editor_viewport_3d(0)` returns a `SubViewport` (indices 0-3)
- `viewport.get_texture().get_image()` works in @tool plugins
- Images include editor gizmos/overlays — no API to exclude them
- `viewport.get_camera_3d()` returns the editor's Camera3D for transform reads
- Source: Godot PR #68696, forum posts

### RenderingServer.frame_post_draw — CRITICAL DIFFERENCE

| Signal | When | Use for |
|--------|------|---------|
| `RenderingServer.frame_post_draw` | After GPU finishes rendering | Viewport texture readback |
| `get_tree().process_frame` | After _process(), BEFORE rendering | Logic sync only |

**MUST use `frame_post_draw` for screenshots** — `process_frame` fires before rendering, would return stale/blank image.

Two awaits recommended:
- Frame 1: camera transform applied
- Frame 2: viewport texture updated with new render

### Camera Control — PR #105613 MERGED (Godot 4.5+)

**Major finding**: PR #105613 (merged April 2025, Godot 4.5) fixes `_apply_camera_transform_to_cursor()`:
- Setting `camera.global_transform` now properly syncs the internal cursor
- Interpolation is removed for external modifications
- Camera position should now PERSIST across user interactions

**For Godot <4.5**: Camera positioning is temporary — editor interpolation resets on interaction.

**Recommendation**:
- Read camera state → always works
- Set camera state → works reliably on 4.5+ (PR #105613), temporary on <4.5
- The "atomic screenshot" pattern (move→capture→restore) works on all versions since we only need the position for 2 frames

### Sources

- PR #68696: Editor viewport exposure
- PR #105613: Camera cursor sync fix (4.5)
- Proposal #12112: Camera control API request (still open)
- Forum: Editor viewport screenshot capture
- Docs Issue #2685: frame_post_draw documentation
