Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
extends SceneTree
|
|
|
|
|
|
|
|
|
|
# Headless gameplay test: run with
|
|
|
|
|
# godot --headless -s res://tests/test_payments_use_fleet_hold.gd
|
2026-09-02 13:43:56 +02:00
|
|
|
# A payment draws on the bank and, only at the place being paid, what the
|
|
|
|
|
# ships are carrying. Fleet cargo is physical: a building across the map may
|
|
|
|
|
# not reach into it (#180).
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
#
|
|
|
|
|
# It was also a deadlock. A new game starts with an empty bank, ore is mined
|
|
|
|
|
# into the fleet hold, and the only way to bank it is to deliver it to a
|
|
|
|
|
# mothership - so charging construction to the bank alone made the first
|
|
|
|
|
# mothership cost ore that could not yet exist.
|
|
|
|
|
#
|
|
|
|
|
# Covers:
|
|
|
|
|
# A. the deadlock premise - a bank-only check rejects what the fleet can
|
|
|
|
|
# plainly pay for, so this is guarding something real
|
|
|
|
|
# B. paying takes from the bank first and the hold only for the shortfall
|
|
|
|
|
# C. an unaffordable purchase takes nothing at all
|
|
|
|
|
# D. EVERY payment route is pooled, not just building: the skill tree, ship
|
|
|
|
|
# lab upgrades, and multi-resource vessel research
|
|
|
|
|
# E. ammo is deliberately NOT pooled - a shot comes out of what the ships
|
|
|
|
|
# are physically carrying
|
|
|
|
|
# F. end to end: an empty bank and ore in the hold actually gets a
|
|
|
|
|
# mothership built through BuildController
|
2026-09-02 13:43:56 +02:00
|
|
|
# G. no payment position, or a distant one, cannot drain the fleet hold
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
|
|
|
|
|
const HOLD_ORE: int = 40
|
2026-09-02 13:43:56 +02:00
|
|
|
const PAYMENT_AT := Vector2(120.0, 60.0)
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
|
|
|
|
|
var game_state: Node
|
|
|
|
|
var scene: Node
|
|
|
|
|
var build_controller: Node
|
|
|
|
|
var stage: int = 0
|
|
|
|
|
var elapsed: float = 0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _initialize() -> void:
|
|
|
|
|
game_state = root.get_node("GameState")
|
|
|
|
|
game_state.reset_progression()
|
|
|
|
|
game_state.buildings.clear()
|
|
|
|
|
game_state.extractors.clear()
|
2026-09-02 13:43:56 +02:00
|
|
|
game_state.fleet_position = PAYMENT_AT
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
|
#138: structures and doctrines cost more than rocks, and more the deeper they are
Both halves of the ticket, in the shape vessels have used since gas bombers
and laser ships: a definition carries an ore figure and, when it wants more
than ore, a dictionary that IS the price. `building_costs()` and
`skill_costs()` answer it the way `product_costs()` already did, so there is
one question with one answer whatever is being bought, and every payment
path now goes through `spend_resources` rather than `spend`.
Seven of ten structures and thirteen of seventeen doctrines now want gas,
energy or dark matter alongside their rocks, and the totals climb with tree
depth - structures 16/31/58/62/96 by depth, doctrines 15/30/73/96/143/258/530.
The constraint that shaped the table is REACHABILITY, and it is the reason
some prices stayed ore-only. Energy exists only where a gas burner stands,
and a gas burner needs Power Systems - so nothing at or before that depth
may be priced in energy, or it is a wall with no way through. Dark matter is
rarer still and appears once, on the deepest node in the game. The extractor
stays rocks-only despite its depth allowing gas, because the quest chain
reaches it before it has sent the player for any.
test_progressive_costs checks those as properties rather than transcribing
the numbers, so rebalancing a price costs nothing while breaking the shape
fails: nothing unreachable at its own depth, prices rising with depth, the
ore field agreeing with the dictionary (repairs quote the field alone, since
a repair drone carries ore and nothing else), and the quest chain never
asking for a structure it has not funded.
Two things cleaned up on the way:
- The per-building cost constants in GameState are gone. Nothing but tests
used them, and they were a second copy of a number the definitions already
hold - exactly what would drift once a price gained a resource.
- research_for_building() returns a display NAME, and the id-keyed half of
the tree wants an id. That mix-up is silent - skill_depth() of a name finds
no node and answers 0, so a deep structure reads as an ungated one, which
is how the first draft of the test let an unreachable price through.
research_node_for_building() is the id, and the name is expressed from it.
PriceTag no longer rebuilds its labels when re-told the same price; it
refreshes the held figures instead. The craft menu re-states prices every
frame and the build menu now does too, so that was a dozen Labels being torn
down and rebuilt sixty times a second.
2026-08-28 15:30:30 +02:00
|
|
|
var cost: int = int(game_state.building_costs(GameState.BUILDING_MOTHERSHIP)[GameState.RESOURCE_ORE])
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
|
|
|
|
|
# --- A. the deadlock, exactly as a fresh game presents it ---------------
|
|
|
|
|
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 0)
|
|
|
|
|
# Deliberately more than one starting interceptor can carry (its hold is
|
|
|
|
|
# 20, which is exactly MOTHERSHIP_COST), so "the hold paid" shows up as a
|
|
|
|
|
# remainder rather than as the hold happening to empty to zero.
|
|
|
|
|
game_state.fleet_inventory.total_capacity = HOLD_ORE
|
|
|
|
|
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, HOLD_ORE)
|
|
|
|
|
if game_state.bank_inventory.amount(GameState.RESOURCE_ORE) >= cost:
|
|
|
|
|
_fail("the bank was not actually empty, so nothing below tests the deadlock")
|
|
|
|
|
return
|
2026-09-02 13:43:56 +02:00
|
|
|
if game_state.can_afford(cost):
|
|
|
|
|
_fail("a location-free affordability check reached into the fleet hold")
|
|
|
|
|
return
|
|
|
|
|
if not game_state.can_afford(cost, PAYMENT_AT):
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
_fail("a fleet holding %d ore could not afford a %d purchase" % [HOLD_ORE, cost])
|
|
|
|
|
return
|
|
|
|
|
print("bank 0 + hold %d can afford a %d purchase" % [HOLD_ORE, cost])
|
|
|
|
|
|
|
|
|
|
# --- B. bank first, hold for the shortfall -------------------------------
|
2026-09-02 13:43:56 +02:00
|
|
|
if not game_state.spend(cost, PAYMENT_AT):
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
_fail("spend refused a purchase it had just called affordable")
|
|
|
|
|
return
|
|
|
|
|
if game_state.bank_inventory.amount(GameState.RESOURCE_ORE) != 0 \
|
|
|
|
|
or game_state.fleet_inventory.amount(GameState.RESOURCE_ORE) != HOLD_ORE - cost:
|
|
|
|
|
_fail("paying from an empty bank should have taken the whole %d from the hold: bank %d, hold %d" \
|
|
|
|
|
% [cost, game_state.bank_inventory.amount(GameState.RESOURCE_ORE),
|
|
|
|
|
game_state.fleet_inventory.amount(GameState.RESOURCE_ORE)])
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 8)
|
|
|
|
|
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, 30)
|
2026-09-02 13:43:56 +02:00
|
|
|
if not game_state.spend(20, PAYMENT_AT):
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
_fail("a 20 purchase was refused with 8 banked and 30 held")
|
|
|
|
|
return
|
|
|
|
|
if game_state.bank_inventory.amount(GameState.RESOURCE_ORE) != 0 \
|
|
|
|
|
or game_state.fleet_inventory.amount(GameState.RESOURCE_ORE) != 18:
|
|
|
|
|
_fail("a split payment should empty the bank first: bank %d, hold %d (expected 0 and 18)" \
|
|
|
|
|
% [game_state.bank_inventory.amount(GameState.RESOURCE_ORE),
|
|
|
|
|
game_state.fleet_inventory.amount(GameState.RESOURCE_ORE)])
|
|
|
|
|
return
|
|
|
|
|
print("split payment drained the bank first: bank 8 -> 0, hold 30 -> 18")
|
|
|
|
|
|
|
|
|
|
# --- C. no partial charge -----------------------------------------------
|
|
|
|
|
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 5)
|
|
|
|
|
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, 5)
|
2026-09-02 13:43:56 +02:00
|
|
|
if game_state.spend(50, PAYMENT_AT):
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
_fail("a purchase costing more than everything owned was allowed")
|
|
|
|
|
return
|
|
|
|
|
if game_state.bank_inventory.amount(GameState.RESOURCE_ORE) != 5 \
|
|
|
|
|
or game_state.fleet_inventory.amount(GameState.RESOURCE_ORE) != 5:
|
|
|
|
|
_fail("a rejected purchase still took resources: bank %d, hold %d (expected 5 and 5)" \
|
|
|
|
|
% [game_state.bank_inventory.amount(GameState.RESOURCE_ORE),
|
|
|
|
|
game_state.fleet_inventory.amount(GameState.RESOURCE_ORE)])
|
|
|
|
|
return
|
|
|
|
|
print("a purchase nobody can afford takes nothing")
|
|
|
|
|
|
2026-09-02 13:43:56 +02:00
|
|
|
# --- G. cargo does not teleport to a remote purchase ------------------
|
|
|
|
|
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 0)
|
|
|
|
|
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, cost)
|
|
|
|
|
game_state.fleet_position = PAYMENT_AT + Vector2(9000.0, 0.0)
|
|
|
|
|
if game_state.spend(cost, PAYMENT_AT):
|
|
|
|
|
_fail("a remote purchase took resources from the fleet hold")
|
|
|
|
|
return
|
|
|
|
|
if game_state.fleet_resource(GameState.RESOURCE_ORE) != cost:
|
|
|
|
|
_fail("a refused remote payment still drained the hold")
|
|
|
|
|
return
|
|
|
|
|
if game_state.spend(cost):
|
|
|
|
|
_fail("a purchase with no location reached into the fleet hold")
|
|
|
|
|
return
|
|
|
|
|
game_state.fleet_position = PAYMENT_AT
|
|
|
|
|
print("distant and location-free payments leave fleet cargo untouched")
|
|
|
|
|
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
# The hold has a real capacity (one starting interceptor carries 20), and
|
|
|
|
|
# set_amount() is clamped by it - which silently gave the checks below
|
|
|
|
|
# less than they asked for and made an affordable price look unaffordable.
|
|
|
|
|
# Capacity is not what any of them are about, so take it out of the way;
|
|
|
|
|
# the end-to-end stage sets a concrete one again.
|
|
|
|
|
game_state.fleet_inventory.total_capacity = -1
|
|
|
|
|
|
|
|
|
|
if not _check_multi_resource_atomicity():
|
|
|
|
|
return
|
|
|
|
|
if not _check_skill_tree():
|
|
|
|
|
return
|
|
|
|
|
if not _check_ship_lab():
|
|
|
|
|
return
|
|
|
|
|
if not _check_vessel_research():
|
|
|
|
|
return
|
|
|
|
|
if not _check_ammo_is_not_pooled():
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# --- F. end to end, through the real build path --------------------------
|
|
|
|
|
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 0)
|
|
|
|
|
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, HOLD_ORE)
|
#84: move 215 root files into a folder hierarchy
Everything lived in one flat directory, so a diff gave no clue what area it
touched and finding the file you wanted meant knowing its exact name. Twelve
folders now, grouped by what a thing IS rather than what type it is - a
vessel's definition, its component scenes and its upgrade resources sit
together, because that is the set you read when you change one of them.
buildings core controllers enemies logistics planets
resources shaders surface ui vessels vfx world
332 files moved and every res:// reference rewritten - 306 files touched,
including the autoload and main-scene paths in project.godot. Nothing renamed:
this is purely where files sit, so the diff is a pile of pure moves and no
behaviour is buried in it. ui_theme.tres, icon.svg and web_shell.html stay at
the root, where Godot and the export preset expect them.
Verified three ways, because a suite alone would not have caught all of it:
the full 117 pass, the web export builds clean, and the game renders.
The export is why that matters. The FIRST export after the move failed with
"Attempt to open script 'res://GameState.gd'" - the old path - while
project.godot on disk plainly said res://core/GameState.gd. That is this
project's documented Android FUSE mtime trap: Godot served a cached copy of a
file whose content had changed. A `touch` fixed it. Nothing in the test suite
would have found that, because the suite runs from a copy on a native
filesystem - the one place the bug cannot happen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 19:44:14 +02:00
|
|
|
scene = load("res://world/Main.tscn").instantiate()
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
root.add_child(scene)
|
|
|
|
|
current_scene = scene
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# A price in two resources where only one is coverable must take NEITHER.
|
|
|
|
|
func _check_multi_resource_atomicity() -> bool:
|
|
|
|
|
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 10)
|
|
|
|
|
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, 10)
|
|
|
|
|
game_state.bank_inventory.set_amount(GameState.RESOURCE_GAS, 1)
|
|
|
|
|
game_state.fleet_inventory.set_amount(GameState.RESOURCE_GAS, 1)
|
2026-09-02 13:43:56 +02:00
|
|
|
if game_state.spend_resources({GameState.RESOURCE_ORE: 15, GameState.RESOURCE_GAS: 40}, PAYMENT_AT):
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
return _fail("a two-resource price was charged with only one of them affordable")
|
|
|
|
|
if game_state.available_ore() != 20 or game_state.available_resource(GameState.RESOURCE_GAS) != 2:
|
|
|
|
|
return _fail("a refused two-resource purchase still took some ore or gas (ore %d, gas %d)" \
|
|
|
|
|
% [game_state.available_ore(), game_state.available_resource(GameState.RESOURCE_GAS)])
|
2026-09-02 13:43:56 +02:00
|
|
|
if not game_state.spend_resources({GameState.RESOURCE_ORE: 15, GameState.RESOURCE_GAS: 2}, PAYMENT_AT):
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
return _fail("an affordable two-resource price was refused")
|
|
|
|
|
if game_state.available_ore() != 5 or game_state.available_resource(GameState.RESOURCE_GAS) != 0:
|
|
|
|
|
return _fail("a two-resource purchase charged wrongly (ore %d left, gas %d left)" \
|
|
|
|
|
% [game_state.available_ore(), game_state.available_resource(GameState.RESOURCE_GAS)])
|
|
|
|
|
print("multi-resource prices are all-or-nothing, and pooled per resource")
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _check_skill_tree() -> bool:
|
|
|
|
|
var id: String = "shipyards"
|
|
|
|
|
var node: Dictionary = game_state.skill_node(id)
|
|
|
|
|
var cost: int = int(node.cost)
|
|
|
|
|
game_state.unlocked.clear()
|
|
|
|
|
game_state.research_points = 999
|
|
|
|
|
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 0)
|
|
|
|
|
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, cost)
|
2026-09-02 13:43:56 +02:00
|
|
|
if not game_state.can_unlock(id, PAYMENT_AT):
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
return _fail("the skill tree refused '%s' (cost %d) with the whole price in the fleet hold" % [id, cost])
|
2026-09-02 13:43:56 +02:00
|
|
|
if not game_state.unlock(id, PAYMENT_AT):
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
return _fail("unlocking '%s' failed despite can_unlock saying yes" % id)
|
|
|
|
|
if game_state.fleet_inventory.amount(GameState.RESOURCE_ORE) != 0:
|
|
|
|
|
return _fail("the skill unlock did not charge the hold: %d left of %d" \
|
|
|
|
|
% [game_state.fleet_inventory.amount(GameState.RESOURCE_ORE), cost])
|
|
|
|
|
print("skill tree: unlocked '%s' for %d paid entirely from the hold" % [id, cost])
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _check_ship_lab() -> bool:
|
|
|
|
|
# Ship-lab upgrades need a lab standing; the subject is where the money
|
|
|
|
|
# comes from, not the prerequisite.
|
|
|
|
|
game_state.buildings.clear()
|
|
|
|
|
game_state.buildings.append({"id": 1, "type": GameState.BUILDING_SHIP_LAB,
|
|
|
|
|
"position": Vector2.ZERO})
|
|
|
|
|
if not game_state.has_ship_lab():
|
|
|
|
|
return _fail("could not place a ship lab, so the upgrade path cannot be tested")
|
|
|
|
|
var id: String = ""
|
|
|
|
|
for upgrade in game_state.upgrades_for(GameState.VESSEL_INTERCEPTOR):
|
|
|
|
|
if not game_state.upgrade_maxed(String(upgrade.id)):
|
|
|
|
|
id = String(upgrade.id)
|
|
|
|
|
break
|
|
|
|
|
if id == "":
|
|
|
|
|
return _fail("no ship-lab upgrade was available to buy")
|
|
|
|
|
var cost: int = game_state.upgrade_cost(id)
|
|
|
|
|
game_state.research_points = 999
|
|
|
|
|
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 0)
|
|
|
|
|
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, cost)
|
2026-09-02 13:43:56 +02:00
|
|
|
if not game_state.can_buy_upgrade(id, PAYMENT_AT):
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
return _fail("the ship lab refused upgrade '%s' (cost %d) with the whole price in the hold" % [id, cost])
|
2026-09-02 13:43:56 +02:00
|
|
|
if not game_state.buy_upgrade(id, PAYMENT_AT):
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
return _fail("buying '%s' failed despite can_buy_upgrade saying yes" % id)
|
|
|
|
|
if game_state.fleet_inventory.amount(GameState.RESOURCE_ORE) != 0:
|
|
|
|
|
return _fail("the upgrade did not charge the hold: %d left of %d" \
|
|
|
|
|
% [game_state.fleet_inventory.amount(GameState.RESOURCE_ORE), cost])
|
|
|
|
|
print("ship lab: bought '%s' for %d paid entirely from the hold" % [id, cost])
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _check_vessel_research() -> bool:
|
|
|
|
|
# Vessel unlocks are the multi-resource payment route.
|
|
|
|
|
var vessel: String = ""
|
|
|
|
|
var costs: Dictionary = {}
|
|
|
|
|
for definition in game_state.VESSEL_DEFINITIONS.all():
|
|
|
|
|
if String(definition.unlock_id) == "" or game_state.vessel_is_unlocked(String(definition.definition_id)):
|
|
|
|
|
continue
|
|
|
|
|
vessel = String(definition.definition_id)
|
|
|
|
|
costs = game_state.research_costs(String(definition.unlock_id))
|
|
|
|
|
if not costs.is_empty():
|
|
|
|
|
break
|
|
|
|
|
if vessel == "" or costs.is_empty():
|
|
|
|
|
return _fail("no locked vessel with a resource cost was available to research")
|
|
|
|
|
|
|
|
|
|
game_state.research_points = 999
|
|
|
|
|
for resource_type in costs:
|
|
|
|
|
game_state.bank_inventory.set_amount(String(resource_type), 0)
|
|
|
|
|
game_state.fleet_inventory.set_amount(String(resource_type), int(costs[resource_type]))
|
2026-09-02 13:43:56 +02:00
|
|
|
if not game_state.unlock_vessel(vessel, PAYMENT_AT):
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
return _fail("researching '%s' was refused with its whole price (%s) in the fleet hold" % [vessel, str(costs)])
|
|
|
|
|
for resource_type in costs:
|
|
|
|
|
if game_state.available_resource(String(resource_type)) != 0:
|
|
|
|
|
return _fail("researching '%s' left %d %s unspent" \
|
|
|
|
|
% [vessel, game_state.available_resource(String(resource_type)), resource_type])
|
|
|
|
|
print("vessel research: unlocked '%s' for %s paid entirely from the hold" % [vessel, str(costs)])
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _check_ammo_is_not_pooled() -> bool:
|
|
|
|
|
# Firing is about where the matter physically IS, so it reads the hold
|
|
|
|
|
# directly rather than going through the pooled payment path.
|
|
|
|
|
game_state.bank_inventory.set_amount(GameState.RESOURCE_GAS, 500)
|
|
|
|
|
game_state.fleet_inventory.set_amount(GameState.RESOURCE_GAS, 0)
|
|
|
|
|
if game_state.fleet_resource(GameState.RESOURCE_GAS) != 0:
|
|
|
|
|
return _fail("banked gas leaked into the fleet hold reading that ammo checks use")
|
|
|
|
|
print("ammo still reads the fleet hold alone, ignoring 500 banked gas")
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _process(delta: float) -> bool:
|
|
|
|
|
# quit() takes effect on the NEXT frame, so a failure raised during
|
|
|
|
|
# _initialize still gets one _process call with nothing set up. Without
|
|
|
|
|
# this the test spent five minutes reporting a null scene instead of the
|
|
|
|
|
# reason it actually stopped.
|
|
|
|
|
if scene == null:
|
|
|
|
|
return true
|
|
|
|
|
elapsed += delta
|
|
|
|
|
match stage:
|
|
|
|
|
0:
|
|
|
|
|
if elapsed < 0.3:
|
|
|
|
|
return false
|
|
|
|
|
scene.get_node("AsteroidField").set_process(false)
|
|
|
|
|
scene.get_node("EnemySpawner").set_process(false)
|
|
|
|
|
build_controller = scene.get_node("BuildController")
|
|
|
|
|
|
|
|
|
|
game_state.bank_inventory.set_amount(GameState.RESOURCE_ORE, 0)
|
|
|
|
|
game_state.fleet_inventory.total_capacity = HOLD_ORE
|
|
|
|
|
game_state.fleet_inventory.set_amount(GameState.RESOURCE_ORE, HOLD_ORE)
|
|
|
|
|
var before: int = game_state.buildings.size()
|
|
|
|
|
|
|
|
|
|
build_controller.pending_build_type = GameState.BUILDING_MOTHERSHIP
|
|
|
|
|
build_controller._confirm_build()
|
|
|
|
|
|
|
|
|
|
if game_state.buildings.size() != before + 1:
|
|
|
|
|
return _fail("building a mothership from the fleet's own hold did not place one (%d -> %d): %s" \
|
2026-08-31 02:18:08 +02:00
|
|
|
% [before, game_state.buildings.size(), build_controller.build_menu.status.text])
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
# Checked as a TOTAL, not as the hold alone: a mothership standing
|
|
|
|
|
# where the fleet is starts taking delivery of the hold on the
|
|
|
|
|
# very next tick, so ore legitimately moves from hold to bank
|
|
|
|
|
# straight after the build. What must be true is that exactly the
|
|
|
|
|
# cost left the pool - and that it left, since the bank was empty
|
|
|
|
|
# and could not have paid.
|
|
|
|
|
var total: int = game_state.available_ore()
|
#138: structures and doctrines cost more than rocks, and more the deeper they are
Both halves of the ticket, in the shape vessels have used since gas bombers
and laser ships: a definition carries an ore figure and, when it wants more
than ore, a dictionary that IS the price. `building_costs()` and
`skill_costs()` answer it the way `product_costs()` already did, so there is
one question with one answer whatever is being bought, and every payment
path now goes through `spend_resources` rather than `spend`.
Seven of ten structures and thirteen of seventeen doctrines now want gas,
energy or dark matter alongside their rocks, and the totals climb with tree
depth - structures 16/31/58/62/96 by depth, doctrines 15/30/73/96/143/258/530.
The constraint that shaped the table is REACHABILITY, and it is the reason
some prices stayed ore-only. Energy exists only where a gas burner stands,
and a gas burner needs Power Systems - so nothing at or before that depth
may be priced in energy, or it is a wall with no way through. Dark matter is
rarer still and appears once, on the deepest node in the game. The extractor
stays rocks-only despite its depth allowing gas, because the quest chain
reaches it before it has sent the player for any.
test_progressive_costs checks those as properties rather than transcribing
the numbers, so rebalancing a price costs nothing while breaking the shape
fails: nothing unreachable at its own depth, prices rising with depth, the
ore field agreeing with the dictionary (repairs quote the field alone, since
a repair drone carries ore and nothing else), and the quest chain never
asking for a structure it has not funded.
Two things cleaned up on the way:
- The per-building cost constants in GameState are gone. Nothing but tests
used them, and they were a second copy of a number the definitions already
hold - exactly what would drift once a price gained a resource.
- research_for_building() returns a display NAME, and the id-keyed half of
the tree wants an id. That mix-up is silent - skill_depth() of a name finds
no node and answers 0, so a deep structure reads as an ungated one, which
is how the first draft of the test let an unreachable price through.
research_node_for_building() is the id, and the name is expressed from it.
PriceTag no longer rebuilds its labels when re-told the same price; it
refreshes the held figures instead. The craft menu re-states prices every
frame and the build menu now does too, so that was a dozen Labels being torn
down and rebuilt sixty times a second.
2026-08-28 15:30:30 +02:00
|
|
|
if total != HOLD_ORE - int(game_state.building_costs(GameState.BUILDING_MOTHERSHIP)[GameState.RESOURCE_ORE]):
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
return _fail("the mothership was placed but %d ore remains, expected %d" \
|
#138: structures and doctrines cost more than rocks, and more the deeper they are
Both halves of the ticket, in the shape vessels have used since gas bombers
and laser ships: a definition carries an ore figure and, when it wants more
than ore, a dictionary that IS the price. `building_costs()` and
`skill_costs()` answer it the way `product_costs()` already did, so there is
one question with one answer whatever is being bought, and every payment
path now goes through `spend_resources` rather than `spend`.
Seven of ten structures and thirteen of seventeen doctrines now want gas,
energy or dark matter alongside their rocks, and the totals climb with tree
depth - structures 16/31/58/62/96 by depth, doctrines 15/30/73/96/143/258/530.
The constraint that shaped the table is REACHABILITY, and it is the reason
some prices stayed ore-only. Energy exists only where a gas burner stands,
and a gas burner needs Power Systems - so nothing at or before that depth
may be priced in energy, or it is a wall with no way through. Dark matter is
rarer still and appears once, on the deepest node in the game. The extractor
stays rocks-only despite its depth allowing gas, because the quest chain
reaches it before it has sent the player for any.
test_progressive_costs checks those as properties rather than transcribing
the numbers, so rebalancing a price costs nothing while breaking the shape
fails: nothing unreachable at its own depth, prices rising with depth, the
ore field agreeing with the dictionary (repairs quote the field alone, since
a repair drone carries ore and nothing else), and the quest chain never
asking for a structure it has not funded.
Two things cleaned up on the way:
- The per-building cost constants in GameState are gone. Nothing but tests
used them, and they were a second copy of a number the definitions already
hold - exactly what would drift once a price gained a resource.
- research_for_building() returns a display NAME, and the id-keyed half of
the tree wants an id. That mix-up is silent - skill_depth() of a name finds
no node and answers 0, so a deep structure reads as an ungated one, which
is how the first draft of the test let an unreachable price through.
research_node_for_building() is the id, and the name is expressed from it.
PriceTag no longer rebuilds its labels when re-told the same price; it
refreshes the held figures instead. The craft menu re-states prices every
frame and the build menu now does too, so that was a dozen Labels being torn
down and rebuilt sixty times a second.
2026-08-28 15:30:30 +02:00
|
|
|
% [total, HOLD_ORE - int(game_state.building_costs(GameState.BUILDING_MOTHERSHIP)[GameState.RESOURCE_ORE])])
|
Pay for everything from the bank and the fleet hold, not just buildings
Construction already pooled the two; research, ship-lab upgrades and vessel
unlocks still charged the bank alone. That split was arbitrary from the
player's side - the resources are a couple of taps away from being banked
either way, so refusing a purchase over where they happen to be sitting was
ceremony. Worse, it was inconsistent: the same ore could buy a mothership and
not a skill node.
So the pooling moved down into can_afford/spend themselves, and the
construction-specific pair is gone. can_afford_resources now pools per
resource type, and a new spend_resources() charges bank first and hold for
the shortfall, settling affordability in full before taking anything - a
multi-resource price must not charge for the half it can cover and then fail.
unlock_vessel goes through it instead of withdrawing from the bank directly.
The skill menu's "you have %d" quoted the bank, which would have told the
player they could not afford something they could.
Ammo stays out of it. AimedFleetAbilityController reads the fleet hold
directly, and should: a gas bomb is fired from what the ships are physically
carrying, which is a fact about where the matter is rather than a convenience
about where the money is.
test_build_uses_fleet_hold became test_payments_use_fleet_hold and now walks
every route - skill tree, ship lab, vessel research, multi-resource
atomicity, and ammo staying unpooled. Two of its own setup steps were being
silently clamped by the fleet hold's capacity, which is what made an
affordable price look unaffordable; capacity is not what those checks are
about, so it is taken out of the way explicitly.
Full suite 106/106.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:15:00 +02:00
|
|
|
print("built a mothership with an empty bank, paid out of the fleet hold")
|
|
|
|
|
|
|
|
|
|
print("TEST_PAYMENTS_USE_FLEET_HOLD: PASS")
|
|
|
|
|
quit(0)
|
|
|
|
|
return true
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _fail(message: String) -> bool:
|
|
|
|
|
print("TEST_PAYMENTS_USE_FLEET_HOLD: FAIL (%s)" % message)
|
|
|
|
|
quit(1)
|
|
|
|
|
return false
|