Files
aetherbound-guild/runtime/tests/p9_3/reference_fidelity_runtime_test.gd
T

213 lines
8.6 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)
var failures: Array[String] = []
var game: Control
var experience: Control
var touch_count := 0
var capture_root := ""
var contact_captured := false
var ranged_captured := false
var recording_only := false
func _initialize() -> void:
call_deferred("run_test")
func run_test() -> void:
capture_root = OS.get_environment("ABG_P9_3_CAPTURE_ROOT")
recording_only = OS.get_environment("ABG_P9_3_RECORDING_ONLY") == "1"
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
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
expect(experience != null, "modular experience starts independently")
if experience == null:
for failure in failures:
push_error(failure)
quit(1)
return
expect(float(experience.get_meta("visible_world_ratio", 0.0)) >= 0.82, "world owns at least 82 percent of the surface")
expect(find_control(experience, "P93EnterHallButton") != null, "Title exposes one entry action")
await capture("01-title")
await recording_hold(45)
await tap("P93EnterHallButton")
expect(experience.service.state.phase == State.Phase.HALL, "Title enters the Hall directly")
expect(find_control(experience, "P93OfferTarget_Wayrunner") != null, "Wayrunner body is a touch target")
expect(find_control(experience, "P93OfferTarget_Farstring") != null, "Farstring body is a touch target")
expect(find_control(experience, "P93OfferTarget_Kindhand") != null, "Kindhand body is a touch target")
expect(count_named_fragment(experience, "Card") == 0, "Hall contains no card surface")
await capture("02-hall")
await recording_hold(55)
await tap("P93OfferTarget_Farstring")
expect(experience.service.state.focused_offer == "Farstring", "first actor tap focuses the person")
expect(find_control(experience, "P93OfferDetail") != null, "focused person exposes a short in-world detail")
await capture("03-offer-focused")
await recording_hold(45)
await tap("P93OfferTarget_Farstring")
expect(experience.service.state.phase == State.Phase.ENCOUNTER, "second actor tap hires and opens threats")
expect(experience.service.state.company == ["Hearthguard", "Farstring"], "hired actor joins the line")
expect(experience.service.state.coin == 12, "hire deducts the exact 12 Coin")
expect(find_control(experience, "P93EncounterTarget_open_road") != null, "Open Road is a world target")
expect(find_control(experience, "P93EncounterTarget_crosswind") != null, "Crosswind is a world target")
expect(find_control(experience, "P93EncounterTarget_bell_breach") != null, "Bell Breach is a world target")
await capture("04-encounters")
await recording_hold(55)
await tap("P93EncounterTarget_open_road")
expect(experience.service.state.selected_encounter == "open_road", "first enemy tap selects actual pressure")
await capture("05-encounter-selected")
await recording_hold(45)
await tap("P93EncounterTarget_open_road")
expect(experience.service.state.phase == State.Phase.BATTLE, "second enemy tap enters automatic combat")
expect(experience.service.battle.state.living_enemy_ids().size() == 1, "Open Road contains one living enemy")
if not recording_only:
await tap("P93SpeedButton")
var contact_seen := false
var ranged_event_seen := false
var projectile_seen := false
var guard := 0
while experience.service.state.phase == State.Phase.BATTLE and guard < 1400:
for event in experience.service.battle.state.event_log:
contact_seen = contact_seen or String(event.get("kind", "")) == "contact"
ranged_event_seen = ranged_event_seen or (String(event.get("kind", "")) == "member_action" and String(event.get("source_member_id", "")) == "Farstring")
if contact_seen and not contact_captured:
await capture("06-contact")
contact_captured = true
var projectile := find_control(experience, "P93Projectile")
projectile_seen = projectile_seen or (projectile != null and projectile.is_visible_in_tree())
if projectile_seen and not ranged_captured:
await settle(10)
await capture("07-ranged-action")
ranged_captured = true
await process_frame
guard += 1
expect(contact_seen, "the horizontal lines visibly reach contact")
expect(ranged_event_seen, "the recruited profession commits its role action")
expect(projectile_seen, "the ranged role action exposes a visible projectile")
expect(experience.service.state.phase == State.Phase.RESULT, "automatic battle reaches a bounded result")
expect(experience.service.state.last_outcome == "victory", "the first Open Road line wins")
experience._process(0.8)
await settle(55)
expect(find_control(experience, "P93BattleResult") != null, "battlefield keeps the visible result")
await capture("08-result")
await recording_hold(45)
await tap("P93ReturnHallButton")
expect(experience.service.state.phase == State.Phase.HALL, "result returns to the same Hall")
expect(experience.service.state.company == ["Hearthguard", "Farstring"], "return preserves the hired line")
expect(experience.service.state.coin == 20, "return preserves exact reward Coin")
expect(find_control(experience, "P93ReturnReceipt") != null, "returned Hall shows the earned change")
verify_touch_bounds(experience, Rect2(Vector2.ZERO, Vector2(VIEWPORT_SIZE)))
await capture("09-returned-hall")
await recording_hold(45)
if recording_only:
finish_test()
return
game.queue_free()
await settle(90)
finish_test()
func finish_test() -> void:
if failures.is_empty():
if recording_only:
print("ABG_P9_3_REFERENCE_FIDELITY_RECORDING_OK viewport=844x390 touch=%d fallback=0 direct=0 title=true hall=true hire=true encounters=3 battle=true contact=true ranged=true reward=true return=true" % touch_count)
else:
print("ABG_P9_3_REFERENCE_FIDELITY_RUNTIME_OK viewport=844x390 touch=%d fallback=0 direct=0 title=true hall=true hire=true encounters=3 battle=true contact=true ranged=true reward=true return=true cards=0" % touch_count)
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func tap(node_name: String) -> void:
var control: Control = find_control(experience if experience != null else game, 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(name: String) -> void:
if capture_root.is_empty():
return
DirAccess.make_dir_recursive_absolute(capture_root)
await settle(3)
await RenderingServer.frame_post_draw
var error := root.get_texture().get_image().save_png(capture_root.path_join(name + ".png"))
expect(error == OK, "capture %s writes" % name)
func verify_touch_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("button %s outside viewport: %s" % [node.name, rect])
if rect.size.x < 44.0 or rect.size.y < 44.0:
failures.append("button %s below 44px: %s" % [node.name, rect.size])
for child in node.get_children():
verify_touch_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 settle(frames: int) -> void:
for _frame in frames:
await process_frame
func recording_hold(frames: int) -> void:
if recording_only:
await settle(frames)
func expect(condition: bool, message: String) -> void:
if not condition:
failures.append("P9.3 reference-fidelity runtime: %s" % message)