# Sprint 1 — Parser .tscn: Brief per Deep Plan

## Obiettivo
Implementare un parser completo per file .tscn (Godot 4 text scene format) che estrae tutta l'informazione strutturata necessaria agli altri tool di GodotIQ. Questo è il componente più critico dell'intero progetto — 10+ tool dipendono da questo parser.

## Contesto progetto
GodotIQ è un MCP server Python per Godot 4.x. Sprint 0 (scaffolding) è completato: il package si installa, il server MCP parte, i test passano. I placeholder dei parser con le dataclass sono già definiti in `src/godotiq/parsers/tscn_parser.py`.

**IMPORTANTE**: Sprint 0 ha usato il layout `src/godotiq/` (non `src/`). Gli import sono `from godotiq.server import mcp`, `from godotiq.parsers.tscn_parser import ...`. Rispetta questa struttura.

## Cosa deve fare il parser

### Input
Un path a un file `.tscn`. Il parser legge il file e restituisce una struttura dati completa.

### Output — Struttura dati
Il parser deve estrarre e restituire:

#### 1. Header
- `format` (3 o 4)
- `load_steps`
- `uid`

#### 2. External Resources (`[ext_resource ...]`)
Dizionario id → {type, path, uid}
```
[ext_resource type="Script" uid="uid://cf18ldkmiyqje" path="res://scripts/entities/printer.gd" id="1_iqbk5"]
→ {"1_iqbk5": {"type": "Script", "path": "res://scripts/entities/printer.gd", "uid": "uid://cf18ldkmiyqje"}}
```

#### 3. Sub Resources (`[sub_resource ...]`)
Dizionario id → {type, properties: {}}
```
[sub_resource type="BoxShape3D" id="BoxShape3D_1ufgv"]
size = Vector3(0.7, 0.65, 0.56)
→ {"BoxShape3D_1ufgv": {"type": "BoxShape3D", "properties": {"size": [0.7, 0.65, 0.56]}}}
```

#### 4. Nodes (`[node ...]`)
Lista di nodi con:
- `name` (string)
- `type` (string, può essere assente se instance)
- `parent` (string path, "." = figlio del root, assente = è il root)
- `instance` (ext_resource id, se è una scena instanziata)
- `unique_id` (int, opzionale)
- `groups` (list[str])
- `script` (ext_resource id, se ha script)
- `transform` → decomposto in `position`, `rotation`, `scale`
- `properties` (dict di tutte le altre proprietà: zone_name, unlock_cost, collision_layer, ecc.)
- `metadata` (dict per chiavi con prefisso `metadata/`)

#### 5. Signal Connections (`[connection ...]`)
Lista di {signal, from, to, method, flags, binds}
(Nota: nei file forniti non ci sono [connection] blocks, ma il parser deve supportarli)

#### 6. Gerarchia ricostruita
Il parser deve ricostruire l'albero parent-child dai path `parent`. Il root node (senza parent) è la radice. Ogni nodo ha una lista `children`.

### Transform3D Decomposition
Questo è il pezzo più critico. Godot salva i transform come:
```
Transform3D(basis_xx, basis_xy, basis_xz, basis_yx, basis_yy, basis_yz, basis_zx, basis_zy, basis_zz, origin_x, origin_y, origin_z)
```

Il parser deve decomporre in:
- **position**: `(origin_x, origin_y, origin_z)` — diretto, ultimi 3 valori
- **scale**: lunghezza dei 3 vettori colonna della basis matrix
- **rotation**: normalizzare la basis (dividere per scale), poi estrarre angoli Euler (in radianti o gradi, coerente)

Esempi reali dai file forniti:
```
# Identity (no rotation, no scale)
Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -36.117043, 0, -1.9471664)
→ pos=(-36.117, 0, -1.947), rot=(0,0,0), scale=(1,1,1)

# Uniform scale 0.35
Transform3D(0.35, 0, 0, 0, 0.35, 0, 0, 0, 0.35, 0, 0.326, 0)
→ pos=(0, 0.326, 0), rot=(0,0,0), scale=(0.35, 0.35, 0.35)

# Non-uniform scale (Y stretched to 1.25)
Transform3D(1, 0, 0, 0, 1.25, 0, 0, 0, 1, -37.617043, 0, -9.447166)
→ pos=(-37.617, 0, -9.447), rot=(0,0,0), scale=(1, 1.25, 1)

# Rotation + scale (rotated 180° around Y, scale 1.2)
Transform3D(-1.1999992, 0, 0.0013823411, 0, 1.2, 0, -0.0013823411, 0, -1.1999992, -3.5543537, -2.8530726, -0.7081247)
→ pos=(-3.554, -2.853, -0.708), rot≈(0, π, 0), scale≈(1.2, 1.2, 1.2)

# Complex rotation + non-uniform scale
Transform3D(-0.0015359356, 0, -2.096356, 0, 1.2611113, 0, 0.9999988, 0, -0.0032198713, -27.602243, 0.6470086, -6.4769073)
→ pos=(-27.602, 0.647, -6.477), scale≈(1.0, 1.261, 2.096), rot = complessa

# Notation scientifica (quasi-zero = floating point noise)
Transform3D(-3.059797e-08, 0, 0.7, 0, 0.7, 0, -0.7, 0, -3.059797e-08, -36.71275, 0.68696713, 1.5071771)
→ pos=(-36.713, 0.687, 1.507), rot≈(0, -π/2, 0), scale=(0.7, 0.7, 0.7)
```

### Value Type Parsing
Il parser deve gestire questi tipi di valore nelle properties:
- `Vector3(x, y, z)` → tuple/list di 3 float
- `Vector2(x, y)` → tuple/list di 2 float
- `Color(r, g, b, a)` → tuple/list di 4 float
- `Transform3D(...)` → decomposizione speciale (vedi sopra)
- `int` (es. `5000`)
- `float` (es. `0.3`, `1.5`, `-3.059797e-08`)
- `string` (es. `"Post-Processing"`)
- `bool` (es. `true`, `false`)
- `ExtResource("id")` → reference a ext_resource
- `SubResource("id")` → reference a sub_resource
- `null`

## File fixture per i test

I seguenti file sono stati copiati da Print Farm Tycoon e coprono tutti i pattern:

1. **`tests/fixtures/worker.tscn`** (19 righe, 4 nodi) — SEMPLICE
   - CharacterBody3D root con script
   - sub_resource CapsuleShape3D con properties (radius, height)
   - SubResource reference
   - Nesting base (2 livelli)

2. **`tests/fixtures/fdm_printer.tscn`** (29 righe, 6 nodi) — MEDIO
   - ext_resource multipli (Script, PackedScene x2)
   - sub_resource BoxShape3D
   - instance=ExtResource (scene instanziate)
   - groups=["interactable"]
   - Transform3D con scale uniforme (0.35)

3. **`tests/fixtures/furniture_slot_placeholder.tscn`** (37 righe, 6 nodi) — MEDIO+
   - 3 sub_resources diversi (StandardMaterial3D, BoxMesh, BoxShape3D)
   - Properties varie: transparency, shading_mode, albedo_color (Color), billboard (int)
   - material_override = SubResource reference
   - mesh = SubResource reference

4. **`tests/fixtures/main.tscn`** (4711 righe, 876 nodi) — STRESS TEST
   - 68 ext_resources, 142 sub_resources
   - format=4 (diverso da tutti gli altri che sono format=3)
   - Groups multipli su singolo nodo: `groups=["material_store", "storage_points"]`
   - Custom properties: `zone_name`, `unlock_cost`, `tier_required`, `zone_size`
   - metadata: `metadata/station_type = "desk"`
   - Transform3D con rotazioni complesse e notazione scientifica
   - Nodi senza type ma con instance
   - Deep nesting (6+ livelli: Building/LockedZones/PostProcessingZone/PostProcessingFloor/FloorTile/...)
   - Nomi con caratteri speciali: `@CSGBox3D@106885`, `floorFull(Clone)2`
   - CSGBox3D con size = Vector3

## Test da scrivere

### test_tscn_parser.py — Unit Tests

```
test_parse_header_format3          → worker.tscn → format=3, no load_steps
test_parse_header_format4          → main.tscn → format=4
test_parse_ext_resources           → fdm_printer.tscn → 3 ext_resources corretti
test_parse_sub_resources           → furniture_slot.tscn → 3 sub_resources con properties
test_parse_nodes_basic             → worker.tscn → 4 nodi, nomi e tipi corretti
test_parse_node_hierarchy          → fdm_printer.tscn → parent path ricostruiti, children corretti
test_parse_deep_nesting            → main.tscn → nodo a 6+ livelli trovato
test_parse_groups_single           → fdm_printer.tscn → FDMPrinter ha group "interactable"
test_parse_groups_multiple         → main.tscn → filament_storage ha ["material_store", "storage_points"]
test_parse_transform_identity      → worker.tscn → CollisionShape3D pos=(0, 0.5, 0)
test_parse_transform_uniform_scale → fdm_printer.tscn → PrinterModel scale=(0.35, 0.35, 0.35)
test_parse_transform_nonuniform    → main.tscn → wall con scale Y=1.25
test_parse_transform_rotation      → main.tscn → GaragePanel con rotazione complessa
test_parse_transform_scientific    → main.tscn → nodo con -3.059797e-08
test_parse_instance_node           → fdm_printer.tscn → PrinterModel è instance di ext_resource
test_parse_script_assignment       → worker.tscn → root ha script ExtResource("1_qf3b7")
test_parse_custom_properties       → main.tscn → LockedZone ha zone_name, unlock_cost, tier_required
test_parse_metadata                → main.tscn → ResearchDesk ha metadata/station_type = "desk"
test_parse_value_types             → furniture_slot.tscn → Color, Vector3, int, float tutti parsati
test_parse_node_without_type       → fdm_printer.tscn → PrinterModel (has instance, no explicit type)
test_parse_special_node_names      → main.tscn → "@CSGBox3D@106885" parsato correttamente
test_node_count_stress             → main.tscn → exactly 876 nodi
test_ext_resource_count_stress     → main.tscn → exactly 68 ext_resources
```

### test_tscn_parser_integration.py — Integration

```
test_full_parse_roundtrip          → parse each fixture, verify no exceptions, all nodes have valid data
test_find_node_by_path             → utility function: dato un path "Building/LockedZones/PostProcessingZone" trovi il nodo
test_get_all_scripts               → da main.tscn, estrai tutti i path di script referenziati
test_get_all_instances             → da main.tscn, estrai tutte le scene instanziate
test_get_nodes_by_group            → da main.tscn, filtra nodi per gruppo "printer_slots" → 6 risultati
test_get_nodes_by_type             → da main.tscn, filtra nodi per tipo "Marker3D" → N risultati
```

## Approccio di parsing suggerito

**State machine line-by-line:**
1. Leggi file riga per riga
2. Rileva sezioni con regex: `^\[(gd_scene|ext_resource|sub_resource|node|connection) ...]`
3. Per ogni sezione, parsa gli attributi dall'header `[tag key=value key=value]`
4. Le righe successive (fino alla prossima sezione `[`) sono properties del blocco corrente
5. Per le properties, parsa `key = value` con value type detection
6. Dopo il parsing flat, ricostruisci l'albero parent-child in un secondo passaggio

**NON usare un parser XML/YAML generico** — il formato .tscn è custom Godot, non è nessun formato standard.

## Vincoli implementativi
- Zero dipendenze esterne (solo stdlib Python)
- Type hints ovunque
- Docstring su ogni funzione/classe pubblica
- Le dataclass esistenti in `src/godotiq/parsers/tscn_parser.py` possono essere modificate/estese se necessario
- Performance target: main.tscn (4711 righe) parsato in < 200ms
- Test con pytest, fixture file reali in `tests/fixtures/`

## Struttura file attesa

```
src/godotiq/parsers/
├── __init__.py
├── tscn_parser.py         # parser principale + dataclass
└── value_parser.py        # (opzionale) parsing dei tipi di valore Godot

tests/
├── fixtures/
│   ├── worker.tscn
│   ├── fdm_printer.tscn
│   ├── furniture_slot_placeholder.tscn
│   └── main.tscn
├── test_parsers/
│   ├── test_tscn_parser.py
│   └── test_tscn_integration.py
```

## NON fare
- NON toccare `src/godotiq/server.py` o i test di Sprint 0
- NON aggiungere dipendenze esterne
- NON implementare altri parser (.gd, .tres) — solo .tscn
- NON implementare tool MCP — solo il parser
- NON ottimizzare prematuramente — prima correttezza, poi performance

## Criteri di completamento
1. `pytest tests/test_parsers/` → TUTTI i test passano
2. `pytest` (full suite) → TUTTI i test passano (inclusi Sprint 0)
3. `parse_tscn("tests/fixtures/main.tscn")` ritorna 876 nodi con gerarchie corrette
4. Transform3D decomposition produce valori corretti per tutti i casi test
5. Nessun test di Sprint 0 rotto
