extends SceneTree # Headless gameplay test: run with # godot --headless -s res://tests/test_asteroid_spawning.gd # Verifies AsteroidField spawns moving asteroids once the fleet enters an # ASTEROID_FIELD biome chunk, and despawns them again once the fleet moves # far enough away - guards against unbounded node accumulation over a long # play session while still populating newly-entered regions on the fly. var main_scene: Node var fleet: Node var asteroid_field: Node var asteroid_chunk: Vector2i var spawned_asteroids: Array = [] var elapsed: float = 0.0 var stage: int = 0 var ok: bool = true func _initialize() -> void: var found: bool = false for x in range(-15, 15): for y in range(-15, 15): var chunk: Vector2i = Vector2i(x, y) if WorldGen.biome_at_chunk(chunk) == WorldGen.Biome.ASTEROID_FIELD: asteroid_chunk = chunk found = true break if found: break if not found: print("FAIL: could not find any ASTEROID_FIELD chunk to test with") quit(1) return var packed: PackedScene = load("res://world/Main.tscn") main_scene = packed.instantiate() root.add_child(main_scene) fleet = main_scene.get_node("Fleet") asteroid_field = main_scene.get_node("AsteroidField") func _process(delta: float) -> bool: elapsed += delta match stage: 0: # Wait one tick so Fleet._ready() has run and fleet.ships is # populated before we try to teleport them - reposition # attempted synchronously in _initialize() would silently # no-op on an empty ships array. var target_pos: Vector2 = WorldGen.chunk_origin(asteroid_chunk) + Vector2.ONE * WorldGen.CHUNK_SIZE * 0.5 for ship in fleet.ships: ship.position = target_pos fleet.move_to(target_pos) elapsed = 0.0 stage = 1 return false 1: if elapsed < asteroid_field.check_interval + 0.1: return false _check_spawned() elapsed = 0.0 stage = 2 return false 2: var far_pos: Vector2 = WorldGen.chunk_origin(asteroid_chunk) + Vector2(50000, 50000) for ship in fleet.ships: ship.position = far_pos fleet.move_to(far_pos) elapsed = 0.0 stage = 3 return false 3: if elapsed < asteroid_field.check_interval + 0.1: return false _check_despawned() return true return false func _check_spawned() -> void: var has_chunk: bool = asteroid_field.populated_chunks.has(asteroid_chunk) if not has_chunk: ok = false print("FAIL: asteroid_chunk %s not populated after check_interval" % asteroid_chunk) return spawned_asteroids = asteroid_field.populated_chunks[asteroid_chunk] var count_ok: bool = spawned_asteroids.size() == asteroid_field.asteroids_per_chunk var moving_ok: bool = true for asteroid in spawned_asteroids: if asteroid.velocity.length() <= 0.1: moving_ok = false ok = ok and count_ok and moving_ok print("spawned_count=%d expected=%d moving_ok=%s" % [spawned_asteroids.size(), asteroid_field.asteroids_per_chunk, moving_ok]) func _check_despawned() -> void: var chunk_removed: bool = not asteroid_field.populated_chunks.has(asteroid_chunk) var all_freed: bool = true for asteroid in spawned_asteroids: if is_instance_valid(asteroid): all_freed = false ok = ok and chunk_removed and all_freed print("chunk_removed=%s all_freed=%s" % [chunk_removed, all_freed]) if ok: print("TEST_ASTEROID_SPAWNING: PASS") quit(0) else: print("TEST_ASTEROID_SPAWNING: FAIL") quit(1)