213 lines
8.6 KiB
GDScript
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)
|