diff --git a/BuildController.gd b/BuildController.gd index 3fd7f04..c9bc7d0 100644 --- a/BuildController.gd +++ b/BuildController.gd @@ -197,8 +197,6 @@ var context_actions_expanded: bool = false # Gap between the last resource row and the first row of HUD buttons. const HUD_COLUMN_MARGIN: float = 18.0 -# Vertical gap between one row of HUD buttons and the next. -const HUD_ROW_GAP: float = 16.0 # Seats the top-anchored HUD button columns below the resource readout instead @@ -440,17 +438,8 @@ func _process(delta: float) -> void: func _layout_context_buttons() -> void: # Build is permanently under the resource counter. The contextual berth # directly below it is compact until it has more than one real choice. - # - # "Directly below it" is MEASURED from the build button rather than - # written down as a y. This used to hardcode 324, which was the right - # answer only while the build button sat at the 160 the scene authored - - # once _seat_hud_columns() started moving the whole block down to clear - # the resource readout, this ran on the next context change and dropped - # the action row straight back on top of the build button. - var row_top: float = build_button.position.y + build_button.size.y + HUD_ROW_GAP - var row_height: float = build_button.size.y - action_button.offset_top = row_top - action_button.offset_bottom = row_top + row_height + action_button.offset_top = 324 + action_button.offset_bottom = 472 var buttons: Array = _context_action_buttons() if not context_actions_expanded: for button in buttons: @@ -464,8 +453,8 @@ func _layout_context_buttons() -> void: button.visible = true button.offset_left = 188 + i * 164 button.offset_right = button.offset_left + 148 - button.offset_top = row_top - button.offset_bottom = row_top + row_height + button.offset_top = 324 + button.offset_bottom = 472 func _context_action_buttons() -> Array: diff --git a/GameState.gd b/GameState.gd index 5d514bf..c41a18b 100644 --- a/GameState.gd +++ b/GameState.gd @@ -5,10 +5,6 @@ const RESOURCE_DEFINITIONS = preload("res://ResourceDefinitions.gd") const BUILDING_DEFINITIONS = preload("res://BuildingDefinitions.gd") const VESSEL_DEFINITIONS = preload("res://VesselDefinitions.gd") const PLANET_DEFINITIONS = preload("res://PlanetDefinitions.gd") -# By path, not by its `WorldGen` global name: an autoload compiles before the -# global class table is necessarily populated, the same hazard PlanetSetups.gd -# works around. -const WORLD_GEN = preload("res://WorldGen.gd") # Autoload singleton - the one thing that survives change_scene_to_file() @@ -18,7 +14,7 @@ const WORLD_GEN = preload("res://WorldGen.gd") # survive the trip has to live here instead. var has_return_position: bool = false -var surviving_ship_count: int = STARTING_SHIP_COUNT +var surviving_ship_count: int = 5 var surviving_vessel_types: PackedStringArray = PackedStringArray() var return_world_position: Vector2 = Vector2.ZERO var return_camera_zoom: float = 1.0 @@ -56,20 +52,6 @@ func set_fleet_speed_multiplier(value: float) -> void: signal research_points_changed(total: int) var research_points: int = 0 -# Set only by start_dev_game(), which the main menu offers only when running -# from Godot. Makes WorldGen plant one of every planet type around the spawn. -# Cleared by reset_new_game(), so starting a normal game after a dev one -# cannot leave a dev world behind. -# -# The setter mirrors it into WorldGen, which cannot read it back: world -# generation is static and dependency-free by design. This stays the single -# place to set it - assigning dev_mode anywhere, tests included, reaches -# world generation. -var dev_mode: bool = false: - set(value): - dev_mode = value - WORLD_GEN.dev_planets_enabled = value - # Explored-chunk history for the world map's fog-of-war reveal. Lives # here (not on ExplorationTracker, which gets destroyed and recreated on # every scene swap) so exploration progress isn't lost every time the @@ -86,11 +68,7 @@ const RESOURCE_TYPES: PackedStringArray = RESOURCE_DEFINITIONS.ORDER # All resource-bearing locations use the same container. The old public # names below are computed compatibility accessors, not separate storage. -# Starts EMPTY. A new game used to open with 500 ore banked, which quietly -# skipped the opening of the game - the first mining run is what teaches the -# fleet hold, the return trip and the mothership. Dev mode (start_dev_game) -# is where a stocked start belongs. -var bank_inventory: RefCounted = INVENTORY.new() +var bank_inventory: RefCounted = INVENTORY.new({RESOURCE_ORE: 500}) var fleet_inventory: RefCounted = INVENTORY.new() var rocks: int: @@ -346,11 +324,7 @@ const SKILL_TREE: Array = [ "desc": "Build gas burners. Each converts gas into energy at its own site - hub transporters keep it stocked and collect what it makes.", }, { - # Cheaper than it was (30 ore / 1 point): the fleet now starts at - # BASE_FLEET_SIZE 3 rather than 5, so the first two doctrines are no - # longer a mid-game luxury but the ordinary way out of the opening. - # Pricing them as a luxury made the early game feel stuck. - "id": "wing", "name": "Wing Doctrine", "cost": 15, "requires": "coordination", + "id": "wing", "name": "Wing Doctrine", "cost": 30, "requires": "coordination", "x": 0.82, "fleet_bonus": 1, "research_points": 1, "desc": "Permanent: +1 vessel in the fleet.", }, @@ -361,41 +335,18 @@ const SKILL_TREE: Array = [ # prerequisite, also x=0.82) instead, so the whole fleet-size # doctrine branch now runs straight rather than zigzagging into # someone else's lane. - # Also cut (70 ore / 2 points), for the same reason as wing. - "id": "squadron", "name": "Squadron Doctrine", "cost": 35, "requires": "wing", - "x": 0.82, "fleet_bonus": 1, "research_points": 1, - "desc": "Permanent: +1 vessel in the fleet.", - }, - { - "id": "armada", "name": "Armada Doctrine", "cost": 90, "requires": "squadron", + "id": "squadron", "name": "Squadron Doctrine", "cost": 70, "requires": "wing", "x": 0.82, "fleet_bonus": 1, "research_points": 2, "desc": "Permanent: +1 vessel in the fleet.", }, { - # The last two tiers grant +2 rather than +1. Seven single-vessel - # nodes in one lane would be a long, monotonous chain for a branch - # that only ever does one thing; two larger, costlier steps carry the - # ceiling from 6 to 10 while keeping the branch a readable length. - "id": "battlegroup", "name": "Battlegroup Doctrine", "cost": 180, "requires": "armada", - "x": 0.82, "fleet_bonus": 2, "research_points": 4, - "desc": "Permanent: +2 vessels in the fleet.", - }, - { - "id": "grand_fleet", "name": "Grand Fleet Doctrine", "cost": 320, "requires": "battlegroup", - "x": 0.82, "fleet_bonus": 2, "research_points": 6, - "desc": "Permanent: +2 vessels in the fleet.", + "id": "armada", "name": "Armada Doctrine", "cost": 150, "requires": "squadron", + "x": 0.82, "fleet_bonus": 1, "research_points": 3, + "desc": "Permanent: +1 vessel in the fleet.", }, ] -# How many SLOTS the fleet has before any doctrine is researched. Distinct -# from STARTING_SHIP_COUNT below: this is capacity, that is how many vessels -# are actually flying. The two used to be one constant, which forced a new -# game to open with a full fleet and left the first crafter with nothing to -# do - there was no room to put a vessel until a doctrine was researched. -const BASE_FLEET_SIZE: int = 3 -# A new game opens with a SINGLE vessel. Building the fleet up is the early -# game, so the two empty slots are the first thing a ship crafter is for. -const STARTING_SHIP_COUNT: int = 1 +const BASE_FLEET_SIZE: int = 5 var unlocked: Array = [] @@ -497,7 +448,7 @@ func reset_progression() -> void: # fresh game. func reset_new_game() -> void: has_return_position = false - surviving_ship_count = STARTING_SHIP_COUNT + surviving_ship_count = BASE_FLEET_SIZE surviving_vessel_types = PackedStringArray() return_world_position = Vector2.ZERO return_camera_zoom = 1.0 @@ -508,9 +459,8 @@ func reset_new_game() -> void: _distorder_drain_progress = 0.0 boost_depleted_notice = false research_points = 0 - dev_mode = false explored_chunks.clear() - bank_inventory = INVENTORY.new() + bank_inventory = INVENTORY.new({RESOURCE_ORE: 500}) fleet_inventory = INVENTORY.new() buildings.clear() next_building_id = 1 @@ -523,29 +473,6 @@ func reset_new_game() -> void: reset_formation() -# Amounts a dev game opens with: enough of everything to reach any part of -# the game immediately, without being so large that costs stop registering. -const DEV_RESOURCE_AMOUNT: int = 2000 -const DEV_RESEARCH_POINTS: int = 500 - - -# A new game stocked for play-testing: every resource banked, research points -# to spend, and one of each planet type next to the spawn (WorldGen reads -# dev_mode for that). Offered by the main menu only when running from Godot - -# see MainMenu.gd - so it cannot be reached in a shipped build. -# -# Deliberately built ON TOP of reset_new_game() rather than beside it, so a -# dev game is a normal game plus a stock rather than a second, separately -# maintained definition of what a fresh game is. -func start_dev_game() -> void: - reset_new_game() - dev_mode = true - for resource_type in resource_types(): - bank_inventory.set_amount(resource_type, DEV_RESOURCE_AMOUNT) - research_points = DEV_RESEARCH_POINTS - research_points_changed.emit(research_points) - - # --- building stock and production queue (#16) ----------------------------- # Since #12, a building cannot just deduct from the bank at the moment of @@ -1174,13 +1101,9 @@ func get_mothership_position() -> Variant: # capacity upgrade is researched, but they are defined up front so the wedge # keeps a deliberate shape as it grows rather than having positions invented # at unlock time. -# Ten slots, because max_fleet_size() caps itself on this array's length - -# so the wedge has to be long enough for every doctrine to be worth buying. -# The last pair continues the V rather than starting a new shape. const DEFAULT_FORMATION: Array = [ Vector2(0, -48), Vector2(-30, -8), Vector2(30, -8), Vector2(-60, 32), Vector2(60, 32), Vector2(-90, 72), Vector2(90, 72), Vector2(0, 56), - Vector2(-120, 112), Vector2(120, 112), ] var formation: Array = DEFAULT_FORMATION.duplicate() diff --git a/MainMenu.gd b/MainMenu.gd index bb44be4..7ea9255 100644 --- a/MainMenu.gd +++ b/MainMenu.gd @@ -1,35 +1,8 @@ extends Node2D -# The menu stacks whatever entries are actually available, rather than -# trusting the positions authored in the scene. -# -# Two of the four entries only exist when the game is run from Godot, so a -# shipped build shows a two-entry menu and an editor run shows four. Fixed -# offsets would leave holes where the hidden entries used to be - the same -# failure the HUD's button columns had, where a literal y meant "below the -# thing above me" and stopped being true when that thing moved. Here the -# stack is measured instead, and centred on whatever it ends up containing. - -const ROW_BUTTON_HEIGHT: float = 128.0 -const ROW_LABEL_OFFSET: float = 134.0 -const ROW_LABEL_HEIGHT: float = 56.0 -const ROW_HEIGHT: float = ROW_LABEL_OFFSET + ROW_LABEL_HEIGHT -const ROW_GAP: float = 20.0 -const ROW_PITCH: float = ROW_HEIGHT + ROW_GAP -# The stack is centred on this rather than on the screen, to leave the title -# above it room to breathe. -const STACK_CENTER_Y: float = 40.0 -const STATUS_GAP: float = 20.0 -const STATUS_HEIGHT: float = 70.0 - @onready var play_button: IconButton = $UI/PlayButton -@onready var play_label: Label = $UI/PlayLabel @onready var editor_button: IconButton = $UI/EditorButton -@onready var editor_label: Label = $UI/EditorLabel -@onready var dev_button: IconButton = $UI/DevModeButton -@onready var dev_label: Label = $UI/DevModeLabel @onready var quit_button: IconButton = $UI/QuitButton -@onready var quit_label: Label = $UI/QuitLabel @onready var status_label: Label = $UI/StatusLabel @@ -37,78 +10,16 @@ func _ready() -> void: play_button.pressed.connect(_on_play_pressed) editor_button.pressed.connect(_on_editor_pressed) editor_button.enabled = true - dev_button.pressed.connect(_on_dev_mode_pressed) - dev_button.enabled = true quit_button.pressed.connect(_on_quit_pressed) quit_button.visible = not OS.has_feature("web") - # Dev mode and the map editor are development tools, not features of the - # game: they are for building and play-testing it, and a player who - # installed a build has no use for either. `editor` is the feature tag - # Godot sets when running from the editor and clears in an export, which - # is exactly the "started from Godot" distinction wanted here. - var developing: bool = is_developer_build() - editor_button.visible = developing - editor_label.visible = developing - dev_button.visible = developing - dev_label.visible = developing - - _layout_stack() - - -# Wrapped so tests can state the rule they are checking rather than -# duplicating the feature-tag string. -func is_developer_build() -> bool: - return OS.has_feature("editor") - - -func _layout_stack() -> void: - var rows: Array = [] - for row in [[play_button, play_label], [editor_button, editor_label], - [dev_button, dev_label], [quit_button, quit_label]]: - if row[0].visible: - rows.append(row) - if rows.is_empty(): - return - - # The last row carries no trailing gap, so the block is one pitch per row - # minus that gap. - var block_height: float = float(rows.size()) * ROW_PITCH - ROW_GAP - var top: float = STACK_CENTER_Y - block_height * 0.5 - for row in rows: - var button: Control = row[0] - var label: Label = row[1] - button.offset_top = top - button.offset_bottom = top + ROW_BUTTON_HEIGHT - label.offset_top = top + ROW_LABEL_OFFSET - label.offset_bottom = top + ROW_LABEL_OFFSET + ROW_LABEL_HEIGHT - top += ROW_PITCH - - # Status sits under the whole stack, where a message about any entry can - # be read without it shifting the entries themselves. - var status_top: float = top - ROW_GAP + STATUS_GAP - status_label.offset_top = status_top - status_label.offset_bottom = status_top + STATUS_HEIGHT - func _on_play_pressed() -> void: GameState.reset_new_game() get_tree().change_scene_to_file("res://Main.tscn") -func _on_dev_mode_pressed() -> void: - # Guarded as well as hidden. Visibility is presentation; this is the rule. - if not is_developer_build(): - status_label.text = "Dev mode only runs from Godot" - return - GameState.start_dev_game() - get_tree().change_scene_to_file("res://Main.tscn") - - func _on_editor_pressed() -> void: - if not is_developer_build(): - status_label.text = "Map editor only runs from Godot" - return if editor_button.enabled: get_tree().change_scene_to_file("res://PlanetSetupEditor.tscn") return diff --git a/MainMenu.tscn b/MainMenu.tscn index 20899a8..7cf4450 100644 --- a/MainMenu.tscn +++ b/MainMenu.tscn @@ -97,33 +97,6 @@ theme_override_font_sizes/font_size = 32 horizontal_alignment = 1 text = "Map Editor" -[node name="DevModeButton" type="Control" parent="UI"] -anchor_left = 0.5 -anchor_right = 0.5 -anchor_top = 0.5 -anchor_bottom = 0.5 -offset_left = -64 -offset_right = 64 -offset_top = 190 -offset_bottom = 318 -script = ExtResource("2") -icon = 29 -glow_color = Color(1, 0.7, 0.25, 1) - -[node name="DevModeLabel" type="Label" parent="UI"] -mouse_filter = 2 -anchor_left = 0.5 -anchor_right = 0.5 -anchor_top = 0.5 -anchor_bottom = 0.5 -offset_left = -200 -offset_right = 200 -offset_top = 324 -offset_bottom = 380 -theme_override_font_sizes/font_size = 32 -horizontal_alignment = 1 -text = "Dev Mode" - [node name="StatusLabel" type="Label" parent="UI"] anchor_left = 0.5 anchor_right = 0.5 diff --git a/PlanetAtmosphereController.gd b/PlanetAtmosphereController.gd index 8faca25..36ac960 100644 --- a/PlanetAtmosphereController.gd +++ b/PlanetAtmosphereController.gd @@ -40,12 +40,7 @@ func _ready() -> void: camera.zoom = Vector2(GameState.return_camera_zoom, GameState.return_camera_zoom) GameState.has_return_position = false else: - # A brand new game. STARTING_SHIP_COUNT rather than a literal: this - # said 5, which was the old BASE_FLEET_SIZE written out by hand, and - # it silently became "however many slots exist" once max_fleet_size() - # clamped it. The opening fleet size is one decision and it lives in - # GameState. - fleet.spawn_ships(GameState.STARTING_SHIP_COUNT, Vector2.ZERO) + fleet.spawn_ships(5, Vector2.ZERO) # AsteroidField/PlanetField normally only populate nearby chunks once # their own check_interval timer elapses (~1s) - fine during normal diff --git a/WorldGen.gd b/WorldGen.gd index d051788..1fb1c58 100644 --- a/WorldGen.gd +++ b/WorldGen.gd @@ -29,42 +29,12 @@ const BLACK_HOLE_CHANCE: float = 0.05 # research points had become to progression. const RESEARCH_LAB_CHANCE: float = 0.16 -# DEV MODE ONLY: one of every planet type, parked in the chunks immediately -# around the spawn so all four can be reached in seconds. -# -# This replaces the guaranteed gas giant that used to sit in the origin -# chunk for every game. That was a play-testing convenience which had become -# part of the real world's shape - every player got the same landmark in the -# same place, and the surrounding space was generated as if it were normal. -# Play-testing wants ALL the planet types to hand, not just gas, and a -# normal game wants none of them handed over; those are different needs and -# they now have different mechanisms. -# -# The local positions keep each planet off the spawn point itself: a fresh -# fleet starts at world origin, which is the top-left corner of chunk (0,0). -const DEV_PLANET_CHUNKS: Dictionary = { - Vector2i(0, 0): PLANET_DEFINITIONS.GAS_GIANT_ID, - Vector2i(1, 0): PLANET_DEFINITIONS.ROCKY_ID, - Vector2i(0, 1): PLANET_DEFINITIONS.RESEARCH_LAB_ID, - Vector2i(1, 1): PLANET_DEFINITIONS.BLACK_HOLE_ID, -} -const DEV_PLANET_LOCAL_POS: Vector2 = Vector2(600.0, 600.0) - - -# Dev mode plants a fixed set of planets; a normal game must not see them. -# -# PUSHED here by GameState.dev_mode's setter rather than read back out of the -# autoload. World generation is deliberately dependency-free - it is static, -# seeded purely by chunk coordinates, and callable from anywhere - and an -# autoload cannot be named from here anyway: `GameState` is not resolvable as -# a global identifier this early, the same load-order hazard PlanetSetups.gd -# and PlanetSurfaceController.gd already work around. GameState.dev_mode -# stays the single source of truth; this is its mirror. -static var dev_planets_enabled: bool = false - - -static func _dev_planets_active() -> bool: - return dev_planets_enabled +# A predictable early gas source for play-testing. The origin chunk is +# deterministically normal space. Its explicit in-chunk position puts it +# roughly 1,080 world units from a fresh fleet: easy to reach, but not +# sitting on the spawn. +const NEARBY_GAS_GIANT_CHUNK: Vector2i = Vector2i.ZERO +const NEARBY_GAS_GIANT_LOCAL_POS: Vector2 = Vector2(900.0, 600.0) static var _noise: FastNoiseLite static var _planet_noise: FastNoiseLite @@ -106,7 +76,7 @@ static func biome_at_chunk(chunk: Vector2i) -> int: static func has_planet_at_chunk(chunk: Vector2i) -> bool: - if _dev_planets_active() and DEV_PLANET_CHUNKS.has(chunk): + if chunk == NEARBY_GAS_GIANT_CHUNK: return true var n: float = _get_planet_noise().get_noise_2d(float(chunk.x), float(chunk.y)) return (biome_at_chunk(chunk) == Biome.ASTEROID_FIELD and n > PLANET_THRESHOLD) or (biome_at_chunk(chunk) == Biome.SPACE and n > GAS_GIANT_THRESHOLD) @@ -122,9 +92,8 @@ static func planet_data_at_chunk(chunk: Vector2i) -> Dictionary: # can never drift out of sync with each other. var rng: RandomNumberGenerator = RandomNumberGenerator.new() rng.seed = hash(chunk) - var forced_definition_id: String = String(DEV_PLANET_CHUNKS.get(chunk, "")) \ - if _dev_planets_active() else "" - var local_pos: Vector2 = DEV_PLANET_LOCAL_POS if not forced_definition_id.is_empty() \ + var guaranteed_nearby_giant: bool = chunk == NEARBY_GAS_GIANT_CHUNK + var local_pos: Vector2 = NEARBY_GAS_GIANT_LOCAL_POS if guaranteed_nearby_giant \ else Vector2(rng.randf() * CHUNK_SIZE, rng.randf() * CHUNK_SIZE) var shape_seed: int = rng.randi() # Drawn unconditionally, in the same fixed position every call, @@ -136,20 +105,15 @@ static func planet_data_at_chunk(chunk: Vector2i) -> Dictionary: var research_lab_roll: float = rng.randf() # A stable per-chunk roll makes gas giants persistent landmarks rather # than a different resource whenever the scene reloads. - var gas_giant: bool = biome_at_chunk(chunk) == Biome.SPACE + var gas_giant: bool = guaranteed_nearby_giant or biome_at_chunk(chunk) == Biome.SPACE var definition_id: String = PLANET_DEFINITIONS.GAS_GIANT_ID if gas_giant else PLANET_DEFINITIONS.ROCKY_ID # A rare third outcome, seeded the same deterministic way as everything - # else here. - if black_hole_roll < BLACK_HOLE_CHANCE: + # else here. Never overrides the guaranteed early gas giant landmark - + # that chunk's whole purpose is to always be the same reachable source. + if not guaranteed_nearby_giant and black_hole_roll < BLACK_HOLE_CHANCE: definition_id = PLANET_DEFINITIONS.BLACK_HOLE_ID - elif research_lab_roll < RESEARCH_LAB_CHANCE: + elif not guaranteed_nearby_giant and research_lab_roll < RESEARCH_LAB_CHANCE: definition_id = PLANET_DEFINITIONS.RESEARCH_LAB_ID - # Dev mode's fixed planting wins over every roll above. Applied last, and - # AFTER all the rng draws, so switching it on cannot shift which random - # values any other chunk consumes - a dev world and a normal one differ - # only in these four chunks. - if not forced_definition_id.is_empty(): - definition_id = forced_definition_id var definition: Resource = PLANET_DEFINITIONS.get_definition(definition_id) var names: PackedStringArray = definition.names var planet_name: String = names[rng.randi() % names.size()] diff --git a/tests/test_dev_mode.gd b/tests/test_dev_mode.gd deleted file mode 100644 index 38b8a2f..0000000 --- a/tests/test_dev_mode.gd +++ /dev/null @@ -1,152 +0,0 @@ -extends SceneTree - -# Headless test: run with -# godot --headless -s res://tests/test_dev_mode.gd -# A "Dev Mode" start for play-testing: one of every planet type next to the -# spawn, every resource banked, and research points to spend. Offered by the -# main menu only when the game is run from Godot, alongside the map editor. -# -# The same change took the training wheels off a NORMAL game: no 500 free -# ore, and no gas giant guaranteed in the origin chunk. Both of those were -# play-testing conveniences that had become part of the real game, so this -# test covers both sides - what dev mode hands you, and what a normal game -# no longer does. -# -# Covers: -# A. a dev game banks every resource type and research points -# B. a dev game is a normal game PLUS that, not a separate definition -# C. one of each planet type sits within a chunk or two of the spawn -# D. none of that leaks into a normal game, and starting one clears it -# E. a normal game starts with an empty bank -# F. the menu gates dev mode and the map editor on the same rule - -var game_state: Node -var menu: Node -var stage: int = 0 -var elapsed: float = 0.0 - - -func _initialize() -> void: - game_state = root.get_node("GameState") - - -func _process(delta: float) -> bool: - elapsed += delta - match stage: - 0: - # --- E. a normal game hands over nothing ---------------------- - game_state.reset_new_game() - if game_state.rocks != 0: - return _fail("a normal game started with %d ore banked" % game_state.rocks) - for resource_type in game_state.resource_types(): - if game_state.banked_resource(resource_type) != 0: - return _fail("a normal game started with %s banked" % resource_type) - if game_state.research_points != 0: - return _fail("a normal game started with research points") - if game_state.dev_mode: - return _fail("a normal game started in dev mode") - print("normal game starts with an empty bank and no research points") - - # --- D. no dev planets in a normal game ----------------------- - # The dev arrangement must not survive into a normal world. Noise - # could coincidentally place a planet in one of these chunks, so - # what is asserted is that the ARRANGEMENT is not reproduced, not - # that each chunk is empty. - var reproduced: bool = true - for chunk in WorldGen.DEV_PLANET_CHUNKS: - var expected: String = String(WorldGen.DEV_PLANET_CHUNKS[chunk]) - var data: Dictionary = WorldGen.planet_data_at_chunk(chunk) - if not WorldGen.has_planet_at_chunk(chunk) or String(data.get("definition_id", "")) != expected: - reproduced = false - if Vector2(data.get("local_pos", Vector2.ZERO)) == WorldGen.DEV_PLANET_LOCAL_POS: - return _fail("a normal game placed a planet at dev mode's fixed in-chunk position, in chunk %s" % chunk) - if reproduced: - return _fail("a normal game reproduced dev mode's whole planet arrangement") - print("normal game does not carry dev mode's planets") - - # --- A + B. what a dev game hands over ------------------------ - game_state.add_building(GameState.BUILDING_MOTHERSHIP, Vector2.ZERO) - game_state.planet_capture["chunk:9:9"] = {"captured": [true], "conquered": true} - game_state.start_dev_game() - if not game_state.dev_mode: - return _fail("start_dev_game() did not set dev_mode") - for resource_type in game_state.resource_types(): - var banked: int = game_state.banked_resource(resource_type) - if banked != GameState.DEV_RESOURCE_AMOUNT: - return _fail("dev game banked %d of %s, expected %d" \ - % [banked, resource_type, GameState.DEV_RESOURCE_AMOUNT]) - if game_state.research_points != GameState.DEV_RESEARCH_POINTS: - return _fail("dev game granted %d research points, expected %d" \ - % [game_state.research_points, GameState.DEV_RESEARCH_POINTS]) - # B: it still went through reset_new_game, so a dirty run is gone. - if not game_state.buildings.is_empty() or not game_state.planet_capture.is_empty(): - return _fail("a dev game did not reset the previous run - it must be a fresh game plus a stock") - print("dev game banks %d of each resource and %d research points, from a clean slate" \ - % [GameState.DEV_RESOURCE_AMOUNT, GameState.DEV_RESEARCH_POINTS]) - - # --- C. one of each planet type, close by --------------------- - var found: Dictionary = {} - for chunk in WorldGen.DEV_PLANET_CHUNKS: - if not WorldGen.has_planet_at_chunk(chunk): - return _fail("dev mode chunk %s holds no planet" % chunk) - var data: Dictionary = WorldGen.planet_data_at_chunk(chunk) - var definition_id: String = String(data.get("definition_id", "")) - if definition_id != String(WorldGen.DEV_PLANET_CHUNKS[chunk]): - return _fail("dev mode chunk %s holds a %s, expected a %s" \ - % [chunk, definition_id, WorldGen.DEV_PLANET_CHUNKS[chunk]]) - found[definition_id] = true - # "Near the start" is the whole point - a play-tester should - # reach any of them in seconds, not go exploring. - var world_pos: Vector2 = WorldGen.chunk_origin(chunk) + Vector2(data.local_pos) - if world_pos.length() > WorldGen.CHUNK_SIZE * 2.5: - return _fail("dev mode's %s spawned %.0f units away, too far to be 'near the start'" \ - % [definition_id, world_pos.length()]) - for definition_id in [PlanetDefinitions.ROCKY_ID, PlanetDefinitions.GAS_GIANT_ID, - PlanetDefinitions.BLACK_HOLE_ID, PlanetDefinitions.RESEARCH_LAB_ID]: - if not found.has(definition_id): - return _fail("dev mode is missing a %s - it must plant one of EVERY planet type" % definition_id) - print("dev mode plants all %d planet types within %.0f units of the spawn" \ - % [found.size(), WorldGen.CHUNK_SIZE * 2.5]) - - # --- D. starting a normal game clears it ---------------------- - game_state.reset_new_game() - if game_state.dev_mode: - return _fail("starting a normal game after a dev one left dev_mode set") - if game_state.rocks != 0 or game_state.research_points != 0: - return _fail("starting a normal game after a dev one kept the dev stock") - print("starting a normal game afterwards clears dev mode entirely") - - menu = load("res://MainMenu.tscn").instantiate() - root.add_child(menu) - current_scene = menu - elapsed = 0.0 - stage = 1 - return false - 1: - # --- F. the menu gates both on the same rule ------------------ - if elapsed < 0.1: - return false - var developing: bool = menu.is_developer_build() - if menu.dev_button.visible != developing: - return _fail("the dev mode button's visibility did not follow is_developer_build()") - if menu.editor_button.visible != developing: - return _fail("the map editor button's visibility did not follow is_developer_build()") - if menu.play_button.visible != true or menu.quit_button.visible != (not OS.has_feature("web")): - return _fail("gating the development entries also hid an entry a player needs") - # Hiding is presentation; pressing it anyway must still be refused. - if not developing: - menu._on_dev_mode_pressed() - if game_state.dev_mode: - return _fail("dev mode started outside a developer build") - print("dev mode and the map editor are both gated on is_developer_build() (%s here)" % developing) - - print("TEST_DEV_MODE: PASS") - quit(0) - return true - return false - - -func _fail(message: String) -> bool: - print("TEST_DEV_MODE: FAIL (%s)" % message) - quit(1) - return true diff --git a/tests/test_enemy_collision.gd b/tests/test_enemy_collision.gd index 8ea1d12..923fe41 100644 --- a/tests/test_enemy_collision.gd +++ b/tests/test_enemy_collision.gd @@ -32,13 +32,6 @@ func _physics_process(delta: float) -> bool: # happens strictly after _ready(). Here, at the point _ready() is # already confirmed complete, it's safe. main_scene.get_node("EnemySpawner").set_process(false) - # Crew the fleet HERE, not in _initialize(), for the same reason the - # set_process(false) call above lives here: work done in _initialize() - # can be undone by the engine's deferred tree-entry finalization. A - # new game flies one vessel into three slots now, and this test needs - # a full one. - root.get_node("GameState").unlocked.append_array(["wing", "squadron", "armada"]) - fleet.spawn_ships(5, Vector2.ZERO) target_ship = fleet.ships[0] var enemy_scene: PackedScene = load("res://Enemy.tscn") enemy = enemy_scene.instantiate() diff --git a/tests/test_enemy_combat.gd b/tests/test_enemy_combat.gd index aa1951e..18e1ad3 100644 --- a/tests/test_enemy_combat.gd +++ b/tests/test_enemy_combat.gd @@ -32,13 +32,6 @@ func _physics_process(delta: float) -> bool: # happens strictly after _ready(). Here, at the point _ready() is # already confirmed complete, it's safe. main_scene.get_node("EnemySpawner").set_process(false) - # Crew the fleet HERE, not in _initialize(), for the same reason the - # set_process(false) call above lives here: work done in _initialize() - # can be undone by the engine's deferred tree-entry finalization. A - # new game flies one vessel into three slots now, and this test needs - # a full one. - root.get_node("GameState").unlocked.append_array(["wing", "squadron", "armada"]) - fleet.spawn_ships(5, Vector2.ZERO) var enemy_scene: PackedScene = load("res://Enemy.tscn") enemy = enemy_scene.instantiate() enemy.position = fleet.get_centroid() + SPAWN_OFFSET diff --git a/tests/test_enemy_mothership.gd b/tests/test_enemy_mothership.gd index 4424988..e55f778 100644 --- a/tests/test_enemy_mothership.gd +++ b/tests/test_enemy_mothership.gd @@ -54,9 +54,6 @@ var elapsed: float = 0.0 func _initialize() -> void: game_state = root.get_node("GameState") beam_script = load("res://EnemyBigLaserBeam.gd") - # Four vessels are crewed below, past BASE_FLEET_SIZE's 3 slots, so the - # capacity doctrines are granted rather than assumed. - game_state.unlocked.append_array(["wing", "squadron", "armada"]) main = load("res://Main.tscn").instantiate() root.add_child(main) fleet = main.get_node("Fleet") diff --git a/tests/test_fleet_growth.gd b/tests/test_fleet_growth.gd deleted file mode 100644 index dbbcabc..0000000 --- a/tests/test_fleet_growth.gd +++ /dev/null @@ -1,139 +0,0 @@ -extends SceneTree - -# Headless gameplay test: run with -# godot --headless -s res://tests/test_fleet_growth.gd -# A new game opens with a SINGLE vessel and three slots, and the fleet grows -# from there through the doctrine branch of the skill tree. -# -# BASE_FLEET_SIZE used to be both "how many slots exist" and "how many ships -# you start with", which forced a new game to open with a full fleet - there -# was nowhere to put a vessel until a doctrine was researched, so the first -# ship crafter had nothing to do. The two are now separate constants, and -# this test pins the distinction. -# -# Covers: -# A. a new game starts with one vessel and three slots -# B. the fleet really spawns one ship, and can be grown into its slots -# C. every doctrine raises the ceiling by its own stated bonus -# D. the doctrine branch is a chain, in increasing cost order -# E. the tree cannot promise a slot the formation has no room for - the -# invariant that made extra doctrines silently worthless - -const DOCTRINE_CHAIN: Array = ["wing", "squadron", "armada", "battlegroup", "grand_fleet"] - -var game_state: Node -var scene: Node -var fleet: Node -var stage: int = 0 -var elapsed: float = 0.0 - - -func _initialize() -> void: - game_state = root.get_node("GameState") - game_state.reset_new_game() - - -func _process(delta: float) -> bool: - elapsed += delta - match stage: - 0: - # --- A. the opening position --------------------------------- - if GameState.STARTING_SHIP_COUNT != 1: - return _fail("a new game starts with %d vessels, expected 1" % GameState.STARTING_SHIP_COUNT) - if GameState.BASE_FLEET_SIZE != 3: - return _fail("the fleet has %d slots by default, expected 3" % GameState.BASE_FLEET_SIZE) - if game_state.surviving_ship_count != GameState.STARTING_SHIP_COUNT: - return _fail("reset_new_game left %d surviving ships" % game_state.surviving_ship_count) - if game_state.max_fleet_size() != GameState.BASE_FLEET_SIZE: - return _fail("an unresearched fleet caps at %d, expected %d" \ - % [game_state.max_fleet_size(), GameState.BASE_FLEET_SIZE]) - print("new game: %d vessel, %d slots" % [GameState.STARTING_SHIP_COUNT, GameState.BASE_FLEET_SIZE]) - - # --- C + D. the doctrine chain ------------------------------- - var expected: int = GameState.BASE_FLEET_SIZE - var previous_cost: int = -1 - var previous_points: int = -1 - var previous_id: String = "coordination" - for id in DOCTRINE_CHAIN: - var node: Dictionary = game_state.skill_node(id) - if node.is_empty(): - return _fail("no skill node '%s' - the doctrine chain is broken" % id) - var bonus: int = int(node.get("fleet_bonus", 0)) - if bonus <= 0: - return _fail("'%s' is in the doctrine chain but grants no fleet_bonus" % id) - if String(node.get("requires", "")) != previous_id: - return _fail("'%s' requires '%s', expected it to follow '%s' - the branch is not one chain" \ - % [id, node.get("requires", ""), previous_id]) - # Later tiers must not be cheaper than earlier ones, or the - # order the player meets them stops matching what they cost. - var cost: int = int(node.get("cost", 0)) - var points: int = int(node.get("research_points", 0)) - if cost <= previous_cost: - return _fail("'%s' costs %d ore, not more than the tier before it (%d)" % [id, cost, previous_cost]) - if points < previous_points: - return _fail("'%s' costs %d research points, fewer than the tier before it (%d)" \ - % [id, points, previous_points]) - previous_cost = cost - previous_points = points - previous_id = id - - game_state.unlocked.append(id) - expected += bonus - if game_state.max_fleet_size() != expected: - return _fail("after researching '%s' the fleet caps at %d, expected %d" \ - % [id, game_state.max_fleet_size(), expected]) - print(" %-12s +%d -> %2d slots (%d ore, %d research)" % [id, bonus, expected, cost, points]) - - # --- E. no doctrine may be swallowed by the formation --------- - # max_fleet_size() clamps to the formation's length, so a doctrine - # added without extending DEFAULT_FORMATION would cost the player - # real resources and grant nothing. - if expected > GameState.DEFAULT_FORMATION.size(): - return _fail("the doctrines promise %d slots but DEFAULT_FORMATION only defines %d - the last ones buy nothing" \ - % [expected, GameState.DEFAULT_FORMATION.size()]) - print("full doctrine chain reaches %d slots, with %d formation positions defined" \ - % [expected, GameState.DEFAULT_FORMATION.size()]) - - # --- B. what actually flies ---------------------------------- - game_state.reset_new_game() - scene = load("res://Main.tscn").instantiate() - root.add_child(scene) - fleet = scene.get_node("Fleet") - elapsed = 0.0 - stage = 1 - return false - 1: - if elapsed < 0.3: - return false - scene.get_node("AsteroidField").set_process(false) - scene.get_node("EnemySpawner").set_process(false) - if fleet.ships.size() != GameState.STARTING_SHIP_COUNT: - return _fail("a fresh scene spawned %d vessels, expected %d" \ - % [fleet.ships.size(), GameState.STARTING_SHIP_COUNT]) - print("a fresh game really flies %d vessel" % fleet.ships.size()) - - # The two empty slots are what the opening is for, so they have to - # be fillable without researching anything. - for i in range(GameState.BASE_FLEET_SIZE - GameState.STARTING_SHIP_COUNT): - if not fleet.add_ship(GameState.VESSEL_INTERCEPTOR): - return _fail("could not crew slot %d of the starting fleet" % (i + 2)) - if fleet.ships.size() != GameState.BASE_FLEET_SIZE: - return _fail("the fleet holds %d after filling its slots, expected %d" \ - % [fleet.ships.size(), GameState.BASE_FLEET_SIZE]) - # And the cap has to actually bite past that. - if fleet.add_ship(GameState.VESSEL_INTERCEPTOR): - return _fail("a %dth vessel joined an unresearched fleet of %d slots" \ - % [fleet.ships.size(), GameState.BASE_FLEET_SIZE]) - print("the starting slots fill to %d and refuse a %dth" \ - % [GameState.BASE_FLEET_SIZE, GameState.BASE_FLEET_SIZE + 1]) - - print("TEST_FLEET_GROWTH: PASS") - quit(0) - return true - return false - - -func _fail(message: String) -> bool: - print("TEST_FLEET_GROWTH: FAIL (%s)" % message) - quit(1) - return true diff --git a/tests/test_gas_system.gd b/tests/test_gas_system.gd index 7f98ee6..ef8234f 100644 --- a/tests/test_gas_system.gd +++ b/tests/test_gas_system.gd @@ -20,21 +20,15 @@ func _initialize() -> void: func _process(delta: float) -> bool: elapsed += delta if stage == 0: - # A guaranteed gas source near the spawn is now a DEV MODE guarantee - # rather than something every game gets - a normal game has to go and - # find one. Dev mode is therefore where this premise still holds, and - # where a play-tester reaching gas quickly still matters. - game_state.dev_mode = true - var nearby_chunk: Vector2i = Vector2i.ZERO - for chunk in WorldGen.DEV_PLANET_CHUNKS: - if String(WorldGen.DEV_PLANET_CHUNKS[chunk]) == "gas_giant": - nearby_chunk = chunk + var nearby_chunk: Vector2i = WorldGen.NEARBY_GAS_GIANT_CHUNK var nearby_data: Dictionary = WorldGen.planet_data_at_chunk(nearby_chunk) var nearby_position: Vector2 = WorldGen.chunk_origin(nearby_chunk) + nearby_data.local_pos if not WorldGen.has_planet_at_chunk(nearby_chunk): - return _fail("dev mode's gas giant chunk does not contain a planet") + return _fail("nearby gas giant chunk does not contain a planet") + if WorldGen.biome_at_chunk(nearby_chunk) != WorldGen.Biome.SPACE: + return _fail("nearby gas giant is not in the normal space biome") if String(nearby_data.get("definition_id", "")) != "gas_giant": - return _fail("dev mode's guaranteed planet is not a gas source") + return _fail("nearby guaranteed planet is not a gas source") if nearby_position.length() > WorldGen.CHUNK_SIZE * 2.0: return _fail("nearby gas giant spawned too far away: %s" % nearby_position) diff --git a/tests/test_hud_layout.gd b/tests/test_hud_layout.gd deleted file mode 100644 index 8cf4570..0000000 --- a/tests/test_hud_layout.gd +++ /dev/null @@ -1,130 +0,0 @@ -extends SceneTree - -# Headless test: run with -# godot --headless -s res://tests/test_hud_layout.gd -# The HUD's top-anchored button columns must not collide with the resource -# readout, and must not collide with each other. -# -# Both halves are regressions that actually shipped: -# - ResourceReadout builds one row per resource type AT RUNTIME, so the -# scene's authored offset_bottom understated it and the build button, -# at a hardcoded y, ended up underneath the resource list. -# - Seating the columns below the readout then exposed a second hardcoded -# y: _layout_context_buttons() wrote the action row back to 324 on the -# next context change, dropping it on top of the moved build button. -# -# Both were a literal coordinate that encoded "below the thing above me" and -# stopped being true when the thing above moved. So this asserts the -# RELATIONSHIP rather than any particular coordinate - a fresh hardcoded y -# fails here whatever value someone picks. -# -# Covers: -# A. every HUD button clears the resource readout's real bottom -# B. no two simultaneously-usable buttons overlap each other -# C. both still hold after _layout_context_buttons() runs, collapsed and -# expanded - the path that broke -# D. it holds with MORE resource types than the scene was authored for, -# which is the direction this drifts - -var scene: Node -var controller: Node -var readout: Node -var stage: int = 0 -var elapsed: float = 0.0 - -# Deliberately share a row: a building either offers its own action or a -# hangar, never both, so their rects coinciding is the design. -const EXCLUSIVE_PAIRS: Array = [["building_action_button", "hangar_button"]] - - -func _initialize() -> void: - scene = load("res://Main.tscn").instantiate() - root.add_child(scene) - controller = scene.get_node("BuildController") - readout = scene.get_node("UI/ResourceReadout") - - -func _process(delta: float) -> bool: - elapsed += delta - match stage: - 0: - if elapsed < 0.2: - return false - scene.get_node("AsteroidField").set_process(false) - scene.get_node("EnemySpawner").set_process(false) - if not _check("as laid out at load"): - return true - - # --- C. the path that broke it ------------------------------ - controller.context_actions_expanded = false - controller._layout_context_buttons() - if not _check("after _layout_context_buttons() collapsed"): - return true - - controller.context_actions_expanded = true - controller._layout_context_buttons() - if not _check("after _layout_context_buttons() expanded"): - return true - - print("TEST_HUD_LAYOUT: PASS") - quit(0) - return true - return false - - -func _buttons() -> Dictionary: - return { - "build_button": controller.build_button, - "transfer_button": controller.transfer_button, - "action_button": controller.action_button, - "landing_button": controller.landing_button, - "building_action_button": controller.building_action_button, - "hangar_button": controller.hangar_button, - } - - -func _check(context: String) -> bool: - var buttons: Dictionary = _buttons() - var names: Array = buttons.keys() - - # --- A. clear of the resource readout --------------------------------- - # content_bottom() is the readout's REAL extent, from the live resource - # list - not the stale height the scene happens to declare. - var readout_bottom: float = readout.content_bottom() - for name in names: - var button: Control = buttons[name] - if button == null: - continue - if button.position.y < readout_bottom: - return _fail("%s sits at y %.0f, inside the resource readout which reaches %.0f (%s)" \ - % [name, button.position.y, readout_bottom, context]) - - # --- B. clear of each other ------------------------------------------- - for i in range(names.size()): - for j in range(i + 1, names.size()): - var a_name: String = names[i] - var b_name: String = names[j] - if _is_exclusive(a_name, b_name): - continue - var a: Control = buttons[a_name] - var b: Control = buttons[b_name] - if a == null or b == null: - continue - if a.get_rect().intersects(b.get_rect()): - return _fail("%s %s overlaps %s %s (%s)" \ - % [a_name, a.get_rect(), b_name, b.get_rect(), context]) - print("clear of the readout (reaches %.0f) and of each other - %s" % [readout_bottom, context]) - return true - - -func _is_exclusive(a: String, b: String) -> bool: - for pair in EXCLUSIVE_PAIRS: - if (a == pair[0] and b == pair[1]) or (a == pair[1] and b == pair[0]): - return true - return false - - -func _fail(message: String) -> bool: - print("TEST_HUD_LAYOUT: FAIL (%s)" % message) - quit(1) - return false diff --git a/tests/test_hud_layout.gd.uid b/tests/test_hud_layout.gd.uid deleted file mode 100644 index 0ca49ed..0000000 --- a/tests/test_hud_layout.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://65cagevj7qbj diff --git a/tests/test_main_menu.gd b/tests/test_main_menu.gd index 938ca89..8d2982f 100644 --- a/tests/test_main_menu.gd +++ b/tests/test_main_menu.gd @@ -35,14 +35,6 @@ func _process(delta: float) -> bool: return false if not menu.editor_button.enabled: return _fail("map editor button is not enabled (#48 shipped)") - # Both development entries follow the same rule, and the rule is - # what is under test rather than the concrete boolean - this run - # is from Godot, so it exercises the "shown" branch. - var developing: bool = menu.is_developer_build() - if menu.editor_button.visible != developing or menu.editor_label.visible != developing: - return _fail("map editor entry visibility did not follow is_developer_build()") - if menu.dev_button.visible != developing or menu.dev_label.visible != developing: - return _fail("dev mode entry visibility did not follow is_developer_build()") if menu.quit_button.visible != (not OS.has_feature("web")): return _fail("quit button visibility did not follow OS.has_feature('web')") @@ -82,10 +74,8 @@ func _process(delta: float) -> bool: if elapsed < 0.3: return false var on_main: bool = current_scene != null and current_scene.name == "Main" - # A normal game now starts with NOTHING banked - the opening - # mining run is the tutorial, and 500 free ore skipped it. - var reset_ok: bool = game_state.rocks == 0 and game_state.buildings.is_empty() \ - and game_state.surviving_ship_count == GameState.STARTING_SHIP_COUNT \ + var reset_ok: bool = game_state.rocks == 500 and game_state.buildings.is_empty() \ + and game_state.surviving_ship_count == GameState.BASE_FLEET_SIZE \ and game_state.planet_capture.is_empty() print("on_main=%s reset_ok=%s rocks=%d buildings=%d ship_count=%d" % [ on_main, reset_ok, game_state.rocks, game_state.buildings.size(), diff --git a/tests/test_pivot_controls.gd b/tests/test_pivot_controls.gd index 8396c82..1d60512 100644 --- a/tests/test_pivot_controls.gd +++ b/tests/test_pivot_controls.gd @@ -19,7 +19,6 @@ var cw: IconButton var stage: int = 0 var elapsed: float = 0.0 var angle_at_hold_start: float = 0.0 -var hold_amount_at_start: float = 0.0 var angle_after_hold: float = 0.0 # GameState (an autoload) is not available as a bare identifier from a # script that extends SceneTree - fetch it via the tree root instead. @@ -46,13 +45,6 @@ func _process(delta: float) -> bool: 0: scene.get_node("AsteroidField").set_process(false) scene.get_node("EnemySpawner").set_process(false) - # Crew the fleet HERE, not in _initialize(): work done there can - # be undone by the engine's deferred tree-entry finalization, the - # same reason the set_process(false) calls above sit at this - # point. A new game flies one vessel now, and the checks below - # index ships[1]. - game_state.unlocked.append_array(["wing", "squadron", "armada"]) - fleet.spawn_ships(5, Vector2.ZERO) # Hold the button down without releasing it. angle_at_hold_start = _angle() cw.pressed_down = true @@ -135,7 +127,6 @@ func _process(delta: float) -> bool: return true angle_at_hold_start = _angle() - hold_amount_at_start = controller.pivot_hold_amount elapsed = 0.0 stage = 2 return false @@ -144,12 +135,7 @@ func _process(delta: float) -> bool: return false # Both fingers still down: the formation must have kept turning # while the stick kept re-issuing move orders every frame. - # The ACCUMULATED turn, not the angle difference. Comparing - # formation angles wraps at +/-pi, so a hold that happened to - # rotate a full turn read as no rotation at all - and how far it - # gets in 0.4s depends on frame times, so this failed only under - # load. pivot_hold_amount is the same quantity without the wrap. - var turned_while_steering: float = controller.pivot_hold_amount - hold_amount_at_start + var turned_while_steering: float = absf(angle_difference(angle_at_hold_start, _angle())) print("turned %.3f rad while steering" % turned_while_steering) if turned_while_steering < 0.05: print("TEST_PIVOT_CONTROLS: FAIL (no rotation while the stick was held)") diff --git a/tests/test_planet_atmosphere_transition.gd b/tests/test_planet_atmosphere_transition.gd index 8a78fce..109bfa2 100644 --- a/tests/test_planet_atmosphere_transition.gd +++ b/tests/test_planet_atmosphere_transition.gd @@ -64,12 +64,6 @@ func _initialize() -> void: current_scene = main_scene fleet = main_scene.get_node("Fleet") - # A new game now flies STARTING_SHIP_COUNT (1) vessel into BASE_FLEET_SIZE - # (3) slots. This test needs a crewed fleet, so it asks for one outright - # rather than inheriting whatever a fresh game happens to open with - - # which is exactly what changed underneath it. - root.get_node("GameState").unlocked.append_array(["wing", "squadron", "armada"]) - fleet.spawn_ships(5, Vector2.ZERO) planet_field = main_scene.get_node("PlanetField") landing_controller = main_scene.get_node("LandingController") asteroid_field_ref = main_scene.get_node("AsteroidField") diff --git a/tests/test_planet_landing_proximity.gd b/tests/test_planet_landing_proximity.gd index bdc98c6..aa42319 100644 --- a/tests/test_planet_landing_proximity.gd +++ b/tests/test_planet_landing_proximity.gd @@ -20,13 +20,7 @@ func _initialize() -> void: for x in range(-15, 15): for y in range(-15, 15): var chunk: Vector2i = Vector2i(x, y) - # Not a black hole: it destroys ships on contact, and this - # scan takes the FIRST planet it finds. That used to be the gas - # giant guaranteed in the origin chunk; with that guarantee gone - # the scan can land the fleet on something that kills it, which - # is not what either of these tests is about. - if WorldGen.has_planet_at_chunk(chunk) \ - and String(WorldGen.planet_data_at_chunk(chunk).get("definition_id", "")) != "black_hole": + if WorldGen.has_planet_at_chunk(chunk): target_chunk = chunk found = true break @@ -63,18 +57,6 @@ func _process(delta: float) -> bool: # complete (fleet.ships is reliably populated by now), it's safe. main_scene.get_node("AsteroidField").set_process(false) main_scene.get_node("EnemySpawner").set_process(false) - # Crew the fleet HERE rather than in _initialize(): work done - # there can be undone by the engine's deferred tree-entry - # finalization, the same reason the set_process(false) calls - # above live at this point. A new game flies one vessel now. - root.get_node("GameState").unlocked.append_array(["wing", "squadron", "armada"]) - fleet.spawn_ships(5, Vector2.ZERO) - # Silencing the field does not remove what it already placed, and - # those keep drifting - a rock taking the fleet out mid-test reads - # here as a landing failure. - for rock in get_nodes_in_group("asteroids"): - if is_instance_valid(rock): - rock.queue_free() # PlanetField only scans chunks near the fleet's CURRENT # position, so it never even looks at target_chunk unless the diff --git a/tests/test_research_points.gd b/tests/test_research_points.gd index 0a2cf69..72adc94 100644 --- a/tests/test_research_points.gd +++ b/tests/test_research_points.gd @@ -61,10 +61,6 @@ func _initialize() -> void: # --- F. a fresh game still reaches a crafter and an extractor ------------- game_state.reset_new_game() - # can_unlock() also checks the ore price, and a new game now banks - # nothing - the opening mining run is the point. Stock the ore so this - # checks what it says it checks: reachability at ZERO RESEARCH POINTS. - game_state.rocks = 500 if not game_state.can_unlock("shipyards"): return _fail("shipyards is not reachable with 0 research points") game_state.unlock("shipyards") diff --git a/tests/test_resource_transfer.gd b/tests/test_resource_transfer.gd index 90299f7..8854082 100644 --- a/tests/test_resource_transfer.gd +++ b/tests/test_resource_transfer.gd @@ -55,12 +55,6 @@ func _initialize() -> void: scene = load("res://Main.tscn").instantiate() root.add_child(scene) fleet = scene.get_node("Fleet") - # A new game now flies STARTING_SHIP_COUNT (1) vessel into BASE_FLEET_SIZE - # (3) slots. This test needs a crewed fleet, so it asks for one outright - # rather than inheriting whatever a fresh game happens to open with - - # which is exactly what changed underneath it. - root.get_node("GameState").unlocked.append_array(["wing", "squadron", "armada"]) - fleet.spawn_ships(5, Vector2.ZERO) controller = scene.get_node("BuildController") diff --git a/tests/test_resources_and_buildings.gd b/tests/test_resources_and_buildings.gd index a954b8c..17c6780 100644 --- a/tests/test_resources_and_buildings.gd +++ b/tests/test_resources_and_buildings.gd @@ -104,10 +104,6 @@ func _process(delta: float) -> bool: if elapsed < 0.3: return false # --- crafter adds a ship -------------------------------------- - # Crafting adds a FOURTH vessel below, one past the 3 slots a - # new game has, so make room first - the subject here is the - # crafter, not the fleet cap. - game_state.unlocked.append_array(["wing", "squadron", "armada"]) fleet.spawn_ships(3, Vector2.ZERO) var before_ships: int = fleet.ships.size() var before_rocks: int = game_state.rocks diff --git a/tests/test_return_position_matches_planet.gd b/tests/test_return_position_matches_planet.gd index 99b4beb..c7550c3 100644 --- a/tests/test_return_position_matches_planet.gd +++ b/tests/test_return_position_matches_planet.gd @@ -25,13 +25,7 @@ func _initialize() -> void: for x in range(-15, 15): for y in range(-15, 15): var chunk: Vector2i = Vector2i(x, y) - # Not a black hole: it destroys ships on contact, and this - # scan takes the FIRST planet it finds. That used to be the gas - # giant guaranteed in the origin chunk; with that guarantee gone - # the scan can land the fleet on something that kills it, which - # is not what either of these tests is about. - if WorldGen.has_planet_at_chunk(chunk) \ - and String(WorldGen.planet_data_at_chunk(chunk).get("definition_id", "")) != "black_hole": + if WorldGen.has_planet_at_chunk(chunk): target_chunk = chunk found = true break @@ -59,18 +53,6 @@ func _process(delta: float) -> bool: 0: main_scene.get_node("AsteroidField").set_process(false) main_scene.get_node("EnemySpawner").set_process(false) - # Crew the fleet HERE rather than in _initialize(): work done - # there can be undone by the engine's deferred tree-entry - # finalization, the same reason the set_process(false) calls - # above live at this point. A new game flies one vessel now. - root.get_node("GameState").unlocked.append_array(["wing", "squadron", "armada"]) - fleet.spawn_ships(5, Vector2.ZERO) - # Silencing the field does not remove what it already placed, and - # those keep drifting - a rock taking the fleet out mid-test reads - # here as a landing failure. - for rock in get_nodes_in_group("asteroids"): - if is_instance_valid(rock): - rock.queue_free() var vicinity_pos: Vector2 = WorldGen.chunk_origin(target_chunk) + Vector2.ONE * WorldGen.CHUNK_SIZE * 0.5 for ship in fleet.ships: ship.position = vicinity_pos