extends SceneTree # Headless gameplay test: run with # godot --headless -s res://tests/test_world_map_ui.gd # Verifies the map UI: hidden by default, opening it recenters on the # fleet, a single-finger drag while the map is open pans the map content # instead of leaking through to the gameplay fleet-move/zoom controls, and # closing hides it again. const POS_TOLERANCE: float = 3.0 var main_scene: Node var fleet: Node var camera: Camera2D var world_map: Control var content: Node2D var fleet_target_before: Vector2 var camera_zoom_before: float var content_pos_before_drag: Vector2 var elapsed: float = 0.0 var stage: int = 0 func _initialize() -> void: var packed: PackedScene = load("res://Main.tscn") main_scene = packed.instantiate() root.add_child(main_scene) fleet = main_scene.get_node("Fleet") camera = main_scene.get_node("Camera2D") world_map = main_scene.get_node("UI/WorldMap") content = main_scene.get_node("UI/WorldMap/MapContent") func _process(delta: float) -> bool: elapsed += delta match stage: 0: if elapsed < 0.1: return false if world_map.visible: print("TEST_WORLD_MAP_UI: FAIL (map visible by default)") quit(1) return true world_map.open_map() stage = 1 elapsed = 0.0 return false 1: if elapsed < 0.1: return false var fleet_local: Vector2 = fleet.get_centroid() * content.WORLD_TO_LOCAL var viewport_center: Vector2 = main_scene.get_viewport_rect().size / 2.0 var expected_content_pos: Vector2 = viewport_center - fleet_local * content.scale.x var recenter_ok: bool = content.position.distance_to(expected_content_pos) <= POS_TOLERANCE print("open_visible=%s content_pos=%s expected=%s recenter_ok=%s" % [ world_map.visible, content.position, expected_content_pos, recenter_ok ]) if not (world_map.visible and recenter_ok): print("TEST_WORLD_MAP_UI: FAIL (open/recenter)") quit(1) return true fleet_target_before = fleet.fleet_target camera_zoom_before = camera.zoom.x content_pos_before_drag = content.position _touch(0, true, Vector2(200, 600)) stage = 2 elapsed = 0.0 return false 2: if elapsed < 0.1: return false _drag(0, Vector2(260, 640)) stage = 3 elapsed = 0.0 return false 3: if elapsed < 0.1: return false _touch(0, false, Vector2(260, 640)) # Headless mode's placeholder window doesn't match the configured # viewport size, so Godot's own stretch transform rescales raw # synthetic touch coordinates by some environment-specific factor # before delivery - checking an exact pixel count here would be # testing that artifact, not the pan logic. Instead verify the # drag moved the map in the right DIRECTION and roughly the right # PROPORTION (dragged twice as far on X as Y, so the pan should # reflect that same ratio), which the scaling factor cancels out of. var pan_delta: Vector2 = content.position - content_pos_before_drag var moved_ok: bool = pan_delta.length() > 1.0 var direction_ok: bool = pan_delta.x > 0.0 and pan_delta.y > 0.0 var ratio_ok: bool = pan_delta.y != 0.0 and abs((pan_delta.x / pan_delta.y) - 1.5) <= 0.1 var pan_ok: bool = moved_ok and direction_ok and ratio_ok var fleet_untouched: bool = fleet.fleet_target == fleet_target_before var zoom_untouched: bool = camera.zoom.x == camera_zoom_before print("pan_delta=%s moved_ok=%s direction_ok=%s ratio_ok=%s fleet_untouched=%s zoom_untouched=%s" % [ pan_delta, moved_ok, direction_ok, ratio_ok, fleet_untouched, zoom_untouched ]) if not (pan_ok and fleet_untouched and zoom_untouched): print("TEST_WORLD_MAP_UI: FAIL (pan/isolation)") quit(1) return true world_map.close_map() stage = 4 elapsed = 0.0 return false 4: if elapsed < 0.1: return false var closed_ok: bool = not world_map.visible print("closed_ok=%s" % closed_ok) if closed_ok: print("TEST_WORLD_MAP_UI: PASS") quit(0) else: print("TEST_WORLD_MAP_UI: FAIL (did not close)") quit(1) return true return false func _touch(index: int, pressed: bool, pos: Vector2) -> void: var ev: InputEventScreenTouch = InputEventScreenTouch.new() ev.index = index ev.pressed = pressed ev.position = pos Input.parse_input_event(ev) func _drag(index: int, pos: Vector2) -> void: var ev: InputEventScreenDrag = InputEventScreenDrag.new() ev.index = index ev.position = pos Input.parse_input_event(ev)