Replace Master B placeholder marks with compact Chinese and English runtime labels, reposition kiosk and member text, and cover the updated visual anchors in captures.
221 lines
11 KiB
GDScript
221 lines
11 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 SEED := "13572468ace0bdf924681357fdb0eca1"
|
|
const CONTRACT_OPERATION := "fedcba9876543210fedcba9876543210"
|
|
const FIXTURES := [
|
|
{"width": 760, "height": 360, "locale": "zh_CN", "scale": 1.0},
|
|
{"width": 844, "height": 390, "locale": "en", "scale": 1.0},
|
|
{"width": 844, "height": 390, "locale": "zh_CN", "scale": 1.3},
|
|
{"width": 1280, "height": 720, "locale": "en", "scale": 1.3},
|
|
]
|
|
const STATES := ["guild", "contract", "market", "recruit_review", "market_one_hire", "market_complete"]
|
|
|
|
var failures: Array[String] = []
|
|
var output_root := ""
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("run_test")
|
|
|
|
|
|
func run_test() -> void:
|
|
output_root = OS.get_environment("ABG_EVIDENCE_DIR")
|
|
if output_root.is_empty():
|
|
output_root = "/tmp/abg-dev3-guild-market-captures"
|
|
DirAccess.make_dir_recursive_absolute(output_root)
|
|
var fixture_indexes := range(FIXTURES.size())
|
|
var requested_fixture := OS.get_environment("ABG_CAPTURE_FIXTURE_INDEX")
|
|
if not requested_fixture.is_empty():
|
|
var fixture_index := requested_fixture.to_int()
|
|
if fixture_index < 0 or fixture_index >= FIXTURES.size() or str(fixture_index) != requested_fixture:
|
|
push_error("invalid ABG_CAPTURE_FIXTURE_INDEX: %s" % requested_fixture)
|
|
quit(2)
|
|
return
|
|
fixture_indexes = [fixture_index]
|
|
var count := 0
|
|
for fixture_index in fixture_indexes:
|
|
var fixture: Dictionary = FIXTURES[fixture_index]
|
|
for state in STATES:
|
|
await capture_fixture(fixture, state, fixture_index * STATES.size() + count)
|
|
count += 1
|
|
if failures.is_empty():
|
|
print("ABG_DEV3_CAPTURE_OK frames=%d states=guild,contract,market,recruit_review,market_one_hire,market_complete fixture_scope=%s matrix=760x360,844x390,1280x720:zh_CN,en:100,130 layout=inside renderer=normal" % [count, requested_fixture if not requested_fixture.is_empty() else "all"])
|
|
quit(0)
|
|
else:
|
|
for failure in failures:
|
|
push_error(failure)
|
|
quit(1)
|
|
|
|
|
|
func capture_fixture(fixture: Dictionary, state: String, ordinal: int) -> void:
|
|
var draft_path := "user://dev3_capture_%d.json" % ordinal
|
|
var company_path := "%s.company" % draft_path
|
|
_cleanup_path(draft_path)
|
|
var company := _create_company(draft_path, company_path)
|
|
var viewport := SubViewport.new()
|
|
viewport.size = Vector2i(int(fixture.width), int(fixture.height))
|
|
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)
|
|
shell.settings = SettingsStore.new().defaults()
|
|
shell.settings.setup_completed = true
|
|
shell.settings.locale = String(fixture.locale)
|
|
shell.settings.text_scale = float(fixture.scale)
|
|
if state == "guild":
|
|
shell.show_guild_handoff(company)
|
|
elif state == "contract":
|
|
shell.show_contract_choice()
|
|
else:
|
|
var contract := ContractService.new()
|
|
contract.company_path = company_path
|
|
var created: Dictionary = contract.create_contract(CONTRACT_OPERATION, "CTR-R1-01", "standard")
|
|
company = created.company
|
|
var offers: Array = company.active_contract.market_board.recruit_offers
|
|
if state in ["market_one_hire", "market_complete"]:
|
|
company = contract.hire_recruit(_hire_operation(String(offers[0].source_offer_id)), String(offers[0].source_offer_id), 14).company
|
|
if state == "market_complete":
|
|
company = contract.hire_recruit(_hire_operation(String(offers[1].source_offer_id)), String(offers[1].source_offer_id), 14).company
|
|
shell.guild_company = company.duplicate(true)
|
|
if state == "recruit_review":
|
|
shell.market_category = "recruit"
|
|
shell.market_selected_kind = "recruit"
|
|
shell.market_selected_offer_id = String(offers[0].source_offer_id)
|
|
shell.show_recruit_review(String(offers[0].source_offer_id))
|
|
else:
|
|
shell.market_category = "recruit"
|
|
shell.market_selected_kind = ""
|
|
shell.market_selected_offer_id = ""
|
|
shell.show_market(company)
|
|
await _frames(6)
|
|
verify_state(shell, fixture, state)
|
|
await RenderingServer.frame_post_draw
|
|
var image := viewport.get_texture().get_image()
|
|
expect(not image.is_empty() and image.get_width() == int(fixture.width) and image.get_height() == int(fixture.height), "%s returns a complete frame" % state)
|
|
var file_name := "%s-%dx%d-%s-%d.png" % [state, int(fixture.width), int(fixture.height), String(fixture.locale), int(round(float(fixture.scale) * 100.0))]
|
|
var error := image.save_png(output_root.path_join(file_name))
|
|
if error != OK:
|
|
failures.append("capture %s failed: %s" % [file_name, error])
|
|
viewport.queue_free()
|
|
await _frames(4)
|
|
_cleanup_path(draft_path)
|
|
|
|
|
|
func verify_state(shell: Control, fixture: Dictionary, state: String) -> void:
|
|
verify_controls_inside(shell, fixture, state)
|
|
match state:
|
|
"guild":
|
|
expect(shell.current_screen == "guild_handoff" and shell.find_child("GuildRoster", true, false).get_child_count() == 2, "Guild shows opening pair")
|
|
expect(not shell.find_child("GuildContractBoard", true, false).disabled and shell.find_child("GuildMarketUnavailable", true, false).disabled, "Guild next action is truthful")
|
|
verify_guild_master_canvas(shell, fixture)
|
|
expect(shell.find_child("GuildCoinStatus", true, false) != null and shell.find_child("GuildProgressStatus", true, false) != null, "Guild resource placeholders are replaced by engine values")
|
|
expect(shell.find_child("GuildKioskCopyArea", true, false) != null and shell.find_child("GuildMasterIdentityPlate0", true, false) != null, "Guild kiosk and member copy use anchored plates")
|
|
expect(shell.find_child("GuildTitleCommandLabel", true, false) != null and shell.find_child("GuildRouteStatusLabel", true, false) != null, "Guild bottom command placeholders are replaced")
|
|
if String(fixture.locale) == "zh_CN":
|
|
expect(shell.find_child("GuildAnchorTitle", true, false).text == "公会大厅", "Guild Chinese title is visible")
|
|
expect("成员" in shell.find_child("GuildAnchorCompanyStatus", true, false).text, "Guild Chinese member count is visible")
|
|
"contract":
|
|
expect(shell.current_screen == "contract_choice" and shell.find_child("ContractCompanyMember_0", true, false) != null, "Contract review keeps Company visible")
|
|
expect(shell.find_child("ContractDifficultySegments", true, false).get_child_count() == 4 and shell.find_child("ContractConfirm", true, false) != null, "four difficulties and explicit confirmation fit")
|
|
"market":
|
|
expect(shell.current_screen == "market" and shell.find_child("MarketOfferList", true, false).get_child_count() == 3, "Market shows three Recruit rows")
|
|
expect(shell.find_child("MarketNoSelection", true, false) != null and shell.find_child("MarketCompanyStatus", true, false).text.contains("2"), "Market starts unselected at Company two")
|
|
"recruit_review":
|
|
expect(shell.current_screen == "recruit_review" and shell.find_child("RecruitReviewPortraitTexture", true, false) != null, "Recruit review shows full figure")
|
|
expect(shell.find_child("RecruitReviewScroll", true, false) != null and shell.find_child("RecruitHire", true, false) != null, "review details and individual hire fit")
|
|
"market_one_hire":
|
|
expect(shell.find_child("MarketCompanyStatus", true, false).text.contains("3") and shell.find_child("MarketCoinStatus", true, false).text.contains("38"), "first receipt state is visible")
|
|
"market_complete":
|
|
expect(shell.find_child("MarketCompanyStatus", true, false).text.contains("4") and shell.find_child("MarketCoinStatus", true, false).text.contains("24"), "two-receipt stop is visible")
|
|
|
|
|
|
func verify_guild_master_canvas(shell: Control, fixture: Dictionary) -> void:
|
|
var viewport_size := Vector2(float(fixture.width), float(fixture.height))
|
|
var viewport_rect := Rect2(Vector2.ZERO, viewport_size)
|
|
var world := shell.find_child("GuildMasterWorldExtension", true, false) as TextureRect
|
|
var canvas := shell.find_child("GuildMasterDesignCanvas", true, false) as Control
|
|
var composite := shell.find_child("GuildMasterBComposite", true, false) as TextureRect
|
|
expect(world != null and canvas != null and composite != null, "Guild responsive composition layers exist")
|
|
if world == null or canvas == null or composite == null:
|
|
return
|
|
var canvas_rect := canvas.get_global_rect()
|
|
var expected_aspect := 16.0 / 9.0
|
|
expect(is_equal_approx(canvas_rect.size.x / canvas_rect.size.y, expected_aspect), "Guild master canvas preserves 16:9 without distortion")
|
|
expect(viewport_rect.encloses(canvas_rect), "Guild master canvas remains complete inside the device frame")
|
|
expect(canvas_rect.get_center().is_equal_approx(viewport_rect.get_center()), "Guild master canvas remains centered")
|
|
expect(world.get_global_rect().is_equal_approx(viewport_rect), "Guild World extension fills the device frame")
|
|
expect(composite.get_global_rect().is_equal_approx(canvas_rect), "Guild composite and engine overlays share one design canvas")
|
|
var viewport_aspect := viewport_size.x / viewport_size.y
|
|
if viewport_aspect < expected_aspect:
|
|
expect(is_equal_approx(canvas_rect.size.x, viewport_size.x) and canvas_rect.position.y > 0.0, "narrow landscape adds World space above and below")
|
|
elif is_equal_approx(viewport_aspect, expected_aspect):
|
|
expect(canvas_rect.is_equal_approx(viewport_rect), "16:9 landscape uses the complete device frame")
|
|
else:
|
|
expect(is_equal_approx(canvas_rect.size.y, viewport_size.y) and canvas_rect.position.x > 0.0, "wide landscape adds World space at the sides")
|
|
|
|
|
|
func verify_controls_inside(shell: Control, fixture: Dictionary, state: String) -> void:
|
|
var bounds := Rect2(Vector2.ZERO, Vector2(float(fixture.width), float(fixture.height)))
|
|
for node in _all_controls(shell):
|
|
if not node.visible or node is TextureRect or node is ColorRect or node is Container:
|
|
continue
|
|
var rect := node.get_global_rect()
|
|
if rect.size.x <= 0.0 or rect.size.y <= 0.0:
|
|
continue
|
|
if not bounds.encloses(rect):
|
|
failures.append("%s %dx%d %s outside bounds: %s" % [state, int(fixture.width), int(fixture.height), node.name, rect])
|
|
|
|
|
|
func _all_controls(root_control: Control) -> Array[Control]:
|
|
var result: Array[Control] = []
|
|
var pending: Array[Node] = [root_control]
|
|
while not pending.is_empty():
|
|
var node: Node = pending.pop_back()
|
|
if node is Control:
|
|
result.append(node)
|
|
for child in node.get_children():
|
|
pending.append(child)
|
|
return result
|
|
|
|
|
|
func _create_company(draft_path: String, company_path: String) -> Dictionary:
|
|
var service := DraftService.new()
|
|
service.path = draft_path
|
|
service.company_path = company_path
|
|
var draft: Dictionary = service.begin_or_load(SEED)
|
|
service.set_selection([String(draft.draft.offers[0].offer_id), String(draft.draft.offers[2].offer_id)])
|
|
return service.commit_company().company
|
|
|
|
|
|
func _hire_operation(offer_id: String) -> String:
|
|
return ("capture-hire|%s" % offer_id).sha256_text().substr(0, 32)
|
|
|
|
|
|
func _cleanup_path(base: String) -> void:
|
|
for suffix in ["", ".bak", ".candidate", ".bak.candidate", ".corrupt"]:
|
|
var file_path := "%s%s" % [base, suffix]
|
|
if FileAccess.file_exists(file_path):
|
|
DirAccess.remove_absolute(file_path)
|
|
var company_file := "%s.company%s" % [base, suffix]
|
|
if FileAccess.file_exists(company_file):
|
|
DirAccess.remove_absolute(company_file)
|
|
|
|
|
|
func _frames(count: int) -> void:
|
|
for frame in range(count):
|
|
await process_frame
|
|
|
|
|
|
func expect(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
failures.append("DEV-3 capture: %s" % message)
|