CapturePoint tracks neutral/blocked/capturing/captured state by polling proximity each frame (a ship in capture_radius, no enemy within enemy_clear_radius), draining gradually rather than resetting when interrupted, and calling GameState.mark_point_captured() on completion. PlanetSetupHost now instantiates the real CapturePoint scene per authored point (restoring already-captured state immediately on re-landing) and spawns each setup's authored enemies, replacing the placeholder markers from #42. CaptureLabel is a small self-polling HUD (same convention as ResourceCounter) showing "CAPTURED n/m" or "CONQUERED" with a one-shot fade on completion, themed per surface profile alongside the existing bank/fleet captions. test_planet_atmosphere_transition.gd's "no enemies on a landed surface" assumption no longer holds now that setups can place guard enemies by design - narrowed the check to what it actually needs to prove: no space-side EnemySpawner/AsteroidField node leaks across the scene swap. Part of the #40 conquest-mechanic breakdown.
140 lines
5.6 KiB
GDScript
140 lines
5.6 KiB
GDScript
extends SceneTree
|
|
|
|
# Headless gameplay test: run with
|
|
# godot --headless -s res://tests/test_planet_setups.gd
|
|
# Verifies:
|
|
# 1. setup_for_planet is deterministic per planet_id and only returns
|
|
# setups matching the requested surface profile
|
|
# 2. dropping a new .tres into res://PlanetSetups/ makes it appear via
|
|
# PlanetSetups.rescan() with no other code change
|
|
# 3. landing on a rocky/gas planet composes a PlanetSetupHost carrying the
|
|
# resolved setup, with one marker per capture point
|
|
|
|
const SCRATCH_SETUP_PATH: String = "res://PlanetSetups/_test_scratch_setup.tres"
|
|
|
|
# Resolved via load() inside _initialize(), NOT a top-level `const ...
|
|
# preload(...)` - a top-level preload in a script-mode ENTRY point (-s
|
|
# this_file.gd) evaluates during this file's own early compilation, which
|
|
# can happen before autoloads are registered, and CapturePoint.gd
|
|
# references GameState in a function body (resolved at compile time). See
|
|
# test_capture_points.gd for the full explanation.
|
|
var PLANET_SETUP_SCRIPT: Script
|
|
var CAPTURE_POINT_SCRIPT: Script
|
|
|
|
var game_state: Node
|
|
var rocky_setup: Resource
|
|
var stage: int = 0
|
|
var elapsed: float = 0.0
|
|
var pending_surface: Node
|
|
|
|
|
|
func _initialize() -> void:
|
|
PLANET_SETUP_SCRIPT = load("res://PlanetSetup.gd")
|
|
CAPTURE_POINT_SCRIPT = load("res://CapturePoint.gd")
|
|
# --- 1. determinism and profile filtering ---
|
|
rocky_setup = PlanetSetups.setup_for_planet("chunk:3:7", "rocky")
|
|
if rocky_setup == null:
|
|
return _fail("no rocky setup resolved for a rocky planet id")
|
|
if rocky_setup.surface_profile_id != "rocky":
|
|
return _fail("resolved setup does not match the requested profile")
|
|
var rocky_setup_again: Resource = PlanetSetups.setup_for_planet("chunk:3:7", "rocky")
|
|
if rocky_setup != rocky_setup_again:
|
|
return _fail("setup_for_planet was not deterministic for the same planet id")
|
|
|
|
var gas_setup: Resource = PlanetSetups.setup_for_planet("chunk:3:7", "gas")
|
|
if gas_setup == null:
|
|
return _fail("no gas setup resolved for the same planet id under the gas profile")
|
|
if gas_setup.surface_profile_id != "gas":
|
|
return _fail("gas request returned a mismatched-profile setup")
|
|
|
|
for setup in PlanetSetups.setups_for_profile("rocky"):
|
|
if setup.surface_profile_id != "rocky":
|
|
return _fail("setups_for_profile('rocky') leaked a non-rocky setup")
|
|
|
|
# --- 2. dropping a new .tres is picked up by rescan() ---
|
|
var before_count: int = PlanetSetups.setups_for_profile("rocky").size()
|
|
var scratch: Resource = PLANET_SETUP_SCRIPT.new()
|
|
scratch.setup_id = "_test_scratch_setup"
|
|
scratch.display_name = "Scratch"
|
|
scratch.surface_profile_id = "rocky"
|
|
scratch.capture_points = PackedVector2Array([Vector2(100, 100)])
|
|
var save_err: int = ResourceSaver.save(scratch, SCRATCH_SETUP_PATH)
|
|
if save_err != OK:
|
|
return _fail("could not save the scratch setup for the pickup check (err %d)" % save_err)
|
|
PlanetSetups.rescan()
|
|
var after_count: int = PlanetSetups.setups_for_profile("rocky").size()
|
|
DirAccess.remove_absolute(ProjectSettings.globalize_path(SCRATCH_SETUP_PATH))
|
|
var import_file: String = SCRATCH_SETUP_PATH + ".import"
|
|
if FileAccess.file_exists(import_file):
|
|
DirAccess.remove_absolute(ProjectSettings.globalize_path(import_file))
|
|
PlanetSetups.rescan()
|
|
if after_count != before_count + 1:
|
|
return _fail("registry did not pick up a newly saved setup (before=%d after=%d)" % [
|
|
before_count, after_count
|
|
])
|
|
|
|
# --- 3. landing composes a PlanetSetupHost with the resolved setup ---
|
|
# Deferred to _process(): a scene instantiated and added to the tree
|
|
# inside _initialize() has not had its _ready() chain flushed yet (this
|
|
# runs before the engine's first frame), so children added during
|
|
# PlanetSurfaceController._ready() are not observable until a frame has
|
|
# actually processed - see test_gas_system.gd for the same pattern.
|
|
game_state = root.get_node("GameState")
|
|
game_state.planet_name = "Setup Test"
|
|
game_state.planet_id = "chunk:3:7"
|
|
game_state.planet_definition_id = "rocky"
|
|
pending_surface = load("res://PlanetSurface.tscn").instantiate()
|
|
root.add_child(pending_surface)
|
|
|
|
|
|
func _process(delta: float) -> bool:
|
|
elapsed += delta
|
|
match stage:
|
|
0:
|
|
if elapsed < 0.1:
|
|
return false
|
|
var host: Node = pending_surface.get_node_or_null("SurfaceFeatures/PlanetSetupHost")
|
|
if host == null:
|
|
pending_surface.free()
|
|
return _fail("landing did not compose a PlanetSetupHost")
|
|
if host.setup != rocky_setup:
|
|
pending_surface.free()
|
|
return _fail("host did not receive the deterministically resolved setup")
|
|
var point_count: int = 0
|
|
for child in host.get_children():
|
|
if child.get_script() == CAPTURE_POINT_SCRIPT:
|
|
point_count += 1
|
|
if point_count != rocky_setup.capture_points.size():
|
|
pending_surface.free()
|
|
return _fail("host placed %d capture points for %d authored ones" % [
|
|
point_count, rocky_setup.capture_points.size()
|
|
])
|
|
pending_surface.free()
|
|
|
|
game_state.planet_definition_id = "gas_giant"
|
|
pending_surface = load("res://PlanetSurface.tscn").instantiate()
|
|
root.add_child(pending_surface)
|
|
elapsed = 0.0
|
|
stage = 1
|
|
return false
|
|
1:
|
|
if elapsed < 0.1:
|
|
return false
|
|
var gas_host: Node = pending_surface.get_node_or_null("SurfaceFeatures/PlanetSetupHost")
|
|
if gas_host == null or gas_host.setup.surface_profile_id != "gas":
|
|
pending_surface.free()
|
|
return _fail("gas landing did not compose a matching-profile setup host")
|
|
pending_surface.free()
|
|
|
|
print("TEST_PLANET_SETUPS: PASS")
|
|
quit(0)
|
|
return true
|
|
return false
|
|
|
|
|
|
func _fail(message: String) -> bool:
|
|
print("TEST_PLANET_SETUPS: FAIL (%s)" % message)
|
|
if FileAccess.file_exists(SCRATCH_SETUP_PATH):
|
|
DirAccess.remove_absolute(ProjectSettings.globalize_path(SCRATCH_SETUP_PATH))
|
|
quit(1)
|
|
return true
|