spaceship/tests/test_cargo_readout.gd
Loïc Wisniewski 0582181f26 Generalize the resource HUD readouts (#49)
Replaces ResourceCounter/GasCounter/CargoCounter/GasCargoCounter - one
hardcoded node plus one script per resource, times two scenes - with a
single ResourceReadout.gd that builds one banked + one fleet-hold row
per GameState.resource_types() entry at _ready(). A new resource added
to ResourceDefinitions gets a HUD row with no scene edit and no new
script, matching how ResourceBar and ResourceTransferList already
generalize over resources.

Preserves the existing visual layout (BANKED left, FLEET HOLD right),
the ore-on-bright-surface contrast (via a bright_background flag
replacing PlanetSurface's per-node color overrides), and the amber
fleet-in-transit color convention. Reuses the already-generic
ResourceGlyph for icons instead of NeonNumber's hardcoded ore hexagon.

Uncovered and fixed a second bug along the way: test_cargo_readout.gd
looked up its NeonNumber nodes immediately after add_child() in
_initialize(), but ResourceReadout builds its rows in _ready(), which
for a -s entry-point script does not fire synchronously during that
add_child() - it's deferred to the first frame. Moved the lookup to
after the existing elapsed wait in _process(), which is also what
tests/test_resource_readout.gd's setup does from the start.

Repo notes for the next agent, confirmed empirically this session:
- A -s entry-point script can only reach an autoload's CONSTANTS via
  the bare identifier (GameState.RESOURCE_ORE works everywhere). Calling
  any of its METHODS that way (GameState.resource_types()) fails to
  compile with "Identifier not found: GameState", even from
  _initialize(). Route method calls through the actual instance
  (root.get_node("GameState")) instead, as the rest of the suite already
  does.
- `git status --short | awk '{print $2}' | xargs touch` recreates
  deleted files as empty stubs, since touch on a missing path creates
  it. Filter out D-status paths before touching, or touch only the
  paths you know still exist.

🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 08:30:10 +02:00

129 lines
4.6 KiB
GDScript

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 mothership_id: 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
# Banking is a transfer TO the mothership now (#24), and a transfer needs
# a building to aim at. Only the record is required - the transfer
# primitives work off GameState, not off the spawned node - so this stays
# a display test rather than becoming one about the building system.
mothership_id = game_state.add_building(GameState.BUILDING_MOTHERSHIP, Vector2.ZERO)
scene = load("res://Main.tscn").instantiate()
root.add_child(scene)
func _process(delta: float) -> bool:
elapsed += delta
match stage:
0:
if elapsed < 0.2:
return false
# ResourceReadout builds its rows in _ready(), which for a -s
# entry-point script does not fire synchronously inside
# _initialize()'s add_child() - it is deferred to the first
# frame. Looking these up here, after the elapsed wait above,
# instead of right after add_child(), is what makes them non-null.
var readout: Node = scene.get_node("UI/ResourceReadout")
banked = readout.banked_number(GameState.RESOURCE_ORE)
cargo = readout.fleet_number(GameState.RESOURCE_ORE)
# --- 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.transfer_to_building(mothership_id, game_state.fleet_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