# Sprint 3e Research Findings

## Codebase Research

### 1. Signal Map Tool (`godotiq_signal_map`)

**Location:** `src/godotiq/tools/code/__init__.py` (lines 301-464)

**Missing signal detection (`find="missing"`):**
```python
# Current implementation (lines 393-404):
for emitter_path in unique_emitters:
    emitter_gd = session.get_script(emitter_path)
    if emitter_gd is None:
        continue
    emitter_defs = {s.name for s in emitter_gd.signals}
    if name not in emitter_defs:
        missing_list.append({
            "name": name,
            "emitted_in": emitter_path,
            "note": "Emitted but no signal definition found in that file",
        })
```

**Key finding:** Only checks if signal is defined in the **same emitting file**. Does NOT check other files in the project. This is the root cause of Fix 1's false positives.

**Constants:** `_MAX_SIGNALS = 30`, `_MAX_BUSIEST = 10`

---

### 2. Spatial Audit Tool (`godotiq_spatial_audit`)

**Location:** `src/godotiq/tools/spatial/__init__.py` (lines 373-637)

**Z-fighting detection:**
- Uses `_is_mesh_geometry()` to filter to actual mesh nodes (MeshInstance3D or .glb/.gltf/.obj instances)
- Distance threshold: `_Z_FIGHTING_DISTANCE = 0.01`
- All z-fighting issues get severity "warning"

**Current severity assignment (hardcoded):**
- `"warning"`: floating_objects, scale_mismatch, z_fighting
- `"info"`: empty_markers, overlapping, extreme_positions

**No severity filtering exists.** Issues sorted by severity but ALL returned. Cap: `_MAX_AUDIT_ISSUES = 50`.

**Severity ordering:** `{"warning": 0, "info": 1}`

---

### 3. Suggest Scale Tool (`godotiq_suggest_scale`)

**Location:** `src/godotiq/tools/assets/__init__.py` (lines 324-485)

**`model` is REQUIRED** — no default value. The tool uses a 3-tier matching system:
- Tier 1: Exact filename match
- Tier 2: Same directory
- Tier 3: Group match
- Tier 4: No match → `recommended_scale: None`

**Scale calculation:** Median per axis from references.

**Position suggestion:** When `near_node` provided, suggests +1.0 on X axis.

---

### 4. Scene Map Tool (`godotiq_scene_map`)

**Location:** `src/godotiq/tools/spatial/__init__.py` (lines 153-369)

**Current caps:**
```python
_MAX_NODES_NO_FOCUS = 50      # Without focus: root + direct children
_MAX_NODES_WITH_FOCUS = 30    # With focus: top 30 nearest nodes
_MAX_DISTANCE_PAIRS = 50
```

**Detail levels already differentiated:**
- `"brief"`: name, type, position, depth
- `"normal"`: adds full_path, rotation_deg, scale, is_instance, instance_source, groups, script
- `"full"`: adds properties, metadata, children_count

**Focus mode:** Finds nodes within `radius` meters, sorts by distance, caps at 30 (`_MAX_NODES_WITH_FOCUS`).

**Key finding:** The nearby cap is 30 for ALL detail levels. Need to make it detail-dependent (10/20/30).

---

### 5. Project Summary Tool (`godotiq_project_summary`)

**Location:** `src/godotiq/tools/memory/__init__.py` (lines 35-189)

**Current detail levels:**
- `"brief"`: project_name, engine, type, counts, autoloads (list of names)
- `"normal"`: adds architecture (main_scene, autoload_details as name→path dict, scene_tree, key_systems)
- `"full"`: adds scene_structure (top 10 scenes), autoload_analysis (signals, functions, references), file_tree, detected_patterns

**Autoload analysis in "full"** already includes per-autoload: signals list, public_functions, referenced_by. But does NOT include signal_health summary or project-wide health metrics.

**Caps:** `_MAX_SCENE_STRUCTURE = 10`, `_MAX_PUBLIC_FUNCTIONS = 5`

---

### WorldNode Data Structure

```python
@dataclass
class WorldNode:
    node: TscnNode
    world_position: tuple[float, float, float]
    world_rotation: tuple[float, float, float]
    world_scale: tuple[float, float, float]
    full_path: str
    depth: int
    is_instance_root: bool
    instance_source: str | None
```

Key: `full_path` uses `/` separator (e.g., `"Building/Floor/FloorTile_3_2"`). Parent path = all but last segment.

---

### Session API

```python
session._index.autoloads          # dict[name] → script_path
session._index.scenes             # dict[res_path] → TscnScene
session._index.scripts            # set of res:// script paths
session._gd_scripts               # dict[res_path] → GdScript
```

---

### Test Pattern

All tools use a shared fixture:
```python
@pytest.fixture
def session(sample_root: Path) -> GodotIQSession:
    s = GodotIQSession(sample_root)
    s.load()
    return s
```

Tests import internal `_build_*` functions directly (not the async MCP tool). Use dict assertions on results. Fixtures in `tests/fixtures/sample_project/`.

---

## Web Research: Godot Event Bus Patterns

### How It Works

The autoload singleton pattern: a GDScript extending `Node` declares all global signals, registered as Autoload in Project Settings.

**Typical events.gd:**
```gdscript
extends Node

signal hero_health_changed(new_value: float)
signal level_completed(level_id: int)
signal item_collected(item_name: String, quantity: int)
```

### Signal Emission Syntax

| Syntax | Version | Status |
|--------|---------|--------|
| `Events.emit_signal("signal_name", args)` | Godot 3 style | Still works in 4, discouraged |
| `Events.signal_name.emit(args)` | Godot 4 style | **Recommended** |

Both patterns exist in real projects. The GD parser must handle both.

### Relevance to Fix 1

The event bus pattern means signals are declared in one file (`events.gd`) but emitted from many files throughout the project. The current "missing" check that only looks in the emitting file will always flag these as false positives. The fix (project-wide signal name lookup) is the correct approach.

### Sources
- GDQuest: Event Bus Singleton pattern tutorial
- Godot Docs: Using Signals (4.5)
- Godot GitHub Issues: #57360 (emit_signal deprecation), #89632 (UNUSED_SIGNAL warning)
