222 lines
12 KiB
GDScript
222 lines
12 KiB
GDScript
extends SceneTree
|
|
|
|
const Adapter = preload("res://campaign/fg4_adapter.gd")
|
|
const Contract1Battle = preload("res://fg4/contract1_battle_service.gd")
|
|
const Controller = preload("res://campaign/fg4_runtime_controller.gd")
|
|
const Fixture = preload("res://tests/fg4/contract1_fixture.gd")
|
|
|
|
var failures: Array[String] = []
|
|
var experience
|
|
var capture_root := ""
|
|
var captures := 0
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("run_test")
|
|
|
|
|
|
func run_test() -> void:
|
|
capture_root = OS.get_environment("ABG_FG4_MATRIX_CAPTURE_ROOT")
|
|
if not capture_root.is_empty():
|
|
DirAccess.make_dir_recursive_absolute(capture_root)
|
|
await fixture("01-market1-small-zh", Fixture.new().market_1_snapshot(), Vector2i(760, 360), "zh_CN", 1.0, false, false, Controller.SCREEN_MARKET, "market")
|
|
await plan_fixture("02-plan-reference-en130", Vector2i(844, 390), "en", 1.3)
|
|
await ordinary_battle_fixture("03-ordinary-battle-large-zh", Vector2i(1280, 720), "zh_CN", 1.0)
|
|
await fixture("04-recovery-small-en130", recovery_snapshot(), Vector2i(760, 360), "en", 1.3, false, false, Controller.SCREEN_RECOVERY, "recovery")
|
|
await boss_fixture("05-boss1-reduced-low-power", Fixture.new().boss_muster_snapshot(), "BO-R3-01", "vent_and_brace", Vector2i(844, 390), "zh_CN", 1.0, true, true)
|
|
await fixture("06-record1-reference-en", first_record_snapshot(), Vector2i(844, 390), "en", 1.0, false, false, Controller.SCREEN_REWARD, "contract_record")
|
|
await fixture("07-handoff-small-zh", Fixture.new().contract2_handoff_snapshot(), Vector2i(760, 360), "zh_CN", 1.0, false, false, Controller.SCREEN_REWARD, "contract_handoff")
|
|
await fixture("08-contract2-market-large-zh130", Fixture.new().contract2_market_1_snapshot(), Vector2i(1280, 720), "zh_CN", 1.3, false, false, Controller.SCREEN_MARKET, "market")
|
|
await promotion_fixture("09-promotion-reference-en", Vector2i(844, 390), "en", 1.0)
|
|
await boss_fixture("10-boss2-reference-en", Fixture.new().contract2_boss_muster_snapshot(), "BO-R3-02", "vary_tags", Vector2i(844, 390), "en", 1.0, false, false)
|
|
await fixture("11-record2-small-zh", Fixture.new().region_result_snapshot(), Vector2i(760, 360), "zh_CN", 1.0, false, false, Controller.SCREEN_REWARD, "region_record")
|
|
await fixture("12-rg04-large-en-reduced", Fixture.new().region_closed_snapshot(), Vector2i(1280, 720), "en", 1.0, true, true, Controller.SCREEN_MARKET, "region_closed")
|
|
await release_experience()
|
|
if failures.is_empty():
|
|
print("ABG_FG4_CAMPAIGN_RUNTIME_MATRIX_OK fixtures=12 states=market,plan,battle,recovery,boss1,record1,handoff,contract2_market,promotion,boss2,record2,rg04 viewports=760x360,844x390,1280x720 locales=zh_CN,en text_130=true reduced_motion=true reduced_flashes=true muted=true low_power=true controls=48 panels=0 cards=0 environment=RG-03 sparks=8 captures=%d" % captures)
|
|
quit(0)
|
|
else:
|
|
for failure in failures:
|
|
push_error(failure)
|
|
quit(1)
|
|
|
|
|
|
func fixture(label: String, snapshot_value: Dictionary, viewport: Vector2i, locale_value: String, scale_value: float, reduced: bool, low_power_value: bool, expected_screen: String, expected_mode: String) -> void:
|
|
await mount(snapshot_value, viewport, locale_value, scale_value, reduced, low_power_value)
|
|
expect(experience.screen == expected_screen, "%s restores %s" % [label, expected_screen])
|
|
expect(String(experience.view.get("mode", "")) == expected_mode, "%s restores mode %s" % [label, expected_mode])
|
|
verify_current(label, viewport, locale_value, scale_value, reduced, low_power_value)
|
|
await capture(label)
|
|
|
|
|
|
func plan_fixture(label: String, viewport: Vector2i, locale_value: String, scale_value: float) -> void:
|
|
await mount(Fixture.new().market_1_snapshot(), viewport, locale_value, scale_value, false, false)
|
|
experience._depart_pressed()
|
|
await settle_frames()
|
|
expect(experience.screen == Controller.SCREEN_MARKET and experience.view.mode == "plan", "%s shows response plans" % label)
|
|
expect(experience.view.plans.size() == 4, "%s keeps all four responses" % label)
|
|
verify_current(label, viewport, locale_value, scale_value, false, false)
|
|
await capture(label)
|
|
|
|
|
|
func ordinary_battle_fixture(label: String, viewport: Vector2i, locale_value: String, scale_value: float) -> void:
|
|
await mount(Fixture.new().market_1_snapshot(), viewport, locale_value, scale_value, false, false)
|
|
experience._depart_pressed()
|
|
experience._depart_pressed()
|
|
experience.advance_battle_ticks(8)
|
|
await settle_frames()
|
|
expect(experience.screen == Controller.SCREEN_BATTLE, "%s shows automatic battle" % label)
|
|
var vfx = experience.find_child("VfxLayer", true, false)
|
|
expect(bool(vfx.get_meta("fg4_pressure_visible", false)), "%s renders the pressure valve" % label)
|
|
verify_current(label, viewport, locale_value, scale_value, false, false)
|
|
await capture(label)
|
|
|
|
|
|
func boss_fixture(label: String, snapshot_value: Dictionary, boss_id: String, plan_id: String, viewport: Vector2i, locale_value: String, scale_value: float, reduced: bool, low_power_value: bool) -> void:
|
|
await mount(snapshot_value, viewport, locale_value, scale_value, reduced, low_power_value)
|
|
experience.selected_route_id = plan_id
|
|
experience._depart_pressed()
|
|
experience.advance_battle_ticks(8)
|
|
await settle_frames()
|
|
expect(experience.screen == Controller.SCREEN_BATTLE and String(experience.view.battle.boss_id) == boss_id, "%s shows %s in battle" % [label, boss_id])
|
|
var actors = experience.find_child("ActorsLayer", true, false)
|
|
expect(boss_id in Array(actors.get_meta("authored_actor_ids", [])), "%s renders authored %s" % [label, boss_id])
|
|
if boss_id == "BO-R3-02":
|
|
var vfx = experience.find_child("VfxLayer", true, false)
|
|
expect(int(vfx.get_meta("fg4_spark_slot_count", 0)) == 8, "%s composes eight spark slots" % label)
|
|
verify_current(label, viewport, locale_value, scale_value, reduced, low_power_value)
|
|
await capture(label)
|
|
|
|
|
|
func promotion_fixture(label: String, viewport: Vector2i, locale_value: String, scale_value: float) -> void:
|
|
await mount(Fixture.new().contract2_market_1_snapshot(), viewport, locale_value, scale_value, false, false)
|
|
experience._depart_pressed()
|
|
experience.selected_route_id = "safe_promotion"
|
|
experience._depart_pressed()
|
|
experience.advance_battle_ticks(8)
|
|
await settle_frames()
|
|
var vfx = experience.find_child("VfxLayer", true, false)
|
|
expect(int(vfx.get_meta("fg4_spark_slot_count", 0)) == 8, "%s composes eight sparks" % label)
|
|
expect(int(vfx.get_meta("fg4_promoted_spark_count", 0)) == 1, "%s promotes one spark" % label)
|
|
verify_current(label, viewport, locale_value, scale_value, false, false)
|
|
await capture(label)
|
|
|
|
|
|
func recovery_snapshot() -> Dictionary:
|
|
var snapshot_value := Fixture.new().market_1_snapshot()
|
|
var company: Array = Array(snapshot_value.current_run.company).duplicate()
|
|
var deployed_count := mini(4, company.size())
|
|
snapshot_value.current_run.deployed_recruit_ids = company.slice(0, deployed_count)
|
|
snapshot_value.current_run.reserve_recruit_ids = company.slice(deployed_count)
|
|
snapshot_value.current_run.formation_proposal = company.slice(0, deployed_count)
|
|
var adapter := Adapter.new()
|
|
if not bool(adapter.configure(snapshot_value).get("ok", false)):
|
|
return {}
|
|
var route: Dictionary = adapter.view_state().routes[2]
|
|
if not bool(adapter.prepare_route(String(route.offer_id)).get("ok", false)):
|
|
return {}
|
|
var primary := String(Contract1Battle.PRIMARY_PLAN[1])
|
|
var secondary := String(Contract1Battle.SECONDARY_PLAN[1])
|
|
var wrong_plan := ""
|
|
for candidate in Contract1Battle.PLAN_IDS:
|
|
if candidate not in [primary, secondary]:
|
|
wrong_plan = String(candidate)
|
|
break
|
|
if wrong_plan.is_empty() or not bool(adapter.start_plan(wrong_plan).get("ok", false)):
|
|
return {}
|
|
adapter.run_battle_to_terminal()
|
|
var settled: Dictionary = adapter.settle_terminal()
|
|
return adapter.snapshot() if bool(settled.get("ok", false)) else {}
|
|
|
|
|
|
func first_record_snapshot() -> Dictionary:
|
|
var adapter := Adapter.new()
|
|
if not bool(adapter.configure(Fixture.new().boss_muster_snapshot()).get("ok", false)):
|
|
return {}
|
|
if not bool(adapter.start_plan("vent_and_brace").get("ok", false)):
|
|
return {}
|
|
adapter.run_battle_to_terminal()
|
|
var settled: Dictionary = adapter.settle_terminal()
|
|
return adapter.snapshot() if bool(settled.get("ok", false)) else {}
|
|
|
|
|
|
func mount(snapshot_value: Dictionary, viewport: Vector2i, locale_value: String, scale_value: float, reduced: bool, low_power_value: bool) -> void:
|
|
await release_experience()
|
|
root.size = viewport
|
|
root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
|
|
root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND
|
|
root.content_scale_size = viewport
|
|
experience = Controller.new()
|
|
experience.battle_auto_advance = false
|
|
experience.locale = locale_value
|
|
experience.text_scale = scale_value
|
|
experience.reduced_motion = reduced
|
|
experience.reduced_flashes = reduced
|
|
experience.muted = true
|
|
experience.low_power = low_power_value
|
|
experience.size = Vector2(viewport)
|
|
experience.configure_entry(snapshot_value)
|
|
root.add_child(experience)
|
|
await settle_frames(10)
|
|
|
|
|
|
func release_experience() -> void:
|
|
if not is_instance_valid(experience):
|
|
return
|
|
experience.audio_router.cleanup()
|
|
await create_timer(0.10).timeout
|
|
experience.queue_free()
|
|
experience = null
|
|
await settle_frames(30)
|
|
|
|
|
|
func verify_current(label: String, viewport: Vector2i, locale_value: String, scale_value: float, reduced: bool, low_power_value: bool) -> void:
|
|
var bounds := Rect2(Vector2.ZERO, Vector2(viewport))
|
|
var expected_grid := "380x180@2x" if viewport == Vector2i(760, 360) else "320x180@4x" if viewport == Vector2i(1280, 720) else "422x195@2x"
|
|
expect(String(experience.get_meta("logical_grid", "")) == expected_grid, "%s uses %s" % [label, expected_grid])
|
|
expect(experience.locale == locale_value and is_equal_approx(experience.text_scale, scale_value), "%s preserves locale and text scale" % label)
|
|
expect(experience.reduced_motion == reduced and experience.reduced_flashes == reduced, "%s preserves reduced motion/flashes" % label)
|
|
expect(experience.low_power == low_power_value and experience.muted, "%s preserves low power and mute" % label)
|
|
expect(float(experience.get_meta("visible_world_ratio", 0.0)) >= 0.80, "%s keeps world-first composition" % label)
|
|
expect(experience.find_children("*", "Panel", true, false).is_empty(), "%s adds no app Panel surfaces" % label)
|
|
for layer_name in ["BackgroundLayer", "MidgroundLayer", "ActorsLayer", "ForegroundLayer", "VfxLayer"]:
|
|
expect(experience.find_child(layer_name, true, false) != null, "%s retains %s" % [label, layer_name])
|
|
var background = experience.find_child("BackgroundLayer", true, false)
|
|
expect(String(background.get_meta("environment_region_id", "")) == "RG-03", "%s uses Emberloom" % label)
|
|
var recovery_cause := experience.find_child("FG4RecoveryCause", true, false) as Label
|
|
if recovery_cause != null:
|
|
expect(recovery_cause.autowrap_mode == TextServer.AUTOWRAP_WORD_SMART, "%s wraps recovery explanation" % label)
|
|
expect(recovery_cause.text_overrun_behavior == TextServer.OVERRUN_NO_TRIMMING, "%s never ellipsizes recovery explanation" % label)
|
|
expect(recovery_cause.get_visible_line_count() == recovery_cause.get_line_count(), "%s shows every recovery line" % label)
|
|
for node in experience.find_children("*", "Button", true, false):
|
|
var button := node as Button
|
|
if not button.visible or button.is_queued_for_deletion():
|
|
continue
|
|
expect(button.size.x >= 48 and button.size.y >= 48, "%s target %s is at least 48px" % [label, button.name])
|
|
expect(bounds.encloses(button.get_global_rect()), "%s target %s stays inside %s" % [label, button.name, viewport])
|
|
expect(not button.text.strip_edges().is_empty(), "%s target %s has readable copy" % [label, button.name])
|
|
for node in experience.find_children("*", "Control", true, false):
|
|
var lowered := String(node.name).to_lower()
|
|
expect("card" not in lowered and "modal" not in lowered and "dialog" not in lowered, "%s has no card/modal surface: %s" % [label, node.name])
|
|
|
|
|
|
func capture(label: String) -> void:
|
|
if capture_root.is_empty():
|
|
return
|
|
await RenderingServer.frame_post_draw
|
|
var error := root.get_texture().get_image().save_png(capture_root.path_join(label + ".png"))
|
|
expect(error == OK, "%s capture writes" % label)
|
|
if error == OK:
|
|
captures += 1
|
|
await process_frame
|
|
|
|
|
|
func settle_frames(count := 5) -> void:
|
|
for _index in count:
|
|
await process_frame
|
|
|
|
|
|
func expect(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
failures.append("FG-4 campaign runtime matrix: %s" % message)
|