diff --git a/surface/GasVentField.gd b/surface/GasVentField.gd index 29f2f82..e2476ce 100644 --- a/surface/GasVentField.gd +++ b/surface/GasVentField.gd @@ -13,6 +13,14 @@ var vents: Array[Vector2] = [] var progress: float = 0.0 var emission_phase: float = 0.0 func _ready() -> void: + # #91: the vents are ground features and must never cover the fleet. + # SurfaceFeatures is added AFTER Fleet in PlanetSurface.tscn, so at the + # default z_index of 0 the later sibling simply wins and the player flies + # UNDER their own gas source. Same fix, and the same reason, as #50's + # capture rings and the rocks on a rocky surface (Rock.gd) - the + # difference is that this field draws in its own _draw() rather than + # spawning child nodes, so the depth belongs on the field itself. + z_index = -1 var rng := RandomNumberGenerator.new() rng.seed = hash(GameState.planet_id if GameState.planet_id != "" else GameState.planet_name) for i in range(6): vents.append(Vector2(rng.randf_range(-800,800), rng.randf_range(-650,650))) diff --git a/tests/test_surface_feature_depth.gd b/tests/test_surface_feature_depth.gd new file mode 100644 index 0000000..c6f2af3 --- /dev/null +++ b/tests/test_surface_feature_depth.gd @@ -0,0 +1,133 @@ +extends SceneTree + +# Headless test: run with +# godot --headless -s res://tests/test_surface_feature_depth.gd +# Forgejo #91: on a gas planet the gas source was drawn ON TOP of the player. +# +# SurfaceFeatures is added after Fleet in PlanetSurface.tscn, so anything +# down there that keeps the default z_index of 0 wins the tie against the +# ships purely by being the later sibling. The player then flies under their +# own scenery, which reads as a rendering fault rather than as depth. +# +# Checks the RULE rather than the one node that was wrong: every feature that +# actually draws, on every surface profile that has features, must end up +# behind the fleet. A feature added later is covered without editing this. +# +# Covers: +# A. the gas giant's vent field draws behind the ships (the #91 report) +# B. the same for every other drawing feature on every profile, rocks and +# capture rings included +# C. the fleet's own ships are where the comparison says they are, so a +# future change that pushed the ships back instead cannot make A and B +# pass by moving the goalposts + +const PROFILE_PLANETS: Array = ["gas_giant", "rocky", "research_lab"] + +var game_state: Node +var surface: Node +var planets: Array = [] +var checked: int = 0 +var stage: int = 0 +var elapsed: float = 0.0 + + +func _initialize() -> void: + # The autoload is reached by node, not by the bare `GameState` name: from + # a SceneTree script that name resolves only far enough for constants, + # and an instance member through it is a COMPILE error for the whole + # file. Every other test here does the same. + game_state = root.get_node("GameState") + planets = PROFILE_PLANETS.duplicate() + game_state.reset_progression() + game_state.surviving_ship_count = 3 + _load_next() + + +func _load_next() -> void: + if surface != null and is_instance_valid(surface): + surface.queue_free() + surface = null + if planets.is_empty(): + return + var definition_id: String = String(planets.pop_front()) + game_state.planet_definition_id = definition_id + game_state.planet_name = "Depth Probe" + game_state.planet_id = "depth-probe-%s" % definition_id + surface = load("res://surface/PlanetSurface.tscn").instantiate() + surface.get_node("SurfaceController").fade_duration = 0.0 + root.add_child(surface) + + +# z_index is RELATIVE to the parent by default, so a node's real depth is the +# running sum up the chain - reading z_index alone would call a +1 child of a +# -1 parent "behind", which it is not. +func _effective_z(node: CanvasItem) -> int: + var total: int = 0 + var current: Node = node + while current is CanvasItem: + total += (current as CanvasItem).z_index + if not (current as CanvasItem).z_as_relative: + break + current = current.get_parent() + return total + + +func _drawing_descendants(node: Node, found: Array) -> Array: + for child in node.get_children(): + # Enemies are spawned under SurfaceFeatures by the planet's setup + # markers, but they are combatants, not scenery: they share the + # fleet's plane on purpose, the same way they do in space. Only the + # ground the fight happens on has to stay behind it. + if child.is_in_group("enemies"): + continue + # has_method("_draw") is true only where a script defines one, which + # is exactly "this node puts pixels on the surface". Plain Node2D + # hosts that only spawn their children draw nothing themselves and + # are free to sit at 0 - the rocky surface's RockField is one. + if child is CanvasItem and child.has_method("_draw"): + found.append(child) + _drawing_descendants(child, found) + return found + + +func _process(delta: float) -> bool: + elapsed += delta + if elapsed < 0.35: + return false + elapsed = 0.0 + + var fleet: Node2D = surface.get_node("Fleet") + if fleet.ships.is_empty(): + return _fail("no ships spawned, so there is nothing to be in front of") + var ship_z: int = _effective_z(fleet.ships[0]) + if ship_z != 0: + # C. The comparison below is "behind the ships", and the ships being + # at 0 is what makes "negative" mean that. + return _fail("ships are at z=%d, not 0 - this test's premise moved" % ship_z) + + var features: Array = _drawing_descendants(surface.get_node("SurfaceFeatures"), []) + if features.is_empty(): + return _fail("%s has no drawing surface features to check" % game_state.planet_definition_id) + for feature in features: + var z: int = _effective_z(feature) + if z >= ship_z: + return _fail("%s on %s draws at z=%d, in front of the fleet at z=%d" % [ + feature.name, game_state.planet_definition_id, z, ship_z + ]) + print("%s: %d drawing features, all behind the fleet" % [ + game_state.planet_definition_id, features.size() + ]) + checked += features.size() + + if planets.is_empty(): + print("TEST_SURFACE_FEATURE_DEPTH: PASS (%d features checked)" % checked) + quit(0) + return true + _load_next() + return false + + +func _fail(message: String) -> bool: + print("TEST_SURFACE_FEATURE_DEPTH: FAIL (%s)" % message) + quit(1) + return true diff --git a/tests/test_surface_feature_depth.gd.uid b/tests/test_surface_feature_depth.gd.uid new file mode 100644 index 0000000..2ad8ca7 --- /dev/null +++ b/tests/test_surface_feature_depth.gd.uid @@ -0,0 +1 @@ +uid://c2jc6arbx322h