diff --git a/tests/fixtures/anim_project/project.godot b/tests/fixtures/anim_project/project.godot
new file mode 100644
index 0000000..1eab07c
--- /dev/null
+++ b/tests/fixtures/anim_project/project.godot
@@ -0,0 +1,3 @@
+[application]
+
+config/name="AnimTest"
diff --git a/tests/fixtures/anim_project/scenes/animated_character.tscn b/tests/fixtures/anim_project/scenes/animated_character.tscn
new file mode 100644
index 0000000..ca49c16
--- /dev/null
+++ b/tests/fixtures/anim_project/scenes/animated_character.tscn
@@ -0,0 +1,154 @@
+[gd_scene load_steps=7 format=3 uid="uid://anim_character"]
+
+[ext_resource type="Script" path="res://scripts/entities/character.gd" id="1_script"]
+
+[sub_resource type="Animation" id="Animation_idle"]
+resource_name = "idle"
+length = 2.0
+loop_mode = 1
+tracks/0/type = "value"
+tracks/0/path = NodePath("Sprite2D:position")
+tracks/0/interp = 1
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector2(0, 0)]
+}
+tracks/1/type = "value"
+tracks/1/path = NodePath("Sprite2D:modulate")
+tracks/1/interp = 1
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Color(1, 1, 1, 1)]
+}
+tracks/2/type = "value"
+tracks/2/path = NodePath("Sprite2D:scale")
+tracks/2/interp = 1
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector2(1, 1)]
+}
+tracks/3/type = "value"
+tracks/3/path = NodePath("Sprite2D:rotation")
+tracks/3/interp = 1
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [0.0]
+}
+
+[sub_resource type="Animation" id="Animation_walk"]
+resource_name = "walk"
+length = 1.0
+loop_mode = 1
+tracks/0/type = "value"
+tracks/0/path = NodePath("Sprite2D:position")
+tracks/0/interp = 1
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.5),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Vector2(0, 0), Vector2(10, 0)]
+}
+tracks/1/type = "value"
+tracks/1/path = NodePath("Sprite2D:scale")
+tracks/1/interp = 1
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector2(1, 1)]
+}
+tracks/2/type = "value"
+tracks/2/path = NodePath("Sprite2D:modulate")
+tracks/2/interp = 1
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Color(1, 1, 1, 1)]
+}
+tracks/3/type = "method"
+tracks/3/path = NodePath(".:play_footstep")
+tracks/3/interp = 1
+tracks/3/imported = false
+tracks/3/enabled = true
+tracks/3/keys = {
+"times": PackedFloat32Array(0.5),
+"transitions": PackedFloat32Array(1),
+"values": [{"method": "play_footstep", "args": []}]
+}
+
+[sub_resource type="Animation" id="Animation_attack"]
+resource_name = "attack"
+length = 0.5
+loop_mode = 0
+tracks/0/type = "value"
+tracks/0/path = NodePath("Sprite2D:position")
+tracks/0/interp = 1
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.25),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Vector2(0, 0), Vector2(5, 0)]
+}
+tracks/1/type = "value"
+tracks/1/path = NodePath("NonExistent:position")
+tracks/1/interp = 1
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector2(0, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_idle_ground"]
+resource_name = "idle_ground"
+length = 1.5
+loop_mode = 0
+tracks/0/type = "value"
+tracks/0/path = NodePath("Sprite2D:position")
+tracks/0/interp = 1
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector2(0, 0)]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_main"]
+_data = { "idle": SubResource("Animation_idle"), "walk": SubResource("Animation_walk"), "attack": SubResource("Animation_attack"), "idle_ground": SubResource("Animation_idle_ground") }
+
+[node name="Character" type="Node2D"]
+script = ExtResource("1_script")
+
+[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
+libraries = { "": SubResource("AnimationLibrary_main") }
+
+[node name="Sprite2D" type="Sprite2D" parent="."]
diff --git a/tests/fixtures/anim_project/scenes/animated_scene_tree.tscn b/tests/fixtures/anim_project/scenes/animated_scene_tree.tscn
new file mode 100644
index 0000000..c6b95f6
--- /dev/null
+++ b/tests/fixtures/anim_project/scenes/animated_scene_tree.tscn
@@ -0,0 +1,62 @@
+[gd_scene load_steps=9 format=3 uid="uid://anim_tree_scene"]
+
+[sub_resource type="Animation" id="Animation_idle"]
+resource_name = "idle"
+length = 2.0
+loop_mode = 1
+tracks/0/type = "value"
+tracks/0/path = NodePath(".:position")
+tracks/0/interp = 1
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 0, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_walk"]
+resource_name = "walk"
+length = 1.0
+loop_mode = 1
+tracks/0/type = "value"
+tracks/0/path = NodePath(".:position")
+tracks/0/interp = 1
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.5),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(1, 0, 0)]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_tree"]
+_data = { "idle": SubResource("Animation_idle"), "walk": SubResource("Animation_walk") }
+
+[sub_resource type="AnimationNodeAnimation" id="AnimNode_idle"]
+animation = &"idle"
+
+[sub_resource type="AnimationNodeAnimation" id="AnimNode_walk"]
+animation = &"walk"
+
+[sub_resource type="AnimationNodeStateMachineTransition" id="Trans_idle_walk"]
+
+[sub_resource type="AnimationNodeStateMachineTransition" id="Trans_walk_idle"]
+
+[sub_resource type="AnimationNodeStateMachine" id="StateMachine_main"]
+states/idle/node = SubResource("AnimNode_idle")
+states/idle/position = Vector2(300, 100)
+states/walk/node = SubResource("AnimNode_walk")
+states/walk/position = Vector2(500, 100)
+transitions = [&"idle", &"walk", SubResource("Trans_idle_walk"), &"walk", &"idle", SubResource("Trans_walk_idle")]
+
+[node name="Scene3D" type="Node3D"]
+
+[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
+libraries = { "": SubResource("AnimationLibrary_tree") }
+
+[node name="AnimationTree" type="AnimationTree" parent="."]
+tree_root = SubResource("StateMachine_main")
+anim_player = NodePath("../AnimationPlayer")
diff --git a/tests/fixtures/anim_project/scenes/empty_player_scene.tscn b/tests/fixtures/anim_project/scenes/empty_player_scene.tscn
new file mode 100644
index 0000000..916a2d7
--- /dev/null
+++ b/tests/fixtures/anim_project/scenes/empty_player_scene.tscn
@@ -0,0 +1,5 @@
+[gd_scene format=3 uid="uid://empty_player"]
+
+[node name="EmptyScene" type="Node3D"]
+
+[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
diff --git a/tests/fixtures/anim_project/scenes/glb_entity.tscn b/tests/fixtures/anim_project/scenes/glb_entity.tscn
new file mode 100644
index 0000000..a8c1218
--- /dev/null
+++ b/tests/fixtures/anim_project/scenes/glb_entity.tscn
@@ -0,0 +1,6 @@
+[gd_scene format=3 uid="uid://glb_entity"]
+
+[ext_resource type="Script" path="res://scripts/entities/glb_animated_entity.gd" id="1_glb_script"]
+
+[node name="GLBEntity" type="Node3D"]
+script = ExtResource("1_glb_script")
diff --git a/tests/fixtures/anim_project/scenes/no_animation_scene.tscn b/tests/fixtures/anim_project/scenes/no_animation_scene.tscn
new file mode 100644
index 0000000..0359fe4
--- /dev/null
+++ b/tests/fixtures/anim_project/scenes/no_animation_scene.tscn
@@ -0,0 +1,7 @@
+[gd_scene format=3 uid="uid://no_anim_scene"]
+
+[node name="StaticScene" type="Node3D"]
+
+[node name="Mesh" type="MeshInstance3D" parent="."]
+
+[node name="Body" type="StaticBody3D" parent="."]
diff --git a/tests/fixtures/anim_project/scripts/entities/character.gd b/tests/fixtures/anim_project/scripts/entities/character.gd
new file mode 100644
index 0000000..e2c6d2f
--- /dev/null
+++ b/tests/fixtures/anim_project/scripts/entities/character.gd
@@ -0,0 +1,22 @@
+extends Node2D
+
+const ANIM_MAP := {
+	"idle": "idle",
+	"walk": "walk",
+	"attack": "attack",
+	"special": "special_move",
+}
+
+const LOOPING_ANIMS := ["idle", "walk"]
+
+var anim_player: AnimationPlayer
+
+func _ready() -> void:
+	anim_player = $AnimationPlayer
+	anim_player.play("idle")
+
+func attack() -> void:
+	anim_player.play("attack")
+
+func special() -> void:
+	anim_player.play("special_move")
diff --git a/tests/fixtures/anim_project/scripts/entities/glb_animated_entity.gd b/tests/fixtures/anim_project/scripts/entities/glb_animated_entity.gd
new file mode 100644
index 0000000..c3f19fe
--- /dev/null
+++ b/tests/fixtures/anim_project/scripts/entities/glb_animated_entity.gd
@@ -0,0 +1,19 @@
+extends Node3D
+
+const ANIM_MAP := {
+	"idle": "NlaTrack_001",
+	"walk": "NlaTrack_002",
+	"run": "NlaTrack_003",
+}
+
+const LOOPING_ANIMS := ["idle", "walk"]
+
+var anim_player: AnimationPlayer
+
+func _ready() -> void:
+	anim_player = $AnimationPlayer
+	anim_player.play("NlaTrack_001")
+
+func play_animation(anim_name: String) -> void:
+	var track_name = ANIM_MAP.get(anim_name, "")
+	anim_player.play(track_name)
diff --git a/tests/test_tools/conftest.py b/tests/test_tools/conftest.py
index efdd687..3c3a9b8 100644
--- a/tests/test_tools/conftest.py
+++ b/tests/test_tools/conftest.py
@@ -21,3 +21,17 @@ def session(sample_root: Path) -> GodotIQSession:
     s = GodotIQSession(sample_root)
     s.load()
     return s
+
+
+@pytest.fixture
+def anim_root() -> Path:
+    """Path to the animation test fixture project."""
+    return Path(__file__).resolve().parent.parent / "fixtures" / "anim_project"
+
+
+@pytest.fixture
+def anim_session(anim_root: Path) -> GodotIQSession:
+    """A loaded GodotIQSession for the animation test project."""
+    s = GodotIQSession(anim_root)
+    s.load()
+    return s
