extends SceneTree # Headless gameplay test: run with # godot --headless -s res://tests/test_building_redraw_cost.gd # Animated buildings must not re-run _draw() to animate (Forgejo issues #6 # and #10). # # Every NeonDraw call expands into four antialiased polylines, so redrawing # a whole shipyard each frame to advance a crane re-tessellates all of it. # The moving elements are now separate NeonPiece children, rasterised once # and thereafter only moved, scaled, faded or hidden - operations that reuse # the cached geometry. Ship.gd and Asteroid.gd already worked this way. # # Verifies: # 1. an animated building's geometry is rasterised exactly ONCE, however # long it animates # 2. it is genuinely still animating (so this cannot be "passed" by # freezing the animation) # 3. the moving parts really do move - the crane travels and the # mothership's satellites orbit - proving the animation reaches the # nodes rather than being silently dropped # 4. the static command centre needs no per-frame work either var scene: Node var field: Node var crafter: Node2D var mothership: Node2D var command: Node2D var stage: int = 0 var elapsed: float = 0.0 var crane_x_start: float = 0.0 var satellite_rotation_start: float = 0.0 func _initialize() -> void: var game_state: Node = root.get_node("GameState") game_state.buildings.clear() game_state.extractors.clear() scene = load("res://Main.tscn").instantiate() root.add_child(scene) field = scene.get_node("BuildingField") func _process(delta: float) -> bool: elapsed += delta match stage: 0: scene.get_node("AsteroidField").set_process(false) scene.get_node("EnemySpawner").set_process(false) field.place(GameState.BUILDING_SHIP_CRAFTER, Vector2(600.0, 0.0)) field.place(GameState.BUILDING_MOTHERSHIP, Vector2(-600.0, 0.0)) field.place(GameState.BUILDING_COMMAND_CENTER, Vector2(0.0, 600.0)) elapsed = 0.0 stage = 1 return false 1: if elapsed < 0.3: return false for b in field.spawned: if not is_instance_valid(b): continue match b.building_type: GameState.BUILDING_SHIP_CRAFTER: crafter = b.visual GameState.BUILDING_MOTHERSHIP: mothership = b.visual GameState.BUILDING_COMMAND_CENTER: command = b.visual if crafter == null or mothership == null or command == null: print("TEST_BUILDING_REDRAW_COST: FAIL (buildings never spawned)") quit(1) return true crane_x_start = crafter._crane.position.x satellite_rotation_start = mothership._satellites.rotation elapsed = 0.0 stage = 2 return false 2: # Long enough to cross several assembly phases and a good arc of # the satellites' orbit. if elapsed < 2.5: return false print("rasterisations after %.1fs of animation - crafter:%d mothership:%d command:%d" % [ elapsed, crafter.draw_count, mothership.draw_count, command.draw_count ]) for b in [crafter, mothership, command]: if b.draw_count != 1: print("TEST_BUILDING_REDRAW_COST: FAIL (%s redrew its geometry %d times)" % [ b.name, b.draw_count ]) quit(1) return true var animating: bool = crafter.anim_time > 0.0 and mothership.phase_time > 0.0 var crane_moved: bool = absf(crafter._crane.position.x - crane_x_start) > 1.0 var orbit_moved: bool = absf(mothership._satellites.rotation - satellite_rotation_start) > 0.05 print("still animating=%s crane travelled=%s satellites orbited=%s" % [ animating, crane_moved, orbit_moved ]) if not (animating and crane_moved and orbit_moved): print("TEST_BUILDING_REDRAW_COST: FAIL (nothing actually moved - not a fix)") quit(1) return true print("TEST_BUILDING_REDRAW_COST: PASS") quit(0) return true return false