Show the extractor derrick the moment it is built, not on respawn

Follow-up to #9: the marker was resolved once in Planet._ready() on the
assumption that extractors are built on PlanetSurface.tscn, so a Planet
node could never outlive a change to its own answer. That assumption was
wrong. Extractors are built from SPACE, via
BuildController._planet_under_fleet() with the fleet parked on the
planet, so the Planet node is already in the tree with _ready() long
since run. The derrick therefore did not appear until the planet
happened to despawn and respawn - building one read as doing nothing.

GameState now emits extractor_added, and Planet keeps its marker in step
with it, redrawing only when its own answer actually changes.

The test missed this because it spawned a fresh planet after registering
the extractor, which is not the order the game does it in. It now keeps
a planet alive across the build and asserts the derrick appears on that
node.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Loïc Wisniewski 2026-08-11 15:43:43 +02:00
commit 2eb779e5de
3 changed files with 42 additions and 4 deletions

View file

@ -270,12 +270,20 @@ func has_extractor_at(planet_name: String) -> bool:
return false return false
signal extractor_added(planet_name: String)
func add_extractor(planet_name: String, at_position: Vector2) -> void: func add_extractor(planet_name: String, at_position: Vector2) -> void:
extractors.append({ extractors.append({
"position": at_position, "position": at_position,
"planet_name": planet_name, "planet_name": planet_name,
"progress": 0.0, "progress": 0.0,
}) })
# An extractor is built from SPACE, with the fleet parked on the planet -
# so the Planet node is already in the tree with its _ready() long since
# run. Without this it kept showing no derrick until it happened to
# despawn and respawn, which reads as the build having done nothing.
extractor_added.emit(planet_name)
func get_hauler_route_time(extractor: Dictionary) -> float: func get_hauler_route_time(extractor: Dictionary) -> float:

View file

@ -29,9 +29,12 @@ const EXTRACTOR_MARKER_COLOR: Color = Color(1.0, 0.85, 0.3)
const DERRICK_HEIGHT: float = 44.0 const DERRICK_HEIGHT: float = 44.0
const DERRICK_HALF_WIDTH: float = 16.0 const DERRICK_HALF_WIDTH: float = 16.0
# Resolved once in _ready() rather than polled: building an extractor happens # Kept in step with GameState.extractor_added rather than polled per frame.
# on PlanetSurface.tscn, and coming back to space rebuilds Main.tscn from # An extractor is built from SPACE (BuildController._planet_under_fleet(),
# scratch, so a Planet node never outlives a change to its own answer. # with the fleet parked on the planet), so this node is already in the tree
# with its _ready() long since run when the answer changes - resolving it
# only at _ready() left the planet looking untouched until it happened to
# despawn and respawn.
var shows_extractor: bool = false var shows_extractor: bool = false
@ -47,6 +50,16 @@ func _ready() -> void:
var radius: float = rng.randf_range(70.0, 100.0) var radius: float = rng.randf_range(70.0, 100.0)
shape_points.append(Vector2(cos(angle), sin(angle)) * radius) shape_points.append(Vector2(cos(angle), sin(angle)) * radius)
shows_extractor = GameState.has_extractor_at(planet_name) shows_extractor = GameState.has_extractor_at(planet_name)
GameState.extractor_added.connect(_on_extractor_added)
queue_redraw()
func _on_extractor_added(built_on: String) -> void:
# Only the planet it was actually built on, and only a redraw when the
# answer really changed - every other planet in range gets this too.
if built_on != planet_name or shows_extractor:
return
shows_extractor = true
queue_redraw() queue_redraw()

View file

@ -29,6 +29,7 @@ var map_content: Node2D
var planet_chunk: Vector2i var planet_chunk: Vector2i
var planet_name: String = "" var planet_name: String = ""
var stage: int = 0 var stage: int = 0
var already_spawned: Node2D
func _initialize() -> void: func _initialize() -> void:
@ -78,13 +79,29 @@ func _process(_delta: float) -> bool:
print("TEST_PLANET_EXTRACTOR_MARKER: FAIL (map badge with no extractor)") print("TEST_PLANET_EXTRACTOR_MARKER: FAIL (map badge with no extractor)")
quit(1) quit(1)
return true return true
clean.queue_free()
print("no extractor -> no derrick, no badge (planet '%s')" % planet_name) print("no extractor -> no derrick, no badge (planet '%s')" % planet_name)
# Deliberately keep this planet ALIVE across the build. An
# extractor is built from space with the fleet parked on the
# planet (BuildController._planet_under_fleet()), so the node is
# already in the tree with _ready() long since run. Resolving the
# marker only at _ready() meant the planet kept looking untouched
# until it happened to despawn and respawn - the build read as
# having done nothing at all. Spawning a fresh planet afterwards
# (as the checks below do) cannot catch that.
already_spawned = clean
game_state.add_extractor(planet_name, WorldGen.chunk_origin(planet_chunk)) game_state.add_extractor(planet_name, WorldGen.chunk_origin(planet_chunk))
stage = 1 stage = 1
return false return false
1: 1:
# --- the planet that was already on screen when it was built --
if not already_spawned.shows_extractor:
print("TEST_PLANET_EXTRACTOR_MARKER: FAIL (a planet already in the tree never picked up its derrick - the build reads as doing nothing)")
quit(1)
return true
print("planet already in the tree picked up its derrick on build")
already_spawned.queue_free()
# --- extractor built ---------------------------------------- # --- extractor built ----------------------------------------
var mined: Node2D = _spawn_planet() var mined: Node2D = _spawn_planet()
if not mined.shows_extractor: if not mined.shows_extractor: