Show what the fleet is carrying next to what is banked

Forgejo #15, part 2 of #12. Since #14 ore is cargo until the fleet
delivers it to the mothership, so the banked readout does not move on
pickup - mining looked broken, because gathering ore changed no number
on screen until the fleet happened to fly home.

Three things separate the carried readout from the banked one, since
colour alone is a weak signal on a phone in a game that is mostly teal:
amber rather than ore-teal (the same amber already used for ore in
transit - the extractor derrick, the hauler route), smaller and sitting
under the banked figure so it reads as subordinate, and present only
while actually carrying, so an empty hold is not a permanent 0 parked
beside the real total.

Rendered via tests/screenshot.sh and looked at, not only asserted on:
headless draws no pixels and cannot say whether two numbers read as two
different things.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Loïc Wisniewski 2026-08-11 16:04:40 +02:00
commit d1b82f4f5a
7 changed files with 183 additions and 0 deletions

33
CargoCounter.gd Normal file
View file

@ -0,0 +1,33 @@
extends NeonNumber
# What the fleet is CARRYING, shown under the banked total (#15, part 2 of
# #12). Since #14 ore is cargo until the fleet delivers it to the mothership,
# so without this, mining visibly does nothing - the banked figure above does
# not move until the fleet flies home, which reads as gathering being broken.
#
# Three things separate it from the banked readout, because colour alone is a
# weak signal on a phone screen in a game that is mostly teal:
# - amber rather than ore-teal, the same amber used elsewhere for ore in
# transit (the extractor derrick, the hauler route)
# - smaller, sitting UNDER the banked figure: subordinate to it, because
# this is the number that is not yours yet
# - present only while actually carrying something, so an empty hold is not
# a permanent zero taking up space next to the real total
#
# Reads GameState directly for the same reason ResourceCounter does: it stays
# correct across the space <-> planet-surface scene swap with nobody having
# to push updates into it.
const CARGO_GLOW: Color = Color(1.0, 0.78, 0.25)
const CARGO_CORE: Color = Color(0.22, 0.12, 0.0)
func _ready() -> void:
glow_color = CARGO_GLOW
core_color = CARGO_CORE
func _process(_delta: float) -> void:
visible = GameState.fleet_cargo > 0
if visible:
set_amount(GameState.fleet_cargo)

1
CargoCounter.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://bmrcja23g5oi6

View file

@ -21,6 +21,7 @@
[ext_resource type="Script" path="res://NeonNumber.gd" id="19"] [ext_resource type="Script" path="res://NeonNumber.gd" id="19"]
[ext_resource type="Script" path="res://NeonPanel.gd" id="20"] [ext_resource type="Script" path="res://NeonPanel.gd" id="20"]
[ext_resource type="Script" path="res://SkillTreeView.gd" id="21"] [ext_resource type="Script" path="res://SkillTreeView.gd" id="21"]
[ext_resource type="Script" path="res://CargoCounter.gd" id="22"]
[sub_resource type="ShaderMaterial" id="1"] [sub_resource type="ShaderMaterial" id="1"]
shader = ExtResource("5") shader = ExtResource("5")
@ -89,6 +90,15 @@ offset_bottom = 92
script = ExtResource("16") script = ExtResource("16")
digit_height = 60.0 digit_height = 60.0
[node name="CargoCounter" type="Control" parent="UI"]
mouse_filter = 2
offset_left = 28
offset_top = 96
offset_right = 320
offset_bottom = 146
script = ExtResource("22")
digit_height = 42.0
[node name="PlanetNameLabel" type="Label" parent="UI"] [node name="PlanetNameLabel" type="Label" parent="UI"]
visible = false visible = false
offset_left = 24 offset_left = 24

View file

@ -0,0 +1,15 @@
extends Node2D
# Preview, not a test. The resource readouts with the fleet carrying ore, so
# the #15 banked-vs-carried split can actually be looked at - headless draws
# no pixels and cannot say whether two numbers read as two different things.
#
# bash tests/screenshot.sh res://tests/_cargo_readout_preview.tscn /tmp/hud.png 20 720x1280
func _ready() -> void:
GameState.buildings.clear()
GameState.extractors.clear()
GameState.rocks = 128
GameState.fleet_cargo = 34
add_child(load("res://Main.tscn").instantiate())

View file

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://tests/_cargo_readout_preview.gd" id="1"]
[node name="CargoReadoutPreview" type="Node2D"]
script = ExtResource("1")

117
tests/test_cargo_readout.gd Normal file
View file

@ -0,0 +1,117 @@
extends SceneTree
# Headless gameplay test: run with
# godot --headless -s res://tests/test_cargo_readout.gd
# Forgejo #15 (part 2 of #12): what the fleet is carrying has to be visible
# separately from what is banked.
#
# Since #14, ore is cargo until the fleet delivers it to the mothership. The
# banked readout does not move on pickup, so without a second readout mining
# looks broken - you gather ore and no number on screen changes.
#
# --headless has no GPU and draws no pixels, so this asserts on the two
# readouts' state, not on their appearance. tests/screenshot.sh covers
# whether they are legible.
#
# Verifies:
# 1. the banked readout keeps showing the BANK, not the total
# 2. the cargo readout shows the hold, and appears only while carrying
# 3. an empty hold hides it rather than parking a permanent 0 beside the
# real total
# 4. delivering moves the number from one readout to the other
# 5. the two are actually told apart on screen, rather than being the same
# number twice in the same colour at the same place
var game_state: Node
var scene: Node
var banked: Control
var cargo: Control
var stage: int = 0
var elapsed: float = 0.0
func _initialize() -> void:
game_state = root.get_node("GameState")
game_state.buildings.clear()
game_state.extractors.clear()
game_state.rocks = 42
game_state.fleet_cargo = 0
scene = load("res://Main.tscn").instantiate()
root.add_child(scene)
banked = scene.get_node("UI/ResourceCounter")
cargo = scene.get_node("UI/CargoCounter")
func _process(delta: float) -> bool:
elapsed += delta
match stage:
0:
if elapsed < 0.2:
return false
# --- 3. nothing carried -> no cargo readout -----------------
if cargo.visible:
print("TEST_CARGO_READOUT: FAIL (cargo readout showing with an empty hold)")
quit(1)
return true
if banked.amount != 42:
print("TEST_CARGO_READOUT: FAIL (banked readout shows %d, bank is 42)" % banked.amount)
quit(1)
return true
print("empty hold -> cargo readout hidden, banked shows %d" % banked.amount)
game_state.collect_cargo(7)
elapsed = 0.0
stage = 1
return false
1:
if elapsed < 0.2:
return false
# --- 1 + 2. carried and banked are separate numbers ---------
if not cargo.visible:
print("TEST_CARGO_READOUT: FAIL (carrying 7 and the cargo readout is hidden)")
quit(1)
return true
if cargo.amount != 7:
print("TEST_CARGO_READOUT: FAIL (cargo readout shows %d, hold is 7)" % cargo.amount)
quit(1)
return true
if banked.amount != 42:
print("TEST_CARGO_READOUT: FAIL (banked readout moved to %d on a pickup - it is showing the total, not the bank)" % banked.amount)
quit(1)
return true
print("carrying 7 -> cargo=%d banked=%d" % [cargo.amount, banked.amount])
# --- 5. genuinely distinguishable ---------------------------
if cargo.glow_color == banked.glow_color:
print("TEST_CARGO_READOUT: FAIL (both readouts drawn in the same colour)")
quit(1)
return true
if cargo.position == banked.position:
print("TEST_CARGO_READOUT: FAIL (both readouts in the same place)")
quit(1)
return true
print("distinct: colours %s vs %s, at %s vs %s" % [
cargo.glow_color, banked.glow_color, cargo.position, banked.position
])
# --- 4. delivery moves it across ----------------------------
game_state.deliver_cargo()
elapsed = 0.0
stage = 2
return false
2:
if elapsed < 0.2:
return false
if cargo.visible:
print("TEST_CARGO_READOUT: FAIL (cargo readout still showing after delivery)")
quit(1)
return true
if banked.amount != 49:
print("TEST_CARGO_READOUT: FAIL (banked shows %d after delivering 7 into 42)" % banked.amount)
quit(1)
return true
print("delivered -> cargo hidden, banked shows %d" % banked.amount)
print("TEST_CARGO_READOUT: PASS")
quit(0)
return true
return false

View file

@ -0,0 +1 @@
uid://c2y4e2f1rgera