extends SceneTree var failures: Array[String] = [] func _init() -> void: _check_event_bus_autoload() _check_current_main_scene_loads() _check_player_components() _check_skill_resources() _check_project_layers_and_inputs() _finish() func _check_event_bus_autoload() -> void: _expect(ProjectSettings.has_setting("autoload/EventBus"), "EventBus should be registered as a game autoload") if ProjectSettings.has_setting("autoload/EventBus"): var path := str(ProjectSettings.get_setting("autoload/EventBus")) _expect(path.contains("res://autoload/event_bus.gd"), "EventBus autoload should point at autoload/event_bus.gd") var event_bus_script := load("res://autoload/event_bus.gd") _expect(event_bus_script != null, "autoload/event_bus.gd should exist") _expect(ProjectSettings.has_setting("autoload/RhythmManager"), "RhythmManager should be registered as a game autoload") _expect(ProjectSettings.has_setting("autoload/CombatManager"), "CombatManager should be registered as a game autoload") func _check_current_main_scene_loads() -> void: var main_scene_path := str(ProjectSettings.get_setting("application/run/main_scene", "")) _expect(main_scene_path == "res://scenes/main/main.tscn", "Phase-7 main scene should route through the migrated Stage/Main layer") var main_scene: PackedScene = load(main_scene_path) _expect(main_scene != null, "%s should load" % main_scene_path) if main_scene != null: var main := main_scene.instantiate() get_root().add_child(main) _expect(main.has_node("Stage"), "Phase-7 main scene should instance Stage") _expect(main.has_node("ChartRunner"), "Phase-7 main scene should own ChartRunner") _expect(not main.has_node("RhythmConductor"), "Main scene should use RhythmManager autoload instead of a RhythmConductor child") main.free() func _check_player_components() -> void: var player_scene: PackedScene = load("res://scenes/characters/player.tscn") _expect(player_scene != null, "player.tscn should load") if player_scene != null: var player := player_scene.instantiate() get_root().add_child(player) for node_name: String in [ "StateMachine", "InputComponent", "ComboWindow", "ActionResolver", "ActionExecutor", "MotionExecutor", "MovementMotor", "BurstComponent", "ChargeComponent", "EnergyComponent", "EffectContainer", "HealthComponent", "DamageReceiver", "DamageEmitter", ]: _expect(player.has_node(node_name), "Player should have %s child component" % node_name) player.free() var source := _read_text("res://scenes/characters/player.gd") _expect_not_contains(source, "KEY_", "Player should not match raw KEY_* values") _expect_not_contains(source, "get_first_node_in_group(\"rhythm_conductor\")", "Player should not look up RhythmConductor by group") _expect_not_contains(source, "PlayerProjectile.new()", "Player should not instantiate projectiles directly") _expect_not_contains(source, "get_parent()", "Player should not reach upward with get_parent()") func _check_skill_resources() -> void: var action_script := load("res://resources/action_data.gd") _expect(action_script != null, "resources/action_data.gd should exist") var action_dir := DirAccess.open("res://resources/actions") _expect(action_dir != null, "resources/actions should exist") if action_dir != null: var action_files: Array[String] = [] for file_name: String in action_dir.get_files(): if file_name.ends_with(".tres"): action_files.append(file_name) _expect(action_files.size() >= 12, "phase-4+ player actions should be saved as .tres resources") var tracker_script := load("res://scenes/components/combo_window.gd") var resolver_script := load("res://scenes/combat/action_resolver.gd") _expect(tracker_script != null, "ComboWindow script should load") _expect(resolver_script != null, "ActionResolver script should load") if tracker_script == null or resolver_script == null: return var window = tracker_script.new() window.record(&"A") var resolved = resolver_script.resolve(window) _expect(resolved is Resource, "ActionResolver.resolve should return an ActionData Resource, not a Dictionary") if resolved is Resource: _expect(resolved.get("input_pattern") is Array, "Resolved ActionData should expose input_pattern") _expect(str(resolved.get("id")) == "ground_attack_left_1", "A pattern should resolve to ground_attack_left_1") resolver_script.clear_cache() window.free() func _check_project_layers_and_inputs() -> void: var expected_layers := { "layer_names/2d_physics/layer_1": "world", "layer_names/2d_physics/layer_2": "player_hurtbox", "layer_names/2d_physics/layer_3": "enemy_hurtbox", "layer_names/2d_physics/layer_4": "player_hitbox", "layer_names/2d_physics/layer_5": "enemy_hitbox", } for key: String in expected_layers: _expect(ProjectSettings.has_setting(key), "%s should be configured" % key) if ProjectSettings.has_setting(key): _expect(str(ProjectSettings.get_setting(key)) == expected_layers[key], "%s should be named %s" % [key, expected_layers[key]]) for action_name: String in ["move_left", "move_right", "combo_w", "combo_a", "combo_d", "combo_s", "combo_space"]: _expect(InputMap.has_action(action_name), "InputMap should define %s" % action_name) _expect(not InputMap.has_action("player_space"), "InputMap should remove duplicate player_space action") func _read_text(path: String) -> String: var file := FileAccess.open(path, FileAccess.READ) if file == null: failures.append("Could not read %s" % path) return "" return file.get_as_text() func _expect(condition: bool, label: String) -> void: if not condition: failures.append(label) func _expect_not_contains(source: String, needle: String, label: String) -> void: if source.contains(needle): failures.append(label) func _finish() -> void: if failures.is_empty(): print("PASS architecture refactor") quit(0) else: for failure: String in failures: push_error(failure) quit(1)