extends SceneTree ## Smoke test for animated .glb model instantiation and clip playback (Task 38d23c9dc0cb). var _failures: int = 0 var _checks: int = 0 const StaticObjectNodeClass = preload("res://scripts/static_object_node.gd") const WorldObjectClass = preload("res://scripts/world_object.gd") func _init() -> void: print("--- Running Smoke Test: Model Animation Playback ---") test_animation_clip_detection() test_animation_playback_and_looping() test_animation_speed_and_autoplay() test_inspector_animation_properties() if _failures == 0: print("--- PASS: All %d model animation checks passed! ---" % _checks) quit(0) else: printerr("--- FAIL: %d failures out of %d checks ---" % [_failures, _checks]) quit(1) func _check(desc: String, cond: bool) -> void: _checks += 1 if cond: print(" ✓ %s" % desc) else: _failures += 1 printerr(" ✗ FAIL: %s" % desc) func _create_test_animated_node(asset_id: String, properties: Dictionary = {}) -> StaticObjectNode: var wo := WorldObjectClass.new() wo.id = "test_anim_obj" wo.type = "prop" wo.asset_id = asset_id wo.position = Vector3(0, 0, 0) wo.properties = properties.duplicate(true) var node = StaticObjectNodeClass.new() node.world_object = wo root.add_child(node) return node func test_animation_clip_detection() -> void: print("Testing animation clip discovery...") # Create a dummy node with an AnimationPlayer and animation library var wo := WorldObjectClass.new() wo.id = "mock_anim_obj" wo.type = "prop" wo.asset_id = "props/crate_small.glb" wo.properties = {} var node = StaticObjectNodeClass.new() node.world_object = wo root.add_child(node) # Manually attach an AnimationPlayer with 2 mock clips to test clip discovery var ap := AnimationPlayer.new() ap.name = "AnimationPlayer" var lib := AnimationLibrary.new() var anim_walk := Animation.new() anim_walk.length = 1.0 var anim_idle := Animation.new() anim_idle.length = 2.0 lib.add_animation("walk", anim_walk) lib.add_animation("idle", anim_idle) ap.add_animation_library("", lib) node.add_child(ap) # Call _setup_animation_and_motion node.call("_load_mesh") node.call("_setup_animation_and_motion") var anim_list: Array[String] = node.get_available_animations() _check("Found AnimationPlayer", node.get_animation_player() != null) _check("Discovered 2 animation clips", anim_list.size() >= 2) _check("Contains 'walk' clip", anim_list.has("walk")) _check("Contains 'idle' clip", anim_list.has("idle")) _check("Recorded available_animations in world_object properties", wo.properties.has("available_animations") and wo.properties["available_animations"].has("walk")) node.queue_free() func test_animation_playback_and_looping() -> void: print("Testing animation clip playback & loop modes...") var wo := WorldObjectClass.new() wo.id = "playback_obj" wo.type = "prop" wo.properties = { "animation": { "clip": "spin", "autoplay": true, "loop": true, "speed": 1.5 } } var node = StaticObjectNodeClass.new() node.world_object = wo root.add_child(node) var ap := AnimationPlayer.new() var lib := AnimationLibrary.new() var anim_spin := Animation.new() anim_spin.length = 1.0 lib.add_animation("spin", anim_spin) ap.add_animation_library("", lib) node.add_child(ap) node.call("_load_mesh") node.call("_setup_animation_and_motion") _check("Animation 'spin' is playing automatically", ap.is_playing() and ap.current_animation == "spin") _check("Animation speed scale set to 1.5", is_equal_approx(ap.speed_scale, 1.5)) _check("Animation loop mode set to LOOP_LINEAR", anim_spin.loop_mode == Animation.LOOP_LINEAR) # Test stopping and manual play node.stop_animation() _check("Animation stopped", not ap.is_playing()) node.play_animation("spin", 2.0, false) _check("Animation restarted with speed 2.0", ap.is_playing() and is_equal_approx(ap.speed_scale, 2.0)) _check("Loop mode changed to LOOP_NONE", anim_spin.loop_mode == Animation.LOOP_NONE) node.queue_free() func test_animation_speed_and_autoplay() -> void: print("Testing autoplay disable and speed settings...") var wo := WorldObjectClass.new() wo.id = "no_autoplay_obj" wo.type = "prop" wo.properties = { "animation": { "clip": "dance", "autoplay": false, "loop": false, "speed": 0.5 } } var node = StaticObjectNodeClass.new() node.world_object = wo root.add_child(node) var ap := AnimationPlayer.new() var lib := AnimationLibrary.new() var anim_dance := Animation.new() anim_dance.length = 2.5 lib.add_animation("dance", anim_dance) ap.add_animation_library("", lib) node.add_child(ap) node.call("_load_mesh") node.call("_setup_animation_and_motion") _check("Animation does not autoplay when autoplay=false", not ap.is_playing()) _check("Speed scale configured properly", is_equal_approx(ap.speed_scale, 0.5)) node.queue_free() func test_inspector_animation_properties() -> void: print("Testing dynamic property updates on StaticObjectNode...") var wo := WorldObjectClass.new() wo.id = "dynamic_anim_obj" wo.type = "prop" wo.properties = {} var node = StaticObjectNodeClass.new() node.world_object = wo root.add_child(node) var ap := AnimationPlayer.new() var lib := AnimationLibrary.new() var a1 := Animation.new() a1.length = 1.0 var a2 := Animation.new() a2.length = 1.0 lib.add_animation("run", a1) lib.add_animation("jump", a2) ap.add_animation_library("", lib) node.add_child(ap) node.call("_load_mesh") node.call("_setup_animation_and_motion") # Update animation config dynamically as InspectorPanel would node.update_animation({ "clip": "jump", "autoplay": true, "loop": true, "speed": 1.25 }) _check("Updated animation is 'jump'", ap.is_playing() and ap.current_animation == "jump") _check("Updated speed is 1.25", is_equal_approx(ap.speed_scale, 1.25)) node.queue_free()