Files
aetherbound-guild/runtime/tests/p9_4/second_cycle_adaptation_runtime_test.gd
T

209 lines
8.1 KiB
GDScript

extends SceneTree
const MainScene = preload("res://main.tscn")
const State = preload("res://p9_3/core_loop_state.gd")
const VIEWPORT_SIZE := Vector2i(844, 390)
const PATHS := {
"Wayrunner": {"tick": 99, "hearthguard_hp": 22, "heals": 0, "coin": 22},
"Kindhand": {"tick": 133, "hearthguard_hp": 84, "heals": 11, "coin": 21},
}
var failures: Array[String] = []
var game: Control
var experience: Control
var touch_count := 0
var capture_root := ""
func _initialize() -> void:
call_deferred("run_test")
func run_test() -> void:
capture_root = OS.get_environment("ABG_P9_4_CAPTURE_ROOT")
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
for recruit_id in ["Wayrunner", "Kindhand"]:
await run_path(recruit_id)
await destroy_game()
if failures.is_empty():
print("ABG_P9_4_SECOND_CYCLE_RUNTIME_OK paths=2 touch=%d fallback=0 direct=0 ticks=99,133 hearthguard_hp=22,84 wayrunner=true healing=11 coin=22,21 company=3/10 cards=0" % touch_count)
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func run_path(recruit_id: String) -> void:
var expected: Dictionary = PATHS[recruit_id]
game = MainScene.instantiate()
game.auto_boot = false
game.audio_enabled = false
root.add_child(game)
await settle(8)
game.start_p93_experience()
await settle(8)
experience = game.p93_experience
prepare_returned_hall()
experience.returned_from_battle = true
experience._render_current()
await settle(8)
expect(find_control(experience, "P94IncomingThreat") != null, "%s sees Crosswind before input" % recruit_id)
expect(find_control(experience, "P93OfferTarget_%s" % recruit_id) != null, "%s body is a direct target" % recruit_id)
await capture(recruit_id, "01-returned-threat")
await tap("P93OfferTarget_%s" % recruit_id)
expect(experience.service.state.focused_offer == recruit_id, "%s first touch focuses" % recruit_id)
expect(find_control(experience, "P93OfferDetail") != null, "%s exposes role detail" % recruit_id)
await capture(recruit_id, "02-focused")
await tap("P93OfferTarget_%s" % recruit_id)
expect(experience.service.state.phase == State.Phase.ENCOUNTER, "%s second touch hires" % recruit_id)
expect(experience.service.state.company == ["Hearthguard", "Farstring", recruit_id], "%s joins the 3/10 line" % recruit_id)
await tap("P93EncounterTarget_crosswind")
expect(experience.service.state.selected_encounter == "crosswind", "%s selects Crosswind" % recruit_id)
await tap("P93EncounterTarget_crosswind")
expect(experience.service.state.phase == State.Phase.BATTLE, "%s starts the second battle" % recruit_id)
await tap("P93SpeedButton")
var role_seen := false
var role_captured := false
var role_cue_seen := false
var guard := 0
while experience.service.state.phase == State.Phase.BATTLE and guard < 1600:
for event in experience.service.battle.state.event_log:
if String(event.get("kind", "")) != "member_action" or String(event.get("source_member_id", "")) != recruit_id:
continue
if recruit_id == "Kindhand" and String(event.get("qualifier_en", "")) != "healing":
continue
role_seen = true
role_cue_seen = role_cue_seen or find_control(experience, "P94RoleCue_%s" % recruit_id) != null
if role_seen and not role_captured:
await settle(10)
role_cue_seen = role_cue_seen or find_control(experience, "P94RoleCue_%s" % recruit_id) != null
await capture(recruit_id, "03-role-action")
role_captured = true
await process_frame
guard += 1
expect(role_seen, "%s performs the required role action" % recruit_id)
expect(role_cue_seen, "%s exposes a visible role cue" % recruit_id)
expect(experience.service.state.phase == State.Phase.RESULT and experience.service.state.last_outcome == "victory", "%s reaches victory" % recruit_id)
expect(experience.service.battle.state.tick == int(expected.tick), "%s preserves the exact terminal tick" % recruit_id)
expect(int(experience.service.battle.state.member_hp.Hearthguard) == int(expected.hearthguard_hp), "%s preserves the exact Hearthguard consequence" % recruit_id)
expect(count_heals(recruit_id) == int(expected.heals), "%s preserves the exact heal count" % recruit_id)
experience._process(0.8)
await settle(55)
expect(find_control(experience, "P93BattleResult") != null, "%s result holds on the field" % recruit_id)
await capture(recruit_id, "04-result")
await tap("P93ReturnHallButton")
expect(experience.service.state.phase == State.Phase.HALL, "%s returns to Hall" % recruit_id)
expect(experience.service.state.coin == int(expected.coin), "%s preserves exact Coin" % recruit_id)
expect(experience.service.state.company == ["Hearthguard", "Farstring", recruit_id], "%s preserves the selected Company" % recruit_id)
expect(find_control(experience, "P93Company_%s" % recruit_id) != null, "%s is visible in the returned line" % recruit_id)
expect(count_named_fragment(experience, "Card") == 0, "%s path adds no cards" % recruit_id)
verify_control_bounds(experience, Rect2(Vector2.ZERO, Vector2(VIEWPORT_SIZE)))
await capture(recruit_id, "05-returned-company")
func prepare_returned_hall() -> void:
var service = experience.service
service.enter_hall()
service.focus_offer("Farstring")
service.hire("Farstring")
service.select_encounter("open_road")
service.start_battle()
while service.state.phase == State.Phase.BATTLE:
service.advance_battle_ticks(1)
service.claim_and_return()
func count_heals(recruit_id: String) -> int:
var result := 0
for event in experience.service.battle.state.event_log:
if String(event.get("kind", "")) == "member_action" and String(event.get("source_member_id", "")) == recruit_id and String(event.get("qualifier_en", "")) == "healing":
result += 1
return result
func tap(node_name: String) -> void:
var control := find_control(experience, node_name)
if control == null or not control.is_visible_in_tree() or (control is BaseButton and control.disabled):
failures.append("unavailable parsed-touch target %s" % node_name)
return
var position := root.get_final_transform() * control.get_global_rect().get_center()
var down := InputEventScreenTouch.new()
down.index = 0
down.position = position
down.pressed = true
Input.parse_input_event(down)
await process_frame
var up := InputEventScreenTouch.new()
up.index = 0
up.position = position
up.pressed = false
Input.parse_input_event(up)
touch_count += 1
await settle(6)
func capture(path_id: String, name: String) -> void:
if capture_root.is_empty():
return
var path := capture_root.path_join(path_id.to_lower())
DirAccess.make_dir_recursive_absolute(path)
await settle(3)
await RenderingServer.frame_post_draw
var error := root.get_texture().get_image().save_png(path.path_join(name + ".png"))
expect(error == OK, "capture %s/%s writes" % [path_id, name])
func verify_control_bounds(node: Node, bounds: Rect2) -> void:
if node is BaseButton and node.is_visible_in_tree():
var rect: Rect2 = node.get_global_rect()
if rect.position.x < -0.5 or rect.position.y < -0.5 or rect.end.x > bounds.end.x + 0.5 or rect.end.y > bounds.end.y + 0.5:
failures.append("control %s outside viewport: %s" % [node.name, rect])
if rect.size.x < 44.0 or rect.size.y < 44.0:
failures.append("control %s below 44px: %s" % [node.name, rect.size])
for child in node.get_children():
verify_control_bounds(child, bounds)
func count_named_fragment(node: Node, fragment: String) -> int:
var result := 1 if fragment in String(node.name) else 0
for child in node.get_children():
result += count_named_fragment(child, fragment)
return result
func find_control(node: Node, node_name: String) -> Control:
if node.name == node_name and node is Control and not node.is_queued_for_deletion():
return node
for child in node.get_children():
var found := find_control(child, node_name)
if found != null:
return found
return null
func destroy_game() -> void:
if is_instance_valid(game):
game.queue_free()
await settle(90)
game = null
experience = null
func settle(frames: int) -> void:
for _frame in frames:
await process_frame
func expect(condition: bool, message: String) -> void:
if not condition:
failures.append("P9.4 second-cycle runtime: %s" % message)