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") # Single authoritative size (owner policy): two shipped locales, both scales. const FIXTURES := [ {"width": 1280, "height": 720, "locale": "zh_CN", "scale": 1.0}, {"width": 1280, "height": 720, "locale": "en", "scale": 1.3}, ] const STATES := ["market_ready", "equipment_review", "equipment_equipped", "roster", "recruit_detail", "contract_return_review"] const SEED := "1234567890abcdeffedcba0987654321" const CONTRACT_OP := "abcdefabcdefabcdefabcdefabcdefab" const HIRE_OP_1 := "11112222333344445555666677778888" const HIRE_OP_2 := "88887777666655554444333322221111" var output_root := "" var failures: Array[String] = [] var fixture_index := 0 var viewport_size := Vector2i(844, 390) var locale := "en" var text_scale := 1.0 func _initialize() -> void: call_deferred("run_capture") func run_capture() -> void: var requested := OS.get_environment("ABG_CAPTURE_FIXTURE_INDEX") fixture_index = requested.to_int() if not requested.is_empty() else -1 var indexes := range(FIXTURES.size()) if fixture_index < 0 else [fixture_index] output_root = OS.get_environment("ABG_EVIDENCE_DIR") if output_root.is_empty(): output_root = "/tmp/abg-dev6-candidate" output_root = output_root.path_join("captures") DirAccess.make_dir_recursive_absolute(output_root) for index in indexes: if index < 0 or index >= FIXTURES.size(): failures.append("unknown fixture index %d" % index) continue var fixture: Dictionary = FIXTURES[index] viewport_size = Vector2i(int(fixture.width), int(fixture.height)) locale = String(fixture.locale) text_scale = float(fixture.scale) for state in STATES: await _capture_state(index, String(state), fixture) if failures.is_empty(): print("ABG_DEV6_CAPTURE_OK frames=%d states=market_ready,equipment_review,equipment_equipped,roster,recruit_detail,contract_return_review matrix=1280x720:zh_CN,en:100,130 layout=inside renderer=normal" % [indexes.size() * STATES.size()]) quit(0) else: for failure in failures: push_error(failure) quit(1) func _capture_state(index: int, state: String, fixture: Dictionary) -> void: var draft_path := "user://dev6_capture_%d_%s.json" % [index, state] var company_path := "%s.company" % draft_path _cleanup_path(draft_path) var company := _create_company(draft_path, company_path) if company.is_empty(): failures.append("%s fixture could not create Company" % state) return var viewport := SubViewport.new() viewport.size = viewport_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 = locale settings.text_scale = text_scale shell.settings = settings shell.setup_draft = settings.duplicate(true) var equipment_offer := _cheapest_affordable_equipment(company) var shown_company := company match state: "market_ready": shell.show_market(company) "equipment_review": shell.market_category = "equipment" shell.market_selected_kind = "equipment" shell.market_selected_offer_id = String(equipment_offer.offer_id) shell.show_equipment_review(String(equipment_offer.offer_id)) "equipment_equipped": var service := _contract_service(company_path) var operation_id := ("%s|capture-equip|%s|%s" % [String(company.company_id), String(equipment_offer.offer_id), String(company.owned_recruits[0].recruit_id)]).sha256_text().substr(0, 32) var purchased: Dictionary = service.purchase_equipment(operation_id, String(equipment_offer.offer_id), String(company.owned_recruits[0].recruit_id), int(equipment_offer.list_price)) if not purchased.ok: failures.append("equipment capture purchase failed: %s" % String(purchased.get("code", "unknown"))) else: shown_company = purchased.company shell.show_market(shown_company) "roster": shell.show_roster(company) "recruit_detail": shell.show_recruit_detail(String(company.owned_recruits[0].recruit_id)) "contract_return_review": shell.show_market(company) await _frames(2) shell.open_contract_return_review() await _frames(6) _verify_buttons(shell, state) await RenderingServer.frame_post_draw var image := viewport.get_texture().get_image() if image.is_empty() or image.get_width() != viewport_size.x or image.get_height() != viewport_size.y: failures.append("%s returns a complete %dx%d frame" % [state, viewport_size.x, viewport_size.y]) else: var path := output_root.path_join("fixture%d-%s.png" % [index, state]) if image.save_png(path) != OK: failures.append("capture failed: %s" % path) shell.queue_free() viewport.queue_free() await _frames(5) _cleanup_path(draft_path) func _verify_buttons(node: Node, state: String) -> void: for child in node.get_children(): if child is BaseButton and child.is_visible_in_tree(): var rect: Rect2 = child.get_global_rect() if rect.position.x < -0.5 or rect.position.y < -0.5 or rect.end.x > viewport_size.x + 0.5 or rect.end.y > viewport_size.y + 0.5: failures.append("%s button %s outside viewport: %s" % [state, child.name, rect]) if rect.size.x < 44.0 or rect.size.y < 44.0: failures.append("%s button %s below 44px: %s" % [state, child.name, rect.size]) _verify_buttons(child, state) func _create_company(draft_path: String, company_path: 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)]) var committed: Dictionary = draft_service.commit_company() if not committed.ok: return {} var contract := _contract_service(company_path) var created: Dictionary = contract.create_contract(CONTRACT_OP, "CTR-R1-01", "standard") if not created.ok: return {} var offers: Array = created.company.active_contract.market_board.recruit_offers contract.hire_recruit(HIRE_OP_1, String(offers[0].source_offer_id), 14) var hired: Dictionary = contract.hire_recruit(HIRE_OP_2, String(offers[1].source_offer_id), 14) return hired.company if hired.ok else {} func _cheapest_affordable_equipment(company: Dictionary) -> Dictionary: var chosen: Dictionary = {} for offer in company.active_contract.market_board.equipment_offers: if int(offer.list_price) <= int(company.active_contract.coin) and (chosen.is_empty() or int(offer.list_price) < int(chosen.list_price)): chosen = Dictionary(offer).duplicate(true) return chosen func _contract_service(path: String) -> RefCounted: var service := ContractService.new() service.company_path = path return service func _cleanup_path(path: String) -> void: for candidate in [path, "%s.bak" % path, "%s.candidate" % path, "%s.bak.candidate" % path, "%s.company" % path, "%s.company.bak" % path, "%s.company.candidate" % path, "%s.company.bak.candidate" % path, "%s.corrupt" % path]: if FileAccess.file_exists(candidate): DirAccess.remove_absolute(candidate) func _frames(count: int) -> void: for _index in range(count): await process_frame