#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 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)))
|
||||
|
|
|
|||
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