extends RefCounted ## QA ordinary Main destruction only; application/window close is separate QA. ## Reuse the existing four fixtures' real WeakRef/mixer drain without editing it. const ExistingCleanup := preload("res://tests/helpers/ui_audio_teardown.gd") const OWNER_META := &"_the_tree_ui_close_owners" static func free_owner_and_wait(tree: SceneTree, owner: Node) -> Dictionary: var owner_id: int = owner.get_instance_id() if is_instance_valid(owner) else 0 var controls_before: Dictionary = _controls(tree) # ExistingCleanup captures weak audio targets synchronously before queue_free. # Only weak targets cross its await. It neither changes Clock flags nor calls # application close, and Main._exit_tree keeps the ordinary owner contract. var audio: Dictionary = await ExistingCleanup.free_owner_and_wait(tree, owner, 500000) var controls_after: Dictionary = _controls(tree) var before_owners: Array = controls_before[&"owner_ids"] var expected_remaining: Array = before_owners.duplicate() expected_remaining.erase(owner_id) var ordinary_release: bool = bool(controls_before[&"owner_meta_present"]) and not bool(controls_before[&"closing"]) and before_owners.has(owner_id) if expected_remaining.is_empty(): ordinary_release = ordinary_release and not bool(controls_after[&"owner_meta_present"]) and bool(controls_after[&"auto_accept_quit"]) == bool(controls_before[&"original_auto_accept_quit"]) else: ordinary_release = ordinary_release and bool(controls_after[&"owner_meta_present"]) and not bool(controls_after[&"closing"]) and controls_after[&"owner_ids"] == expected_remaining and not bool(controls_after[&"auto_accept_quit"]) var clock_unchanged: bool = controls_before[&"clock_process"] == controls_after[&"clock_process"] and controls_before[&"clock_physics_process"] == controls_after[&"clock_physics_process"] and controls_before[&"clock_process_mode"] == controls_after[&"clock_process_mode"] var root_input_unchanged: bool = controls_before[&"root_gui_disable_input"] == controls_after[&"root_gui_disable_input"] var report: Dictionary = {&"owned_engine_pid": OS.get_process_id(), &"owner_instance_id": owner_id, &"before": controls_before, &"after": controls_after, &"expected_remaining_owner_ids": expected_remaining, &"ordinary_owner_release_verified": ordinary_release, &"clock_process_flags_unchanged": clock_unchanged, &"root_gui_input_flag_unchanged": root_input_unchanged, &"audio_teardown_passed": bool(audio.get(&"passed", false)), &"maximum_wait_us": 500000, &"whole_Runtime_field_deletions": 0, &"gameplay_expectations_changed": false, &"clock_flags_intentionally_modified": false, &"application_close_called": false, &"SceneTree_quit_overridden": false, &"WindowQuit_verified": false, &"formal_acceptance": false} report[&"passed"] = bool(audio.get(&"passed", false)) and ordinary_release and clock_unchanged and root_input_unchanged var path: String = OS.get_environment("THE_TREE_AUDIO_FIXTURE_CONTROL_PROOF") if path.is_empty() or not path.is_absolute_path() or FileAccess.file_exists(path): report[&"passed"] = false return report var file: FileAccess = FileAccess.open(path, FileAccess.WRITE) if file == null: report[&"passed"] = false return report file.store_string(JSON.stringify(report, "\t") + "\n") file.close() return report static func _controls(tree: SceneTree) -> Dictionary: # Local strong Node/WeakRef.get_ref results are released on helper return. var clock: Node = tree.root.get_node("GameClock") var present: bool = tree.root.has_meta(OWNER_META) var state: Dictionary = tree.root.get_meta(OWNER_META) if present else {} var ids: Array = [] for reference: WeakRef in state.get(&"owners", []): var node: Variant = reference.get_ref() if is_instance_valid(node): ids.append(node.get_instance_id()) ids.sort() return {&"clock_process": clock.is_processing(), &"clock_physics_process": clock.is_physics_processing(), &"clock_process_mode": clock.process_mode, &"root_gui_disable_input": tree.root.gui_disable_input, &"auto_accept_quit": tree.auto_accept_quit, &"owner_meta_present": present, &"original_auto_accept_quit": bool(state.get(&"original_auto_accept_quit", tree.auto_accept_quit)), &"closing": bool(state.get(&"closing", false)), &"owner_ids": ids}