extends SceneTree var failures: Array[String] = [] func _init() -> void: _run.call_deferred() func _run() -> void: for path: String in [ "res://scenes/components/input_intent.gd", "res://scenes/components/input_component.gd", "res://scenes/components/combo_window.gd", "res://scenes/components/state_machine.gd", "res://scenes/components/energy_component.gd", "res://scenes/components/burst_component.gd", "res://scenes/components/charge_component.gd", "res://scenes/components/action_controller.gd", "res://scenes/combat/action_resolver.gd", "res://resources/player_action_bindings.gd", "res://resources/player_action_bindings.tres", ]: _expect(load(path) != null, "%s should load" % path) var resolver_script: Script = load("res://scenes/combat/action_resolver.gd") if resolver_script != null: if resolver_script.has_method("clear_cache"): resolver_script.call("clear_cache") var expected := { "A": &"ground_attack_left_1", "AA": &"ground_attack_left_2", "D": &"ground_attack_right_1", "DD": &"ground_attack_right_2", "W": &"launcher_up", "S": &"block_start", "SS": &"block_start", } for pattern: String in expected: var action: Resource = resolver_script.call("resolve_pattern", pattern) _expect(action != null, "ActionResolver should resolve %s" % pattern) if action != null: _expect(StringName(str(action.get("id"))) == expected[pattern], "%s should resolve to %s" % [pattern, expected[pattern]]) _check_s_basic_fallback_keeps_exact_combos(resolver_script) var gd_files := _all_gd_files(["res://scenes/components", "res://scenes/combat", "res://resources"]) for path: String in gd_files: var text := _read_text(path) _expect(not text.contains("legacy_skill_"), "%s should not hardcode old skill ids" % path) _expect(not text.contains("legacy_time_phase"), "%s should not keep legacy time phase placeholders" % path) _expect(not text.contains("legacy_time_anchor"), "%s should not keep legacy time anchor placeholders" % path) _finish() func _check_s_basic_fallback_keeps_exact_combos(resolver_script: Script) -> void: resolver_script.call("reload", "res://tests/fixtures/actions") var s_action: Resource = resolver_script.call("resolve_pattern", "S") var combo_action: Resource = resolver_script.call("resolve_pattern", "SD") var fallback_action: Resource = resolver_script.call("resolve_pattern", "AS") _expect(s_action != null, "S fixture action should resolve") if s_action != null: _expect(StringName(str(s_action.get("id"))) == &"test_block_start", "S alone should resolve to the immediate block action") _expect(combo_action != null, "S follow-up combo fixture should resolve") if combo_action != null: _expect(StringName(str(combo_action.get("id"))) == &"test_s_followup_combo", "S follow-up combo should beat trailing S fallback") _expect(fallback_action != null, "Unmatched pattern ending in S should still fall back to immediate block") if fallback_action != null: _expect(StringName(str(fallback_action.get("id"))) == &"test_block_start", "Only unmatched S suffixes should use block fallback") resolver_script.call("clear_cache") func _all_gd_files(dirs: Array[String]) -> Array[String]: var result: Array[String] = [] for dir_path: String in dirs: _collect_gd_files(dir_path, result) return result func _collect_gd_files(dir_path: String, result: Array[String]) -> void: var dir := DirAccess.open(dir_path) if dir == null: return for subdir: String in dir.get_directories(): _collect_gd_files("%s/%s" % [dir_path, subdir], result) for file_name: String in dir.get_files(): if file_name.ends_with(".gd"): result.append("%s/%s" % [dir_path, file_name]) func _read_text(path: String) -> String: var file := FileAccess.open(path, FileAccess.READ) if file == null: failures.append("Could not read %s" % path) return "" var text := file.get_as_text() file.close() return text func _expect(condition: bool, label: String) -> void: if not condition: failures.append(label) func _finish() -> void: if failures.is_empty(): print("PASS rhythm action architecture") quit(0) else: for failure: String in failures: push_error(failure) quit(1)