#220: every berth past the first paid one costs 1.12x the one before it, on top of the flat per-vessel charge. A linear bill makes the tenth hull no more of a decision than the second; compounding weighs each addition against the whole fleet behind it. Shallow on purpose - the thirteenth berth costs about three and a half times the second, the twentieth about seven and a half. Fleet Offices gain a menu naming the whole bill per minute, a line per paying berth with its multiplier, and the arithmetic in words. It renders fleet_upkeep_breakdown(), which is the calculation the charging loop itself runs: a readout that summed independently could quote a bill the game does not take, which is the thing worth preventing here. The test pins that what the menu quotes for a berth is what that office is charged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019AJMdB7iuHuLFZM1aBu3GD
111 lines
4.7 KiB
GDScript
111 lines
4.7 KiB
GDScript
extends SceneTree
|
|
|
|
# Forgejo #220: the fleet costs more per hull as it grows, and the Fleet
|
|
# Office says so with the arithmetic shown.
|
|
#
|
|
# The interesting failure is not "the sum is wrong" - it is the readout and
|
|
# the charging loop drifting apart, so the menu quotes a bill the game does
|
|
# not take. So what is checked is that the SAME numbers come out of both.
|
|
#
|
|
# Covers:
|
|
# A. the first paid berth pays the base rate, undialled
|
|
# B. each berth past it pays more than the one before, compounding
|
|
# C. the growth is slow - a mid-sized fleet is dearer, not ruinous
|
|
# D. the breakdown's per-minute total matches its own per-berth charges
|
|
# E. and the charges it quotes are the ones actually withdrawn
|
|
# F. an empty berth is quoted nothing
|
|
|
|
var state: Node
|
|
|
|
|
|
func _initialize() -> void:
|
|
state = root.get_node("GameState")
|
|
state.buildings.clear()
|
|
state.transporters.clear()
|
|
state.reset_progression()
|
|
state.reset_new_game()
|
|
state.intro_active = false
|
|
state.fleet_vessel_types = PackedStringArray()
|
|
|
|
|
|
func _process(_delta: float) -> bool:
|
|
var vessel: String = GameState.VESSEL_LASER
|
|
var base: Dictionary = state.fleet_office_upkeep(vessel, GameState.BASE_FLEET_SIZE)
|
|
if base.is_empty():
|
|
return _fail("a laser berth is charged nothing at all")
|
|
|
|
# --- A. the first paid berth is the base rate -------------------
|
|
if not is_equal_approx(state.fleet_upkeep_scale(GameState.BASE_FLEET_SIZE), 1.0):
|
|
return _fail("the first paid berth is already dialled up (x%.2f)" \
|
|
% state.fleet_upkeep_scale(GameState.BASE_FLEET_SIZE))
|
|
|
|
# --- B. and every berth after it costs more ---------------------
|
|
var previous: float = 0.0
|
|
for slot in range(GameState.BASE_FLEET_SIZE, GameState.BASE_FLEET_SIZE + 12):
|
|
var scale: float = state.fleet_upkeep_scale(slot)
|
|
if scale < previous:
|
|
return _fail("berth %d is cheaper than the one before it" % (slot + 1))
|
|
previous = scale
|
|
var twelfth: float = state.fleet_upkeep_scale(GameState.BASE_FLEET_SIZE + 11)
|
|
if twelfth <= 1.0:
|
|
return _fail("twelve berths in, upkeep has not risen at all")
|
|
|
|
# --- C. slowly, though ------------------------------------------
|
|
if twelfth > 6.0:
|
|
return _fail("the twelfth berth costs x%.1f - that is not a slow exponential" % twelfth)
|
|
print("berth scaling: 1.00 at the first paid berth, x%.2f twelve in" % twelfth)
|
|
|
|
# --- D/E/F. the quoted bill IS the charged one ------------------
|
|
# Three offices, two of them crewed: the third berth stands empty and
|
|
# must be quoted nothing.
|
|
var office_ids: Array = []
|
|
for i in range(3):
|
|
office_ids.append(state.add_building(GameState.BUILDING_FLEET_OFFICE,
|
|
Vector2(500.0 * float(i + 1), 0.0)))
|
|
state.fleet_vessel_types = PackedStringArray([
|
|
GameState.VESSEL_INTERCEPTOR, vessel, vessel])
|
|
|
|
var breakdown: Dictionary = state.fleet_upkeep_breakdown()
|
|
var berths: Array = breakdown.berths
|
|
if berths.size() != 2:
|
|
return _fail("%d berths quoted for a fleet of 3 with 3 offices, expected 2 paying" \
|
|
% berths.size())
|
|
if int(berths[1].costs.get(GameState.RESOURCE_ORE, 0)) \
|
|
<= int(berths[0].costs.get(GameState.RESOURCE_ORE, 0)) - 1:
|
|
return _fail("the second paid berth is quoted less than the first")
|
|
|
|
# The per-minute figure has to be the per-berth charges, at the rate they
|
|
# are actually taken - not a separate estimate.
|
|
var charges: float = 60.0 / float(breakdown.charge_seconds)
|
|
for resource_type in breakdown.per_minute:
|
|
var summed: float = 0.0
|
|
for berth in berths:
|
|
summed += float(berth.costs.get(resource_type, 0)) * charges
|
|
if not is_equal_approx(float(breakdown.per_minute[resource_type]), summed):
|
|
return _fail("%s quoted at %.2f/min, its own berths add to %.2f" % [
|
|
resource_type, float(breakdown.per_minute[resource_type]), summed])
|
|
print("bill: %s, from %d paying berths" % [breakdown.per_minute, berths.size()])
|
|
|
|
# And a charge actually taken matches what was quoted for that berth.
|
|
var quoted: int = int(berths[0].costs.get(GameState.RESOURCE_ORE, 0))
|
|
var office: Dictionary = state.find_building(int(office_ids[0]))
|
|
var store: RefCounted = state.building_inventory(int(office_ids[0]))
|
|
store.set_amount(GameState.RESOURCE_ORE, 500)
|
|
store.set_amount(GameState.RESOURCE_ENERGY, 500)
|
|
var before: int = store.amount(GameState.RESOURCE_ORE)
|
|
office.upkeep_progress = GameState.FLEET_OFFICE_UPKEEP_SECONDS
|
|
state._advance_fleet_office_upkeep(0.1)
|
|
var taken: int = before - store.amount(GameState.RESOURCE_ORE)
|
|
if taken != quoted:
|
|
return _fail("berth 2 was quoted %d ore and charged %d" % [quoted, taken])
|
|
print("what the menu quotes for a berth is what the office pays: %d ore" % taken)
|
|
|
|
print("TEST_FLEET_UPKEEP_SCALING: PASS")
|
|
quit(0)
|
|
return true
|
|
|
|
|
|
func _fail(message: String) -> bool:
|
|
print("TEST_FLEET_UPKEEP_SCALING: FAIL (%s)" % message)
|
|
quit(1)
|
|
return true
|