extends SceneTree const MainScene = preload("res://main.tscn") var failures: Array[String] = [] func _initialize() -> void: call_deferred("run_test") func run_test() -> void: var game := MainScene.instantiate() game.auto_boot = false root.add_child(game) await settle(10) game.locale = "zh_CN" game.muted = true prepare_first_victory(game, "Hearthguard", "Farstring") expect(game.page == game.AppPage.VICTORY, "first battle reaches diagnosis") expect(game.coin == 20 and game.first_reward_committed, "first victory grants 20 Coin exactly once") expect(find_control(game, "VictoryDiagnosis") != null, "first result renders causal diagnosis") expect(find_control(game, "FirstReward") != null, "first result renders the reward") game.commit_first_victory_reward() expect(game.coin == 20, "replaying reward commit cannot duplicate Coin") game.open_adaptation() expect(game.page == game.AppPage.ADAPTATION, "diagnosis opens adaptation") expect(game.adaptation_choice.is_empty(), "adaptation has no preselection") expect(find_control(game, "Adaptation_EQ_002") != null and find_control(game, "Adaptation_EQ_061") != null, "both stable choices render") var begin := find_control(game, "BeginSecondBattle") as BaseButton expect(begin != null and begin.disabled, "second battle is disabled until a choice") game.choose_adaptation("EQ-061") game.choose_adaptation("EQ-002") expect(game.adaptation_choice == "EQ-002", "selection is reversible before commit") game.begin_second_battle() expect(game.page == game.AppPage.SECOND_BATTLE and game.adaptation_committed, "departure locks the chosen loan") game.choose_adaptation("EQ-061") expect(game.adaptation_choice == "EQ-002", "committed loan cannot change") var safe_time := resolve_second_battle(game) expect(game.page == game.AppPage.SECOND_RESULT, "safe route reaches second result") expect(game.front_hp == 90 and game.enemy_hp == 0, "Frontpin Farstring route keeps 90 front HP and wins") expect(game.coin == 20, "second battle does not duplicate first reward") expect(game.battle_feedback_history.size() == 4, "safe route emits four exact HP feedback events") expect(find_control(game, "SecondResultFacts") != null, "second result renders exact facts") game.request_restart() game.confirm_restart() expect(game.page == game.AppPage.MAIN_MENU, "confirmed replay returns to Main Menu") expect(game.coin == 0 and game.adaptation_choice.is_empty() and not game.adaptation_committed, "restart clears reward and adaptation") prepare_first_victory(game, "Hearthguard", "Farstring") game.open_adaptation() game.choose_adaptation("EQ-061") game.begin_second_battle() var fast_time := resolve_second_battle(game) expect(game.front_hp == 70 and game.enemy_hp == 0, "Early Bell Farstring route accepts full strike and wins") expect(game.battle_feedback_history.size() == 5, "fast route emits initiative plus four HP feedback events") expect(fast_time < safe_time, "Early Bell route is faster than Frontpin route") game.clear_experience() prepare_first_victory(game, "Hearthguard", "Kindhand") game.open_adaptation() game.choose_adaptation("EQ-002") game.begin_second_battle() resolve_second_battle(game) expect(game.front_hp == 100, "Frontpin Kindhand route caps healing at 100") game.clear_experience() prepare_first_victory(game, "Wayrunner", "Kindhand") game.open_adaptation() game.choose_adaptation("EQ-061") game.begin_second_battle() resolve_second_battle(game) expect(game.front_hp == 82, "Early Bell Kindhand route heals 70 to 82") game.go_home() expect(game.page == game.AppPage.MAIN_MENU and game.coin == 0 and game.front_choice.is_empty(), "Home clears the complete transient loop") game.queue_free() await settle(80) if failures.is_empty(): print("ABG_P9_0R4_SECOND_CYCLE_OK coin=20 once=true choices=2 unselected=true revision=true lock=true safe_hp=90,100 fast_hp=70,82 safe_time=%.2f fast_time=%.2f feedback=4,5 cleanup=true" % [safe_time, fast_time]) quit(0) else: for failure in failures: push_error(failure) quit(1) func prepare_first_victory(game: Control, front: String, rear: String) -> void: game.front_choice = front game.rear_choice = rear game.depart() for phase in range(6): game.advance_battle_phase() func resolve_second_battle(game: Control) -> float: var declared_time := 0.0 for phase in range(6): var duration: float = game.phase_duration(phase) declared_time += duration game.second_battle_elapsed += duration game.advance_battle_phase() return declared_time func find_control(node: Node, node_name: String) -> Control: if node.name == node_name and node is Control and not node.is_queued_for_deletion(): return node for child in node.get_children(): var found := find_control(child, node_name) if found != null: return found return null func settle(frames: int) -> void: for frame in range(frames): await process_frame func expect(condition: bool, message: String) -> void: if not condition: failures.append("P9.0R4 second cycle: %s" % message)