#211 conquest is the fight now: a planet registers the garrison its setup authors, each defender's own death counts against it, and the last one takes the world. The capture points - stand in a ring, wait out a timer, once per ring - are gone, along with the editor palette that placed them. #212 conquest opens a dialog on the surface naming the research points recovered; the award travels with the signal, after it is banked. #209 the prologue's charted sectors go with the breach, so the map the survivors keep is of where they now are - and "chart 30 sectors" is no longer complete before they have flown anywhere. #218 enemy variety is gated on the FLEET, not on wave strength: a starting fleet meets chasers only at any wave number, and each vessel past three opens one more kind. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019AJMdB7iuHuLFZM1aBu3GD
140 lines
5.1 KiB
GDScript
140 lines
5.1 KiB
GDScript
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
|
|
# 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"), [])
|
|
# A profile is allowed to have no scenery of its own - the research lab
|
|
# had none but the capture rings, and #211 took those away. The rule is
|
|
# about features that EXIST, so an empty profile is skipped rather than
|
|
# failed; the run-wide check below is what stops this test quietly
|
|
# checking nothing at all.
|
|
if features.is_empty():
|
|
print("%s: no drawing surface features of its own" \
|
|
% 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():
|
|
if checked == 0:
|
|
return _fail("no profile had a single drawing feature - this test proved nothing")
|
|
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
|