# Sprint 5f — Spatial Validation in node_ops — Complete Specification

## Objective

Add spatial validation to the `godotiq_node_ops` MCP tool. When operations include `validate: true`, the tool uses scene_map (Phase 1 filesystem .tscn analysis) to check the target position BEFORE executing the operation. If the position is problematic, it blocks execution and returns a diagnostic message instead of blindly executing.

This is entirely Python-side. No GDScript/addon changes needed.

## Design Decisions

### API Design

- **`validate` flag**: Per-operation boolean field. Explicit opt-in (`validate: true`). No validation unless requested.
- **`scene` parameter**: Top-level parameter alongside `operations`. One scene for all validated ops. Falls back to project's main scene if not provided.
- **Batch behavior**: Atomic — if ANY operation is BLOCKED by validation, NONE execute. All-or-nothing.

Example call:
```json
{
  "operations": [
    {"op": "move", "node": "Shelf_04", "position": [1.8, 0, -0.5], "validate": true},
    {"op": "scale", "node": "Printer_01", "scale": [0.5, 0.5, 0.5], "validate": true}
  ],
  "scene": "res://scenes/main.tscn"
}
```

### Validation Checks (v1)

1. **Wall proximity**: Distance to nearest StaticBody3D or node with "Wall"/"Floor"/"Ceiling" in name
   - < 0.2m → BLOCKED
   - < 0.5m → WARNING
   - Uses XZ horizontal distance (ignores Y)

2. **Node overlap**: Distance to any other node at same position
   - < 0.1m → BLOCKED (positions overlap)

3. **Sibling outlier**: Distance from nodes with similar name prefix (Shelf_01, Shelf_02, etc.)
   - > 3x avg inter-sibling distance AND > 5m → WARNING

4. **Scale outlier**: Scale comparison against sibling nodes
   - > 3x or < 1/3x average sibling scale → WARNING

### Wall Detection Heuristic (v1)

Simple type + name matching:
- Node type contains `StaticBody` → wall candidate
- Node name contains `Wall`, `Floor`, `Ceiling`, `Ground`, `Barrier` (case-insensitive) → wall candidate

No group/collision layer checks in v1. No collision shape dimension analysis in v1.

### Severity Levels

- **OK**: Position is safe. Operation executes normally.
- **WARNING**: Position has concerns (close to wall, scale outlier). Executes with warning attached to result.
- **BLOCKED**: Position is invalid (inside wall, overlapping). Operation NOT executed. No suggestion computation in v1.

### Architecture

```
MCP wrapper (godotiq_node_ops in __init__.py)
  │
  ├── ctx.request_context.lifespan_context["session"]  → GodotIQSession
  │
  ├── If any op has validate:true AND scene provided:
  │     session.resolve_file_path(scene) → res:// path
  │     _build_scene_map(session, scene_path) → scene_data dict
  │     Pass scene_data to node_ops()
  │
  └── node_ops(operations, scene_data=None)
        │
        ├── For each op with validate:true:
        │     spatial_validator.validate_move/validate_scale(target, name, scene_data)
        │     → {severity, message, checks, valid}
        │
        ├── If any BLOCKED → return immediately, no execution
        │
        └── If all OK/WARNING → bridge.request("node_ops", ...) → attach validation to result
```

**Key insight**: `spatial_validator.py` is pure Python. Takes dicts, returns dicts. No session, no bridge, no async. Fully testable in isolation.

The session access happens in the MCP wrapper (`__init__.py`), which already has `ctx`. The wrapper calls `_build_scene_map` to get scene data, then passes it as a dict to `node_ops()`.

### Scene Data Format

The validator consumes the `nodes` list from scene_map output:
```python
{
    "nodes": [
        {"name": "Wall_East", "type": "StaticBody3D", "position": [5, 0, 0], "scale": [1,1,1], ...},
        {"name": "Shelf_01", "type": "Node3D", "position": [1, 0, 0], "scale": [0.4, 0.4, 0.4], ...},
    ]
}
```

Each node dict has at minimum: `name`, `type`, `position` (list of 3 floats). Optional: `scale`, `groups`, `depth`.

## Files

### DO NOT TOUCH
- `godot-addon/` — no GDScript changes
- `src/godotiq/parsers/` — use, don't modify
- `src/godotiq/resolvers/` — use, don't modify
- Existing test files

### CREATE
- `src/godotiq/tools/bridge/spatial_validator.py` — pure validation logic
- `tests/test_bridge/test_spatial_validator.py` — unit tests (11+)

### MODIFY
- `src/godotiq/tools/bridge/__init__.py` — update MCP wrapper to pass session/scene_data, update tool description
- `src/godotiq/tools/bridge/node_ops.py` — add validate parameter, call spatial_validator
- `src/godotiq/prompts/godot_development.md` — document validate parameter

## Deferred to v2
- Room bounds from `.godotiq.json` config
- Pushback suggestion computation (safe position)
- Group/collision layer wall detection
- Collision shape dimension analysis (BoxShape3D extents)
- Configurable wall detection rules
