Files
aetherbound-guild/runtime/tests/fg4/rg04_campaign_runtime_matrix_test.gd
T

222 lines
11 KiB
GDScript

extends SceneTree
const Adapter = preload("res://campaign/fg4_adapter.gd")
const Contract1Battle = preload("res://fg4/rg04_contract1_battle_service.gd")
const Contract2Battle = preload("res://fg4/rg04_contract2_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 snapshots: Dictionary = {}
var capture_root := ""
var captures := 0
func _initialize() -> void:
call_deferred("run_test")
func run_test() -> void:
capture_root = OS.get_environment("ABG_FG4_RG04_MATRIX_CAPTURE_ROOT")
if not capture_root.is_empty():
DirAccess.make_dir_recursive_absolute(capture_root)
build_snapshots()
await fixture("01-entry-small-zh", snapshots.market, Vector2i(760, 360), "zh_CN", 1.0, false, false, Controller.SCREEN_MARKET, "market")
await battle_fixture("02-clause-copy-reference-en130", snapshots.market, Vector2i(844, 390), "en", 1.3, false, false, false)
await terminal_battle_fixture("03-ordinary-victory-large-zh", snapshots.market, Vector2i(1280, 720), "zh_CN", 1.0, false, false)
await fixture("04-recovery-small-en130", snapshots.recovery, Vector2i(760, 360), "en", 1.3, false, false, Controller.SCREEN_RECOVERY, "recovery")
await battle_fixture("05-walking-index-reduced-low-power", snapshots.boss1, Vector2i(844, 390), "zh_CN", 1.0, true, true, true)
await fixture("06-record1-reference-en", snapshots.record1, Vector2i(844, 390), "en", 1.0, false, false, Controller.SCREEN_REWARD, "contract_record")
await fixture("07-handoff-small-zh", snapshots.handoff, Vector2i(760, 360), "zh_CN", 1.0, false, false, Controller.SCREEN_REWARD, "contract_handoff")
await fixture("08-contract2-market-large-zh130", snapshots.contract2_market, Vector2i(1280, 720), "zh_CN", 1.3, false, false, Controller.SCREEN_MARKET, "market")
await battle_fixture("09-redaction-replay-reference-en", snapshots.contract2_market, Vector2i(844, 390), "en", 1.0, false, false, false)
await battle_fixture("10-redaction-hart-reference-en", snapshots.boss2, Vector2i(844, 390), "en", 1.0, false, false, true)
await fixture("11-record2-small-zh", snapshots.record2, Vector2i(760, 360), "zh_CN", 1.0, false, false, Controller.SCREEN_REWARD, "region_record")
await fixture("12-rg05-large-en-reduced", snapshots.closed, Vector2i(1280, 720), "en", 1.0, true, true, Controller.SCREEN_MARKET, "region_closed")
await release_experience()
if failures.is_empty():
print("ABG_FG4_RG04_RUNTIME_MATRIX_OK fixtures=12 states=entry,clause_copy,victory,recovery,boss1,record1,handoff,contract2,redaction_replay,boss2,record2,rg05 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-04 captures=%d" % captures)
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func build_snapshots() -> void:
var adapter := Adapter.new()
var base := Fixture.new().rg04_market_1_snapshot()
expect(not base.is_empty() and adapter.configure(base).ok, "RG-04 matrix source builds")
if base.is_empty() or not adapter.valid:
return
snapshots.market = adapter.snapshot()
snapshots.recovery = recovery_snapshot(base)
for market_index in range(1, 13):
win_round(adapter, String(Contract1Battle.PRIMARY_PLAN[market_index]))
snapshots.boss1 = adapter.snapshot()
win_boss(adapter, "steer_action_count")
snapshots.record1 = adapter.snapshot()
adapter.continue_result()
snapshots.handoff = adapter.snapshot()
adapter.continue_result()
snapshots.contract2_market = adapter.snapshot()
for market_index in range(1, 13):
win_round(adapter, String(Contract2Battle.PRIMARY_PLAN[market_index]))
snapshots.boss2 = adapter.snapshot()
win_boss(adapter, "safe_rule_seed")
snapshots.record2 = adapter.snapshot()
adapter.continue_result()
snapshots.closed = adapter.snapshot()
func win_round(adapter, plan_id: String) -> void:
var route: Dictionary = adapter.view_state().routes[0]
adapter.prepare_route(String(route.offer_id))
adapter.start_plan(plan_id)
adapter.run_battle_to_terminal()
adapter.settle_terminal()
func win_boss(adapter, plan_id: String) -> void:
adapter.start_plan(plan_id)
adapter.run_battle_to_terminal()
adapter.settle_terminal()
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 and String(experience.view.mode) == expected_mode, "%s restores expected state" % label)
verify_current(label, viewport, locale_value, scale_value, reduced, low_power_value)
await capture(label)
func battle_fixture(label: String, snapshot_value: Dictionary, viewport: Vector2i, locale_value: String, scale_value: float, reduced: bool, low_power_value: bool, boss: bool) -> void:
await mount(snapshot_value, viewport, locale_value, scale_value, reduced, low_power_value)
if not boss:
experience._depart_pressed()
experience.selected_route_id = String(experience.view.plans[0].plan_id)
experience._depart_pressed()
experience.advance_battle_ticks(8 if String(experience.view.contract_id) == "CTR-R4-02" else 5)
await settle_frames(4)
expect(experience.screen == Controller.SCREEN_BATTLE, "%s reaches battle" % label)
verify_current(label, viewport, locale_value, scale_value, reduced, low_power_value)
var vfx = experience.find_child("VfxLayer", true, false)
if String(experience.view.contract_id) == "CTR-R4-02":
expect(bool(vfx.get_meta("fg4_redaction_source_visible", false)), "%s shows redaction source" % label)
expect(bool(vfx.get_meta("fg4_replay_order_visible", false)), "%s shows replay order" % label)
elif boss:
expect(bool(vfx.get_meta("fg4_index_pointer_visible", false)), "%s shows index pointer" % label)
else:
expect(bool(vfx.get_meta("fg4_archive_clause_visible", false)) and bool(vfx.get_meta("fg4_copy_source_visible", false)), "%s shows clause and copy" % label)
await capture(label)
func terminal_battle_fixture(label: String, snapshot_value: Dictionary, 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._depart_pressed()
experience.selected_route_id = String(experience.view.plans[0].plan_id)
experience._depart_pressed()
while not experience.battle_result_visible:
experience.advance_battle_ticks(16)
await settle_frames(4)
expect(experience.view.battle.outcome == "victory", "%s reaches ordinary victory" % label)
verify_current(label, viewport, locale_value, scale_value, reduced, low_power_value)
await capture(label)
func recovery_snapshot(source: Dictionary) -> Dictionary:
var value := source.duplicate(true)
var company: Array = Array(value.current_run.company).duplicate()
var deployed_count := mini(4, company.size())
value.current_run.deployed_recruit_ids = company.slice(0, deployed_count)
value.current_run.reserve_recruit_ids = company.slice(deployed_count)
value.current_run.formation_proposal = company.slice(0, deployed_count)
var adapter := Adapter.new()
adapter.configure(value)
var route: Dictionary = adapter.view_state().routes[2]
adapter.prepare_route(String(route.offer_id))
var wrong_plan := ""
for candidate in Contract1Battle.PLAN_IDS:
if candidate not in [Contract1Battle.PRIMARY_PLAN[1], Contract1Battle.SECONDARY_PLAN[1]]:
wrong_plan = String(candidate)
break
adapter.start_plan(wrong_plan)
adapter.run_battle_to_terminal()
adapter.settle_terminal()
return adapter.snapshot()
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()
experience.queue_free()
experience = null
await settle_frames(45)
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/text scale" % label)
expect(experience.reduced_motion == reduced and experience.reduced_flashes == reduced and experience.low_power == low_power_value, "%s preserves accessibility/resource mode" % label)
expect(float(experience.get_meta("visible_world_ratio", 0.0)) >= 0.80, "%s remains world-first" % label)
expect(experience.find_children("*", "Panel", true, false).is_empty(), "%s adds no app Panel" % label)
var background = experience.find_child("BackgroundLayer", true, false)
expect(String(background.get_meta("environment_region_id", "")) == "RG-04", "%s uses Quillroot environment" % label)
var audio_state: Dictionary = experience.audio_router.snapshot()
expect(audio_state.music_id == "FG4-MUS-QUILLROOT" and audio_state.ambience_id == "FG4-AMB-QUILLROOT", "%s uses Quillroot bed" % 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 on screen" % [label, button.name])
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" % label)
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: int) -> void:
for _index in count:
await process_frame
func expect(condition: bool, message: String) -> void:
if not condition:
failures.append("FG-4 RG-04 runtime matrix: %s" % message)