diff --git a/tests/test_tools/test_animation_audit.py b/tests/test_tools/test_animation_audit.py
new file mode 100644
index 0000000..4d9b6b8
--- /dev/null
+++ b/tests/test_tools/test_animation_audit.py
@@ -0,0 +1,159 @@
+"""Tests for godotiq_animation_audit tool."""
+
+from __future__ import annotations
+
+import pytest
+
+from godotiq.tools.animation import _build_animation_audit
+
+
+class TestAuditBrokenTrack:
+    """Test 1: broken_track detects NonExistent:position in attack animation."""
+
+    def test_broken_track_critical(self, anim_session):
+        """Attack animation references NonExistent node — should be critical."""
+        result = _build_animation_audit(
+            anim_session, scope="scene", scene_filter="res://scenes/animated_character.tscn"
+        )
+        broken = [i for i in result["issues"] if i["type"] == "broken_track"]
+        assert len(broken) >= 1
+        assert broken[0]["severity"] == "critical"
+        assert broken[0]["animation"] == "attack"
+        assert "NonExistent" in broken[0]["missing_node"]
+
+
+class TestAuditOrphanAnimation:
+    """Test 2: orphan_animation detects unreferenced idle_ground."""
+
+    def test_orphan_animation_warning(self, anim_session):
+        """idle_ground is defined but never referenced in code — should be warning."""
+        result = _build_animation_audit(
+            anim_session, scope="scene", scene_filter="res://scenes/animated_character.tscn"
+        )
+        orphans = [i for i in result["issues"] if i["type"] == "orphan_animation"]
+        orphan_names = {i["animation"] for i in orphans}
+        assert "idle_ground" in orphan_names
+        assert all(i["severity"] == "warning" for i in orphans)
+
+
+class TestAuditMissingAnimation:
+    """Test 3: missing_animation detects .play() of non-existent animation."""
+
+    def test_missing_animation_warning(self, anim_session):
+        """character.gd calls .play('special_move') which is not in the scene."""
+        result = _build_animation_audit(
+            anim_session, scope="scene", scene_filter="res://scenes/animated_character.tscn"
+        )
+        missing = [i for i in result["issues"] if i["type"] == "missing_animation"]
+        missing_names = {i["animation"] for i in missing}
+        assert "special_move" in missing_names
+
+
+class TestAuditNoLoopSuspect:
+    """Test 4: no_loop_suspect detects short idle-like non-looping animation."""
+
+    def test_no_loop_suspect_info(self, anim_session):
+        """idle_ground is short (1.5s), no loop, name contains 'idle' — should flag."""
+        result = _build_animation_audit(
+            anim_session, scope="scene", scene_filter="res://scenes/animated_character.tscn"
+        )
+        suspects = [i for i in result["issues"] if i["type"] == "no_loop_suspect"]
+        suspect_names = {i["animation"] for i in suspects}
+        assert "idle_ground" in suspect_names
+        for s in suspects:
+            assert s["severity"] == "info"
+
+
+class TestAuditEmptyPlayer:
+    """Test 5: empty_player detects AnimationPlayer with no animations."""
+
+    def test_empty_player_warning(self, anim_session):
+        """empty_player_scene.tscn has AnimationPlayer with no library/animations."""
+        result = _build_animation_audit(
+            anim_session, scope="scene", scene_filter="res://scenes/empty_player_scene.tscn"
+        )
+        empty = [i for i in result["issues"] if i["type"] == "empty_player"]
+        assert len(empty) == 1
+        assert empty[0]["severity"] == "warning"
+        assert empty[0]["player"] == "AnimationPlayer"
+
+
+class TestAuditCodeOnly:
+    """Test 6: code_only_animations for GLB scene references."""
+
+    def test_code_only_info(self, anim_session):
+        """GLB entity has code-only animation references — severity should be info."""
+        result = _build_animation_audit(
+            anim_session, scope="scene", scene_filter="res://scenes/glb_entity.tscn"
+        )
+        code_only = [i for i in result["issues"] if i["type"] == "code_only_animations"]
+        # GLB entity has code references but no scene animations
+        if code_only:
+            assert all(i["severity"] == "info" for i in code_only)
+
+
+class TestAuditScopeAll:
+    """Test 7: scope='all' scans all scenes in the project."""
+
+    def test_scope_all(self, anim_session):
+        """Scanning all scenes should report multiple scenes scanned."""
+        result = _build_animation_audit(anim_session, scope="all")
+        assert result["scope"] == "all"
+        assert result["scenes_scanned"] >= 3
+        assert result["animated_scenes"] >= 1
+        assert result["total_animations"] >= 4
+
+
+class TestAuditScopeScene:
+    """Test 8: scope='scene' scans only the specified scene."""
+
+    def test_scope_scene(self, anim_session):
+        """Single-scene scope should only scan that scene."""
+        result = _build_animation_audit(
+            anim_session, scope="scene", scene_filter="res://scenes/animated_character.tscn"
+        )
+        assert result["scope"] == "scene"
+        assert result["scenes_scanned"] == 1
+        assert result["animated_scenes"] == 1
+        # All issues should reference the same scene
+        for issue in result["issues"]:
+            assert issue["scene"] == "res://scenes/animated_character.tscn"
+
+    def test_scope_scene_missing_param(self, anim_session):
+        """scope='scene' without scene param should return error."""
+        result = _build_animation_audit(anim_session, scope="scene")
+        assert "error" in result
+
+
+class TestAuditSeverityOrdering:
+    """Test 9: issues are sorted critical > warning > info."""
+
+    def test_severity_ordering(self, anim_session):
+        """Issues should be sorted by severity: critical first, then warning, then info."""
+        result = _build_animation_audit(
+            anim_session, scope="scene", scene_filter="res://scenes/animated_character.tscn"
+        )
+        severity_order = {"critical": 0, "warning": 1, "info": 2}
+        issues = result["issues"]
+        assert len(issues) >= 2, "Need at least 2 issues to verify ordering"
+        for i in range(len(issues) - 1):
+            cur = severity_order[issues[i]["severity"]]
+            nxt = severity_order[issues[i + 1]["severity"]]
+            assert cur <= nxt, (
+                f"Issue #{i} ({issues[i]['severity']}) should come before "
+                f"#{i+1} ({issues[i+1]['severity']})"
+            )
+
+
+class TestAuditIssueCap:
+    """Test 10: issues capped at 50, critical preserved."""
+
+    def test_issue_cap_structure(self, anim_session):
+        """by_severity dict should always contain all three levels."""
+        result = _build_animation_audit(anim_session, scope="all")
+        assert "critical" in result["by_severity"]
+        assert "warning" in result["by_severity"]
+        assert "info" in result["by_severity"]
+        assert result["total_issues"] == len(result["issues"])
+        # Our fixture set is small, so issues should be under 50
+        assert result["total_issues"] <= 50
