Ship Lab: per-vessel research, and the shield vessel

Forgejo #5. A new building gated behind a "Vessel Science" research
node. Its own screen upgrades each vessel type - weapon, movement,
shield and freight - and unlocks the shield vessel, which the ship
crafter can then build.

Vessel research is deliberately NOT more nodes in the mothership tree:
that tree gates what may be built at all and is a strategic spine, while
these are repeatable tuning on vessels already owned. Levels cost
base * (level + 1), so the third point in something is not the price of
the first. Upgrades are read live rather than baked in at build time, so
researching one improves the ships already flying.

The shield is personal, not a bubble: it absorbs a hit that would
destroy the vessel - ships have no health, contact kills - and is then
down until it cycles. Its ring is a NeonPiece child toggled with
`visible` per #10, not a redraw of the hull.

The build menu now holds six entries across three rows, which the #21
repacking already handles.

Enemies still have no weapon, so "blocks shots" has nothing to block
yet; that is filed separately rather than widened into this ticket.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Loïc Wisniewski 2026-08-11 23:31:49 +02:00
commit 1cbae8ff70
13 changed files with 876 additions and 5 deletions

View file

@ -15,7 +15,7 @@ enum Icon {
# build menu used to pick from the generic set, which left the mothership
# and the command centre sharing a folded-map glyph and neither of them
# looking anything like the structure they build.
MOTHERSHIP, CRAFTER, COMMAND_CENTER, EXTRACTOR, TRANSPORT_HUB,
MOTHERSHIP, CRAFTER, COMMAND_CENTER, EXTRACTOR, TRANSPORT_HUB, SHIP_LAB,
}
# Size the glyphs were originally drawn against. Every glyph dimension is
@ -139,6 +139,8 @@ func _draw() -> void:
_draw_extractor_icon(center, pulse, s)
Icon.TRANSPORT_HUB:
_draw_transport_hub_icon(center, pulse, s)
Icon.SHIP_LAB:
_draw_ship_lab_icon(center, pulse, s)
func _draw_close(center: Vector2, pulse: float, s: float) -> void:
@ -224,6 +226,22 @@ func _draw_mothership_icon(center: Vector2, pulse: float, s: float) -> void:
NeonDraw.draw_glowing_polygon(self, ring, _active_color(), pulse, core_color)
func _draw_ship_lab_icon(center: Vector2, pulse: float, s: float) -> void:
# A vessel inside brackets: something being examined, not built.
NeonDraw.draw_glowing_polygon(self, PackedVector2Array([
center + Vector2(0, -9) * s, center + Vector2(5, 5) * s,
center + Vector2(0, 2) * s, center + Vector2(-5, 5) * s,
]), _active_color(), pulse, core_color)
for sx in [-1.0, 1.0]:
var x: float = sx * 11.0 * s
NeonDraw.draw_glowing_line(self, center + Vector2(x, -11 * s), center + Vector2(x, 11 * s),
_active_color(), pulse, 0.35 * s, core_color)
NeonDraw.draw_glowing_line(self, center + Vector2(x, -11 * s), center + Vector2(x - sx * 4.0 * s, -11 * s),
_active_color(), pulse, 0.35 * s, core_color)
NeonDraw.draw_glowing_line(self, center + Vector2(x, 11 * s), center + Vector2(x - sx * 4.0 * s, 11 * s),
_active_color(), pulse, 0.35 * s, core_color)
func _draw_transport_hub_icon(center: Vector2, pulse: float, s: float) -> void:
# A ring of docking cradles with a freight box in the middle: reads as
# "things are stationed here and go out from here", which is what the hub