fix(game): charge a ship upgrade against production only once
Tests · Go / test (push) Successful in 2m7s
Tests · Go / test (pull_request) Successful in 2m10s
Tests · Integration / integration (pull_request) Successful in 1m41s

TurnPlanetProductions started its production budget from
PlanetProductionCapacity, which already subtracts the reserved upgrade
cost, and then subtracted each applied upgrade's cost again in the apply
loop — charging every applied upgrade twice. That both starved the
planet's build/research budget and could skip upgrades that were in fact
affordable.

The budget now starts from the planet's full production potential and the
apply loop deducts each upgrade once; PlanetProductionCapacity stays the
report's net-of-upgrades "free L".

Test: TestUpgradeDoesNotDoubleChargeProduction; the TestProduceShips MAT
expectation is updated to the once-charged value.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-31 08:24:46 +02:00
parent b4abf90ec5
commit 53b3cafbc4
2 changed files with 41 additions and 7 deletions
+34 -5
View File
@@ -208,11 +208,40 @@ func TestProduceShips(t *testing.T) {
assert.Equal(t, game.StateInOrbit, c.ShipGroup(0).State())
assert.Equal(t, 1.5, c.ShipGroup(0).TechLevel(game.TechDrive).F())
// Upgrade cost is now recomputed from the group's current ship count
// (calc raw float) rather than read from the Fixed12-rounded stored value,
// which shifts Material at the 12th decimal — far below the 3-decimal
// report precision, so an InDelta check is the robust expectation here.
assert.InDelta(t, 4346.6766, c.MustPlanet(R0_Planet_0_num).Material.F(), 0.001)
// The pending upgrade is now charged once (not twice) against the planet's
// production potential, so MAT production keeps the budget it previously
// lost to the double charge (the pre-fix value here was ~4346.68).
assert.InDelta(t, 7173.3432, c.MustPlanet(R0_Planet_0_num).Material.F(), 0.001)
}
// TestUpgradeDoesNotDoubleChargeProduction guards that a pending upgrade is
// paid for once out of the planet's production potential, leaving the rest for
// the turn's production. The pre-fix code subtracted the upgrade cost twice
// (PlanetProductionCapacity already nets it out for the report, and the apply
// loop netted it again), which both starved production and could skip
// affordable upgrades.
func TestUpgradeDoesNotDoubleChargeProduction(t *testing.T) {
c, _ := newCache()
p := c.MustPlanet(R0_Planet_0_num)
p.Population = 1000
p.Industry = 1000 // ProductionCapacity = 1000*0.75 + 1000*0.25 = 1000
p.Resources = 1 // material produced == leftover production budget
p.Colonists = 0
p.Material = 0
assert.NoError(t, c.PlanetProduce(Race_0_idx, int(R0_Planet_0_num), game.ProductionMaterial, ""))
// One Cruiser with a pending drive upgrade 1.1 -> 2.0:
// block cost = (1 - 1.1/2.0) * 10 * 15 = 67.5 for the single ship.
assert.NoError(t, c.CreateShips(Race_0_idx, ShipType_Cruiser, R0_Planet_0_num, 1))
c.ShipGroup(0).StateUpgrade = &game.InUpgrade{
UpgradeTech: []game.UpgradePreference{{Tech: game.TechDrive, Level: 2.0}},
}
c.TurnPlanetProductions()
assert.InDelta(t, 2.0, c.ShipGroup(0).TechLevel(game.TechDrive).F(), 0.0001)
// 1000 - 67.5 = 932.5; the pre-fix double charge would have left 865.
assert.InDelta(t, 932.5, c.MustPlanet(R0_Planet_0_num).Material.F(), 0.01)
}
func TestProduceShip(t *testing.T) {