# GodotIQ v0.1.4 — Research Findings

## Part 1: Codebase Analysis

### 1. BUILD_SCENE Tool

**Python MCP Side** (`src/godotiq/tools/bridge/build_scene.py`):
- Validation layer (lines 12-106): mode exclusivity, grid/line/scatter/nodes validation
- Grid: requires `rows`/`cols`, default `spacing=1.0`, max 256 nodes
- Line: requires `points` array (≥2), spacing intervals along polyline
- Scatter: returns `items` directly
- Nodes: returns `params["nodes"]` directly
- Grid connectivity validation (lines 109-157): 4-connectivity check for overrides

**GDScript Addon Side** (`godotiq_server.gd`):
- Handler: `_handle_build_scene` (lines 1463-1537)
- Parent resolution: `_find_node_by_name_or_path(parent_str, scene_root)` (lines 1474-1481) → "PARENT_NOT_FOUND" error
- `_expand_build_grid` (lines 1552-1595): Node naming `{prefix}_{row}_{col}`, default prefix="Node", origin defaults to `[0,0,0]`
- `_expand_build_line` (lines 1598-1687): Node naming `{prefix}_{index}`
- Scatter expansion (line 1544-1546): returns items as-is
- `_create_build_node` (lines 1690-1712): PackedScene instantiation or ClassDB

### 2. CHECK_ERRORS Tool

**Python** (`src/godotiq/tools/bridge/check_errors.py`, lines 8-20):
- Minimal wrapper, forwards scope param to bridge

**GDScript** (`godotiq_server.gd`, lines 1885-1955):
- Three scopes: "project", "scene", specific path
- Basic error dict: `{file: path, line: 0, error: "..."}`
- Error types: load failure, parse errors (cannot instantiate), reload failure
- Logger integration (Godot 4.5+): `_get_script_errors()` with `{file, line, error}`
- Dedup key: `"{file}|{line}|{message}"`

### 3. EXEC Tool

**Python** (`src/godotiq/tools/bridge/exec_code.py`, lines 14-70):
- Contexts: "editor" and "game"
- Response: `{status: "OK|BLOCKED|COMPILE_ERROR|ERROR", result, error}`
- Timeout: default 5000ms, max 10.0s bridge timeout

**GDScript**: Game context via EngineDebugger sandbox, editor context with full EditorInterface access

### 4. ASSET_REGISTRY Tool

**Python** (`src/godotiq/tools/assets/__init__.py`):
- Path filter: substring matching (case-sensitive), line 130-132
- Zero results: returns empty `assets_list` with counts
- Categories: models/textures/audio/fonts/scenes/resources/shaders/other
- Detail levels: brief/normal/full

### 5. SCENE_MAP Tool

**Python** (`src/godotiq/tools/spatial/__init__.py`):
- Detail levels: brief (name/type/position/depth), normal (+rotation/scale/groups/script), full (+properties/metadata)
- Bounds calculation: min/max [x,y,z]
- Scene resolution via `session.resolve_scene_spatial(scene_path)`

### 6. SAVE_SCENE Tool

**Python** (`src/godotiq/tools/bridge/save_scene.py`, lines 19-26):
- No params, bridge request to `"save_scene"`, timeout 5.0s
- Response: `{saved: bool, scene_path, scene_name}`

### 7. SCREENSHOT Tool

**Python** (`src/godotiq/tools/bridge/screenshot.py`):
- Params: viewport, scale (0.0-1.0, default 0.25), format (webp/png/jpg), quality, camera_position, camera_target
- Routing: editor=direct, game=retry (max 3, 0.5s delay)
- Response: `{image: base64, format, width, height}`

### 8. INPUT Tool

**Python** (`src/godotiq/tools/bridge/input_sim.py`, lines 35-63):
- Commands: action, wait, key, UI tap
- Signal verification: `wait_for` + timeout
- Side effects tracking
- Timeout: 65.0s

### 9. NODE_OPS Tool

**Python** (`src/godotiq/tools/bridge/node_ops.py`):
- Operations: move, rotate, scale, set_property, add_child, delete, duplicate, reparent
- Max 50 ops per call
- Spatial validation with atomic batch rejection

### 10. PACKAGING & VERSIONING

**Version locations (MUST sync)**:
- `pyproject.toml` line 7: `version = "0.1.3"`
- `godot-addon/addons/godotiq/plugin.cfg` line 6: `version="0.1.0"`
- `godotiq_server.gd` line 7: `const ADDON_VERSION := "0.1.0"`

**CLI** (`src/godotiq/cli.py`):
- `--version` via argparse action
- `install-addon` subcommand copies addon files

**Build** (`pyproject.toml`):
- Hatch build, packages `src/godotiq`, force-includes `godot-addon/addons/godotiq` as `godotiq/addon`

### 11. LICENSE/DEV OVERRIDE

**Module** (`src/godotiq/license.py`):
- Dev override (line 119): `GODOTIQ_LICENSE=pro` → bypass (no validation)
- License key check: `GODOTIQ_LICENSE_KEY` env → disk cache → Polar API → offline fallback
- PRO tools gated with teaser builders for community users

### 12. TESTING

- Config: pytest + pytest-asyncio, `asyncio_mode = "auto"`
- Structure: `tests/test_bridge/`, `tests/test_tools/`, `tests/test_parsers/`
- Patterns: mock sessions/bridges, async test functions
- Fixtures in `tests/conftest.py`: `session`, `sample_root`

---

## Part 2: Web Research

### Image Cropping in Godot 4.x

**`Image.get_region(region: Rect2i) -> Image`** (renamed from `get_rect()` in Godot 4.0):
- Returns a new Image containing a copy of the specified rectangular area
- Most direct way to crop

**Viewport capture + crop pattern:**
```gdscript
var image: Image = get_viewport().get_texture().get_image()
var crop_rect := Rect2i(100, 50, 480, 296)
var cropped: Image = image.get_region(crop_rect)
var png_bytes: PackedByteArray = cropped.save_png_to_buffer()
var base64_string: String = Marshalls.raw_to_base64(png_bytes)
```

**Performance notes:**
- `get_region()` allocates new Image — doubles memory temporarily
- `save_png_to_buffer()` is CPU-bound — crop first, then encode
- Max image size: 16384x16384 pixels

**Alternative: `blit_rect()`** for pre-allocated destination reuse.

### InputEventMouseMotion in Godot 4.x

**Key properties:**
- `relative: Vector2` — mouse movement delta since previous frame (auto-scaled by content scale)
- `screen_relative: Vector2` — unscaled delta (preferred for FPS aiming)
- `position: Vector2` — absolute position in viewport
- `velocity: Vector2` — speed in pixels/sec

**Programmatic dispatch:**
```gdscript
var event := InputEventMouseMotion.new()
event.position = Vector2(400, 300)
event.global_position = Vector2(400, 300)
event.relative = Vector2(200, 0)  # move right 200px
event.velocity = Vector2(2000, 0)
Input.parse_input_event(event)
```

**Important notes:**
- `Input.parse_input_event()` triggers `_input()` and `_unhandled_input()` callbacks
- Does NOT move the OS cursor — use `Input.warp_mouse()` for that
- Multiple events per frame are accumulated — use `+=` not `=`
- Never multiply mouse input by delta (already frame-independent)

**Sources:**
- [Image — Godot 4.4 docs](https://docs.godotengine.org/en/4.4/classes/class_image.html)
- [InputEventMouseMotion — Godot 4.4 docs](https://docs.godotengine.org/en/4.4/classes/class_inputeventmousemotion.html)
- [Input class — Godot 4.4 docs](https://docs.godotengine.org/en/4.4/classes/class_input.html)
