extends SceneTree var failures: Array[String] = [] func _init() -> void: _run.call_deferred() func _run() -> void: var scene: PackedScene = load("res://scenes/ui/main_ui.tscn") if scene == null: push_error("Could not load main_ui.tscn") quit(1) return var ui := scene.instantiate() root.add_child(ui) await process_frame var track := ui.get_node_or_null("RhythmTrack") as Control if track == null: failures.append("Missing RhythmTrack") _finish(ui) return _expect_node(track, "LeftMover", "RhythmTrack should keep the animated left mover") _expect_node(track, "RightMover", "RhythmTrack should keep the animated right mover") _expect_node(track, "CenterFlash", "RhythmTrack should keep the center beat flash") var bus := _event_bus() bus.emit_signal("beat_ticked", 1) await process_frame var center_flash := track.get_node_or_null("CenterFlash") as CanvasItem if center_flash != null: _expect_bool(center_flash.modulate.a > 0.5, true, "CenterFlash should become visible on beat") var marker_container := track.get_node_or_null("ChartMarkerContainer") as Control if marker_container == null: failures.append("RhythmTrack should include ChartMarkerContainer") else: var event_script: Script = load("res://resources/chart_event.gd") var event: Resource = event_script.new() event.set("event_type", &"enemy_attack_active") bus.emit_signal("chart_event_upcoming", event, 0.5) await process_frame _expect_bool(marker_container.get_child_count() > 0, true, "Chart upcoming event should create a rhythm marker") bus.emit_signal("action_judged", &"skill_a", {"label": "perfect", "diff": 0.0, "color": Color("00f2ff")}) await process_frame var label := track.get_node_or_null("JudgementLabel") as Label if label != null: _expect_bool(label.scale.x > 1.0, true, "JudgementLabel should pulse on judgement") _expect_bool(label.modulate.is_equal_approx(Color("00f2ff")), true, "JudgementLabel should use judgement color") var combo_window := ui.get_node_or_null("ComboWindow") as Control if combo_window == null: failures.append("Missing ComboWindow") else: bus.emit_signal("combo_updated", [&"A"]) await process_frame var first_slot := combo_window.get_child(0) as Control _expect_bool(first_slot.scale.x > 1.0, true, "Combo slot should pulse when filled") bus.emit_signal("combo_cleared", &"full") await process_frame _expect_bool(first_slot.scale.x > 1.0, true, "Combo slots should flash when cleared") var charge_bar := ui.get_node_or_null("StatusBars/ChargeBar") as ProgressBar if charge_bar == null: failures.append("Missing ChargeBar") else: bus.emit_signal("player_charge_changed", 1.1, 1.1, true, true) await create_timer(0.05).timeout var first_alpha := charge_bar.modulate.a await create_timer(0.05).timeout var second_alpha := charge_bar.modulate.a _expect_bool(not is_equal_approx(first_alpha, second_alpha), true, "ChargeBar should flash while ready") _finish(ui) func _event_bus() -> Node: var bus := root.get_node_or_null("EventBus") if bus == null: bus = load("res://autoload/event_bus.gd").new() bus.name = "EventBus" root.add_child(bus) return bus func _expect_node(node: Node, path: String, label: String) -> void: if node.get_node_or_null(path) == null: failures.append(label) func _expect_bool(actual: bool, expected: bool, label: String) -> void: if actual != expected: failures.append("%s: expected %s, got %s" % [label, expected, actual]) func _finish(ui: Node) -> void: ui.free() if failures.is_empty(): print("PASS ui animation regression") quit(0) else: for failure: String in failures: push_error(failure) quit(1)