extends SceneTree ## Asset-free state, ownership, dependency and timing regression for cached WMO scenes. const CACHE_STATE_SCRIPT := preload( "res://src/render/wmo/wmo_scene_resource_cache_state.gd" ) const CACHE_STATE_PATH := "res://src/render/wmo/wmo_scene_resource_cache_state.gd" const FINALIZER_PATH := "res://src/render/wmo/wmo_scene_resource_finalizer.gd" const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd" func _initialize() -> void: var failures: Array[String] = [] _verify_invalid_and_request_state(failures) _verify_direct_missing_and_exclusivity(failures) _verify_validated_scene_completion(failures) _verify_failed_request_completion(failures) _verify_transient_and_full_clear(failures) _verify_detached_sorted_diagnostics(failures) _verify_ownership_boundaries(failures) var elapsed_milliseconds := _verify_bounded_timing(failures) if not failures.is_empty(): for failure in failures: push_error("WMO_SCENE_RESOURCE_CACHE_STATE: %s" % failure) quit(1) return print( "WMO_SCENE_RESOURCE_CACHE_STATE PASS cases=11 iterations=100 elapsed_ms=%.3f" % elapsed_milliseconds ) quit(0) func _verify_invalid_and_request_state(failures: Array[String]) -> void: var cache_state: RefCounted = CACHE_STATE_SCRIPT.new() _expect_false( cache_state.call("remember_request", "", "res://cache/a.tscn"), "empty normalized path rejected", failures ) _expect_false( cache_state.call("remember_request", "world/a.wmo", ""), "empty cache path rejected", failures ) _expect_true( cache_state.call("remember_request", "world/a.wmo", "res://cache/a.tscn"), "valid request accepted", failures ) _expect_true( cache_state.call("has_request", "world/a.wmo"), "pending request observable", failures ) _expect_equal( int(cache_state.call("pending_request_count")), 1, "pending request count", failures ) _expect_false( cache_state.call("remember_request", "world/a.wmo", "res://cache/other.tscn"), "duplicate request rejected", failures ) var request_snapshot: Dictionary = cache_state.call("request_paths_snapshot") _expect_string_equal( String(request_snapshot.get("world/a.wmo", "")), "res://cache/a.tscn", "request cache path preserved", failures ) request_snapshot.clear() _expect_true( cache_state.call("has_request", "world/a.wmo"), "request snapshot detached", failures ) func _verify_direct_missing_and_exclusivity(failures: Array[String]) -> void: var cache_state: RefCounted = CACHE_STATE_SCRIPT.new() _expect_false(cache_state.call("mark_missing", ""), "empty missing rejected", failures) _expect_true( cache_state.call("mark_missing", "world/missing.wmo"), "direct missing accepted", failures ) _expect_true( cache_state.call("is_missing", "world/missing.wmo"), "direct missing observable", failures ) _expect_false( cache_state.call("mark_missing", "world/missing.wmo"), "duplicate missing rejected", failures ) _expect_false( cache_state.call( "remember_request", "world/missing.wmo", "res://cache/retry.tscn" ), "missing path rejects request", failures ) func _verify_validated_scene_completion(failures: Array[String]) -> void: var cache_state: RefCounted = CACHE_STATE_SCRIPT.new() var validated_scene := PackedScene.new() cache_state.call("remember_request", "world/b.wmo", "res://cache/b.tscn") _expect_false( cache_state.call("complete_request_with_scene", "world/b.wmo", null), "null completion rejected", failures ) _expect_true( cache_state.call("complete_request_with_scene", "world/b.wmo", validated_scene), "validated PackedScene completes request", failures ) _expect_false( cache_state.call("has_request", "world/b.wmo"), "successful completion removes request", failures ) _expect_true( cache_state.call("contains_scene", "world/b.wmo"), "successful completion cached", failures ) _expect_true( cache_state.call("scene_for", "world/b.wmo") == validated_scene, "cached PackedScene identity preserved", failures ) _expect_false( cache_state.call("mark_missing", "world/b.wmo"), "cached scene rejects missing state", failures ) func _verify_failed_request_completion(failures: Array[String]) -> void: var cache_state: RefCounted = CACHE_STATE_SCRIPT.new() _expect_false( cache_state.call("complete_request_as_missing", "world/unknown.wmo"), "unknown request completion rejected", failures ) cache_state.call("remember_request", "world/failed.wmo", "res://cache/failed.tscn") _expect_true( cache_state.call("complete_request_as_missing", "world/failed.wmo"), "failed request completes as missing", failures ) _expect_true( cache_state.call("is_missing", "world/failed.wmo"), "failed request negative-cached", failures ) _expect_false( cache_state.call("has_request", "world/failed.wmo"), "failed completion removes request", failures ) func _verify_transient_and_full_clear(failures: Array[String]) -> void: var cache_state: RefCounted = CACHE_STATE_SCRIPT.new() var retained_scene := PackedScene.new() cache_state.call("remember_request", "world/retained.wmo", "res://retained.tscn") cache_state.call("complete_request_with_scene", "world/retained.wmo", retained_scene) cache_state.call("remember_request", "world/pending.wmo", "res://pending.tscn") cache_state.call("mark_missing", "world/missing.wmo") cache_state.call("clear_transient_state") _expect_true( cache_state.call("scene_for", "world/retained.wmo") == retained_scene, "transient clear retains PackedScene", failures ) _expect_equal( int(cache_state.call("pending_request_count")), 0, "transient clear removes requests", failures ) _expect_false( cache_state.call("is_missing", "world/missing.wmo"), "transient clear removes missing state", failures ) cache_state.call("clear_all") cache_state.call("clear_all") _expect_false( cache_state.call("contains_scene", "world/retained.wmo"), "full clear idempotently releases PackedScene", failures ) func _verify_detached_sorted_diagnostics(failures: Array[String]) -> void: var cache_state: RefCounted = CACHE_STATE_SCRIPT.new() cache_state.call("remember_request", "world/z.wmo", "res://z.tscn") cache_state.call("remember_request", "world/a.wmo", "res://a.tscn") cache_state.call("mark_missing", "world/m.wmo") var snapshot: Dictionary = cache_state.call("diagnostic_snapshot") _expect_string_array( snapshot["request_paths"], ["world/a.wmo", "world/z.wmo"], "request diagnostics sorted", failures ) _expect_string_array( snapshot["missing_paths"], ["world/m.wmo"], "missing diagnostics", failures ) (snapshot["request_paths"] as Array).clear() var fresh_snapshot: Dictionary = cache_state.call("diagnostic_snapshot") _expect_equal( (fresh_snapshot["request_paths"] as Array).size(), 2, "diagnostics detached", failures ) func _verify_ownership_boundaries(failures: Array[String]) -> void: var loader_source := _read_text(LOADER_PATH, failures) var cache_state_source := _read_text(CACHE_STATE_PATH, failures) var finalizer_source := _read_text(FINALIZER_PATH, failures) _expect_true( loader_source.contains("WMO_SCENE_RESOURCE_CACHE_STATE_SCRIPT.new()"), "loader composes scene cache state", failures ) for legacy_field in [ "var _wmo_scene_resource_cache:", "var _wmo_scene_cache_missing:", "var _wmo_scene_load_requests:", ]: _expect_false( loader_source.contains(legacy_field), "loader removes %s" % legacy_field, failures ) for loader_owned_token in [ "ResourceLoader.", "FileAccess.", "WMO_BUILDER_SCRIPT", "instantiate()", "Node3D", "RID", "Mutex", "Semaphore", ]: _expect_false( cache_state_source.contains(loader_owned_token), "scene cache state omits %s dependency" % loader_owned_token, failures ) for retained_loader_rule in [ "wmo_max_runtime_scene_mb", "_get_resource_file_size(cache_path)", ]: _expect_true( loader_source.contains(retained_loader_rule), "loader retains %s" % retained_loader_rule, failures ) for finalizer_rule in [ "scene.instantiate()", "is_scene_cache_current", "instance.free()", ]: _expect_true( finalizer_source.contains(finalizer_rule), "finalizer owns %s" % finalizer_rule, failures ) func _verify_bounded_timing(failures: Array[String]) -> float: var cache_state: RefCounted = CACHE_STATE_SCRIPT.new() var started_microseconds := Time.get_ticks_usec() for iteration in range(100): for scene_index in range(256): var normalized_path := "world/object_%d.wmo" % scene_index if scene_index % 3 == 0: cache_state.call("mark_missing", normalized_path) else: cache_state.call( "remember_request", normalized_path, "res://cache/object_%d.tscn" % scene_index ) if scene_index % 2 == 0: cache_state.call( "complete_request_with_scene", normalized_path, PackedScene.new() ) else: cache_state.call("complete_request_as_missing", normalized_path) cache_state.call("clear_all") _expect_equal( int(cache_state.call("pending_request_count")), 0, "timing cycle %d clears" % iteration, failures ) var elapsed_milliseconds := float(Time.get_ticks_usec() - started_microseconds) / 1000.0 _expect_true( elapsed_milliseconds < 1000.0, "100 scene/request cycles remain bounded", failures ) return elapsed_milliseconds func _read_text(path: String, failures: Array[String]) -> String: var file := FileAccess.open(path, FileAccess.READ) if file == null: failures.append("cannot open %s" % path) return "" return file.get_as_text() func _expect_equal( actual_value: int, expected_value: int, label: String, failures: Array[String] ) -> void: if actual_value != expected_value: failures.append("%s expected %d, got %d" % [label, expected_value, actual_value]) func _expect_string_equal( actual_value: String, expected_value: String, label: String, failures: Array[String] ) -> void: if actual_value != expected_value: failures.append("%s expected %s, got %s" % [label, expected_value, actual_value]) func _expect_string_array( actual_values: Array, expected_values: Array, label: String, failures: Array[String] ) -> void: if actual_values != expected_values: failures.append("%s expected %s, got %s" % [label, expected_values, actual_values]) func _expect_true(actual_value: bool, label: String, failures: Array[String]) -> void: if not actual_value: failures.append("%s expected true" % label) func _expect_false(actual_value: bool, label: String, failures: Array[String]) -> void: if actual_value: failures.append("%s expected false" % label)