spaceship/tests/test_gas_bomber_cooldowns.gd
Loïc Wisniewski 12bd5eb7bf Clip the cooldown fill with Geometry2D, not by hand
Every cooling vessel logged "Invalid polygon data, triangulation failed"
once per frame, for as long as its cooldown ran - 16 of them in a single
test_laser_vessel run. The fill was drawn anyway, so nothing looked wrong,
but it buried real errors in test logs.

_filled_hull cut each hull edge against the fill line itself, which is
Sutherland-Hodgman, and Sutherland-Hodgman is only sound for a CONVEX
subject. Hulls are not: the gas bomber has a tail notch and the distorder a
waist. On those it stitched the pieces back together with a zero-width
bridge, and a polygon with a zero-width bridge is not one the triangulator
accepts.

Geometry2D.intersect_polygons does the clipping properly and returns a list
of polygons, which is also the truer answer - a notched hull genuinely fills
as two puddles until the level rises past the notch. A weld-and-area guard
stays in front of the renderer for repeated vertices and slivers.

Zero triangulation errors across the vessel tests now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 23:30:05 +02:00

83 lines
3 KiB
GDScript

extends SceneTree
var main: Node
var fleet: Node
var skill: Node
var elapsed := 0.0
func _initialize() -> void:
var state: Node = root.get_node("GameState")
state.buildings.clear()
state.extractors.clear()
state.transporters.clear()
state.vessel_levels["unlock_gas_bomber"] = 1
# #60: shots draw from the fleet hold now - stock enough for both fires.
state.fleet_gas_cargo = 20
main = load("res://world/Main.tscn").instantiate()
root.add_child(main)
fleet = main.get_node("Fleet")
skill = main.get_node("GasBombController")
func _process(delta: float) -> bool:
elapsed += delta
if elapsed < 0.2:
return false
main.get_node("EnemySpawner").set_process(false)
main.get_node("AsteroidField").set_process(false)
# Asteroids take damage now, so anything the field already placed
# would soak a bomb meant for the enemies under test.
for hazard in get_nodes_in_group("asteroids"):
if is_instance_valid(hazard):
hazard.free()
fleet.spawn_ships(2, Vector2.ZERO, PackedStringArray([
GameState.VESSEL_GAS_BOMBER, GameState.VESSEL_GAS_BOMBER]))
skill._process(0.0)
var first: Node = fleet.ships[0].get_component("gas_bomber")
var second: Node = fleet.ships[1].get_component("gas_bomber")
if not first.is_ready() or not second.is_ready() or not skill.button.enabled:
return _fail("two fresh bombers did not each begin ready")
skill.aim_target = Vector2(500, 0)
skill._fire()
skill._process(0.0)
if first.is_ready() or not second.is_ready() or not skill.button.enabled:
return _fail("the first shot disabled more than its firing bomber")
# _filled_hull returns a LIST of polygons: a notched hull fills as two
# separate puddles until the level rises past the notch, and the gas
# bomber's tail is exactly such a notch.
var hull: PackedVector2Array = fleet.ships[0].definition.hull_points
var empty: Array = first._filled_hull(hull, 0.0)
var half: Array = first._filled_hull(hull, 0.5)
var half_points: int = 0
for piece in half:
half_points += piece.size()
if not empty.is_empty() or half_points < 3:
return _fail("bomber cooldown did not produce an empty-to-filling hull")
if skill.projectiles.size() != 1:
return _fail("the first bomber did not launch exactly one projectile")
skill.aim_target = Vector2(-500, 0)
skill._fire()
skill._process(0.0)
if first.is_ready() or second.is_ready() or skill.button.enabled:
return _fail("the skill stayed available after both bombers fired")
if skill.projectiles.size() != 2:
return _fail("independent bombers could not have shots in flight together")
# Rearming one bomber restores one available shot without touching the
# other bomber's cooldown.
first.tick(5.0) # matches GasBombController.COOLDOWN
skill._process(0.0)
if not first.is_ready() or second.is_ready() or not skill.button.enabled:
return _fail("one completed cooldown did not restore exactly one shot")
print("TEST_GAS_BOMBER_COOLDOWNS: PASS")
quit(0)
return true
func _fail(message: String) -> bool:
print("TEST_GAS_BOMBER_COOLDOWNS: FAIL (%s)" % message)
quit(1)
return true