157 lines
6.8 KiB
GDScript
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)
|