extends SceneTree const Controller = preload("res://p9_7/runtime_controller.gd") const Content = preload("res://content/p9_7_content.gd") const LegacyService = preload("res://p9_3/core_loop_service.gd") const LegacyState = preload("res://p9_3/core_loop_state.gd") const MarketService = preload("res://p9_7/market_service.gd") const MarketState = preload("res://p9_7/market_state.gd") const BattleService = preload("res://p9_7/battle_service.gd") const FIXTURES := [ {"name": "market-small-zh", "size": Vector2i(760, 360), "state": "market", "locale": "zh_CN", "scale": 1.0, "reduced": false, "low_power": false}, {"name": "market-reference-en-130", "size": Vector2i(844, 390), "state": "market", "locale": "en", "scale": 1.3, "reduced": true, "low_power": false}, {"name": "market-reference-zh-130", "size": Vector2i(844, 390), "state": "market", "locale": "zh_CN", "scale": 1.3, "reduced": true, "low_power": false}, {"name": "market-large-low-power", "size": Vector2i(1280, 720), "state": "market", "locale": "zh_CN", "scale": 1.0, "reduced": false, "low_power": true}, {"name": "recovery-zh-130", "size": Vector2i(844, 390), "state": "recovery", "locale": "zh_CN", "scale": 1.3, "reduced": true, "low_power": true}, {"name": "relay-en-130", "size": Vector2i(844, 390), "state": "relay", "locale": "en", "scale": 1.3, "reduced": true, "low_power": true}, {"name": "portrait-rotate", "size": Vector2i(390, 844), "state": "market", "locale": "zh_CN", "scale": 1.0, "reduced": true, "low_power": true}, ] var failures: Array[String] = [] var capture_root := "" var capture_count := 0 func _initialize() -> void: call_deferred("run_test") func run_test() -> void: capture_root = OS.get_environment("ABG_P9_7_LAYOUT_CAPTURE_ROOT") var legacy := complete_p96() var snapshots := { "market": legacy, "recovery": recovery_snapshot(legacy), "relay": relay_snapshot(legacy), } for fixture in FIXTURES: await verify_fixture(fixture, snapshots[String(fixture.state)]) if failures.is_empty(): print("ABG_P9_7_RUNTIME_CONTROLLER_COMPILE_OK viewports=760x360,844x390,1280x720,390x844 landscape=world portrait=rotate") print("ABG_P9_7_RUNTIME_LAYOUT_OK fixtures=7 states=market,recovery,relay viewports=760x360,844x390,1280x720,390x844 locales=zh_CN,en text_130=true reduced_motion=true muted=true low_power=true controls=48 cards=0 captures=%d" % capture_count) quit(0) else: for failure in failures: push_error(failure) quit(1) func verify_fixture(fixture: Dictionary, snapshot: Dictionary) -> void: var viewport_size: Vector2i = fixture.size root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND root.content_scale_size = viewport_size root.size = viewport_size var experience = Controller.new() experience.persistence_enabled = false experience.locale = String(fixture.locale) experience.text_scale = float(fixture.scale) experience.reduced_motion = bool(fixture.reduced) experience.muted = true experience.low_power = bool(fixture.low_power) experience.size = Vector2(viewport_size) experience.configure_entry(snapshot) root.add_child(experience) await settle_frames() var fixture_name := String(fixture.name) if viewport_size.y > viewport_size.x: expect(experience.find_child("P97RotateNotice", true, false) != null, "%s renders rotate notice" % fixture_name) expect(experience.find_child("P97MarketHud", true, false) == null, "%s does not squeeze the Market into portrait" % fixture_name) else: expect(experience.find_child("P97WorldBackground", true, false) != null, "%s renders a full world background" % fixture_name) expect(float(experience.get_meta("visible_world_ratio", 0.0)) >= 0.82, "%s preserves the world-first presentation ratio" % fixture_name) expect(count_named_surfaces(experience) == 0, "%s has no card, panel or modal surfaces" % fixture_name) verify_controls(experience, viewport_size, fixture_name) verify_state(experience, String(fixture.state), fixture_name) await capture(fixture_name) experience.queue_free() await settle_frames() func verify_controls(experience, viewport_size: Vector2i, fixture_name: String) -> void: for control_node in experience.find_children("*", "Control", true, false): var control := control_node as Control if not control.is_visible_in_tree(): continue var rect := control.get_global_rect() expect(rect.position.x >= -0.5 and rect.position.y >= -0.5 and rect.end.x <= float(viewport_size.x) + 0.5 and rect.end.y <= float(viewport_size.y) + 0.5, "%s control %s stays inside viewport: %s" % [fixture_name, control.name, rect]) if control is Button: expect(control.size.x >= 48.0 and control.size.y >= 48.0, "%s button %s remains at least 48 logical pixels" % [fixture_name, control.name]) var ribbon := experience.find_child("P97ContextRibbon", true, false) as Control if ribbon != null: for actor_node in experience.find_children("P97CompanyActor_*", "TextureRect", true, false): var actor := actor_node as TextureRect expect(actor.get_global_rect().end.y <= ribbon.get_global_rect().position.y - 3.0, "%s Company actor %s stays above context ribbon" % [fixture_name, actor.name]) func verify_state(experience, state_name: String, fixture_name: String) -> void: if state_name == "recovery": expect(experience.market.state.recovery_required, "%s restores the defeated Company state" % fixture_name) expect(experience.find_child("P97RecoveryButton", true, false) != null, "%s exposes one recovery command" % fixture_name) elif state_name == "relay": expect(experience.market.state.continuation_locked and experience.market.state.relay_restored, "%s restores the Relay threshold state" % fixture_name) expect(experience.find_child("P97RelayTitle", true, false) != null, "%s renders the Relay result" % fixture_name) expect(experience.find_children("P97RouteTarget_*", "Button", true, false).is_empty(), "%s invents no Market 8 routes" % fixture_name) else: expect(experience.find_child("P97MarketHud", true, false) != null, "%s renders the Market HUD" % fixture_name) expect(experience.find_children("P97RecruitTarget_*", "Button", true, false).size() == 3, "%s shows three standing Recruit offers" % fixture_name) expect(experience.find_children("P97EquipmentTarget_*", "Button", true, false).size() == 4, "%s shows four equipment pedestals" % fixture_name) expect(experience.find_children("P97RouteTarget_*", "Button", true, false).size() == 3, "%s shows three route gates" % fixture_name) if fixture_name == "market-reference-en-130": expect(experience.locale == "en" and is_equal_approx(experience.text_scale, 1.3), "%s applies English and 130%% text" % fixture_name) if fixture_name == "market-large-low-power": var actor := experience.find_child("P97CompanyActor_Hearthguard", true, false) as TextureRect expect(actor != null and actor.size.y >= 120.0, "%s keeps the Company readable in the larger world" % fixture_name) func recovery_snapshot(legacy: Dictionary) -> Dictionary: var market = force_market(MarketService.new(legacy), 6) market.focus_recruit_offer("P97-M6-R3") market.hire("layout-recovery-hire", "P97-M6-R3") market.commit_deployment("layout-recovery-deploy", market.auto_order_candidate()) market.focus_route("P97-R6-Q2") market.commit_route("layout-recovery-route", "P97-R6-Q2") var battle = run_battle(market) expect(battle.state.outcome == "defeat", "recovery fixture reaches deterministic defeat") expect(market.settle_battle("layout-recovery-settle", battle.state.snapshot()).ok, "recovery fixture settles once") return market.state.snapshot() func relay_snapshot(legacy: Dictionary) -> Dictionary: var market = MarketService.new(legacy) for step in [ {"market": 5, "recruit": "P97-M5-R2", "route": "P97-R5-Q0"}, {"market": 6, "recruit": "P97-M6-R2", "route": "P97-R6-Q0"}, {"market": 7, "recruit": "P97-M7-R3", "route": "P97-R7-Q0"}, ]: var market_index := int(step.market) market.focus_recruit_offer(String(step.recruit)) market.hire("layout-relay-hire-%d" % market_index, String(step.recruit)) market.commit_deployment("layout-relay-deploy-%d" % market_index, market.auto_order_candidate()) market.focus_route(String(step.route)) market.commit_route("layout-relay-route-%d" % market_index, String(step.route)) var battle = run_battle(market) expect(battle.state.outcome == "victory", "Relay fixture Round %d wins" % market_index) expect(market.settle_battle("layout-relay-settle-%d" % market_index, battle.state.snapshot()).ok, "Relay fixture Round %d settles" % market_index) if market.state.recovery_required: expect(market.recover_injured_company("layout-relay-recover-%d" % market_index).ok, "Relay fixture Round %d recovers" % market_index) return market.state.snapshot() func force_market(market, market_index: int): var raw: Dictionary = market.state.snapshot() raw.cleared_rounds = market_index - 1 raw.line_capacity = MarketState.capacity_for_clears(raw.cleared_rounds) raw.current_market = market_index raw.market_board = Content.snapshot().boards[market_index].duplicate(true) raw.available_recruit_offer_ids = raw.market_board.recruit_offer_ids.duplicate() raw.available_equipment_offer_ids = raw.market_board.equipment_offer_ids.duplicate() raw.available_battle_offer_ids = raw.market_board.battle_offer_ids.duplicate() expect(market.state.restore_snapshot(raw), "fixture restores Market %d" % market_index) return market func run_battle(market): var battle = BattleService.new() expect(battle.start_from_market(market.state).ok, "%s battle starts" % market.state.selected_route) var guard := 0 while not battle.state.terminal and guard < 2000: battle.advance_ticks(1) guard += 1 expect(guard < 2000, "%s battle terminates" % market.state.selected_route) return battle func complete_p96() -> Dictionary: var service = LegacyService.new() service.enter_hall() service.focus_offer("Farstring") service.hire("Farstring") complete_battle(service, "open_road") service.focus_offer("Kindhand") service.hire("Kindhand") complete_battle(service, "crosswind") service.focus_equipment("EQ-002") service.equip_focused("Hearthguard") complete_battle(service, "bell_breach") service.choose_rift_tide_plan("press_on") complete_battle(service, "rift_tide") return service.state.snapshot() func complete_battle(service, encounter_id: String) -> void: service.select_encounter(encounter_id) service.start_battle() var guard := 0 while service.state.phase == LegacyState.Phase.BATTLE and guard < 4000: service.advance_battle_ticks(1) guard += 1 service.claim_and_return() func capture(name: String) -> void: if capture_root.is_empty(): return DirAccess.make_dir_recursive_absolute(capture_root) await settle_frames() await RenderingServer.frame_post_draw var error := root.get_texture().get_image().save_png(capture_root.path_join(name + ".png")) expect(error == OK, "%s capture writes" % name) if error == OK: capture_count += 1 await process_frame func settle_frames() -> void: await process_frame await process_frame func count_named_surfaces(root_node: Node) -> int: var count := root_node.find_children("*", "Panel", true, false).size() for node in root_node.find_children("*", "Control", true, false): var lowered := String(node.name).to_lower() if "card" in lowered or "modal" in lowered or "dialog" in lowered: count += 1 return count func expect(condition: bool, message: String) -> void: if not condition: failures.append("P9.7 runtime layout: %s" % message)