281 lines
12 KiB
GDScript
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)
|