#91: keep the gas source behind the fleet
On a gas planet the vents were drawn on top of the player. SurfaceFeatures is added after Fleet in PlanetSurface.tscn, so at the default z_index of 0 the vent field won the tie purely by being the later sibling, and the fleet flew under its own gas source. Same one-line fix, for the same reason, as #50's capture rings and the rocks on a rocky surface. It goes on the field rather than on children because this one draws in its own _draw(). The test checks the rule instead of the node that was wrong: every drawing feature, on every surface profile that has features, must end up behind the fleet - 22 of them today, and any added later without editing the test. Enemies are excluded and the test says why; they are combatants and share the fleet's plane on purpose. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
b1d179ec9c
commit
03b1c85569
3 changed files with 142 additions and 0 deletions
|
|
@ -13,6 +13,14 @@ var vents: Array[Vector2] = []
|
||||||
var progress: float = 0.0
|
var progress: float = 0.0
|
||||||
var emission_phase: float = 0.0
|
var emission_phase: float = 0.0
|
||||||
func _ready() -> void:
|
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()
|
var rng := RandomNumberGenerator.new()
|
||||||
rng.seed = hash(GameState.planet_id if GameState.planet_id != "" else GameState.planet_name)
|
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)))
|
for i in range(6): vents.append(Vector2(rng.randf_range(-800,800), rng.randf_range(-650,650)))
|
||||||
|
|
|
||||||
133
tests/test_surface_feature_depth.gd
Normal file
133
tests/test_surface_feature_depth.gd
Normal file
|
|
@ -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
|
||||||
1
tests/test_surface_feature_depth.gd.uid
Normal file
1
tests/test_surface_feature_depth.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://c2jc6arbx322h
|
||||||
Loading…
Reference in a new issue