Files
aetherbound-guild/runtime/tests/dev5/m12_capture_test.gd
T

281 lines
12 KiB
GDScript

extends SceneTree
const EntryShell = preload("res://entry/entry_shell.gd")
const SettingsStore = preload("res://entry/settings_store.gd")
const DraftService = preload("res://entry/initial_company_draft_service.gd")
const ContractService = preload("res://entry/guild_contract_service.gd")
const FormationService = preload("res://entry/formation_risk_service.gd")
const BattleService = preload("res://entry/battle_service.gd")
const FIXTURES := [
{"size": Vector2i(760, 360), "locale": "zh_CN"},
{"size": Vector2i(844, 390), "locale": "en"},
{"size": Vector2i(1280, 720), "locale": "zh_CN"},
{"size": Vector2i(1280, 720), "locale": "en"},
]
const STATES := ["battle_opening", "battle_live", "result", "knockout", "reward", "recovery", "next_market", "restart_market"]
var failures: Array[String] = []
var capture_count := 0
var output_root := ""
func _initialize() -> void:
call_deferred("run_capture")
func run_capture() -> void:
output_root = OS.get_environment("ABG_EVIDENCE_DIR")
if output_root.is_empty():
output_root = "/tmp/abg-m12-capture"
output_root = output_root.path_join("captures")
DirAccess.make_dir_recursive_absolute(output_root)
for index in range(FIXTURES.size()):
await _capture_fixture(index, FIXTURES[index])
if failures.is_empty() and capture_count == FIXTURES.size() * STATES.size():
print("ABG_M12_CAPTURE_OK fixtures=4 captures=%d states=battle_opening,battle_live,result,knockout,reward,recovery,next_market,restart_market scale=100 restart=market strict=0" % capture_count)
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func _capture_fixture(index: int, fixture: Dictionary) -> void:
var draft_path := "user://m12_capture_%d_draft.json" % index
var company_path := "user://m12_capture_%d_company.json" % index
_cleanup_path(draft_path, company_path)
var prepared := _create_prepared_company(draft_path, company_path, "97531eca86420bdf97531eca86420bd%d" % index)
if prepared.is_empty():
failures.append("fixture %d did not prepare a Company" % index)
return
var option_id := String(prepared.active_contract.formation.risk_options[1].risk_option_id)
var service := BattleService.new()
service.company_path = company_path
var started: Dictionary = service.begin_battle(("2468ace013579bdf2468ace013579b%d" % index).sha256_text().substr(0, 32), option_id)
if not started.ok:
failures.append("fixture %d did not start Battle" % index)
_cleanup_path(draft_path, company_path)
return
var viewport := SubViewport.new()
viewport.name = "M12CaptureViewport%d" % index
viewport.size = fixture.size
viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
root.add_child(viewport)
var shell := EntryShell.new()
shell.auto_start = false
shell.initial_company_service.path = draft_path
shell.initial_company_service.company_path = company_path
viewport.add_child(shell)
await _frames(3)
var settings := SettingsStore.new().defaults()
settings.setup_completed = true
settings.locale = String(fixture.locale)
settings.text_scale = 1.0
shell.settings = settings
shell.setup_draft = settings.duplicate(true)
shell.show_battle_opening(started.company, false)
await _frames(5)
await _capture_state(viewport, shell, index, String(fixture.locale), "battle_opening")
var start := shell.find_child("BattleStartObservation", true, false) as Button
if start == null:
failures.append("fixture %d missing BattleStartObservation" % index)
else:
# The route marker already verifies parsed touch on the opening page. For
# capture, hand the same persisted receipt to BAT-002 directly so the
# SubViewport fixture does not depend on a window-level pointer bridge.
var battle: Dictionary = started.company.get("active_contract", {}).get("battle_state", {})
var observed: Dictionary = service.begin_observation(String(battle.get("operation_id", "")), String(battle.get("snapshot_hash", "")))
if not observed.ok:
failures.append("fixture %d could not begin observation" % index)
else:
shell.show_battle(observed.company, false)
shell.battle_paused = false
shell._schedule_battle_playback(shell.battle_playback_generation)
await _frames(4)
await _capture_state(viewport, shell, index, String(fixture.locale), "battle_live")
var guard := 0
while shell.battle_live_state != "outcome_locked" and guard < 60:
if shell.battle_paused:
shell.battle_paused = false
shell.advance_battle_phase()
await _frames(1)
guard += 1
if shell.battle_live_state != "outcome_locked":
failures.append("fixture %d did not reach outcome lock" % index)
else:
var next := shell.find_child("BattleLiveNextBoundary", true, false) as Button
if next == null:
failures.append("fixture %d missing OUT-001 boundary" % index)
else:
shell._battle_live_commit_outcome()
await _frames(4)
await _capture_state(viewport, shell, index, String(fixture.locale), "result")
var continue_button := shell.find_child("BattleResultContinue", true, false) as Button
if continue_button == null:
failures.append("fixture %d missing Result Continue" % index)
else:
shell.continue_from_battle_result()
await _frames(4)
if shell.current_screen == "battle_knockout_review":
await _capture_state(viewport, shell, index, String(fixture.locale), "knockout")
var acknowledge := shell.find_child("BattleKnockoutAcknowledge", true, false) as Button
if acknowledge == null:
failures.append("fixture %d missing Knockout acknowledgement" % index)
else:
shell.acknowledge_battle_knockout(shell.guild_company)
await _frames(4)
if shell.current_screen != "battle_reward_choice":
failures.append("fixture %d did not reach Reward Choice" % index)
else:
await _capture_state(viewport, shell, index, String(fixture.locale), "reward")
var reward_button := _first_reward_button(shell)
if reward_button == null:
failures.append("fixture %d has no reward option" % index)
else:
var reward_id := String(reward_button.name).trim_prefix("BattleRewardOption_")
shell.select_battle_reward(reward_id)
await _frames(2)
var claim := shell.find_child("BattleRewardClaim", true, false) as Button
if claim == null:
failures.append("fixture %d missing Reward Claim" % index)
else:
shell.commit_battle_reward()
await _frames(4)
if shell.current_screen != "battle_recovery":
failures.append("fixture %d did not reach Recovery" % index)
else:
await _capture_state(viewport, shell, index, String(fixture.locale), "recovery")
var commit := shell.find_child("BattleRecoveryCommit", true, false) as Button
if commit == null:
failures.append("fixture %d missing Recovery Commit" % index)
else:
shell.commit_battle_recovery()
await _frames(4)
if shell.current_screen != "market":
failures.append("fixture %d did not reach next Market" % index)
else:
await _capture_state(viewport, shell, index, String(fixture.locale), "next_market")
var loaded: Dictionary = service.load_company()
if not loaded.ok:
failures.append("fixture %d cannot reload after recovery" % index)
else:
shell.queue_free()
await _frames(4)
var resumed := EntryShell.new()
resumed.auto_start = false
resumed.initial_company_service.path = draft_path
resumed.initial_company_service.company_path = company_path
viewport.add_child(resumed)
await _frames(3)
resumed.settings = settings
resumed.setup_draft = settings.duplicate(true)
resumed._open_company_destination(loaded.company)
await _frames(4)
if resumed.current_screen != "market":
failures.append("fixture %d restart did not open Market" % index)
else:
await _capture_state(viewport, resumed, index, String(fixture.locale), "restart_market")
resumed.queue_free()
viewport.queue_free()
await _frames(5)
_cleanup_path(draft_path, company_path)
func _capture_state(viewport: SubViewport, shell: Control, fixture_index: int, locale: String, state: String) -> void:
if shell.current_screen == "battle_result":
_expect_node(shell, "BattleResultObjective", state)
elif shell.current_screen == "battle_knockout_review":
_expect_node(shell, "BattleKnockoutCurrentGap", state)
elif shell.current_screen == "battle_reward_choice":
_expect_node(shell, "BattleRewardOptions", state)
elif shell.current_screen == "battle_recovery":
_expect_node(shell, "BattleRecoveryRule", state)
await RenderingServer.frame_post_draw
var image := viewport.get_texture().get_image()
if image.is_empty() or image.get_size() != viewport.size:
failures.append("%s fixture %d has invalid frame dimensions" % [state, fixture_index])
return
var path := output_root.path_join("fixture%d-%s-%s-100.png" % [fixture_index, locale, state])
if image.save_png(path) != OK:
failures.append("could not save %s" % path)
else:
capture_count += 1
func _expect_node(shell: Control, node_name: String, state: String) -> void:
if shell.find_child(node_name, true, false) == null:
failures.append("%s is missing %s" % [state, node_name])
func _first_reward_button(shell: Control) -> Button:
var options := shell.find_child("BattleRewardOptions", true, false)
if options == null:
return null
for child in options.get_children():
if child is Button and String(child.name).begins_with("BattleRewardOption_"):
return child as Button
return null
func _create_prepared_company(draft_path: String, company_path: String, seed: String) -> Dictionary:
var draft_service := DraftService.new()
draft_service.path = draft_path
draft_service.company_path = company_path
var draft: Dictionary = draft_service.begin_or_load(seed)
if not draft.ok:
return {}
draft_service.set_selection([String(draft.draft.offers[0].offer_id), String(draft.draft.offers[1].offer_id)])
if not draft_service.commit_company().ok:
return {}
var contract_service := ContractService.new()
contract_service.company_path = company_path
var created: Dictionary = contract_service.create_contract(("ctr|%s" % seed).sha256_text().substr(0, 32), "CTR-R1-01", "standard")
if not created.ok:
return {}
var offers: Array = created.company.active_contract.market_board.recruit_offers
contract_service.hire_recruit(("hire1|%s" % seed).sha256_text().substr(0, 32), String(offers[0].source_offer_id), 14)
var hired: Dictionary = contract_service.hire_recruit(("hire2|%s" % seed).sha256_text().substr(0, 32), String(offers[1].source_offer_id), 14)
if not hired.ok:
return {}
var formation_service := FormationService.new()
formation_service.company_path = company_path
var begun: Dictionary = formation_service.begin_formation()
var saved: Dictionary = formation_service.save_order(("order|%s" % seed).sha256_text().substr(0, 32), begun.formation.base_front_to_rear_ids)
var ready: Dictionary = formation_service.accept_readiness(("ready|%s" % seed).sha256_text().substr(0, 32), String(saved.receipt.order_hash))
return ready.company if ready.ok else {}
func _touch(control: Control) -> void:
if control == null:
return
var center := control.get_global_rect().get_center()
var press := InputEventScreenTouch.new()
press.position = center
press.pressed = true
press.index = 0
Input.parse_input_event(press)
await _frames(2)
var release := InputEventScreenTouch.new()
release.position = center
release.pressed = false
release.index = 0
Input.parse_input_event(release)
await _frames(5)
func _frames(count: int) -> void:
for _index in range(count):
await process_frame
func _cleanup_path(draft_path: String, company_path: String) -> void:
for base in [draft_path, company_path]:
for suffix in ["", ".bak", ".candidate", ".bak.candidate", ".corrupt"]:
var path := "%s%s" % [base, suffix]
if FileAccess.file_exists(path):
DirAccess.remove_absolute(path)