Files
aetherbound-guild/runtime/tests/p9_0r6_second_cycle_route_test.gd
T

157 lines
6.8 KiB
GDScript

extends SceneTree
const MainScene = preload("res://main.tscn")
const ROUTES := ["R6-01", "R6-02", "R6-03"]
const PROFILES := ["guarded", "tempo", "full_line"]
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_session_result(game, "guarded", "Farstring")
game.open_route_board()
expect(game.page == game.AppPage.ROUTE_BOARD and game.route_choice.is_empty(), "route board opens unselected")
expect(find_control(game, "Route_R6_01") != null and find_control(game, "Route_R6_02") != null and find_control(game, "Route_R6_03") != null, "three stable routes render")
var review := find_control(game, "ReviewRouteButton") as BaseButton
expect(review != null and review.disabled, "review waits for one route")
game.choose_route("invalid")
expect(game.route_choice.is_empty(), "invalid route is rejected")
game.choose_route("R6-01")
game.choose_route("R6-03")
expect(game.route_choice == "R6-03" and game.coin == 8, "selection is reversible and does not mutate Coin")
game.open_route_review()
expect(game.page == game.AppPage.ROUTE_REVIEW, "selected route opens explicit review")
game.cancel_route_review()
expect(game.page == game.AppPage.ROUTE_BOARD and game.route_choice == "R6-03", "cancel preserves selected route")
game.choose_route("R6-01")
game.open_route_review()
game.commit_route()
expect(game.page == game.AppPage.FOURTH_BATTLE and game.route_committed, "commit starts fourth battle")
expect(game.coin == 8 and not game.route_reward_committed, "route commit costs zero and does not prepay reward")
game.commit_route()
expect(game.coin == 8, "duplicate route commit is inert")
resolve_fourth_battle(game)
expect(game.page == game.AppPage.SECOND_CYCLE_RESULT, "fourth battle reaches second-cycle result")
expect(game.coin == 12 and game.route_reward_committed, "R6-01 reward adds 4 Coin exactly once")
game.commit_route_reward()
expect(game.coin == 12, "duplicate reward commit is inert")
for profile in PROFILES:
for route_id in ROUTES:
for original_rear in ["Farstring", "Kindhand"]:
prepare_session_result(game, profile, original_rear)
game.open_route_board()
expect(game.route_coverage_state(route_id) == expected_coverage(route_id, profile), "%s/%s coverage matches contract" % [route_id, profile])
game.choose_route(route_id)
game.open_route_review()
var coin_before: int = game.coin
game.commit_route()
var declared_time := resolve_fourth_battle(game)
var expected := expected_result(route_id, profile, original_rear)
expect(game.front_hp == expected["front"], "%s/%s/%s front HP" % [route_id, profile, original_rear])
expect(game.rear_hp == expected["rear"], "%s/%s/%s rear HP" % [route_id, profile, original_rear])
expect(is_equal_approx(declared_time, expected["time"]), "%s/%s declared time" % [route_id, profile])
expect(game.coin == coin_before + game.route_reward(route_id), "%s reward commits once" % route_id)
expect(game.enemy_hp == 0 and game.page == game.AppPage.SECOND_CYCLE_RESULT, "%s/%s clears" % [route_id, profile])
expect(find_control(game, "SecondCycleFacts") != null, "%s result facts render" % route_id)
game.request_restart()
game.confirm_restart()
expect(game.page == game.AppPage.MAIN_MENU, "confirmed Restart returns to Main Menu")
expect(game.route_choice.is_empty() and not game.route_committed and not game.route_reward_committed and game.coin == 0, "Restart clears both cycles")
game.queue_free()
await settle(90)
if failures.is_empty():
print("ABG_P9_0R6_SECOND_CYCLE_OK routes=3 profiles=3 cells=18 unselected=true revision=true review_cancel=true commit_once=true reward=4,7,10 times=7.40,6.40,7.00,8.00,6.00,6.80,7.80,6.70 cleanup=true")
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func prepare_session_result(game: Control, profile: String, original_rear: String) -> void:
game.clear_experience()
game.front_choice = "Hearthguard"
game.rear_choice = original_rear
game.adaptation_choice = "EQ-002" if profile != "tempo" else "EQ-061"
game.adaptation_committed = true
game.guild_committed = true
game.guild_choice = "recruit_rear" if profile == "full_line" else "retain_item"
game.retained_item = "" if profile == "full_line" else game.adaptation_choice
game.added_rear = ("Kindhand" if original_rear == "Farstring" else "Farstring") if profile == "full_line" else ""
game.coin = 6 if profile == "full_line" else 8
game.front_hp = 82 if profile == "full_line" else (90 if profile == "guarded" else 70)
game.enemy_hp = 0
game.third_result_time = 7.00 if profile == "full_line" else (7.60 if profile == "guarded" else 6.60)
game.page = game.AppPage.SESSION_RESULT
game.render()
func resolve_fourth_battle(game: Control) -> float:
var declared_time := 0.0
for phase in range(7):
var duration: float = game.phase_duration(phase)
declared_time += duration
game.fourth_battle_elapsed += duration
game.advance_battle_phase()
return declared_time
func expected_coverage(route_id: String, profile: String) -> String:
if route_id == "R6-01":
return "covered" if profile == "guarded" else ("partial" if profile == "full_line" else "gap")
if route_id == "R6-02":
return "covered" if profile == "tempo" else ("partial" if profile == "full_line" else "gap")
return "covered" if profile == "full_line" else "gap"
func expected_result(route_id: String, profile: String, original_rear: String) -> Dictionary:
var result := {"front": 100, "rear": 100, "time": 0.0}
match route_id:
"R6-01":
result["front"] = 90 if profile == "guarded" else (60 if profile == "tempo" else 70)
if original_rear == "Kindhand" or profile == "full_line":
result["front"] = mini(100, int(result["front"]) + 12)
result["time"] = 7.40 if profile == "guarded" else (6.40 if profile == "tempo" else 7.00)
"R6-02":
result["front"] = 70
if original_rear == "Kindhand" or profile == "full_line":
result["front"] = 82
result["time"] = 8.00 if profile == "guarded" else (6.00 if profile == "tempo" else 6.80)
"R6-03":
result["rear"] = 88 if profile == "full_line" else (77 if original_rear == "Kindhand" else 65)
result["time"] = 7.80 if profile == "guarded" else (6.70 if profile == "tempo" else 7.00)
return result
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.0R6 second cycle: %s" % message)