spaceship/tests/test_black_hole.gd

200 lines
8.5 KiB
GDScript3
Raw Normal View History

extends SceneTree
# Headless gameplay test: run with
# godot --headless -s res://tests/test_black_hole.gd
# Forgejo #55: a rare landable body yielding dark matter, obtainable only
# through an extractor - never a surface pickup.
#
# Covers:
# A. WorldGen.planet_data_at_chunk() is a pure, deterministic function of
# chunk coordinates, and black holes are noticeably rarer than gas
# giants across a wide scan
# B. dark matter is fully generic - in resource_types(), its own
# ResourceDefinition/glyph/colour, distinct from every other resource
# C. landing on a black hole spawns no resource provider and no capture
# points at all (void's features_scene is null, and it has no
# authored PlanetSetup) - yet the planet is immediately conquered
# (register_capture_points treats zero required points as vacuous),
# so an extractor is buildable there right away
# D. an extractor on a black hole mines dark matter locally exactly like
# any other extractor, and a hub transporter banks it through the
# normal collection path with no black-hole-specific code
var game_state: Node
var stage: int = 0
var elapsed: float = 0.0
var scene: Node
var extractor_index: int = 0
var load_limit: int = 0
var baseline: Dictionary = {}
const BLACK_HOLE_AT := Vector2(500.0, 0.0)
const MOTHERSHIP_AT := Vector2.ZERO
const HUB_AT := Vector2(150.0, 0.0)
func _initialize() -> void:
game_state = root.get_node("GameState")
# --- A. deterministic and rare -----------------------------------------
var black_hole_count: int = 0
var gas_giant_count: int = 0
var total_planets: int = 0
for x in range(-40, 40):
for y in range(-40, 40):
var chunk: Vector2i = Vector2i(x, y)
if not WorldGen.has_planet_at_chunk(chunk):
continue
total_planets += 1
var first: Dictionary = WorldGen.planet_data_at_chunk(chunk)
var second: Dictionary = WorldGen.planet_data_at_chunk(chunk)
if first.definition_id != second.definition_id or first.local_pos != second.local_pos \
or first.shape_seed != second.shape_seed or first.name != second.name:
_fail("planet_data_at_chunk(%s) is not deterministic" % chunk)
return
if first.definition_id == "black_hole":
black_hole_count += 1
elif first.definition_id == "gas_giant":
gas_giant_count += 1
print("scanned %d planet chunks: %d black holes, %d gas giants" % [
total_planets, black_hole_count, gas_giant_count
])
if black_hole_count == 0:
_fail("no black holes generated across a wide scan - the rare outcome never fires")
return
if black_hole_count >= gas_giant_count:
_fail("black holes (%d) are not rarer than gas giants (%d)" % [black_hole_count, gas_giant_count])
return
# --- B. dark matter is fully generic -----------------------------------
if not game_state.resource_types().has(GameState.RESOURCE_DARK_MATTER):
_fail("dark_matter is not in resource_types() - every resource menu would miss it")
return
var definition: Resource = game_state.resource_definition(GameState.RESOURCE_DARK_MATTER)
if definition == null or definition.glyph_scene == null:
_fail("dark_matter has no ResourceDefinition or glyph_scene")
return
var dm_color: Color = game_state.resource_color(GameState.RESOURCE_DARK_MATTER)
for other in [GameState.RESOURCE_ORE, GameState.RESOURCE_GAS, GameState.RESOURCE_ENERGY]:
if dm_color == game_state.resource_color(other):
_fail("dark_matter shares a colour with %s" % other)
return
print("dark matter is generic: in resource_types(), own definition, own colour %s" % dm_color)
# --- structural: the black hole definition itself -----------------------
var black_hole_def: Resource = load("res://PlanetDefinitions.gd").get_definition("black_hole")
if black_hole_def.resource_id != "dark_matter":
_fail("black_hole planet's resource_id is '%s', expected 'dark_matter'" % black_hole_def.resource_id)
return
if black_hole_def.surface_profile_id != "void":
_fail("black_hole planet's surface_profile_id is '%s', expected 'void'" % black_hole_def.surface_profile_id)
return
var void_profile: Resource = load("res://PlanetSurfaceProfiles.gd").get_profile("void")
if void_profile.features_scene != null:
_fail("void surface profile has a features_scene - dark matter must never be a surface pickup")
return
game_state.buildings.clear()
game_state.extractors.clear()
game_state.transporters.clear()
game_state.planet_capture.clear()
game_state.planet_id = "test_black_hole"
game_state.planet_name = "Event Zero"
game_state.planet_definition_id = "black_hole"
scene = load("res://PlanetSurface.tscn").instantiate()
root.add_child(scene)
current_scene = scene
func _process(delta: float) -> bool:
elapsed += delta
match stage:
0:
# --- C. no resource provider, no capture points, yet conquered ---
if elapsed < 0.3:
return false
var feature_host: Node = scene.get_node("SurfaceFeatures")
if feature_host.get_child_count() != 0:
return _fail("black hole surface spawned %d children under SurfaceFeatures - expected none" % feature_host.get_child_count())
if not game_state.is_planet_conquered(game_state.planet_id):
return _fail("a black hole with no capture points required is not marked conquered")
print("no resource provider, no capture points, already conquered")
# Snapshot rather than assert > 0: bank_inventory starts with 500
# ore by default (a fresh game's starting stock), unrelated to
# this planet - "nothing was picked up" has to mean "unchanged",
# not "still zero".
for resource_type in game_state.resource_types():
baseline[resource_type] = game_state.banked_resource(resource_type) + game_state.fleet_resource(resource_type)
var fleet: Node = scene.get_node("Fleet")
for ship in fleet.ships:
ship.position += Vector2(300.0, 200.0)
fleet.move_to(fleet.get_centroid())
elapsed = 0.0
stage = 1
return false
1:
# --- C continued: flying around picks up nothing, ever -----------
if elapsed < 2.0:
return false
for resource_type in game_state.resource_types():
var total: int = game_state.banked_resource(resource_type) + game_state.fleet_resource(resource_type)
if total != baseline.get(resource_type, 0):
return _fail("something (%s) was picked up over a black hole surface" % resource_type)
print("flew around for %.1fs, nothing picked up" % elapsed)
# --- D: extractor mining + transporter banking --------------------
game_state.buildings.clear()
game_state.extractors.clear()
game_state.transporters.clear()
game_state.reset_progression()
for node in GameState.SKILL_TREE:
game_state.unlocked.append(node.id)
game_state.add_building(GameState.BUILDING_MOTHERSHIP, MOTHERSHIP_AT)
game_state.add_building(GameState.BUILDING_TRANSPORT_HUB, HUB_AT)
game_state.add_extractor("Event Zero", BLACK_HOLE_AT, GameState.RESOURCE_DARK_MATTER, "test_black_hole_extractor")
extractor_index = game_state.extractor_index_at("test_black_hole_extractor")
if extractor_index < 0:
return _fail("add_extractor did not register the black hole extractor")
load_limit = game_state.transporter_load_limit()
elapsed = 0.0
stage = 2
return false
2:
# Mining is local, exactly like ore - no black-hole-specific code.
if elapsed < 1.2:
return false
var mined: int = game_state.extractor_resource(extractor_index, GameState.RESOURCE_DARK_MATTER)
if mined <= 0:
return _fail("extractor mined no dark matter locally after %.1fs" % elapsed)
print("extractor mined %d dark matter locally, bank still %d" % [
mined, game_state.banked_resource(GameState.RESOURCE_DARK_MATTER)
])
# Force a full load directly (bypasses waiting out the real
# mining rate for the rest) so the transporter pickup dispatch
# fires without a long extra wait.
game_state.extractors[extractor_index].inventory.set_amount(GameState.RESOURCE_DARK_MATTER, load_limit)
elapsed = 0.0
stage = 3
return false
3:
# --- a transporter collects the full load and banks it -----------
if game_state.banked_resource(GameState.RESOURCE_DARK_MATTER) >= load_limit:
print("transporter banked %d dark matter" % game_state.banked_resource(GameState.RESOURCE_DARK_MATTER))
print("TEST_BLACK_HOLE: PASS")
quit(0)
return true
if elapsed > 10.0:
return _fail("dark matter was never collected to the bank (extractor=%d bank=%d)" % [
game_state.extractor_resource(extractor_index, GameState.RESOURCE_DARK_MATTER),
game_state.banked_resource(GameState.RESOURCE_DARK_MATTER),
])
return false
return false
func _fail(message: String) -> bool:
print("TEST_BLACK_HOLE: FAIL (%s)" % message)
quit(1)
return true