233 lines
11 KiB
GDScript
233 lines
11 KiB
GDScript
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)
|