# Codebase Research — Sprint 9a: build_scene

## 1. Tool Registration Pattern

- FastMCP instance in `server.py`: `mcp = FastMCP(name="godotiq", lifespan=godotiq_lifespan)`
- Each tool category has `__init__.py` with `@mcp.tool()` wrappers
- `server.py` imports each category: `from godotiq.tools import bridge` (line 89)
- This triggers all decorators in the bridge module

### Bridge __init__.py Pattern
```python
from godotiq.tools.bridge.node_ops import node_ops as _node_ops

@mcp.tool(
    name="godotiq_node_ops",
    annotations={"readOnlyHint": False, "destructiveHint": True, ...},
)
async def godotiq_node_ops(operations, scene, detail="normal", ctx=None) -> dict:
    """Docstring."""
    try:
        result = await _node_ops(operations=operations, scene_data=...)
        return apply_output_limit(result, detail)
    except BridgeConnectionError as e:
        return {"error": str(e), "hint": "..."}
    except BridgeTimeoutError as e:
        return {"error": str(e), "hint": "..."}
    except BridgeError as e:
        return {"error": f"[{e.code}] {e.error_message}"}
    except Exception as e:
        return {"error": f"Unexpected error: {e}"}
```

## 2. Bridge Tool Communication

### Flow
1. Call `get_bridge()` → WebSocket connection singleton
2. Call `bridge.request(method, params)` → JSON over WebSocket
3. Response matched by `id` field, returns `BridgeResponse`

### Protocol
- Request: `{"id": str, "method": str, "params": dict}`
- Response: `{"id": str, "status": "ok"|"error", "result": dict, "error": dict}`
- Timeouts in `protocol.py`: `node_ops` = 10.0s, default = 5.0s

### node_ops.py Implementation
- Validates max 50 operations
- Optional spatial validation before sending
- Sends `bridge.request("node_ops", params={"operations": operations})`
- Returns atomic response

## 3. GDScript Handler Pattern

### Dispatch
```gdscript
func _dispatch(peer_id, id, method, params):
    match method:
        "node_ops": _handle_node_ops(peer_id, id, params)
        _: _send_error(peer_id, id, "UNKNOWN_METHOD", "...")
```

### _handle_node_ops Flow
1. Get `operations` array from params
2. Get `edited_scene_root`
3. `undo_redo.create_action("GodotIQ: N node operation(s)")`
4. For each op: `_execute_node_op(op_data, scene_root, undo_redo)`
5. If any succeeded: `undo_redo.commit_action()`
6. `send_response(peer_id, id, {results, scene_modified, undo_available, action_name})`

### _op_add_child Implementation (CRITICAL for build_scene)
```gdscript
func _op_add_child(op_data, scene_root, ur):
    # Find parent by name/path
    var parent = _find_node_by_name_or_path(parent_name, scene_root)

    # Create node from scene or type
    if scene_path != "":
        var packed = ResourceLoader.load(scene_path)
        new_node = packed.instantiate()
    elif type_name != "":
        new_node = ClassDB.instantiate(type_name)

    new_node.name = child_name

    # Register with undo/redo (DEFERRED pattern - no immediate add_child)
    ur.add_do_method(parent, "add_child", new_node)
    ur.add_do_method(self, "_set_owner_recursive", new_node, scene_root)
    ur.add_do_reference(new_node)
    ur.add_undo_method(parent, "remove_child", new_node)

    # Position via add_do_property (also deferred)
    if pos is Array and pos.size() >= 3:
        ur.add_do_property(new_node, "position", Vector3(...))

    return {"op": "add_child", "node": child_name, "parent": parent_name, "status": "ok"}
```

**KEY INSIGHT**: The existing `_op_add_child` does NOT call `parent.add_child()` immediately. It uses `ur.add_do_method()` for deferred execution. All property setting is also via `ur.add_do_property()`. This is different from the spec's proposed approach that calls `parent.add_child()` immediately.

The build_scene handler MUST follow this same deferred pattern. Properties (position, rotation, scale) are set via `add_do_property`, not by directly modifying the node.

### Helper: _find_node_by_name_or_path
```gdscript
func _find_node_by_name_or_path(name_or_path, scene_root):
    var node = scene_root.get_node_or_null(NodePath(name_or_path))
    if node != null: return node
    return _find_by_name_recursive(scene_root, name_or_path)
```

### Helper: _set_owner_recursive
Sets owner on node and all children (needed for scene saving).

## 4. MCP Prompt Structure

File: `src/godotiq/prompts/godot_development_prompt_v2.md` (original was deleted)

### Major Sections
1. Header — overview + core philosophy
2. DO NOT statements — anti-patterns
3. CRITICAL: How to Build 3D Scenes — .tscn-based building
4. Token Efficiency — detail="brief" default
5. Mandatory Workflows — session start, editing, 3D, code changes, testing
6. Tool Reference — UNDERSTAND / EDIT / PLAY & DEBUG
7. Spatial Validation
8. GDScript Conventions
9. Error Recovery

### Relevant Insertion Points
- After "CRITICAL: How to Build 3D Scenes" section
- Or as new section in "Mandatory Workflows" → "3D Scene Work"

## 5. Test Patterns

### Framework
- pytest + pytest-asyncio with `asyncio_mode = "auto"`
- Fixtures in `tests/test_tools/conftest.py`
- Session-based: `GodotIQSession(sample_root).load()`

### Bridge Tool Testing
Bridge tools have NO unit tests (require running addon). Only integration tests exist.
For build_scene: test the Python-side logic (parameter expansion, validation) without needing a bridge connection.

### Output Limiting
```python
_CHAR_LIMITS = {"brief": 2000, "normal": 8000, "full": None}
apply_output_limit(result, detail, list_keys=["results"])
```

## 6. Key Decisions for Implementation

### Python Side
- New file: `src/godotiq/tools/bridge/build_scene.py`
- Pattern: `async def build_scene(grid, line, scatter, nodes, parent, scene) -> dict`
- Sends single `bridge.request("build_scene", params={...})` to addon
- Add timeout to `protocol.py`: `"build_scene": 30.0` (creating many nodes)
- Register in `bridge/__init__.py` with full error handling

### GDScript Side
- Add `"build_scene"` case to `_dispatch()` match
- Implement `_handle_build_scene()` with pattern expansion
- Reuse `_find_node_by_name_or_path()` and `_set_owner_recursive()`
- Follow DEFERRED undo/redo pattern (add_do_method, add_do_property)
- All nodes in ONE undo action

### Pattern Expansion (GDScript side)
Grid, line, scatter expansion happens in GDScript (addon side), not Python. This keeps the WebSocket payload minimal and lets the addon handle Godot-specific math (Vector3, rotations).

### Testing Strategy
- Test Python parameter validation only (no bridge needed)
- Test grid/line/scatter expansion logic if extracted as pure functions
- Verify backward compatibility with existing tests
