159 lines
6.3 KiB
GDScript
159 lines
6.3 KiB
GDScript
extends SceneTree
|
|
|
|
var failures: Array[String] = []
|
|
|
|
|
|
func _init() -> void:
|
|
_check_event_bus_autoload()
|
|
_check_main_is_thin()
|
|
_check_player_components()
|
|
_check_skill_resources()
|
|
_check_stage_and_ui_scenes()
|
|
_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")
|
|
|
|
|
|
func _check_main_is_thin() -> void:
|
|
var main_scene: PackedScene = load("res://scenes/main/main.tscn")
|
|
_expect(main_scene != null, "main.tscn should load")
|
|
if main_scene != null:
|
|
var main := main_scene.instantiate()
|
|
get_root().add_child(main)
|
|
_expect(main.has_node("Stage"), "Main should instance a Stage child")
|
|
_expect(main.has_node("UI"), "Main should instance a standalone UI child")
|
|
_expect(not main.has_node("RhythmConductor"), "Main should use RhythmManager autoload instead of a RhythmConductor child")
|
|
main.free()
|
|
var source := _read_text("res://scenes/main/main.gd")
|
|
_expect_not_contains(source, "has_signal", "Main should not probe child signals with has_signal")
|
|
_expect_not_contains(source, "has_method", "Main should not probe child methods with has_method")
|
|
_expect_not_contains(source, ".call(", "Main should not use dynamic call() for game wiring")
|
|
|
|
|
|
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",
|
|
"BurstComponent",
|
|
"ChargeComponent",
|
|
"EnergyComponent",
|
|
"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() >= 20, "actions should be saved as migrated .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")) == "skill_a", "A pattern should resolve to skill_a")
|
|
resolver_script.clear_cache()
|
|
window.free()
|
|
|
|
|
|
func _check_stage_and_ui_scenes() -> void:
|
|
var stage_scene: PackedScene = load("res://scenes/stage/stage.tscn")
|
|
_expect(stage_scene != null, "stage.tscn should exist")
|
|
if stage_scene != null:
|
|
var stage := stage_scene.instantiate()
|
|
get_root().add_child(stage)
|
|
_expect(stage.has_node("ActorsContainer"), "Stage should own ActorsContainer")
|
|
_expect(stage.has_node("ActorsContainer/Player"), "ActorsContainer should own Player")
|
|
stage.free()
|
|
for scene_path: String in [
|
|
"res://scenes/ui/main_ui.tscn",
|
|
"res://scenes/ui/rhythm_track.tscn",
|
|
"res://scenes/ui/combo_window_hud.tscn",
|
|
"res://scenes/ui/energy_bar.tscn",
|
|
]:
|
|
_expect(load(scene_path) != null, "%s should exist" % scene_path)
|
|
|
|
|
|
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)
|