extends SceneTree ## Synthetic candidate, material, ownership and timing regression for animated ## M2 scene finalization. const FINALIZER_SCRIPT := preload("res://src/render/m2/m2_animated_scene_finalizer.gd") const FINALIZER_PATH := "res://src/render/m2/m2_animated_scene_finalizer.gd" const MATERIALIZER_PATH := "res://src/render/m2/m2_animated_instance_materializer.gd" const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd" func _initialize() -> void: var failures: Array[String] = [] _verify_candidate_instantiation_and_invalid_lifetime(failures) _verify_animation_validation_and_transfer(failures) _verify_material_mapping(failures) _verify_ownership_boundaries(failures) var elapsed_milliseconds := _verify_bounded_timing(failures) if not failures.is_empty(): for failure in failures: push_error("M2_ANIMATED_SCENE_FINALIZER: %s" % failure) quit(1) return print( "M2_ANIMATED_SCENE_FINALIZER PASS cases=13 iterations=10000 elapsed_ms=%.3f" % elapsed_milliseconds ) quit(0) func _verify_candidate_instantiation_and_invalid_lifetime(failures: Array[String]) -> void: var finalizer: RefCounted = FINALIZER_SCRIPT.new() _expect_same(finalizer.call("instantiate_candidate", null), null, "null Resource rejected", failures) _expect_same(finalizer.call("instantiate_candidate", Resource.new()), null, "unsupported Resource rejected", failures) var invalid_source := Node.new() var invalid_scene := PackedScene.new() _expect_equal(invalid_scene.pack(invalid_source), OK, "non-Node3D scene packs", failures) invalid_source.free() var node_count_before := int(Performance.get_monitor(Performance.OBJECT_NODE_COUNT)) _expect_same(finalizer.call("instantiate_candidate", invalid_scene), null, "non-Node3D root rejected", failures) var node_count_after := int(Performance.get_monitor(Performance.OBJECT_NODE_COUNT)) _expect_equal(node_count_after, node_count_before, "invalid instantiated root freed", failures) var valid_source := Node3D.new() var valid_scene := PackedScene.new() _expect_equal(valid_scene.pack(valid_source), OK, "Node3D scene packs", failures) valid_source.free() var candidate: Node3D = finalizer.call("instantiate_candidate", valid_scene) _expect_true(candidate != null, "Node3D candidate returned", failures) if candidate != null: candidate.free() func _verify_animation_validation_and_transfer(failures: Array[String]) -> void: var finalizer: RefCounted = FINALIZER_SCRIPT.new() var empty_candidate := Node3D.new() var empty_candidate_id := empty_candidate.get_instance_id() var rejected: Dictionary = finalizer.call("finalize_candidate", empty_candidate) _expect_true(rejected.is_empty(), "candidate without AnimationPlayer rejected", failures) _expect_false(is_instance_id_valid(empty_candidate_id), "rejected candidate freed", failures) var animated_candidate := Node3D.new() var branch := Node3D.new() var first_player := AnimationPlayer.new() var second_player := AnimationPlayer.new() animated_candidate.add_child(branch) branch.add_child(first_player) animated_candidate.add_child(second_player) var players: Array[AnimationPlayer] = finalizer.call( "animation_players_in_subtree", animated_candidate ) _expect_equal(players.size(), 2, "nested players discovered", failures) _expect_same(players[0], first_player, "player depth-first order retained", failures) var accepted: Dictionary = finalizer.call("finalize_candidate", animated_candidate) _expect_same(accepted.get("prototype"), animated_candidate, "exact candidate transferred", failures) _expect_equal(int(accepted.get("animation_player_count", 0)), 2, "player count transferred", failures) animated_candidate.free() func _verify_material_mapping(failures: Array[String]) -> void: var finalizer: RefCounted = FINALIZER_SCRIPT.new() var material_source_root := Node3D.new() var source_first := MeshInstance3D.new() var source_second := MeshInstance3D.new() source_first.mesh = _make_triangle_mesh(2) source_second.mesh = _make_triangle_mesh(1) var first_override := StandardMaterial3D.new() var second_surface_material := StandardMaterial3D.new() source_first.set_surface_override_material(0, first_override) source_second.mesh.surface_set_material(0, second_surface_material) material_source_root.add_child(source_first) material_source_root.add_child(source_second) var animated_root := Node3D.new() var target_first := MeshInstance3D.new() var target_second := MeshInstance3D.new() target_first.mesh = _make_triangle_mesh(2) target_second.mesh = _make_triangle_mesh(2) animated_root.add_child(target_first) animated_root.add_child(target_second) var target_meshes: Array[MeshInstance3D] = finalizer.call( "mesh_instances_in_subtree", animated_root ) _expect_same(target_meshes[0], target_first, "mesh depth-first order retained", failures) finalizer.call("repair_materials", animated_root, material_source_root) _expect_same(target_first.get_surface_override_material(0), first_override, "source override has priority", failures) _expect_same(target_first.get_surface_override_material(1), first_override, "missing source surface uses first fallback", failures) _expect_same(target_second.get_surface_override_material(0), second_surface_material, "source index maps to target index", failures) _expect_same(target_second.get_surface_override_material(1), first_override, "clamped source missing surface uses fallback", failures) animated_root.free() material_source_root.free() func _verify_ownership_boundaries(failures: Array[String]) -> void: var finalizer_source := FileAccess.get_file_as_string(FINALIZER_PATH) var materializer_source := FileAccess.get_file_as_string(MATERIALIZER_PATH) var loader_source := FileAccess.get_file_as_string(LOADER_PATH) _expect_true(loader_source.contains("M2_ANIMATED_SCENE_FINALIZER_SCRIPT.new()"), "loader composes finalizer", failures) for removed_loader_function in [ "func _repair_m2_animated_materials(", "func _find_animation_players_recursive(", "func _find_mesh_instances_recursive(", ]: _expect_false(loader_source.contains(removed_loader_function), "legacy helper removed: %s" % removed_loader_function, failures) for delegated_call in [ "_m2_animated_scene_finalizer.instantiate_candidate(resource)", "_m2_animated_scene_finalizer.repair_materials(candidate, material_source)", "_m2_animated_scene_finalizer.finalize_candidate(candidate)", "_m2_animated_scene_finalizer.mesh_instances_in_subtree(root)", ]: _expect_equal(loader_source.count(delegated_call), 1, "single loader delegation: %s" % delegated_call, failures) _expect_equal( materializer_source.count("_animated_scene_finalizer.animation_players_in_subtree("), 1, "single materializer player-inventory delegation", failures ) for retained_loader_rule in [ "ResourceLoader.load_threaded_get(path)", "RENDER_BUDGET_SCHEDULER_SCRIPT.M2_ANIMATION_FINALIZE", "_get_or_load_m2_material_prototype(normalized_rel)", "_m2_prototype_cache_state.adopt_animated_prototype(", "_m2_prototype_cache_state.mark_animation_static(normalized_rel)", "M2_ANIM_CACHE path=%s cache=%s players=%d", ]: _expect_true(loader_source.contains(retained_loader_rule), "loader retains %s" % retained_loader_rule, failures) for forbidden_dependency in [ "ResourceLoader.", "FileAccess.", "WorkerThreadPool.", "_m2_prototype_cache_state", "_render_budget_scheduler", "M2Builder", "M2RawModelRepository", ]: _expect_false(finalizer_source.contains(forbidden_dependency), "finalizer omits %s ownership" % forbidden_dependency, failures) func _verify_bounded_timing(failures: Array[String]) -> float: var finalizer: RefCounted = FINALIZER_SCRIPT.new() var root := Node3D.new() var branch := root for _depth_index in range(8): var child_branch := Node3D.new() branch.add_child(child_branch) branch = child_branch branch.add_child(AnimationPlayer.new()) var started_microseconds := Time.get_ticks_usec() for _iteration in range(10000): finalizer.call("animation_players_in_subtree", root) var elapsed_milliseconds := float(Time.get_ticks_usec() - started_microseconds) / 1000.0 root.free() _expect_true(elapsed_milliseconds < 1000.0, "10000 subtree queries under 1 second", failures) return elapsed_milliseconds func _make_triangle_mesh(surface_count: int) -> ArrayMesh: var mesh := ArrayMesh.new() for _surface_index in range(surface_count): var arrays := [] arrays.resize(Mesh.ARRAY_MAX) arrays[Mesh.ARRAY_VERTEX] = PackedVector3Array([ Vector3.ZERO, Vector3.RIGHT, Vector3.UP, ]) arrays[Mesh.ARRAY_INDEX] = PackedInt32Array([0, 1, 2]) mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays) return mesh func _expect_true(condition: bool, label: String, failures: Array[String]) -> void: if not condition: failures.append(label) func _expect_false(condition: bool, label: String, failures: Array[String]) -> void: _expect_true(not condition, label, failures) func _expect_equal(actual: int, expected: int, label: String, failures: Array[String]) -> void: if actual != expected: failures.append("%s expected=%d actual=%d" % [label, expected, actual]) func _expect_same(actual: Variant, expected: Variant, label: String, failures: Array[String]) -> void: if not is_same(actual, expected): failures.append(label)