From 71674c22a412ae10df23d4623ae2036f365f87fe Mon Sep 17 00:00:00 2001 From: Billy Tifft Date: Tue, 15 Sep 2026 11:46:23 -0600 Subject: [PATCH] Fix double-counting of commerce in treasury flows (#864) Inflows() and Outflows() both counted corruption, science and entertainment income. These are allocations of a city's commerce, not treasury gold, so they appeared twice in the domestic advisor's income and expense totals. Netflows() was unaffected because the duplicate terms cancelled out. Make Inflows()/Outflows() gold-only, add CommerceTotal() for the full city commerce figure, and have the domestic advisor show taxes plus wealth production as income from cities while keeping the Civ3 line order for the expense breakdown. --- C7/UIElements/Advisors/DomesticAdvisor.cs | 2 +- C7Engine/C7GameData/Player.cs | 11 ++-- .../GameData/PlayerCommerceBreakdownTest.cs | 54 +++++++++++++++++++ 3 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 EngineTests/GameData/PlayerCommerceBreakdownTest.cs diff --git a/C7/UIElements/Advisors/DomesticAdvisor.cs b/C7/UIElements/Advisors/DomesticAdvisor.cs index e1e5756eb..69070a51c 100644 --- a/C7/UIElements/Advisors/DomesticAdvisor.cs +++ b/C7/UIElements/Advisors/DomesticAdvisor.cs @@ -224,7 +224,7 @@ public void ShowAdvisor() { scienceStatus.Text = playerController.SummarizeScience(gameData); treasury.Text = $"Treasury: {playerController.gold}"; - incomeDetails.Text = $"From cities: +{totalIncome.CityInflows()}\nFrom taxmen: +{totalIncome.taxmenTaxes}\nFrom other civs: +{totalIncome.fromOtherCivs}\nFrom interest: +{totalIncome.interest}"; + incomeDetails.Text = $"From cities: +{totalIncome.taxes + totalIncome.wealthProduction}\nFrom taxmen: +{totalIncome.taxmenTaxes}\nFrom other civs: +{totalIncome.fromOtherCivs}\nFrom interest: +{totalIncome.interest}"; expenseDetails.Text = $"-{totalIncome.beakers}: Science\n-{totalIncome.happiness}: Entertainment\n-{totalIncome.corrupted}: Corruption\n-{totalIncome.maintenance}: Maintenance\n-{totalIncome.unitSupport}: Unit costs\n-{totalIncome.toOtherCivs}: To other civs"; incomeSummary.Text = $"Income: {totalIncome.Inflows()}"; expenseSummary.Text = $"Expenses: {totalIncome.Outflows()}"; diff --git a/C7Engine/C7GameData/Player.cs b/C7Engine/C7GameData/Player.cs index 0b366c90c..59bd131d1 100644 --- a/C7Engine/C7GameData/Player.cs +++ b/C7Engine/C7GameData/Player.cs @@ -25,19 +25,24 @@ public struct PlayerCommerceBreakdown { public int unitSupport; // Expenses due to unit support costs public int wealthProduction; // Amount of extra commerce from "building" an Inflow that produces commerce + // The total treasury gold coming in each turn. Corruption, science and + // entertainment are allocations of a city's commerce, not income. public int Inflows() { - return corrupted + taxes + taxmenTaxes + beakers + happiness + fromOtherCivs + interest + wealthProduction; + return taxes + taxmenTaxes + fromOtherCivs + interest + wealthProduction; } + // The total treasury gold going out each turn. public int Outflows() { - return corrupted + beakers + happiness + toOtherCivs + maintenance + unitSupport; + return toOtherCivs + maintenance + unitSupport; } public int Netflows() { return Inflows() - Outflows(); } - public int CityInflows() { + // The total commerce generated by a player's cities before it is split + // between corruption, science, entertainment and taxes. + public int CommerceTotal() { return corrupted + taxes + beakers + happiness + wealthProduction; } } diff --git a/EngineTests/GameData/PlayerCommerceBreakdownTest.cs b/EngineTests/GameData/PlayerCommerceBreakdownTest.cs new file mode 100644 index 000000000..0f7663c79 --- /dev/null +++ b/EngineTests/GameData/PlayerCommerceBreakdownTest.cs @@ -0,0 +1,54 @@ +using C7GameData; +using Xunit; + +namespace EngineTests.GameData; + +public class PlayerCommerceBreakdownTest { + private static PlayerCommerceBreakdown MakeBreakdown() { + return new PlayerCommerceBreakdown { + corrupted = 3, + taxes = 4, + taxmenTaxes = 2, + beakers = 5, + happiness = 6, + fromOtherCivs = 1, + toOtherCivs = 2, + interest = 3, + maintenance = 4, + unitSupport = 5, + wealthProduction = 6, + }; + } + + [Fact] + public void InflowsIncludesOnlyTreasuryGoldIncome() { + PlayerCommerceBreakdown breakdown = MakeBreakdown(); + + // corrupted, beakers and happiness are allocations of city commerce, + // not income, so they must not appear in the treasury inflow number. + Assert.Equal(4 + 2 + 1 + 3 + 6, breakdown.Inflows()); + } + + [Fact] + public void OutflowsIncludesOnlyTreasuryGoldExpenses() { + PlayerCommerceBreakdown breakdown = MakeBreakdown(); + + // Science, entertainment and corruption are not treasury expenses. + Assert.Equal(2 + 4 + 5, breakdown.Outflows()); + } + + [Fact] + public void NetflowsIsInflowsMinusOutflows() { + PlayerCommerceBreakdown breakdown = MakeBreakdown(); + + Assert.Equal(breakdown.Inflows() - breakdown.Outflows(), breakdown.Netflows()); + Assert.Equal(5, breakdown.Netflows()); + } + + [Fact] + public void CommerceTotalIncludesAllCityCommerce() { + PlayerCommerceBreakdown breakdown = MakeBreakdown(); + + Assert.Equal(3 + 4 + 5 + 6 + 6, breakdown.CommerceTotal()); + } +}