# Sprint 11a — Fix 7 Critical Issues from City Build Test

## Contesto

Durante la costruzione di una "Living Miniature City" con GodotIQ, l'agent
ha documentato 7 problemi tecnici concreti. Tutti impattano l'usabilità
del tool e devono essere fixati.

## Deep plan richiesto

Leggi prima:
- `godot-addon/addons/godotiq/godotiq_server.gd` — handler principale
- `src/godotiq/tools/bridge/run.py` — come runna il gioco
- `src/godotiq/tools/bridge/exec_code.py` — come esegue codice
- `src/godotiq/tools/bridge/build_scene.py` — come crea nodi
- `src/godotiq/tools/bridge/screenshot.py` — come fa screenshot
- `src/godotiq/tools/bridge/check_errors.py` — se esiste già
- `src/godotiq/prompts/godot_development.md` — prompt MCP attuale

Poi genera il plan e aspetta conferma.

---

## Issue 1: `play_main_scene()` non funziona

### Problema
`EditorInterface.play_main_scene()` restituisce "launched" ma
`is_playing_scene()` rimane false. Il gioco non parte. Nessun errore.

`EditorInterface.play_current_scene()` funziona sempre, MA richiede che
la scena corretta sia aperta nell'editor.

### Fix

Nel handler GDScript di `run`:

```gdscript
func _handle_run(peer_id: int, id: String, params: Dictionary) -> void:
    var action: String = params.get("action", "play")

    if action == "play":
        var scene_path: String = params.get("scene", "")

        # Step 1: Se è specificata una scena, aprila prima
        if not scene_path.is_empty():
            if not scene_path.ends_with(".tscn"):
                scene_path = _find_scene_by_name(scene_path)
            if not scene_path.begins_with("res://"):
                scene_path = "res://" + scene_path
            EditorInterface.open_scene_from_path(scene_path)
            await get_tree().process_frame

        # Step 2: SEMPRE usare play_current_scene, non play_main_scene
        EditorInterface.play_current_scene()

        # Step 3: Aspetta con timeout dinamico
        var timeout: float = params.get("timeout", 15.0)
        var waited: float = 0.0
        while not EditorInterface.is_playing_scene() and waited < timeout:
            await get_tree().create_timer(0.5).timeout
            waited += 0.5

        var success := EditorInterface.is_playing_scene()
        send_response(peer_id, id, {
            "action": "play",
            "success": success,
            "scene": EditorInterface.get_edited_scene_root().scene_file_path if EditorInterface.get_edited_scene_root() else "",
            "waited_seconds": waited,
        })
```

**Punti chiave:**
- Mai usare `play_main_scene()` — sempre `play_current_scene()`
- Prima di play: apri la scena se specificata
- Timeout default 15 secondi (non 5) per progetti con molti asset
- Restituisci `success: true/false` nella response

---

## Issue 2: Cache stale di `can_instantiate()` sugli script nuovi

### Problema
Quando l'agent crea un file .gd nuovo, Godot tiene una cache interna.
`can_instantiate()` restituisce false anche se lo script è valido.
Se lo script viene aggiunto come `ext_resource` in un .tscn, il gioco
non parte per il pre-flight check.

### Fix

**A) Dopo ogni script_ops write/create, forzare il reload:**

Handler GDScript:

```gdscript
func _force_script_reload(script_path: String) -> Dictionary:
    var script = load(script_path) as GDScript
    if script == null:
        return {"reloaded": false, "error": "Failed to load script"}

    var err := script.reload()
    if err != OK:
        return {"reloaded": false, "error": "Reload failed with code %d" % err}

    EditorInterface.get_resource_filesystem().scan()

    return {"reloaded": true, "path": script_path}
```

Aggiungi handler:
```gdscript
"reload_script":
    _handle_reload_script(peer_id, id, params)

func _handle_reload_script(peer_id: int, id: String, params: Dictionary) -> void:
    var path: String = params.get("path", "")
    if path.is_empty():
        _send_error(peer_id, id, "NO_PATH", "No script path provided")
        return
    var result := _force_script_reload(path)
    send_response(peer_id, id, result)
```

**B) In Python script_ops, dopo ogni write .gd:**

```python
if path.endswith(".gd"):
    try:
        bridge = await get_bridge()
        await bridge.request("reload_script", params={"path": path})
    except Exception:
        pass  # Best effort — non bloccare se bridge non connesso
```

**C) In build_scene, dopo creazione nodi:**

```gdscript
# Alla fine di _handle_build_scene:
EditorInterface.get_resource_filesystem().scan()
```

---

## Issue 3: class_name causa problemi con cache stale

### Problema
`class_name` su script nuovi causa "non trovato" / "non istanziabile".

### Fix

Nel prompt MCP, sezione GDScript Conventions, aggiungi:

```markdown
### Script Loading Pattern for New Scripts

When creating scripts during a session, use load() instead of class_name:

    # PREFERRED — works reliably:
    var EnemyScript = load("res://scripts/enemy.gd")
    var enemy = EnemyScript.new()

    # AVOID for scripts created during this session:
    # class_name Enemy
    # var enemy = Enemy.new()

class_name works for scripts that existed before the editor was opened.
For scripts you CREATE during this session, always use load() to avoid
Godot's editor cache staleness.
```

---

## Issue 4: Timeout troppo corto per progetti con molti asset

### Problema
Timeout di 5 secondi. Con 120+ GLB serve 10-12 secondi.

### Fix

**A) Timeout default a 15 secondi** (nel fix Issue 1)

**B) Timeout adattivo nel Python-side:**

```python
async def run(action: str = "play", scene: str = "", timeout: float = 0, ...) -> dict:
    if timeout == 0:
        session = get_session()
        if session:
            glb_count = len(list(session._project_root.rglob("*.glb")))
            if glb_count > 100:
                timeout = 20.0
            elif glb_count > 50:
                timeout = 15.0
            else:
                timeout = 10.0
        else:
            timeout = 15.0

    bridge = await get_bridge()
    return await bridge.request("run", params={
        "action": action, "scene": scene, "timeout": timeout
    })
```

**C) Nel prompt MCP:**

```markdown
### Game Loading Time

Projects with many 3D assets (100+ GLB files) take 10-15 seconds to load.
After run(action="play"), wait for the response — it handles timeout
automatically. If you get timeout errors on large projects, the game may
still be loading. Try: input(commands=[{"wait_ms": 15000}]) then
state_inspect to check if the game is running.
```

---

## Issue 5: Nodo non trovato nel scene tree

### Problema
Nodi creati non trovati con `get_node_or_null()` perché il debug mostra
solo i primi 10 figli.

### Fix

**A) build_scene restituisce i nomi dei nodi creati:**

Nella response di build_scene, aggiungi:
```json
{
    "created": 12,
    "node_names": ["RoadNetwork", "Buildings", "Cars"],
    "parent": "Main"
}
```

Implementazione GDScript — nel loop di creazione, raccogli i nomi:
```gdscript
var created_names: Array = []
for spec in node_specs:
    var result := _create_node_from_spec(spec, parent_node, edited_root, undo_redo)
    if result.get("ok", false):
        created += 1
        created_names.append(result.get("name", ""))
    # ...

# Nella response:
response["node_names"] = created_names
```

**B) state_inspect con ricerca ricorsiva:**

Se il path specificato non viene trovato con `get_node_or_null()`,
fai una ricerca per nome in tutto il tree:

```gdscript
var node = root.get_node_or_null(node_path)
if node == null:
    # Fallback: cerca per nome
    node = _find_node_recursive(root, node_path.get_file())

func _find_node_recursive(parent: Node, name: String) -> Node:
    if parent.name == name:
        return parent
    for child in parent.get_children():
        var found = _find_node_recursive(child, name)
        if found:
            return found
    return null
```

---

## Issue 6: Screenshot non può verificare animazioni/interazioni

### Problema
Screenshot = singolo frame. Non può verificare se auto si muovono,
semafori cambiano, slider funziona.

### Fix

**A) Pattern "double state_inspect" nel prompt MCP:**

```markdown
### Verifying Movement and Animations

You cannot see animation in screenshots (they are single frames).
To verify something is moving or animating:

1. state_inspect -> get position or property value
2. input(commands=[{"wait_ms": 2000}]) -> wait 2 seconds
3. state_inspect -> get the same value again
4. Compare: if values changed -> it's working. If same -> it's broken.

Example -- verify a car is moving:
  state_inspect(queries=[{node:"/root/Main/Cars/Car1", properties:["position"]}])
  -> position: Vector3(3.0, 0, 5.0)
  input(commands=[{"wait_ms": 2000}])
  state_inspect(queries=[{node:"/root/Main/Cars/Car1", properties:["position"]}])
  -> position: Vector3(5.2, 0, 5.0)
  -> Car1 IS moving (position changed by 2.2 on X axis)

Example -- verify traffic light changes:
  state_inspect(queries=[{node:"/root/Main/TrafficLight", properties:["current_state"]}])
  -> current_state: "green"
  input(commands=[{"wait_ms": 6000}])
  state_inspect(queries=[{node:"/root/Main/TrafficLight", properties:["current_state"]}])
  -> current_state: "red"
  -> Traffic light IS cycling

ALWAYS verify movement this way for any object that should animate.
```

**B) OPZIONALE -- Tool `godotiq_verify_motion`:**

Se c'è tempo, crea un tool dedicato:

```python
@mcp.tool()
async def godotiq_verify_motion(
    node: str,
    property: str = "position",
    duration: float = 2.0,
    detail: str = "brief",
) -> dict:
    """Verify that a node property is changing over time (proves movement/animation)."""
    bridge = await get_bridge()

    val1 = await bridge.request("state_inspect", params={
        "queries": [{"node": node, "properties": [property]}]
    })

    await asyncio.sleep(duration)

    val2 = await bridge.request("state_inspect", params={
        "queries": [{"node": node, "properties": [property]}]
    })

    changed = str(val1) != str(val2)
    return {
        "node": node, "property": property,
        "before": val1, "after": val2,
        "changed": changed,
        "verdict": "MOVING" if changed else "STATIC",
        "duration_seconds": duration,
    }
```

---

## Issue 7: Non può settare main scene programmaticamente

### Problema
Non c'è modo via GodotIQ di impostare `run/main_scene` in project.godot.

### Fix

**A) Handler GDScript:**

```gdscript
"set_main_scene":
    _handle_set_main_scene(peer_id, id, params)

func _handle_set_main_scene(peer_id: int, id: String, params: Dictionary) -> void:
    var scene_path: String = params.get("scene", "")
    if scene_path.is_empty():
        _send_error(peer_id, id, "NO_PATH", "No scene path provided")
        return

    if not scene_path.begins_with("res://"):
        scene_path = "res://" + scene_path

    ProjectSettings.set_setting("application/run/main_scene", scene_path)
    var err := ProjectSettings.save()

    send_response(peer_id, id, {
        "main_scene": scene_path,
        "saved": err == OK,
    })
```

**B) Python-side tool o integrazione in run:**

Puoi esporre come tool MCP separato o integrarlo in `run`:
quando `run(action="play", scene="res://main.tscn")` viene chiamato
e la main_scene non è settata in project.godot, settala automaticamente.

**C) Nel prompt MCP:**

```markdown
### Setting Main Scene

After creating the main scene for a new project, set it as the default:
  Use exec in editor context:
  exec(code="func run():\n\tProjectSettings.set_setting('application/run/main_scene', 'res://scenes/main.tscn')\n\tProjectSettings.save()\n\treturn 'done'", context="editor")
```

---

## Aggiornamento Prompt MCP -- "Known Godot Quirks"

Aggiungi questa sezione al prompt MCP:

```markdown
## Known Godot Quirks

1. **play_main_scene() unreliable**: Always use run(action="play") which
   uses play_current_scene internally. If you need a specific scene, pass
   it as parameter: run(action="play", scene="res://Main.tscn")

2. **Script cache staleness**: After creating a new .gd file, GodotIQ
   automatically reloads it. But if you get "cannot instantiate" errors,
   use exec to force reload:
   exec(code="func run():\n\tload('res://path.gd').reload()\n\treturn 'ok'", context="editor")

3. **class_name in new scripts**: Avoid for scripts created during this
   session. Use load() pattern instead.

4. **Asset loading time**: Projects with 100+ GLB files need 10-15 seconds.
   run() handles this automatically with adaptive timeout.

5. **Verifying movement**: Use double state_inspect with wait between them.
   Screenshots are single frames -- they cannot show motion.

6. **Setting main scene**: Use exec to set ProjectSettings if needed.
```

---

## File da creare/modificare

| File | Cosa |
|------|------|
| `godot-addon/addons/godotiq/godotiq_server.gd` | Fix run (play_current_scene, timeout), reload_script handler, set_main_scene handler, filesystem scan in build_scene, _find_node_recursive |
| `src/godotiq/tools/bridge/run.py` | Timeout adattivo basato su asset count |
| `src/godotiq/tools/bridge/script_ops.py` | Chiamare reload_script dopo write .gd |
| `src/godotiq/tools/bridge/build_scene.py` | Restituire node_names, filesystem scan |
| `src/godotiq/server.py` | Registrare nuovi handler (reload_script, set_main_scene, verify_motion se implementato) |
| `src/godotiq/prompts/godot_development.md` | Known Godot Quirks, load() pattern, verify motion pattern, game loading time |
| `src/godotiq/tools/bridge/verify_motion.py` | OPZIONALE -- tool per verificare movimento |

## File da NON toccare
- Parser
- Tool di analisi (scene_map, dependency_graph, etc.)
- build_scene (tranne aggiungere node_names e filesystem scan)
- Test esistenti

## Verifica

```bash
pytest tests/ -v  # nessuna regressione
```

Test manuale:
1. Crea progetto, scrivi .gd con errore -> verifica che reload cattura l'errore
2. `run(action="play", scene="res://Main.tscn")` -> verifica che apre e runna
3. Progetto con 100+ GLB -> verifica nessun timeout
4. Crea nodo con build_scene -> verifica node_names nella response
5. Verifica movement con double state_inspect pattern
