Add timed craft queue and hangar
This commit is contained in:
parent
b6427b080d
commit
82552464d2
14 changed files with 660 additions and 191 deletions
|
|
@ -13,6 +13,11 @@ set -uo pipefail
|
|||
PROJECT_DIR="$1"; shift
|
||||
JOBS="$1"; shift
|
||||
VERBOSE="$1"; shift
|
||||
# A broken test can otherwise leave SceneTree alive after an initialization
|
||||
# error, printing the same error every frame forever. Gameplay tests normally
|
||||
# complete in seconds, even on a busy phone, so make that failure explicit
|
||||
# rather than making the suite look hung. Override for deliberate long runs.
|
||||
TEST_TIMEOUT_SECONDS="${TEST_TIMEOUT_SECONDS:-120}"
|
||||
|
||||
GODOT_BIN="/root/Godot_v4.7.1-stable_linux.arm64"
|
||||
cd "$PROJECT_DIR" || exit 2
|
||||
|
|
@ -26,6 +31,8 @@ fi
|
|||
|
||||
outdir="$(mktemp -d)"
|
||||
trap 'rm -rf "$outdir"' EXIT
|
||||
REPORT_DIR="${TEST_REPORT_DIR:-$PROJECT_DIR/.test-results}"
|
||||
mkdir -p "$REPORT_DIR"
|
||||
|
||||
# Import once, serially, before anything runs in parallel. Godot writes to
|
||||
# .godot/ while importing; several instances discovering a cold cache at the
|
||||
|
|
@ -46,7 +53,8 @@ run_one() {
|
|||
# 3 parallel runs. Pinned, a test simulates the same 60fps whatever else
|
||||
# the phone is doing, and its result no longer depends on how busy the
|
||||
# machine was.
|
||||
"$GODOT_BIN" --headless --fixed-fps 60 -s "res://tests/$name" >"$log" 2>&1
|
||||
timeout "$TEST_TIMEOUT_SECONDS" "$GODOT_BIN" --headless --fixed-fps 60 \
|
||||
-s "res://tests/$name" >"$log" 2>&1
|
||||
local rc=$?
|
||||
end=$(date +%s%N)
|
||||
local ms=$(( (end - start) / 1000000 ))
|
||||
|
|
@ -57,6 +65,16 @@ run_one() {
|
|||
# while the log says otherwise.
|
||||
if (( rc == 0 )) && grep -q ": PASS" "$log"; then
|
||||
printf 'PASS %6dms %s\n' "$ms" "$name" > "$outdir/$name.status"
|
||||
elif (( rc == 124 )); then
|
||||
printf 'TIMEOUT %4dms %s (limit %ss)\n' \
|
||||
"$ms" "$name" "$TEST_TIMEOUT_SECONDS" > "$outdir/$name.status"
|
||||
elif (( rc == 137 || rc == 139 )); then
|
||||
# Killed, not failed - almost always the kernel reclaiming memory. Worth
|
||||
# saying plainly: a killed worker and a failing one both simply produced
|
||||
# no verdict before, so a whole suite dying to the OOM killer was
|
||||
# indistinguishable from a hang.
|
||||
printf 'KILLED %4dms %s (signal %d - out of memory? try JOBS=1)\n' \
|
||||
"$ms" "$name" "$(( rc - 128 ))" > "$outdir/$name.status"
|
||||
else
|
||||
printf 'FAIL %6dms %s (exit %d)\n' "$ms" "$name" "$rc" > "$outdir/$name.status"
|
||||
fi
|
||||
|
|
@ -90,4 +108,9 @@ done
|
|||
|
||||
echo
|
||||
echo "$(( total - failed ))/$total passed"
|
||||
# Keep the concise verdicts after the temporary logs are removed. This is
|
||||
# especially useful from Termux, where a long-running child can outlive the
|
||||
# terminal command that launched it and its final buffered output is lost.
|
||||
rm -f "$REPORT_DIR"/*.status
|
||||
cp "$outdir"/*.status "$REPORT_DIR"/
|
||||
(( failed == 0 ))
|
||||
|
|
|
|||
|
|
@ -32,9 +32,36 @@ set -uo pipefail
|
|||
|
||||
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
# Leave headroom: Godot is memory-hungry and this is a phone, not a build
|
||||
# server. Override with JOBS= if you know better.
|
||||
JOBS="${JOBS:-6}"
|
||||
# Worker count comes from FREE MEMORY, not core count. Each Godot instance is
|
||||
# ~400MB and this phone shares its RAM with everything else running on it.
|
||||
# Getting it wrong is bad in both directions:
|
||||
# - too many and the kernel SIGKILLs the runner mid-suite (exit 137), which
|
||||
# looks exactly like a hang
|
||||
# - too many under swap pressure and every worker thrashes: the same suite
|
||||
# measured 100-190s with memory free, and 888s while 9GB sat in swap
|
||||
# One worker per ~900MB available, capped at 6, and forced to 1 once swap is
|
||||
# more than half consumed - past that, parallelism costs more than it buys.
|
||||
# Override with JOBS= when you know better.
|
||||
_pick_jobs() {
|
||||
local avail swap_total swap_free n
|
||||
avail=$(awk '/MemAvailable/ {print int($2 / 1024)}' /proc/meminfo 2>/dev/null)
|
||||
swap_total=$(awk '/SwapTotal/ {print $2}' /proc/meminfo 2>/dev/null)
|
||||
swap_free=$(awk '/SwapFree/ {print $2}' /proc/meminfo 2>/dev/null)
|
||||
if [[ -z "$avail" ]]; then
|
||||
echo 2
|
||||
return
|
||||
fi
|
||||
if [[ -n "$swap_total" ]] && (( swap_total > 0 )) \
|
||||
&& (( (swap_total - swap_free) * 2 > swap_total )); then
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
n=$(( avail / 900 ))
|
||||
(( n < 1 )) && n=1
|
||||
(( n > 6 )) && n=6
|
||||
echo "$n"
|
||||
}
|
||||
JOBS="${JOBS:-$(_pick_jobs)}"
|
||||
VERBOSE="${VERBOSE:-0}"
|
||||
IN_PLACE="${IN_PLACE:-0}"
|
||||
WORK_DIR="${WORK_DIR:-$HOME/.cache/spaceship-tests}"
|
||||
|
|
|
|||
|
|
@ -2,28 +2,29 @@ extends SceneTree
|
|||
|
||||
# Headless gameplay test: run with
|
||||
# godot --headless -s res://tests/test_building_stock_queue.gd
|
||||
# Forgejo #16 (part 3 of #12): a building produces out of its OWN stock, and
|
||||
# an order it cannot pay for yet is queued rather than refused.
|
||||
# Forgejo #16 (part 3 of #12), updated for #23: a building produces out of
|
||||
# its OWN stock, an order it cannot pay for yet is queued rather than
|
||||
# refused, and building now TAKES TIME.
|
||||
#
|
||||
# Before this, BuildController deducted from the bank at the moment of
|
||||
# purchase and refused outright with "Not enough rocks", so nothing ever had
|
||||
# to be delivered anywhere.
|
||||
# What #23 changed here: orders no longer complete the instant the ore
|
||||
# lands. The head of the queue consumes its ore, builds for build_time, and
|
||||
# only then finishes - and it finishes into the mothership hangar rather
|
||||
# than straight into the fleet. So a queue is something the player can watch
|
||||
# and cancel, and a full fleet no longer stops a crafter working.
|
||||
#
|
||||
# Verifies:
|
||||
# 1. ordering with an empty stock queues instead of failing, and produces
|
||||
# nothing yet
|
||||
# 2. it does NOT quietly take the cost out of the bank - the whole point is
|
||||
# that the building has to be supplied
|
||||
# 3. delivering enough stock completes the order on its own
|
||||
# 4. one delivery covering several queued orders clears them all, rather
|
||||
# than dribbling out one per frame
|
||||
# 3. delivering enough stock starts the build, and it completes on its own
|
||||
# 4. orders build ONE AT A TIME in order, not all at once the moment a big
|
||||
# delivery covers them
|
||||
# 5. a part-delivery is not enough: stock below the cost leaves the order
|
||||
# queued and the ore untouched
|
||||
# 6. stock and queue survive the space scene being torn down (the trip to
|
||||
# a planet's surface)
|
||||
# 7. production completes with NO fleet in existence, and the fleet picks
|
||||
# the vessel up when it comes back - orders must not depend on which
|
||||
# scene the player is standing in
|
||||
# 6. stock and queue survive the space scene being torn down
|
||||
# 7. a build completes with NO fleet in existence, into the hangar, and
|
||||
# waits there - it is not lost, and not silently crewed either
|
||||
|
||||
var game_state: Node
|
||||
var scene: Node
|
||||
|
|
@ -34,6 +35,7 @@ var stage: int = 0
|
|||
var elapsed: float = 0.0
|
||||
var ships_at_start: int = 0
|
||||
var bank_at_start: int = 0
|
||||
var stored_at_start: int = 0
|
||||
var ticks: int = 0
|
||||
|
||||
const CRAFTER_AT: Vector2 = Vector2(900.0, 0.0)
|
||||
|
|
@ -43,13 +45,12 @@ func _initialize() -> void:
|
|||
game_state = root.get_node("GameState")
|
||||
game_state.buildings.clear()
|
||||
game_state.extractors.clear()
|
||||
game_state.stored_vessels.clear()
|
||||
game_state.rocks = 500
|
||||
game_state.fleet_cargo = 0
|
||||
game_state.vessels_owed.clear()
|
||||
# Room to grow: the default cap is BASE_FLEET_SIZE (5) and the fleet
|
||||
# starts at exactly 5, so without this every completed order would have
|
||||
# nowhere to put its vessel and the test would be measuring the cap
|
||||
# rather than the queue.
|
||||
# Real build times are 10s+; the timed path is what matters here, not the
|
||||
# wall clock. See GameState.build_time_scale.
|
||||
game_state.build_time_scale = 0.02
|
||||
game_state.unlocked = ["shipyards", "coordination", "wing", "squadron", "armada"]
|
||||
scene = load("res://Main.tscn").instantiate()
|
||||
root.add_child(scene)
|
||||
|
|
@ -57,6 +58,10 @@ func _initialize() -> void:
|
|||
field = scene.get_node("BuildingField")
|
||||
|
||||
|
||||
func _build_time() -> float:
|
||||
return game_state.build_time(GameState.PRODUCT_SHIP)
|
||||
|
||||
|
||||
func _process(delta: float) -> bool:
|
||||
elapsed += delta
|
||||
match stage:
|
||||
|
|
@ -90,7 +95,7 @@ func _process(delta: float) -> bool:
|
|||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (order did not stay queued: %d pending)" % game_state.pending_orders(crafter_id))
|
||||
quit(1)
|
||||
return true
|
||||
if fleet.ships.size() != ships_at_start:
|
||||
if not game_state.stored_vessels.is_empty():
|
||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (a vessel appeared with an empty stock)")
|
||||
quit(1)
|
||||
return true
|
||||
|
|
@ -98,8 +103,8 @@ func _process(delta: float) -> bool:
|
|||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (queueing took %d out of the bank - the building is supposed to be supplied)" % (bank_at_start - game_state.rocks))
|
||||
quit(1)
|
||||
return true
|
||||
print("empty stock -> queued=%d ships=%d bank=%d (untouched)" % [
|
||||
game_state.pending_orders(crafter_id), fleet.ships.size(), game_state.rocks
|
||||
print("empty stock -> queued=%d stored=%d bank=%d (untouched)" % [
|
||||
game_state.pending_orders(crafter_id), game_state.stored_vessels.size(), game_state.rocks
|
||||
])
|
||||
|
||||
# --- 5. a part-delivery is not enough -----------------------
|
||||
|
|
@ -110,7 +115,7 @@ func _process(delta: float) -> bool:
|
|||
3:
|
||||
if elapsed < 0.3:
|
||||
return false
|
||||
if game_state.pending_orders(crafter_id) != 1 or fleet.ships.size() != ships_at_start:
|
||||
if game_state.pending_orders(crafter_id) != 1 or not game_state.stored_vessels.is_empty():
|
||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (produced on a part-delivery of %d against a cost of %d)" % [
|
||||
GameState.SHIP_COST - 1, GameState.SHIP_COST
|
||||
])
|
||||
|
|
@ -124,70 +129,95 @@ func _process(delta: float) -> bool:
|
|||
game_state.building_stock(crafter_id), GameState.SHIP_COST
|
||||
])
|
||||
|
||||
# --- 3. topping it up completes the order -------------------
|
||||
# --- 3. topping it up starts the build ----------------------
|
||||
game_state.deposit_to_building(crafter_id, 1)
|
||||
elapsed = 0.0
|
||||
stage = 4
|
||||
return false
|
||||
4:
|
||||
if elapsed < 0.4:
|
||||
# One tick: the build must have STARTED (ore taken) but not yet
|
||||
# finished, or "takes time" means nothing.
|
||||
ticks += 1
|
||||
if ticks < 2:
|
||||
return false
|
||||
if game_state.pending_orders(crafter_id) != 0:
|
||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (order not completed once the stock arrived)")
|
||||
quit(1)
|
||||
return true
|
||||
if fleet.ships.size() != ships_at_start + 1:
|
||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (no vessel delivered: %d -> %d)" % [
|
||||
ships_at_start, fleet.ships.size()
|
||||
])
|
||||
var queue: Array = game_state.order_list(crafter_id)
|
||||
if queue.is_empty() or not bool(queue[0].started):
|
||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (a fully funded order never started building)")
|
||||
quit(1)
|
||||
return true
|
||||
if game_state.building_stock(crafter_id) != 0:
|
||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (stock not consumed: %d left)" % game_state.building_stock(crafter_id))
|
||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (starting the build did not consume the ore)")
|
||||
quit(1)
|
||||
return true
|
||||
print("supplied -> ships %d->%d, stock spent" % [ships_at_start, fleet.ships.size()])
|
||||
if not game_state.stored_vessels.is_empty():
|
||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (the vessel appeared instantly - builds are supposed to take time)")
|
||||
quit(1)
|
||||
return true
|
||||
print("funded -> building started, ore consumed, nothing delivered yet")
|
||||
elapsed = 0.0
|
||||
stage = 5
|
||||
return false
|
||||
5:
|
||||
if elapsed < _build_time() * 2.0:
|
||||
return false
|
||||
if game_state.pending_orders(crafter_id) != 0:
|
||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (build never finished)")
|
||||
quit(1)
|
||||
return true
|
||||
if game_state.stored_vessels.size() != 1:
|
||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (finished build did not reach the hangar: %d stored)" % game_state.stored_vessels.size())
|
||||
quit(1)
|
||||
return true
|
||||
if fleet.ships.size() != ships_at_start:
|
||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (the vessel crewed itself - the player is supposed to pick it up)")
|
||||
quit(1)
|
||||
return true
|
||||
print("built -> 1 vessel waiting in the hangar, fleet untouched at %d" % fleet.ships.size())
|
||||
|
||||
# --- 4. one delivery clears ALL the orders it covers ---------
|
||||
# Measured in TICKS, not seconds, and with more orders than the
|
||||
# ticks allowed: production that handled one order per frame
|
||||
# would still clear them all inside any generous wall-clock wait,
|
||||
# so a timed check cannot tell "cleared at once" from "dribbled
|
||||
# out". Three orders, two ticks - only clearing them together
|
||||
# passes.
|
||||
ships_at_start = fleet.ships.size()
|
||||
# --- 4. one at a time, in order -----------------------------
|
||||
stored_at_start = game_state.stored_vessels.size()
|
||||
game_state.queue_order(crafter_id)
|
||||
game_state.queue_order(crafter_id)
|
||||
game_state.queue_order(crafter_id)
|
||||
game_state.deposit_to_building(crafter_id, GameState.SHIP_COST * 3)
|
||||
ticks = 0
|
||||
stage = 5
|
||||
return false
|
||||
5:
|
||||
ticks += 1
|
||||
if ticks < 2:
|
||||
return false
|
||||
if game_state.pending_orders(crafter_id) != 0 or fleet.ships.size() != ships_at_start + 3:
|
||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (a delivery covering 3 orders left %d queued after %d ticks, ships %d->%d)" % [
|
||||
game_state.pending_orders(crafter_id), ticks, ships_at_start, fleet.ships.size()
|
||||
])
|
||||
quit(1)
|
||||
return true
|
||||
print("one delivery cleared 3 orders in %d ticks -> ships %d->%d" % [
|
||||
ticks, ships_at_start, fleet.ships.size()
|
||||
])
|
||||
|
||||
# --- 6 + 7. no scene, no fleet: production still completes --
|
||||
# The scene has to be GONE before the order is placed. With a
|
||||
# fleet still alive and under its cap it claims the vessel the
|
||||
# same frame production finishes, which would leave vessels_owed
|
||||
# back at 0 and prove nothing about orders surviving without it.
|
||||
ships_at_start = fleet.ships.size()
|
||||
scene.queue_free()
|
||||
elapsed = 0.0
|
||||
stage = 6
|
||||
return false
|
||||
6:
|
||||
# Enough time for exactly one build, and no more. Funding all
|
||||
# three at once must NOT finish all three at once - that is the
|
||||
# behaviour #23 replaced.
|
||||
if elapsed < _build_time() * 1.4:
|
||||
return false
|
||||
var done: int = game_state.stored_vessels.size() - stored_at_start
|
||||
if done != 1:
|
||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (after one build time, %d of 3 orders finished - they are not building one at a time)" % done)
|
||||
quit(1)
|
||||
return true
|
||||
if game_state.pending_orders(crafter_id) != 2:
|
||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (%d orders left, expected 2 still queued)" % game_state.pending_orders(crafter_id))
|
||||
quit(1)
|
||||
return true
|
||||
print("one build time -> 1 of 3 done, 2 still queued (sequential)")
|
||||
elapsed = 0.0
|
||||
stage = 7
|
||||
return false
|
||||
7:
|
||||
if elapsed < _build_time() * 4.0:
|
||||
return false
|
||||
if game_state.stored_vessels.size() - stored_at_start != 3:
|
||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (the rest of the queue never drained: %d of 3)" % (game_state.stored_vessels.size() - stored_at_start))
|
||||
quit(1)
|
||||
return true
|
||||
print("queue drained in order -> 3 vessels waiting")
|
||||
|
||||
# --- 6 + 7. no scene, no fleet ------------------------------
|
||||
stored_at_start = game_state.stored_vessels.size()
|
||||
scene.queue_free()
|
||||
elapsed = 0.0
|
||||
stage = 8
|
||||
return false
|
||||
8:
|
||||
if elapsed < 0.3:
|
||||
return false
|
||||
if is_instance_valid(fleet):
|
||||
|
|
@ -195,39 +225,22 @@ func _process(delta: float) -> bool:
|
|||
game_state.queue_order(crafter_id)
|
||||
game_state.deposit_to_building(crafter_id, GameState.SHIP_COST)
|
||||
elapsed = 0.0
|
||||
stage = 61
|
||||
stage = 9
|
||||
return false
|
||||
61:
|
||||
if elapsed < 0.4:
|
||||
9:
|
||||
if elapsed < _build_time() * 3.0:
|
||||
return false
|
||||
# The queue and stock live in the autoload, so tearing the space
|
||||
# scene down must not touch them - and the order must complete
|
||||
# even with no Fleet node anywhere to hand the vessel to.
|
||||
# The queue, the stock and the hangar live in the autoload, so
|
||||
# tearing the space scene down must not touch them.
|
||||
if game_state.pending_orders(crafter_id) != 0:
|
||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (order stalled once the space scene was gone)")
|
||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (build stalled once the space scene was gone)")
|
||||
quit(1)
|
||||
return true
|
||||
if game_state.vessels_owed.is_empty():
|
||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (vessel completed with no fleet present was lost, not owed)")
|
||||
if game_state.stored_vessels.size() != stored_at_start + 1:
|
||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (a vessel built with no fleet present was lost)")
|
||||
quit(1)
|
||||
return true
|
||||
print("no scene at all -> order completed, %d vessel owed" % game_state.vessels_owed.size())
|
||||
|
||||
# Come back to space: the fleet should claim what it is owed.
|
||||
scene = load("res://Main.tscn").instantiate()
|
||||
root.add_child(scene)
|
||||
fleet = scene.get_node("Fleet")
|
||||
elapsed = 0.0
|
||||
stage = 8
|
||||
return false
|
||||
8:
|
||||
if elapsed < 0.5:
|
||||
return false
|
||||
if not game_state.vessels_owed.is_empty():
|
||||
print("TEST_BUILDING_STOCK_QUEUE: FAIL (returning fleet never claimed its %d owed vessel(s))" % game_state.vessels_owed.size())
|
||||
quit(1)
|
||||
return true
|
||||
print("fleet returned and claimed the owed vessel (ships=%d)" % fleet.ships.size())
|
||||
print("no scene at all -> build completed into the hangar (%d waiting)" % game_state.stored_vessels.size())
|
||||
print("TEST_BUILDING_STOCK_QUEUE: PASS")
|
||||
quit(0)
|
||||
return true
|
||||
|
|
@ -235,10 +248,6 @@ func _process(delta: float) -> bool:
|
|||
|
||||
|
||||
func _clear_hazards() -> void:
|
||||
# set_process(false) on AsteroidField only stops NEW chunks spawning -
|
||||
# rocks already in the world keep drifting and can destroy a parked ship,
|
||||
# which silently changes the fleet size this test asserts on. This is a
|
||||
# logistics test, not a survival one.
|
||||
for a in get_nodes_in_group("asteroids"):
|
||||
if is_instance_valid(a):
|
||||
a.queue_free()
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@ func _initialize() -> void:
|
|||
game_state.unlocked = ["shipyards", "coordination", "wing", "squadron", "armada"]
|
||||
game_state.rocks = 100
|
||||
game_state.fleet_cargo = 0
|
||||
game_state.vessels_owed.clear()
|
||||
game_state.stored_vessels.clear()
|
||||
game_state.build_time_scale = 0.02
|
||||
scene = load("res://Main.tscn").instantiate()
|
||||
root.add_child(scene)
|
||||
controller = scene.get_node("BuildController")
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ func _initialize() -> void:
|
|||
game_state.transporters.clear()
|
||||
game_state.rocks = 900
|
||||
game_state.fleet_cargo = 0
|
||||
game_state.vessels_owed.clear()
|
||||
game_state.stored_vessels.clear()
|
||||
game_state.build_time_scale = 0.02
|
||||
game_state.unlocked = ["shipyards", "logistics", "freight"]
|
||||
scene = load("res://Main.tscn").instantiate()
|
||||
root.add_child(scene)
|
||||
|
|
|
|||
|
|
@ -52,7 +52,8 @@ func _initialize() -> void:
|
|||
game_state.extractors.clear()
|
||||
game_state.rocks = 40
|
||||
game_state.fleet_cargo = 0
|
||||
game_state.vessels_owed.clear()
|
||||
game_state.stored_vessels.clear()
|
||||
game_state.build_time_scale = 0.02
|
||||
game_state.unlocked = ["shipyards", "coordination", "wing", "squadron", "armada"]
|
||||
scene = load("res://Main.tscn").instantiate()
|
||||
root.add_child(scene)
|
||||
|
|
@ -161,20 +162,21 @@ func _process(delta: float) -> bool:
|
|||
stage = 6
|
||||
return false
|
||||
6:
|
||||
if elapsed < 0.5:
|
||||
# Long enough for the build itself, which #23 made take time.
|
||||
if elapsed < game_state.build_time(GameState.PRODUCT_SHIP) * 3.0:
|
||||
return false
|
||||
# --- 5. the queued order completed off the fleet's own ore ---
|
||||
if game_state.pending_orders(crafter_id) != 0:
|
||||
print("TEST_MANUAL_SUPPLY_ROUTE: FAIL (order still queued after a hand delivery)")
|
||||
quit(1)
|
||||
return true
|
||||
if fleet.ships.size() != ships_before + 1:
|
||||
print("TEST_MANUAL_SUPPLY_ROUTE: FAIL (no vessel: %d -> %d)" % [ships_before, fleet.ships.size()])
|
||||
# Since #23 a finished vessel waits in the hangar rather than
|
||||
# crewing itself, so this is where it lands.
|
||||
if game_state.stored_vessels.size() != 1:
|
||||
print("TEST_MANUAL_SUPPLY_ROUTE: FAIL (no vessel built: %d in the hangar)" % game_state.stored_vessels.size())
|
||||
quit(1)
|
||||
return true
|
||||
print("queued order fulfilled by hand, no transport hub: ships %d->%d" % [
|
||||
ships_before, fleet.ships.size()
|
||||
])
|
||||
print("queued order fulfilled by hand, no transport hub: %d vessel in the hangar" % game_state.stored_vessels.size())
|
||||
|
||||
# --- 6. conservation ----------------------------------------
|
||||
var expected: int = total_before - GameState.SHIP_COST
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ func _initialize() -> void:
|
|||
game_state.transporters.clear()
|
||||
game_state.rocks = 100
|
||||
game_state.fleet_cargo = 0
|
||||
game_state.vessels_owed.clear()
|
||||
game_state.stored_vessels.clear()
|
||||
game_state.unlocked = ["shipyards", "coordination", "wing", "squadron", "armada"]
|
||||
scene = load("res://Main.tscn").instantiate()
|
||||
root.add_child(scene)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ func _initialize() -> void:
|
|||
game_state.buildings.clear()
|
||||
game_state.extractors.clear()
|
||||
game_state.transporters.clear()
|
||||
game_state.vessels_owed.clear()
|
||||
game_state.stored_vessels.clear()
|
||||
game_state.build_time_scale = 0.02
|
||||
game_state.reset_progression()
|
||||
game_state.rocks = 2000
|
||||
scene = load("res://Main.tscn").instantiate()
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ func _initialize() -> void:
|
|||
game_state.buildings.clear()
|
||||
game_state.extractors.clear()
|
||||
game_state.transporters.clear()
|
||||
game_state.vessels_owed.clear()
|
||||
game_state.stored_vessels.clear()
|
||||
game_state.build_time_scale = 0.02
|
||||
game_state.reset_progression()
|
||||
game_state.rocks = 1200
|
||||
game_state.fleet_cargo = 0
|
||||
|
|
@ -154,16 +155,25 @@ func _process(delta: float) -> bool:
|
|||
stage = 2
|
||||
return false
|
||||
2:
|
||||
if elapsed < 0.6:
|
||||
if elapsed < game_state.build_time(GameState.VESSEL_SHIELD) * 3.0:
|
||||
return false
|
||||
# Finished vessels wait at the mothership (#23); crew it here so
|
||||
# the rest of the checks can look at a real ship.
|
||||
if game_state.stored_vessels.size() != 1:
|
||||
print("TEST_SHIP_LAB: FAIL (the shield order produced nothing: %s)" % [game_state.stored_vessels])
|
||||
quit(1)
|
||||
return true
|
||||
if String(game_state.stored_vessels[0]) != GameState.VESSEL_SHIELD:
|
||||
print("TEST_SHIP_LAB: FAIL (a shield order came back as %s)" % game_state.stored_vessels[0])
|
||||
quit(1)
|
||||
return true
|
||||
fleet.add_ship(game_state.take_stored_vessel(0))
|
||||
var shield_ship: Node = null
|
||||
for ship in fleet.ships:
|
||||
if is_instance_valid(ship) and ship.vessel_type == GameState.VESSEL_SHIELD:
|
||||
shield_ship = ship
|
||||
if shield_ship == null:
|
||||
print("TEST_SHIP_LAB: FAIL (the shield order produced no shield vessel: %d ships, owed %s)" % [
|
||||
fleet.ships.size(), game_state.vessels_owed
|
||||
])
|
||||
print("TEST_SHIP_LAB: FAIL (crewing the stored shield vessel produced none: %d ships)" % fleet.ships.size())
|
||||
quit(1)
|
||||
return true
|
||||
if not shield_ship.has_shield():
|
||||
|
|
|
|||
|
|
@ -46,6 +46,14 @@ func _total() -> int:
|
|||
sum += int(b.get("stock", 0))
|
||||
for t in game_state.transporters:
|
||||
sum += int(t.get("carrying", 0))
|
||||
# Ore inside a build that has STARTED but not finished (#23). It has left
|
||||
# the building's stock and has not yet been consumed by a completed
|
||||
# vessel, so without counting it the total dips for the length of every
|
||||
# build - which is a gap in this sum, not a leak in the game.
|
||||
for b in game_state.buildings:
|
||||
for o in b.get("orders", []):
|
||||
if bool(o.started):
|
||||
sum += game_state.product_cost(String(o.product))
|
||||
return sum
|
||||
|
||||
|
||||
|
|
@ -56,7 +64,8 @@ func _initialize() -> void:
|
|||
game_state.transporters.clear()
|
||||
game_state.rocks = 200
|
||||
game_state.fleet_cargo = 0
|
||||
game_state.vessels_owed.clear()
|
||||
game_state.stored_vessels.clear()
|
||||
game_state.build_time_scale = 0.02
|
||||
game_state.unlocked = ["shipyards", "logistics", "freight", "coordination", "wing", "squadron", "armada"]
|
||||
scene = load("res://Main.tscn").instantiate()
|
||||
root.add_child(scene)
|
||||
|
|
@ -118,7 +127,7 @@ func _process(delta: float) -> bool:
|
|||
total_at_start = _total()
|
||||
return false
|
||||
2:
|
||||
if elapsed < 0.3:
|
||||
if elapsed < game_state.build_time(GameState.PRODUCT_TRANSPORTER) * 3.0:
|
||||
return false
|
||||
if game_state.transporters.size() != 1:
|
||||
print("TEST_TRANSPORTER_SUPPLY: FAIL (built %d transporters from one order)" % game_state.transporters.size())
|
||||
|
|
|
|||
Loading…
Reference in a new issue