spaceship/tests/test_transport_hub_building.gd

176 lines
6.7 KiB
GDScript3
Raw Normal View History

Transport Hub building Forgejo #18, part 5 of #12. The building only - the vessels that do the hauling are #19. Placeable, costed at 30, gated behind a new "Freight Handling" research node hanging off Logistics, with its own info popup, build-menu glyph and map marker. Drawn as an octagonal dock ring with berth arms and a squared-off freight core: deliberately radial-but-flat so it does not read as a small mothership, which is the only structure with swept arms. Its stationed transports are NeonPiece children of one rotating holder, per #10 - the geometry is rasterised once and only the holder's rotation changes, so the traffic costs no redraws. Building._draw()'s match has a catch-all that renders anything unrecognised as a ship crafter, so a missing case is silent and looks almost right. The test checks for the hub's own _hub_traffic holder rather than trusting that something plausible appeared - which caught exactly that: a _ready() patch had silently not applied and the hub was rendering through the crafter branch. The build menu grew a third row. Every button, label and price moved: the old row occupied 332px against a 372px pitch, and three of those do not fit above a 1280px screen. Rows are now shorter (128px buttons, 40px price digits) at a 290px pitch, ending at y=1176. Rendered and looked at - the first attempt tightened the pitch alone and left every price overlapping the row beneath it, which no headless test would have seen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 17:45:07 +02:00
extends SceneTree
const BUILDING_DEFINITIONS = preload("res://BuildingDefinitions.gd")
Transport Hub building Forgejo #18, part 5 of #12. The building only - the vessels that do the hauling are #19. Placeable, costed at 30, gated behind a new "Freight Handling" research node hanging off Logistics, with its own info popup, build-menu glyph and map marker. Drawn as an octagonal dock ring with berth arms and a squared-off freight core: deliberately radial-but-flat so it does not read as a small mothership, which is the only structure with swept arms. Its stationed transports are NeonPiece children of one rotating holder, per #10 - the geometry is rasterised once and only the holder's rotation changes, so the traffic costs no redraws. Building._draw()'s match has a catch-all that renders anything unrecognised as a ship crafter, so a missing case is silent and looks almost right. The test checks for the hub's own _hub_traffic holder rather than trusting that something plausible appeared - which caught exactly that: a _ready() patch had silently not applied and the hub was rendering through the crafter branch. The build menu grew a third row. Every button, label and price moved: the old row occupied 332px against a 372px pitch, and three of those do not fit above a 1280px screen. Rows are now shorter (128px buttons, 40px price digits) at a 290px pitch, ending at y=1176. Rendered and looked at - the first attempt tightened the pitch alone and left every price overlapping the row beneath it, which no headless test would have seen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 17:45:07 +02:00
# Headless gameplay test: run with
# godot --headless -s res://tests/test_transport_hub_building.gd
# Forgejo #18 (part 5 of #12): the Transport Hub is a placeable building of
# its own. Only the building here - the vessels that do the hauling are #19.
#
# Verifies:
# 1. it is gated behind its research node, like every other structure
# 2. researching it makes it buildable, and placing it charges its cost
# 3. it is drawn as ITSELF, not as the crafter - Building._draw()'s match
# has a catch-all that renders anything unrecognised as a ship crafter,
# so a missing case is silent and looks almost right
# 4. its geometry is rasterised ONCE however long it animates (#10), and
# its stationed transports really do move
# 5. it has its own build-menu icon and its own map marker rather than
# borrowing another building's - the menu was once unreadable because
# two buildings shared a glyph
var game_state: Node
var scene: Node
var field: Node
var controller: Node
var map_content: Node2D
var hub_building: Node2D
Transport Hub building Forgejo #18, part 5 of #12. The building only - the vessels that do the hauling are #19. Placeable, costed at 30, gated behind a new "Freight Handling" research node hanging off Logistics, with its own info popup, build-menu glyph and map marker. Drawn as an octagonal dock ring with berth arms and a squared-off freight core: deliberately radial-but-flat so it does not read as a small mothership, which is the only structure with swept arms. Its stationed transports are NeonPiece children of one rotating holder, per #10 - the geometry is rasterised once and only the holder's rotation changes, so the traffic costs no redraws. Building._draw()'s match has a catch-all that renders anything unrecognised as a ship crafter, so a missing case is silent and looks almost right. The test checks for the hub's own _hub_traffic holder rather than trusting that something plausible appeared - which caught exactly that: a _ready() patch had silently not applied and the hub was rendering through the crafter branch. The build menu grew a third row. Every button, label and price moved: the old row occupied 332px against a 372px pitch, and three of those do not fit above a 1280px screen. Rows are now shorter (128px buttons, 40px price digits) at a 290px pitch, ending at y=1176. Rendered and looked at - the first attempt tightened the pitch alone and left every price overlapping the row beneath it, which no headless test would have seen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 17:45:07 +02:00
var hub: Node2D
var stage: int = 0
var elapsed: float = 0.0
var spin_at_start: float = 0.0
const HUB_AT: Vector2 = Vector2(700.0, 0.0)
func _initialize() -> void:
game_state = root.get_node("GameState")
game_state.buildings.clear()
game_state.extractors.clear()
game_state.reset_progression()
game_state.rocks = 500
game_state.fleet_cargo = 0
scene = load("res://Main.tscn").instantiate()
root.add_child(scene)
field = scene.get_node("BuildingField")
controller = scene.get_node("BuildController")
map_content = scene.get_node("UI/WorldMap/MapContent")
func _process(delta: float) -> bool:
elapsed += delta
match stage:
0:
scene.get_node("AsteroidField").set_process(false)
scene.get_node("EnemySpawner").set_process(false)
# --- 1. locked until researched ------------------------------
if game_state.is_building_unlocked(GameState.BUILDING_TRANSPORT_HUB):
print("TEST_TRANSPORT_HUB_BUILDING: FAIL (buildable with no research at all)")
quit(1)
return true
var gate: String = game_state.research_for_building(GameState.BUILDING_TRANSPORT_HUB)
if gate == "":
print("TEST_TRANSPORT_HUB_BUILDING: FAIL (no research node gates it)")
quit(1)
return true
print("locked behind research: '%s'" % gate)
# --- 5. its own icon, distinct from every other building -----
var icons: Dictionary = {}
for type in [GameState.BUILDING_MOTHERSHIP, GameState.BUILDING_SHIP_CRAFTER,
GameState.BUILDING_COMMAND_CENTER, GameState.BUILDING_TRANSPORT_HUB]:
var icon: int = BUILDING_DEFINITIONS.get_definition(type).action_icon
Transport Hub building Forgejo #18, part 5 of #12. The building only - the vessels that do the hauling are #19. Placeable, costed at 30, gated behind a new "Freight Handling" research node hanging off Logistics, with its own info popup, build-menu glyph and map marker. Drawn as an octagonal dock ring with berth arms and a squared-off freight core: deliberately radial-but-flat so it does not read as a small mothership, which is the only structure with swept arms. Its stationed transports are NeonPiece children of one rotating holder, per #10 - the geometry is rasterised once and only the holder's rotation changes, so the traffic costs no redraws. Building._draw()'s match has a catch-all that renders anything unrecognised as a ship crafter, so a missing case is silent and looks almost right. The test checks for the hub's own _hub_traffic holder rather than trusting that something plausible appeared - which caught exactly that: a _ready() patch had silently not applied and the hub was rendering through the crafter branch. The build menu grew a third row. Every button, label and price moved: the old row occupied 332px against a 372px pitch, and three of those do not fit above a 1280px screen. Rows are now shorter (128px buttons, 40px price digits) at a 290px pitch, ending at y=1176. Rendered and looked at - the first attempt tightened the pitch alone and left every price overlapping the row beneath it, which no headless test would have seen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 17:45:07 +02:00
if icons.has(icon):
print("TEST_TRANSPORT_HUB_BUILDING: FAIL (%s and %s share an icon)" % [type, icons[icon]])
quit(1)
return true
icons[icon] = type
print("action icons distinct across %d buildings" % icons.size())
# --- 2. research it, then build it ---------------------------
game_state.unlocked = ["shipyards", "logistics", "freight"]
if not game_state.is_building_unlocked(GameState.BUILDING_TRANSPORT_HUB):
print("TEST_TRANSPORT_HUB_BUILDING: FAIL (still locked after researching '%s')" % gate)
quit(1)
return true
var before: int = game_state.rocks
field.place(GameState.BUILDING_TRANSPORT_HUB, HUB_AT)
game_state.spend(controller.cost_of(GameState.BUILDING_TRANSPORT_HUB))
if game_state.rocks != before - GameState.TRANSPORT_HUB_COST:
print("TEST_TRANSPORT_HUB_BUILDING: FAIL (cost %d charged, expected %d)" % [
before - game_state.rocks, GameState.TRANSPORT_HUB_COST
])
quit(1)
return true
print("researched and placed for %d ore" % GameState.TRANSPORT_HUB_COST)
elapsed = 0.0
stage = 1
return false
1:
if elapsed < 0.3:
return false
for b in field.spawned:
if is_instance_valid(b) and b.building_type == GameState.BUILDING_TRANSPORT_HUB:
hub_building = b
hub = b.visual
Transport Hub building Forgejo #18, part 5 of #12. The building only - the vessels that do the hauling are #19. Placeable, costed at 30, gated behind a new "Freight Handling" research node hanging off Logistics, with its own info popup, build-menu glyph and map marker. Drawn as an octagonal dock ring with berth arms and a squared-off freight core: deliberately radial-but-flat so it does not read as a small mothership, which is the only structure with swept arms. Its stationed transports are NeonPiece children of one rotating holder, per #10 - the geometry is rasterised once and only the holder's rotation changes, so the traffic costs no redraws. Building._draw()'s match has a catch-all that renders anything unrecognised as a ship crafter, so a missing case is silent and looks almost right. The test checks for the hub's own _hub_traffic holder rather than trusting that something plausible appeared - which caught exactly that: a _ready() patch had silently not applied and the hub was rendering through the crafter branch. The build menu grew a third row. Every button, label and price moved: the old row occupied 332px against a 372px pitch, and three of those do not fit above a 1280px screen. Rows are now shorter (128px buttons, 40px price digits) at a 290px pitch, ending at y=1176. Rendered and looked at - the first attempt tightened the pitch alone and left every price overlapping the row beneath it, which no headless test would have seen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 17:45:07 +02:00
if hub == null:
print("TEST_TRANSPORT_HUB_BUILDING: FAIL (never spawned)")
quit(1)
return true
if not hub_building.available_actions().is_empty():
print("TEST_TRANSPORT_HUB_BUILDING: FAIL (offers a manual resource transfer)")
quit(1)
return true
for ship in scene.get_node("Fleet").ships:
ship.position = HUB_AT
scene.get_node("Fleet").move_to(HUB_AT)
Transport Hub building Forgejo #18, part 5 of #12. The building only - the vessels that do the hauling are #19. Placeable, costed at 30, gated behind a new "Freight Handling" research node hanging off Logistics, with its own info popup, build-menu glyph and map marker. Drawn as an octagonal dock ring with berth arms and a squared-off freight core: deliberately radial-but-flat so it does not read as a small mothership, which is the only structure with swept arms. Its stationed transports are NeonPiece children of one rotating holder, per #10 - the geometry is rasterised once and only the holder's rotation changes, so the traffic costs no redraws. Building._draw()'s match has a catch-all that renders anything unrecognised as a ship crafter, so a missing case is silent and looks almost right. The test checks for the hub's own _hub_traffic holder rather than trusting that something plausible appeared - which caught exactly that: a _ready() patch had silently not applied and the hub was rendering through the crafter branch. The build menu grew a third row. Every button, label and price moved: the old row occupied 332px against a 372px pitch, and three of those do not fit above a 1280px screen. Rows are now shorter (128px buttons, 40px price digits) at a 290px pitch, ending at y=1176. Rendered and looked at - the first attempt tightened the pitch alone and left every price overlapping the row beneath it, which no headless test would have seen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 17:45:07 +02:00
# --- 3. drawn as itself, not via the crafter catch-all -------
# _hub_traffic only exists if _ready() took the transport-hub
# branch; a missing case falls through to the crafter and would
# leave it null while still rendering something plausible.
if hub._hub_traffic == null:
print("TEST_TRANSPORT_HUB_BUILDING: FAIL (rendered through the crafter catch-all, not as a hub)")
quit(1)
return true
# --- 5. its own map marker ----------------------------------
var markers: Array = map_content.building_markers()
var found: bool = false
for m in markers:
if m.type == GameState.BUILDING_TRANSPORT_HUB:
found = true
if m.color == BUILDING_DEFINITIONS.get_definition(GameState.BUILDING_SHIP_CRAFTER).map_color \
or m.color == BUILDING_DEFINITIONS.get_definition(GameState.BUILDING_MOTHERSHIP).map_color \
or m.color == BUILDING_DEFINITIONS.get_definition(GameState.BUILDING_COMMAND_CENTER).map_color:
Transport Hub building Forgejo #18, part 5 of #12. The building only - the vessels that do the hauling are #19. Placeable, costed at 30, gated behind a new "Freight Handling" research node hanging off Logistics, with its own info popup, build-menu glyph and map marker. Drawn as an octagonal dock ring with berth arms and a squared-off freight core: deliberately radial-but-flat so it does not read as a small mothership, which is the only structure with swept arms. Its stationed transports are NeonPiece children of one rotating holder, per #10 - the geometry is rasterised once and only the holder's rotation changes, so the traffic costs no redraws. Building._draw()'s match has a catch-all that renders anything unrecognised as a ship crafter, so a missing case is silent and looks almost right. The test checks for the hub's own _hub_traffic holder rather than trusting that something plausible appeared - which caught exactly that: a _ready() patch had silently not applied and the hub was rendering through the crafter branch. The build menu grew a third row. Every button, label and price moved: the old row occupied 332px against a 372px pitch, and three of those do not fit above a 1280px screen. Rows are now shorter (128px buttons, 40px price digits) at a 290px pitch, ending at y=1176. Rendered and looked at - the first attempt tightened the pitch alone and left every price overlapping the row beneath it, which no headless test would have seen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 17:45:07 +02:00
print("TEST_TRANSPORT_HUB_BUILDING: FAIL (map marker reuses another building's colour)")
quit(1)
return true
if not found:
print("TEST_TRANSPORT_HUB_BUILDING: FAIL (no map marker - see #11)")
quit(1)
return true
print("has its own map marker")
spin_at_start = hub._hub_traffic.rotation
elapsed = 0.0
stage = 2
return false
2:
# --- 4. animates without ever redrawing (#10) ----------------
if elapsed < 2.0:
return false
if controller.current_building != hub_building \
or controller.action_button.visible or controller.transfer_button.visible:
print("TEST_TRANSPORT_HUB_BUILDING: FAIL (actionless hub context current=%s expected=%s action=%s transfer=%s)" % [
controller.current_building, hub_building, controller.action_button.visible,
controller.transfer_button.visible,
])
quit(1)
return true
Transport Hub building Forgejo #18, part 5 of #12. The building only - the vessels that do the hauling are #19. Placeable, costed at 30, gated behind a new "Freight Handling" research node hanging off Logistics, with its own info popup, build-menu glyph and map marker. Drawn as an octagonal dock ring with berth arms and a squared-off freight core: deliberately radial-but-flat so it does not read as a small mothership, which is the only structure with swept arms. Its stationed transports are NeonPiece children of one rotating holder, per #10 - the geometry is rasterised once and only the holder's rotation changes, so the traffic costs no redraws. Building._draw()'s match has a catch-all that renders anything unrecognised as a ship crafter, so a missing case is silent and looks almost right. The test checks for the hub's own _hub_traffic holder rather than trusting that something plausible appeared - which caught exactly that: a _ready() patch had silently not applied and the hub was rendering through the crafter branch. The build menu grew a third row. Every button, label and price moved: the old row occupied 332px against a 372px pitch, and three of those do not fit above a 1280px screen. Rows are now shorter (128px buttons, 40px price digits) at a 290px pitch, ending at y=1176. Rendered and looked at - the first attempt tightened the pitch alone and left every price overlapping the row beneath it, which no headless test would have seen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 17:45:07 +02:00
print("rasterisations after %.1fs: %d" % [elapsed, hub.draw_count])
if hub.draw_count != 1:
print("TEST_TRANSPORT_HUB_BUILDING: FAIL (redrew its geometry %d times to animate)" % hub.draw_count)
quit(1)
return true
var spun: float = absf(hub._hub_traffic.rotation - spin_at_start)
print("stationed transports turned %.3f rad" % spun)
if spun < 0.05:
print("TEST_TRANSPORT_HUB_BUILDING: FAIL (nothing moved - a frozen hub trivially passes the redraw check)")
quit(1)
return true
print("TEST_TRANSPORT_HUB_BUILDING: PASS")
quit(0)
return true
return false