# Sprint 3d — Animation Intelligence

## Objective
Add 2 tools that give AI agents complete understanding of animations in a Godot project — from ANY source (inline .tscn, GLB embedded, .tres resources, AnimationTree state machines). After this sprint, GodotIQ can answer "what animations exist?", "what's broken?", "what's never used?" — capabilities no other Godot MCP has.

## Context
GodotIQ MCP server. Sprint 3c complete: 371 tests, 11 tools. Print Farm Tycoon: 78 scripts, 37 scenes, 986 nodes.

**Critical insight from real project testing:** PFT has ZERO inline .tscn animations. All animations come from GLB files, referenced only through code (`worker.gd` with `ANIM_MAP`, `.play()` calls, `LOOPING_ANIMS` array). This is the norm for 3D projects. The tools MUST handle both inline and code-only animation patterns.

Session API available:
```python
session._index: ProjectIndex
    .scenes: dict[str, TscnScene]   # res:// path -> parsed scene
    .scripts: list[str]             # res:// paths
    .assets: list[str]              # res:// paths (glb, png, wav, etc.)
    .autoloads: dict[str, str]
    .resource_usage: dict[str, list[str]]
    .scene_instances: dict[str, list[str]]
    .scene_instance_of: dict[str, list[str]]
    .script_to_scenes: dict[str, list[str]]

session._gd_scripts: dict[str, GdScript]
session.get_script(res_path) -> GdScript | None
session.get_scene(res_path) -> TscnScene | None
session.resolve_file_path(path) -> str | None
session._resolve_res_path(res_path) -> Path
session.resolve_scene_spatial(scene_path) -> tuple[ResolvedScene, list[WorldNode]] | None

# GdScript fields: class_name, extends, signals, functions, variables, enums,
#   inner_classes, signal_connections, signal_emissions, autoload_refs, preload_refs,
#   is_instance_valid_lines
# GdFunction: name, args, return_type, is_static, is_virtual, is_private, line, end_line
# GdSignalEmission: signal_name, arg_count, line

# TscnScene has: nodes, ext_resources, sub_resources, connections
# SubResource: id (str), type (str), properties (dict[str, object])
# Node has: name, type, position, groups, instance, script, properties
# ExtResource has: id, type, path
```

**Important: The GD parser does NOT explicitly track `.play()` calls or animation-related patterns.** The animation tools must do their own scanning of raw .gd file text for animation patterns. This is clean — animation-specific regex logic belongs in the animation module, not the general GD parser.

---

## Architecture: Multi-Source Animation Extraction

Animations in Godot 4 come from 4 sources. The extraction helpers must aggregate from all of them:

### Source 1: Inline .tscn animations
Sub_resources of type `Animation` and `AnimationLibrary` inside .tscn files.

**Example .tscn format:**
```
[sub_resource type="Animation" id="Animation_abc"]
resource_name = "walk"
length = 1.0
loop_mode = 1
tracks/0/type = "value"
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite2D:position")
tracks/0/interp = 1
tracks/0/keys = { "times": PackedFloat32Array(0, 0.5, 1), "values": [...] }
tracks/1/type = "method"
tracks/1/path = NodePath(".:play_sound")

[sub_resource type="AnimationLibrary" id="AnimationLibrary_xyz"]
_data = { "walk": SubResource("Animation_abc"), "idle": SubResource("Animation_def") }

[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
libraries = { "": SubResource("AnimationLibrary_xyz") }
```

**Extraction logic:**
- Iterate `scene.sub_resources` where `type == "Animation"`
- `properties["resource_name"]` → animation name
- `properties["length"]` → duration (float, default 1.0)
- `properties["loop_mode"]` → 0=none, 1=linear, 2=pingpong (default 0)
- Count tracks by scanning keys matching `tracks/N/type` regex pattern
- Extract track paths from `tracks/N/path` keys
- Link animations to AnimationPlayer nodes via AnimationLibrary `_data` → SubResource references

### Source 2: Code-referenced animations (GLB embedded)
When animations live inside .glb files, we can't parse the binary. But we extract ALL intelligence from the script:

**Patterns to detect via regex on raw .gd file content:**

```gdscript
# Pattern A: .play() calls
anim_player.play("walk")                    → animation "walk" used
animation_player.play("idle", -1, 1.5)     → animation "idle" used
$AnimationPlayer.play("attack")             → animation "attack" used
get_node("AnimationPlayer").play("run")     → animation "run" used

# Pattern B: Animation name mapping dicts
const ANIM_MAP := { "idle": "NlaTrack_001", "walk": "NlaTrack_002" }
var animations = { "run": "mixamo_run", "jump": "mixamo_jump" }
→ logical names + GLB track names

# Pattern C: Looping animation lists
const LOOPING_ANIMS := ["idle", "walk", "wait"]
var looping_animations = ["idle", "run"]
→ which animations should loop

# Pattern D: AnimationPlayer variable declarations
var anim_player: AnimationPlayer
@onready var animation_player = $Model/AnimationPlayer
→ confirms presence of AnimationPlayer

# Pattern E: AnimationTree references
var anim_tree: AnimationTree
@onready var state_machine = anim_tree.get("parameters/playback")
state_machine.travel("walk")              → state transition
→ AnimationTree usage
```

**Regex patterns to use:**
```python
# .play("name") — capture the animation name string
PLAY_RE = re.compile(r'\.play\(\s*"([^"]+)"')

# Dictionary with animation names as keys or values (const X = { "name": ... })
ANIM_DICT_RE = re.compile(
    r'(?:const|var)\s+(\w+)\s*:?=?\s*\{([^}]+)\}', re.DOTALL
)

# Array of animation names (const X = ["name1", "name2"])
ANIM_ARRAY_RE = re.compile(
    r'(?:const|var)\s+(\w+)\s*:?=?\s*\[([^\]]+)\]'
)

# AnimationPlayer/AnimationTree variable declarations
ANIM_VAR_RE = re.compile(
    r'(?:var|@onready\s+var)\s+(\w+)\s*[:]?\s*=?\s*.*?AnimationPlayer|AnimationTree'
)

# .travel("state") for AnimationTree state machines
TRAVEL_RE = re.compile(r'\.travel\(\s*"([^"]+)"')
```

### Source 3: .tres animation resources
Separate .tres files with Animation resources. Same text format as inline sub_resources.
These are found via `session._index.assets` (files ending in `.tres`) and the TSCN parser can parse them since .tres uses the same format.

### Source 4: AnimationTree state machines
AnimationTree nodes in .tscn with sub_resources of type `AnimationNodeStateMachine`, `AnimationNodeStateMachineTransition`, `AnimationNodeAnimation`, etc.

**Key sub_resource types:**
- `AnimationNodeStateMachine` — has `transitions` array, `states/*` keys
- `AnimationNodeStateMachineTransition` — transition rules (auto_advance, switch_mode)
- `AnimationNodeAnimation` — `animation` property = animation name reference
- `AnimationNodeBlendTree` — blend tree with blend nodes

---

## Fixtures to Create

### Fixture 1: `tests/fixtures/animated_character.tscn`
Scene with AnimationPlayer, inline Animation sub_resources, AnimationLibrary. Mix of:
- 3 animations: "idle" (looping, 2.0s, 4 value tracks), "walk" (looping, 1.0s, 3 value tracks + 1 method track), "attack" (one-shot, 0.5s, 2 tracks)
- 1 AnimationLibrary linking them
- 1 AnimationPlayer node referencing the library
- 1 Sprite2D child (target of animation tracks)
- 1 broken track reference (track pointing to "NonExistent:position") in "attack" — for audit testing

### Fixture 2: `tests/fixtures/scripts/character.gd`
Script that references some animations via .play() and has animation patterns.

### Fixture 3: `tests/fixtures/scripts/glb_animated_entity.gd`
Script that loads a GLB at runtime and references animations only via code (the PFT worker pattern).

### Fixture 4: `tests/fixtures/animated_scene_tree.tscn`
Scene with AnimationTree + StateMachine (for Source 4 testing).

### Fixture 5: `tests/fixtures/no_animation_scene.tscn`
Simple scene with NO animation at all (for testing empty/absent results).

---

## Implementation

### File: `src/godotiq/tools/animation/__init__.py`

Create this new module with:
1. Animation extraction helpers (private functions)
2. Tool D01: `godotiq_animation_info`
3. Tool D02: `godotiq_animation_audit`

### Helper Functions
- `_extract_tscn_animations(scene)` — parse Animation sub_resources
- `_extract_animation_libraries(scene)` — parse AnimationLibrary sub_resources
- `_extract_animation_players(scene)` — find AnimationPlayer nodes
- `_extract_animation_trees(scene)` — find AnimationTree + state machines
- `_extract_code_animations(gd_content)` — regex scan raw .gd files
- `_build_animation_summary(scene_path, scene, scripts, session)` — aggregate all sources

### Tools
- `godotiq_animation_info` — complete animation understanding for a scene/node
- `godotiq_animation_audit` — find animation problems across project/scene

### Tests
- `tests/test_tools/test_animation_info.py` — 12 tests
- `tests/test_tools/test_animation_audit.py` — 10 tests

## DO NOT
- Modify existing parser files, tests, tools, or session.py (unless absolutely necessary)
- Add external dependencies

## Completion Criteria
1. `pytest tests/ -v` → ALL pass
2. 2 new tools registered (total: 13)
3. All animation sources detected (inline, code, AnimationTree)
4. Audit finds broken tracks, orphan/missing animations
5. Both tools handle "no animations" gracefully
