# Section 03: Tool D01 — godotiq_animation_info

## Dependencies
- Section 02 (extraction helpers) — all `_extract_*` and `_build_animation_summary` functions

## File Modified
- `src/godotiq/tools/animation/__init__.py` — added `_build_animation_info`, `godotiq_animation_info`, and helpers

## What Was Built

### `_build_animation_info(session, scene_path, node, detail) -> dict`
Aggregate function that:
1. Calls `_build_animation_summary()` for raw data extraction
2. Resolves animations through libraries → players
3. Applies detail level filtering (brief/normal/full)
4. Handles node filtering with fuzzy matching
5. Merges code animations from all attached scripts
6. Computes summary counts consistent with any active filters

### `godotiq_animation_info` MCP Tool
`@mcp.tool()` async wrapper with signature:
- `scene: str` — res:// path to .tscn file
- `node: str | None = None` — optional AnimationPlayer node filter
- `detail: str = "normal"` — brief/normal/full
- Returns dict with: scene, animation_players, animation_trees, code_animations, summary

### Helper Functions
- `_filter_animations_by_detail(animations, detail)` — brief: name only; normal: no track_paths; full: everything
- `_match_player_by_node(players, node)` — fuzzy match: exact > case-insensitive > substring
- `_merge_code_animations(code_anims)` — merge multiple script results including variable names
- `_determine_sources(summary)` — list animation sources, checking for actual references

### Detail Levels
- **brief** — `{"name": "idle"}` per animation
- **normal** — all fields except `track_paths`
- **full** — everything including `track_paths`

### Output Structure
```
{
  "scene": "res://...",
  "animation_players": [{ "name", "path", "animations", "animation_count", "truncated?" }],
  "animation_trees": [{ "node_name", "node_path", "anim_player_path", "states", "transitions", "animation_refs" }],
  "code_animations": { "play_calls", "travel_calls", "anim_maps", "looping_lists", "has_anim_player", "has_anim_tree", "anim_player_vars", "anim_tree_vars" },
  "summary": { "total_animations", "total_players", "total_trees", "inline_count", "code_only_count", "sources" }
}
```

### Edge Cases Handled
- Scene not found → `{"error": "Scene not found", "scene": "..."}`
- No AnimationPlayer → empty lists, summary all zeros
- No script attached → `code_animations` is empty dict `{}`
- Node filter miss → `{"error": "No AnimationPlayer matching '...'", "alternatives": [...]}"`
- Cap: max 50 animations per player with `truncated: true`

## Code Review Fixes Applied
- Issue 5: `_merge_code_animations` preserves `anim_player_vars` and `anim_tree_vars`
- Issue 6: `_determine_sources` checks for actual animation references (play_calls/travel_calls/anim_maps)
- Issues 7-8: Counts are consistent with node filter (inline_count from filtered players, code_only excluded)
- Issue 9: `code_animations` key always present (empty dict when no scripts)

## Tests
Tests defined in Section 06 (12 tests covering inline, code, tree, detail levels, node filtering, edge cases).
