Files
rthythm_archor_game/tests/test_game_flow.gd
T
2026-07-06 22:59:47 -07:00

149 lines
5.4 KiB
GDScript

extends SceneTree
## Game-flow state machine + boss-room entry rules (AnchorV1.0 §4.5 / §19.5).
var failures: Array[String] = []
func _init() -> void:
_run.call_deferred()
func _run() -> void:
var flow: Node = load("res://autoload/game_flow_manager.gd").new()
flow.set("manage_scene", false)
root.add_child(flow)
await process_frame
# --- screens exist for every menu state ---
for state_name: StringName in [&"Title", &"DifficultySelect", &"Paused", &"VolumeSettings", &"ExitGameConfirm", &"ReturnTitleConfirm", &"Defeat", &"Victory", &"Result"]:
var screens: Dictionary = flow.get("_screens")
_expect_bool(screens.has(state_name), true, "screen for %s exists" % state_name)
var flow_ui := flow.get_node_or_null("FlowUI")
_expect_bool(flow_ui != null, true, "flow UI layer exists")
if flow_ui != null:
_expect_bool(flow_ui.get_node_or_null("PauseButton") == null, true, "HUD should not show a top-right pause button; pause is Esc-only")
# --- pause / resume drive the SceneTree pause + rhythm clock ---
flow.set("state", &"Gameplay_FrontArea")
var esc := InputEventAction.new()
esc.action = &"ui_cancel"
esc.pressed = true
flow.call("_unhandled_input", esc)
_expect_string(str(flow.get("state")), "Paused", "Esc enters Paused")
_expect_bool(root.get_tree().paused, true, "pause stops the SceneTree")
flow.call("_unhandled_input", esc)
_expect_string(str(flow.get("state")), "Gameplay_FrontArea", "Esc resumes gameplay")
_expect_bool(root.get_tree().paused, false, "resume releases the SceneTree")
# --- boss-room entry rules on a synthetic scene (§4.5) ---
var scene := _build_fake_scene()
root.add_child(scene)
current_scene = scene
await process_frame
var player := scene.get_node("Stage/ActorsContainer/Player")
var streak := player.get_node("StreakCounter")
streak.set("streak", 9)
var combo := player.get_node("ComboWindow")
combo.call("record", &"A")
combo.call("record", &"A")
var buff := player.get_node("AttackBuffComponent")
var container := player.get_node("EffectContainer")
for index: int in range(6):
container.call("add_effect", buff.get("buff_definition"), &"test")
_expect_int(int(buff.call("attack_buff_stacks")), 6, "test setup should reach 6 buff stacks")
flow.set("state", &"Gameplay_FrontArea")
flow.set("_boss_room_entered", false)
flow.call("enter_boss_room")
_expect_string(str(flow.get("state")), "Gameplay_BossRoom", "entering the boss room switches state")
_expect_int(int(streak.get("streak")), 0, "boss entry clears the combo counter")
_expect_int((combo.call("get_slots") as Array).size(), 0, "boss entry clears the four-slot window")
_expect_int(int(buff.call("attack_buff_stacks")), 3, "boss entry caps the attack buff at 3 stacks")
var boss := scene.get_node("Stage/ActorsContainer/Boss")
_expect_bool(bool(boss.get("combat_enabled")), true, "boss combat unlocks on entry")
_expect_bool(bool(boss.get("stationary")), false, "boss leaves training-dummy mode on entry")
# --- re-entering is a no-op ---
streak.set("streak", 5)
flow.call("enter_boss_room")
_expect_int(int(streak.get("streak")), 5, "boss entry rules apply only once")
# --- rank appears only on victory (§19.6) ---
flow.set("_victory", true)
flow.set("state", &"Result")
flow.call("_populate_result_screen")
var labels: Dictionary = flow.get("_result_labels")
_expect_bool((labels.get("rank") as Label).visible, true, "victory result shows the rank")
flow.set("_victory", false)
flow.call("_populate_result_screen")
_expect_bool((labels.get("rank") as Label).visible, false, "defeat result hides the rank")
current_scene = null
scene.free()
flow.free()
_finish()
func _build_fake_scene() -> Node:
var scene := Node.new()
scene.name = "Main"
var stage := Node2D.new()
stage.name = "Stage"
scene.add_child(stage)
var actors := Node2D.new()
actors.name = "ActorsContainer"
stage.add_child(actors)
var player := CharacterBody2D.new()
player.name = "Player"
actors.add_child(player)
var streak: Node = load("res://scenes/components/streak_counter.gd").new()
streak.name = "StreakCounter"
player.add_child(streak)
var combo: Node = load("res://scenes/components/combo_window.gd").new()
combo.name = "ComboWindow"
player.add_child(combo)
var container: Node = load("res://scenes/components/effect_container.gd").new()
container.name = "EffectContainer"
player.add_child(container)
var buff: Node = load("res://scenes/components/attack_buff_component.gd").new()
buff.name = "AttackBuffComponent"
player.add_child(buff)
var boss_script := GDScript.new()
boss_script.source_code = "extends Node2D\nvar combat_enabled := false\nvar stationary := true\n"
boss_script.reload()
var boss := Node2D.new()
boss.name = "Boss"
boss.set_script(boss_script)
actors.add_child(boss)
return scene
func _expect_bool(actual: bool, expected: bool, label: String) -> void:
if actual != expected:
failures.append("%s: expected %s, got %s" % [label, expected, actual])
func _expect_int(actual: int, expected: int, label: String) -> void:
if actual != expected:
failures.append("%s: expected %d, got %d" % [label, expected, actual])
func _expect_string(actual: String, expected: String, label: String) -> void:
if actual != expected:
failures.append("%s: expected %s, got %s" % [label, expected, actual])
func _finish() -> void:
paused = false
if failures.is_empty():
print("PASS game flow")
quit(0)
else:
for failure: String in failures:
push_error(failure)
quit(1)