feat(runtime): render real Guild formation

This commit is contained in:
2026-08-13 23:19:45 +08:00
parent f44093a32f
commit bbad4e2df8
3 changed files with 445 additions and 89 deletions
@@ -0,0 +1,197 @@
extends SceneTree
const MainScene = preload("res://main.tscn")
const VIEWPORT_SIZE := Vector2i(844, 390)
var failures: Array[String] = []
var capture_root := ""
var viewport: SubViewport
func _initialize() -> void:
call_deferred("run_test")
func run_test() -> void:
capture_root = OS.get_environment("ABG_P9_1_FORMATION_CAPTURE_ROOT")
if not capture_root.is_empty():
DirAccess.make_dir_recursive_absolute(capture_root)
viewport = SubViewport.new()
viewport.size = VIEWPORT_SIZE
viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
root.add_child(viewport)
var game := MainScene.instantiate()
game.auto_boot = false
game.audio_enabled = false
viewport.add_child(game)
await settle(10)
game.locale = "zh_CN"
game.muted = true
game.boot_verified = true
game.page = game.AppPage.MAIN_MENU
game.render()
start_pair(game, "Farstring", "Kindhand")
resolve_battle(game, 6)
expect(game.page == game.AppPage.VICTORY, "exceptional pair wins battle one")
game.open_adaptation()
game.choose_adaptation("EQ-002")
game.begin_second_battle()
resolve_battle(game, 6)
expect(game.page == game.AppPage.SECOND_RESULT, "exceptional pair reaches the Guild handoff")
game.open_guild_return()
expect(game.facade.session_service.mapped_guild_recruit() == "Hearthguard", "Guild domain maps Hearthguard for Farstring and Kindhand")
var recruit_button := find_control(game, "GuildChoice_recruit_rear") as Button
expect(recruit_button != null and recruit_button.text.contains(game.candidate_name("Hearthguard")), "unselected Guild option names Hearthguard")
expect(recruit_button != null and recruit_button.text.contains("先行位"), "unselected Guild option previews Hearthguard as lead")
game.choose_guild_option("recruit_rear")
expect(game.facade.guild_preview_order() == ["Hearthguard", "Farstring", "Kindhand"], "selected Guild preview uses post-recruit order")
expect(game.facade.guild_choice_guidance().contains("艾连") and game.facade.guild_choice_guidance().contains("艾连 → 妮娅 → 赛布尔"), "Guild guidance names recruit and complete order")
expect(member_meta(game, "GuildFront") == "Hearthguard", "Guild preview puts the recruit at lead")
expect(member_meta(game, "GuildRear") == "Farstring", "Guild preview moves Farstring to second")
expect(member_meta(game, "GuildRecruitPreview") == "Kindhand", "Guild preview keeps Kindhand after Farstring")
await capture_state("01-guild-reformation")
game.begin_third_battle()
expect(game.facade.session_state.field_order == ["Hearthguard", "Farstring", "Kindhand"], "Guild commit produces the previewed order")
expect(game.added_rear == "Hearthguard", "stable origin records Hearthguard as the Guild recruit")
expect(member_meta(game, "FrontActor") == "Hearthguard", "battle three lead actor is Hearthguard")
expect(member_meta(game, "RearActor") == "Farstring", "battle three second actor is Farstring")
expect(member_meta(game, "AddedRearActor") == "Kindhand", "battle three third actor is Kindhand")
expect(control_text(game, "FrontHealth").contains("艾连"), "lead health bar names Hearthguard")
game.advance_battle_phase()
await capture_state("02-battle-three")
game.advance_battle_phase()
game.commit_phase_impact(2)
expect(game.active_battle_feedback.get("target", "") == "front", "Hearthguard damage feedback lands on the current lead")
resolve_remaining_battle(game, 2, 7)
expect(game.page == game.AppPage.SESSION_RESULT, "re-formed line reaches battle-three result")
game.open_route_board()
expect(member_meta(game, "RouteFront") == "Hearthguard", "route board keeps Hearthguard at lead")
expect(member_meta(game, "RouteRear") == "Farstring", "route board keeps Farstring second")
expect(member_meta(game, "RouteAddedRear") == "Kindhand", "route board keeps Kindhand third")
await capture_state("03-route-board")
game.choose_route("R6-03")
game.open_route_review()
game.commit_route()
game.advance_battle_phase()
game.advance_battle_phase()
game.commit_phase_impact(2)
expect(game.facade.session_state.marked_member_id() == "Kindhand", "route pressure retains Kindhand's stable identity")
expect(game.active_battle_feedback.get("target", "") == "added", "Kindhand feedback lands on her current third position")
resolve_remaining_battle(game, 2, 7)
expect(game.page == game.AppPage.SECOND_CYCLE_RESULT, "re-formed line reaches battle-four result")
game.open_training()
expect(training_member(game, "Training_Front") == "Hearthguard", "lead training control maps Hearthguard")
expect(training_member(game, "Training_Rear") == "Farstring", "second training control maps Farstring")
expect(training_member(game, "Training_Added") == "Kindhand", "third training control maps Kindhand")
expect(member_meta(game, "TrainingActor_front") == "Hearthguard", "lead training actor matches its control")
expect(member_meta(game, "TrainingActor_rear") == "Farstring", "second training actor matches its control")
expect(member_meta(game, "TrainingActor_added") == "Kindhand", "third training actor matches its control")
await capture_state("04-training")
game.choose_training("front")
game.open_training_review()
game.commit_training()
resolve_battle(game, 7)
expect(game.page == game.AppPage.THIRD_CYCLE_RESULT, "re-formed line reaches the final result")
expect(member_meta(game, "GrowthResultFront") == "Hearthguard", "final result keeps Hearthguard at lead")
expect(member_meta(game, "GrowthResultRear") == "Farstring", "final result keeps Farstring second")
expect(member_meta(game, "GrowthResultAddedRear") == "Kindhand", "final result keeps Kindhand third")
await capture_state("05-final-result")
game.clear_experience()
game.page = game.AppPage.MAIN_MENU
game.render()
start_pair(game, "Hearthguard", "Farstring")
expect(game.facade.visible_member_ids() == ["Hearthguard", "Farstring"], "standard pair remains a two-person line")
expect(member_meta(game, "FrontActor") == "Hearthguard" and member_meta(game, "RearActor") == "Farstring", "standard pair actor mapping is unchanged")
expect(find_control(game, "AddedRearActor") == null, "standard pair has no third actor")
game.queue_free()
viewport.queue_free()
await settle(90)
if failures.is_empty():
print("ABG_P9_1_REAL_FORMATION_UI_OK preview=HG,FS,KH battle=HG,FS,KH feedback=lead,third training=HG,FS,KH standard=HG,FS")
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func start_pair(game: Control, first: String, second: String) -> void:
game.start_new_game()
game.open_recruit_selection()
game.toggle_recruit(first)
game.toggle_recruit(second)
game.review_automatic_formation()
game.commit_formation()
func resolve_battle(game: Control, phase_count: int) -> void:
for phase in range(phase_count):
game.advance_battle_phase()
func resolve_remaining_battle(game: Control, current_phase: int, phase_count: int) -> void:
for phase in range(current_phase, phase_count):
game.advance_battle_phase()
func training_member(game: Control, node_name: String) -> String:
var control := find_control(game, node_name)
return String(control.get_meta("member_id", "")) if control != null else ""
func capture_state(state_name: String) -> void:
if capture_root.is_empty():
return
await settle(7)
await RenderingServer.frame_post_draw
var image := viewport.get_texture().get_image()
var error := image.save_png(capture_root.path_join(state_name + ".png"))
if error != OK:
failures.append("P9.1 real formation UI: capture %s failed with %s" % [state_name, error])
func member_meta(game: Control, node_name: String) -> String:
var control := find_control(game, node_name)
return String(control.get_meta("member_id", "")) if control != null else ""
func control_text(game: Control, node_name: String) -> String:
var control := find_control(game, node_name)
return collect_text(control) if control != null else ""
func collect_text(node: Node) -> String:
var parts: Array[String] = []
if node is Label or node is Button:
parts.append(node.text)
for child in node.get_children():
parts.append(collect_text(child))
return "\n".join(parts)
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.1 real formation UI: %s" % message)