Reference pair turn1->turn2: 81 leaves closed, 0 regressed (was 78/0). Pair turn2->turn3: 39 closed, 0 regressed (was 36/0). With --commit-blocked=T31 --ai-player 1: 83/0 and 41/0. game/sim/colony: GrowCivilianPopulations models ServerSystem's civilian growth sub-pass. The whole system's delta is clamped to 20,000,000 -- an int64 column of the population-type table, built in the executable from its own literals -- and on both reference pairs that clamp, not the growth curve and not any carrying capacity, is what decides the value: the uncapped delta is 7.5x it and the capacity headroom 25x it. So the pass commits with no tuning table loaded, and says by how much each unmodelled input would have to be wrong before it mattered. The one input genuinely off the wire is the per-species civilian capacity factor. It is handled by running the pass twice, once with the modelled capacity and once with the system's own wire-known dcs limit, and committing only when the two agree. Imperial growth is deliberately NOT committed: it is a no-op on this corpus and would need a capacity the corpus can bound from below but not from above. game/sim/economy: both interest rates in ComputeBudget are WIDENED FLOAT literals, (double)0.01f and (double)0.15f, and are then truncated -- so a treasury of exactly 50,000 earns 499, not 500. This module used the exact decimals, which left the human's savings one money high on the first reference pair and exact on the second. Sixteen hand-computed test expectations moved by one; they were derived from the model, not measured. The live ComputeBudget compare (4,437 calls, 0 divergences) did not catch this because it presented only 20 distinct states and none sat on a rounding boundary. game/sim/colony: ShipRepairCost, the last unmodelled input of the output turn path. The demand is still 0 -- its two design fields are cached stats the save does not carry -- but the zero is now evidenced rather than silent: S13 reports the candidate set, and the independent colony keeps a ten-ship fleet over a colony whose savings close exactly at zero demand. Gates run as separate commands: clean-room OK, host ctest 49/49, and the CT111 shim cross-build exit 0 (required: game/sim is compiled into the shim). The host build and report were also re-run on CT111 and produced identical numbers. docs/G3-civilian-growth.md; notes repo findings/subsystems/population-growth.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
411 lines
16 KiB
C++
411 lines
16 KiB
C++
#include "game/sim/economy.h"
|
|
#include "game/sim/numeric.h"
|
|
|
|
#include "check.h"
|
|
|
|
using namespace sots::sim;
|
|
|
|
// The two interest rates are WIDENED FLOAT literals in the image, so a round treasury earns
|
|
// one less than the exact decimal would give. Every expectation below that moved by exactly 1
|
|
// was hand-computed from the exact decimal and is corrected here (G3).
|
|
static void test_interest() {
|
|
CHECK_EQ(SavingsInterest(1000, true), 9); // 1000 x (double)0.01f = 9.99999977 -> 9
|
|
CHECK_EQ(SavingsInterest(199, true), 1); // 1.99 truncates
|
|
CHECK_EQ(SavingsInterest(1000, false), 0); // no systems, no interest
|
|
CHECK_EQ(SavingsInterest(-500, true), 0);
|
|
CHECK_EQ(SavingsInterest(0, true), 0);
|
|
|
|
CHECK_EQ(DebtInterest(-1000), 150);
|
|
CHECK_EQ(DebtInterest(-7), 1); // 1.05 truncates
|
|
CHECK_EQ(DebtInterest(5), 0);
|
|
CHECK_EQ(DebtInterest(0), 0);
|
|
|
|
CHECK_EQ(MaintenanceCost(100, 2.0), 50);
|
|
CHECK_EQ(MaintenanceCost(100, 1.9), 100); // divisor truncates to 1
|
|
CHECK_EQ(MaintenanceCost(100, 0.5), 100); // divisor 0 guarded
|
|
|
|
CHECK_EQ(SaturatingAdd(1900000000, 500000000), 2000000000);
|
|
CHECK_EQ(SaturatingAdd(-1900000000, -500000000), -2000000000);
|
|
CHECK_EQ(SaturatingAdd(5, -7), -2);
|
|
}
|
|
|
|
static void test_research_points() {
|
|
// 50000 / 50 = 1000; x1.15 = 1150; x0.5 = 575; x0.85 = 488.75 -> 488
|
|
CHECK_EQ(ResearchPointsFromMoney(50000, 1, 1, 0, 0, 1, 1, 1), 488);
|
|
// (ResMod + shrm + TRM) = 2 -> 977.5 -> 977
|
|
CHECK_EQ(ResearchPointsFromMoney(50000, 1, 1, 0.5, 0.5, 1, 1, 1), 977);
|
|
// difficulty 0.5 -> 244.375 -> 244
|
|
CHECK_EQ(ResearchPointsFromMoney(50000, 0.5, 1, 0, 0, 1, 1, 1), 244);
|
|
// tech x2, server x0.5, scale x1 -> unchanged 488
|
|
CHECK_EQ(ResearchPointsFromMoney(50000, 1, 1, 0, 0, 2, 0.5, 1), 488);
|
|
CHECK_EQ(ResearchPointsFromMoney(0, 1, 1, 0, 0, 1, 1, 1), 0);
|
|
// 100 money -> 0.9775 -> 0
|
|
CHECK_EQ(ResearchPointsFromMoney(100, 1, 1, 0, 0, 1, 1, 1), 0);
|
|
}
|
|
|
|
static void test_expenses() {
|
|
std::vector<ExpenseSlider> s = {{100, 300, 0.5f}, {50, 60, 0.1f}};
|
|
// s1: request ftol(0.5 x 1000) - 100 = 400, room 200 -> 200
|
|
// s2: request 100 - 50 = 50, room 10 -> 10; minimums 150 + takes 210 = 360
|
|
CHECK_EQ(ExpenseTotal(s, 1000), 360);
|
|
// avail 200: s1 request 0, s2 request -30 -> 0; only the minimums
|
|
CHECK_EQ(ExpenseTotal(s, 200), 150);
|
|
// avail below the minimums: total is capped at avail
|
|
CHECK_EQ(ExpenseTotal(s, 100), 100);
|
|
CHECK_EQ(ExpenseTotal({}, 1000), 0);
|
|
|
|
// max 0 = unlimited
|
|
CHECK_EQ(ExpenseTotal({{0, 0, 0.25f}}, 1000), 250);
|
|
// a negative minimum counts as 0
|
|
CHECK_EQ(ExpenseTotal({{-50, 100, 0.1f}}, 1000), 100);
|
|
// min above max: the entry contributes its minimum, its (negative) take is absorbed
|
|
CHECK_EQ(ExpenseTotal({{100, 50, 1.0f}}, 1000), 100);
|
|
// takes are capped by what is left after the minimums
|
|
CHECK_EQ(ExpenseTotal({{100, 0, 1.0f}, {200, 0, 1.0f}}, 1000), 1000);
|
|
// the product is taken in single precision: 0.7f x 1000 = 699.99998 -> 699
|
|
CHECK_EQ(ExpenseTotal({{0, 0, 0.7f}}, 1000), 699);
|
|
// ... and the income itself is rounded to a float first
|
|
CHECK_EQ(ExpenseTotal({{0, 0, 1.0f}}, 16777217), 16777216);
|
|
}
|
|
|
|
static BudgetInputs base_inputs() {
|
|
BudgetInputs in;
|
|
in.savings = 10000;
|
|
in.ownsSystems = true;
|
|
in.systemIncome = {5000, 3000, -200};
|
|
in.tradeIncome = 1000;
|
|
in.maintenance = 2000;
|
|
in.maintenanceDivisor = 1.0;
|
|
in.constructionDemand = 500;
|
|
in.researchRate = 0.5;
|
|
in.hasResearchTarget = true;
|
|
return in;
|
|
}
|
|
|
|
static void test_budget_hand_case() {
|
|
Budget b = ComputeBudget(base_inputs(), false);
|
|
CHECK_EQ(b.savingsInterest, 99);
|
|
CHECK_EQ(b.debtInterest, 0);
|
|
CHECK_EQ(b.systemIncomePositive, 8000);
|
|
CHECK_EQ(b.systemIncomeNegative, 200);
|
|
CHECK_EQ(b.maintenance, 2000);
|
|
CHECK_EQ(b.expenses, 0);
|
|
// 8000 + 1000 + 100 - 200 - 2000 = 6900
|
|
CHECK_EQ(b.available, 6899);
|
|
CHECK_EQ(b.construction, 500);
|
|
// (6900 - 500) x 0.5 = 3200
|
|
CHECK_EQ(b.researchMoney, 3199);
|
|
// 3200/50 = 64; x1.15 = 73.6; x0.5 = 36.8; x0.85 = 31.28 -> 31
|
|
CHECK_EQ(b.researchPoints, 31);
|
|
CHECK_EQ(b.totalResearchPoints, 31);
|
|
CHECK(b.hasResearchAllocation);
|
|
CHECK_EQ(b.researchMoneyKept, 3199);
|
|
CHECK_EQ(b.bonusIncome, 0);
|
|
CHECK_EQ(b.savingsGiven, 0);
|
|
// 6900 - 500 - 3200
|
|
CHECK_EQ(b.net, 3200);
|
|
}
|
|
|
|
// A player with no research target still reports its research money and points -- the UI
|
|
// shows them -- but never spends the money: the "kept" line and the research allocation are
|
|
// written in the same branch. (Verified against the live game, docs/B1.md.)
|
|
static void test_budget_without_research_target() {
|
|
BudgetInputs in = base_inputs();
|
|
in.hasResearchTarget = false;
|
|
Budget b = ComputeBudget(in, false);
|
|
CHECK_EQ(b.researchMoney, 3199);
|
|
CHECK_EQ(b.researchPoints, 31);
|
|
CHECK_EQ(b.totalResearchPoints, 31);
|
|
CHECK(!b.hasResearchAllocation);
|
|
CHECK_EQ(b.researchMoneyKept, 0);
|
|
// The research money stays in the treasury: 6900 - 500 construction.
|
|
CHECK_EQ(b.net, 6399);
|
|
}
|
|
|
|
static void test_budget_projected() {
|
|
Budget b = ComputeBudget(base_inputs(), true);
|
|
CHECK_EQ(b.researchMoney, 0);
|
|
CHECK_EQ(b.researchPoints, 0);
|
|
CHECK_EQ(b.net, 6399);
|
|
}
|
|
|
|
static void test_budget_debt() {
|
|
BudgetInputs in = base_inputs();
|
|
in.savings = -1000;
|
|
Budget b = ComputeBudget(in, false);
|
|
CHECK_EQ(b.savingsInterest, 0);
|
|
CHECK_EQ(b.debtInterest, 150);
|
|
// 8000 + 1000 - 200 - 2000 - 150 = 6650
|
|
CHECK_EQ(b.available, 6650);
|
|
CHECK_EQ(b.construction, 500);
|
|
// (6650 - 500) x 0.5 = 3075
|
|
CHECK_EQ(b.researchMoney, 3075);
|
|
CHECK_EQ(b.net, 6650 - 500 - 3075);
|
|
}
|
|
|
|
static void test_budget_aid_and_bonus() {
|
|
BudgetInputs in = base_inputs();
|
|
in.aidResearchPercent = 50;
|
|
in.aidSavings = 100;
|
|
in.tra = 4;
|
|
in.trp = 5;
|
|
Budget b = ComputeBudget(in, false);
|
|
CHECK_EQ(b.researchMoney, 3199);
|
|
CHECK_EQ(b.researchMoneyGiven, 1599);
|
|
CHECK_EQ(b.researchMoneyKept, 1600);
|
|
// 31 + 4 + 5 = 40; given 20; kept 20
|
|
CHECK_EQ(b.researchPointsGiven, 20);
|
|
CHECK_EQ(b.totalResearchPoints, 20);
|
|
CHECK_EQ(b.savingsGiven, 100);
|
|
// 6900 - 500 - 1600 - 1600 - 100
|
|
CHECK_EQ(b.net, 3100);
|
|
|
|
in.aidResearchPercent = 250; // clamps to 100
|
|
in.aidSavings = 0;
|
|
b = ComputeBudget(in, false);
|
|
CHECK_EQ(b.researchMoneyGiven, 3199);
|
|
CHECK_EQ(b.totalResearchPoints, 0);
|
|
|
|
in = base_inputs();
|
|
in.techIncomeMult = 1.1;
|
|
b = ComputeBudget(in, false);
|
|
// full net before the bonus = 3200 -> ftol(0.1 x 3200) = 320
|
|
CHECK_EQ(b.bonusIncome, 320);
|
|
CHECK_EQ(b.net, 3520);
|
|
|
|
// the bonus reads the net *after* research aid was deducted
|
|
in.aidResearchPercent = 50;
|
|
b = ComputeBudget(in, false);
|
|
CHECK_EQ(b.bonusIncome, 320); // 1600 kept + 1600 given: net unchanged
|
|
in.aidResearchPercent = 0;
|
|
|
|
// a multiplier below 1 never takes money away
|
|
in.techIncomeMult = 0.5;
|
|
b = ComputeBudget(in, false);
|
|
CHECK_EQ(b.bonusIncome, 0);
|
|
CHECK_EQ(b.net, 3200);
|
|
|
|
// no bonus on a negative net
|
|
in.techIncomeMult = 1.1;
|
|
in.maintenance = 20000;
|
|
b = ComputeBudget(in, false);
|
|
CHECK_EQ(b.available, 0);
|
|
CHECK_EQ(b.bonusIncome, 0);
|
|
CHECK_EQ(b.net, -11101);
|
|
|
|
// savings aid is capped by the projected treasury, not by the turn net
|
|
in = base_inputs();
|
|
in.savings = -5000; // debt interest 750
|
|
in.aidSavings = 100;
|
|
b = ComputeBudget(in, false);
|
|
// available 8000+1000-200-2000-750 = 6050; construction 500; research 2775; net 2775
|
|
CHECK_EQ(b.researchMoney, 2775);
|
|
CHECK_EQ(b.savingsGiven, 0); // -5000 + 2775 < 0: nothing to give
|
|
CHECK_EQ(b.net, 2775);
|
|
|
|
in.savings = -2000; // debt interest 300
|
|
in.aidSavings = 5000;
|
|
b = ComputeBudget(in, false);
|
|
// available 6500; construction 500; research 3000; net 3000; projected 1000
|
|
CHECK_EQ(b.savingsGiven, 1000);
|
|
CHECK_EQ(b.net, 2000);
|
|
|
|
in.savings = 10000;
|
|
in.techIncomeMult = 1.1; // bonus 320 counts toward the projection
|
|
in.aidSavings = 20000;
|
|
b = ComputeBudget(in, false);
|
|
CHECK_EQ(b.bonusIncome, 320);
|
|
CHECK_EQ(b.savingsGiven, 13520); // 10000 + 3200 + 320: the whole projected treasury
|
|
CHECK_EQ(b.net, -10000); // ... so the treasury ends the turn at 0
|
|
|
|
in.aidSavings = -5; // negative aid gives nothing
|
|
b = ComputeBudget(in, false);
|
|
CHECK_EQ(b.savingsGiven, 0);
|
|
}
|
|
|
|
static void test_budget_edges() {
|
|
BudgetInputs in = base_inputs();
|
|
in.isAI = true;
|
|
Budget b = ComputeBudget(in, false);
|
|
CHECK_EQ(b.construction, 0); // AI path spends construction elsewhere
|
|
CHECK_EQ(b.researchMoney, 3449); // 6900 x 0.5
|
|
|
|
in = base_inputs();
|
|
in.systemIncome = {};
|
|
in.tradeIncome = 0;
|
|
in.maintenance = 5000;
|
|
b = ComputeBudget(in, false);
|
|
CHECK_EQ(b.available, 0); // floored at zero
|
|
CHECK_EQ(b.construction, 0);
|
|
CHECK_EQ(b.researchMoney, 0);
|
|
CHECK_EQ(b.net, 99 - 5000); // interest minus maintenance
|
|
|
|
in = base_inputs();
|
|
in.constructionDemand = 100000;
|
|
b = ComputeBudget(in, false);
|
|
CHECK_EQ(b.construction, 6899); // capped at available
|
|
CHECK_EQ(b.researchMoney, 0);
|
|
|
|
in = base_inputs();
|
|
in.expenses = {{1000, 2000, 0.5f}};
|
|
b = ComputeBudget(in, false);
|
|
// pre-expense avail 6900: request 3450 - 1000 = 2450, room 1000 -> 2000 total
|
|
CHECK_EQ(b.expenses, 2000);
|
|
CHECK_EQ(b.available, 4899);
|
|
}
|
|
|
|
static void test_trade() {
|
|
TuningTable t;
|
|
t.TRADE_ROUTE_REQ_CIVPOPULATION = 1e6;
|
|
t.TRADE_ROUTE_REQ_IMPPOPULATION = 1e6;
|
|
CHECK_EQ(TradeRoutesSupported(2.5e6, 1e6, t), 4); // ceil(2.5) + 1
|
|
CHECK_EQ(TradeRoutesSupported(0, 0, t), 1); // minimum one
|
|
CHECK_EQ(TradeRoutesSupported(1, 0, t), 1);
|
|
TuningTable zero;
|
|
CHECK_EQ(TradeRoutesSupported(5e6, 5e6, zero), 1); // zero requirement guarded
|
|
|
|
t.TRADE_ROUTE_STARTUP_TURNS = 3;
|
|
t.TRADE_ROUTE_STARTUP_INCOME = 7;
|
|
t.TRADE_ROUTE_MIN_INCOME = 100;
|
|
t.TRADE_ROUTE_MAX_FREIGHTERS = 5;
|
|
t.TRADE_ROUTE_INCOME_PERFREIGHTER_CRQ = 30;
|
|
t.TRADE_ROUTE_INCOME_PERFREIGHTER_CR = 20;
|
|
t.TRADE_ROUTE_INCOME_PERFREIGHTER_DE = 10;
|
|
t.STATION_BONUS_TRADE_INCOME = 0.1;
|
|
t.ADDICTION_TRADE_MOD = 0.5;
|
|
t.TRADE_ROUTE_OWNERS_SHARE = 0.6;
|
|
|
|
TradeRouteState r;
|
|
r.ageTurns = 3;
|
|
r.freighters[0] = 2; // CRQ: 2 x 30 = 60, cap left 3
|
|
r.freighters[1] = 4; // CR: min(4,3) = 3 x 20 = 60, cap left 0
|
|
r.freighters[2] = 3; // DE: nothing left
|
|
CHECK_EQ(TradeRouteGrossIncome(r, t), 220);
|
|
r.tradeStationsAtSystem = 2;
|
|
CHECK_EQ(TradeRouteGrossIncome(r, t), 264); // x 1.2
|
|
r.partnerAddicted = true;
|
|
CHECK_EQ(TradeRouteGrossIncome(r, t), 132); // x 0.5
|
|
CHECK_EQ(TradeRouteIncome(r, true, 1.0, t), 79); // 132 x 0.6 = 79.2
|
|
CHECK_EQ(TradeRouteIncome(r, false, 1.0, t), 52); // 132 x 0.4 = 52.8
|
|
CHECK_EQ(TradeRouteIncome(r, true, 2.0, t), 158); // AI trade multiplier
|
|
r.ageTurns = 2;
|
|
CHECK_EQ(TradeRouteGrossIncome(r, t), 7); // startup income is flat
|
|
|
|
TradeRouteState empty;
|
|
empty.ageTurns = 10;
|
|
CHECK_EQ(TradeRouteGrossIncome(empty, t), 100); // MIN_INCOME with no freighters
|
|
t.TRADE_ROUTE_OWNERS_SHARE = 1.5; // clamps to 1
|
|
CHECK_EQ(TradeRouteIncome(empty, true, 1.0, t), 100);
|
|
CHECK_EQ(TradeRouteIncome(empty, false, 1.0, t), 0);
|
|
}
|
|
|
|
static void test_bankruptcy() {
|
|
TuningTable t;
|
|
t.BANKRUPTCY_PROTECTION_LIMIT_FACTOR = 3.3;
|
|
t.BANKRUPTCY_ELIMINATION_TURNS = 5;
|
|
// max income 1000: elimination at 1000 / -0.15 = -6666.67 -> -6666; protection -3300
|
|
BankruptcyLimits l = ComputeBankruptcyLimits(1000, t);
|
|
CHECK_EQ(l.eliminationFloor, -6666);
|
|
CHECK_EQ(l.protectionLimit, -3300);
|
|
CHECK_EQ(BankruptcyLevel(-6667, l), 2);
|
|
CHECK_EQ(BankruptcyLevel(-6666, l), 1);
|
|
CHECK_EQ(BankruptcyLevel(-3301, l), 1);
|
|
CHECK_EQ(BankruptcyLevel(-3300, l), 0);
|
|
CHECK_EQ(BankruptcyLevel(0, l), 0);
|
|
|
|
l = ComputeBankruptcyLimits(100, t);
|
|
CHECK_EQ(l.eliminationFloor, -666);
|
|
CHECK_EQ(l.protectionLimit, -330);
|
|
|
|
// a factor beyond the interest break-even is pinned to the elimination floor
|
|
TuningTable big = t;
|
|
big.BANKRUPTCY_PROTECTION_LIMIT_FACTOR = 10.0;
|
|
l = ComputeBankruptcyLimits(1000, big);
|
|
CHECK_EQ(l.eliminationFloor, -6666);
|
|
CHECK_EQ(l.protectionLimit, -6666);
|
|
|
|
// huge income: the floor saturates at the treasury limit
|
|
l = ComputeBankruptcyLimits(400000000, t);
|
|
CHECK_EQ(l.eliminationFloor, -2000000000);
|
|
CHECK_EQ(l.protectionLimit, -1320000000);
|
|
|
|
// LANE N: the divisor is the widened float literal, not the decimal -0.15. The two
|
|
// disagree for every maxIncome divisible by 3, and maxIncome = 3 is where it first
|
|
// bites: 3 / -0.15 is -20 exactly in decimal but -19.99999920... with the image's
|
|
// constant, and the conversion TRUNCATES.
|
|
CHECK_EQ(ComputeBankruptcyLimits(3, t).eliminationFloor, -19);
|
|
CHECK_EQ(ComputeBankruptcyLimits(6, t).eliminationFloor, -39);
|
|
CHECK_EQ(ComputeBankruptcyLimits(9, t).eliminationFloor, -59);
|
|
// ... and above ~3,000,000 maximum income they differ on essentially every value.
|
|
CHECK_EQ(ComputeBankruptcyLimits(238592, t).eliminationFloor, -1590613);
|
|
CHECK_EQ(ComputeBankruptcyLimits(3000001, t).eliminationFloor, -20000005);
|
|
|
|
BankruptcyLimits none = ComputeBankruptcyLimits(0, t);
|
|
CHECK_EQ(none.eliminationFloor, 0);
|
|
CHECK_EQ(none.protectionLimit, 0);
|
|
CHECK_EQ(BankruptcyLevel(-1, none), 2); // no income at all: any debt is terminal
|
|
|
|
// Stamping happens on the transition; actions run on the previous state.
|
|
BankruptcyState s;
|
|
CHECK_EQ(s.startTurn, -1);
|
|
BankruptcyDecision d = BankruptcyStep(s, 1, 10, t);
|
|
CHECK(!d.costCutting); // first turn at level 1: not yet
|
|
CHECK(!d.eliminate);
|
|
CHECK_EQ(s.warningLevel, 1);
|
|
CHECK_EQ(s.startTurn, 10);
|
|
d = BankruptcyStep(s, 1, 11, t);
|
|
CHECK(d.costCutting); // second turn: cost cutting
|
|
CHECK_EQ(s.startTurn, 10); // no restamp while the level holds
|
|
d = BankruptcyStep(s, 2, 12, t); // 1 -> 2 restamps the clock at 12
|
|
CHECK(d.costCutting);
|
|
CHECK(!d.eliminate);
|
|
CHECK_EQ(s.startTurn, 12);
|
|
d = BankruptcyStep(s, 2, 16, t);
|
|
CHECK(!d.eliminate); // 4 turns < 5
|
|
d = BankruptcyStep(s, 2, 17, t);
|
|
CHECK(d.eliminate); // 5 turns -> eliminated
|
|
d = BankruptcyStep(s, 0, 18, t); // recovering on the turn after the clock
|
|
CHECK(!d.costCutting);
|
|
CHECK(d.eliminate); // ... still acts on the old level-2 state
|
|
CHECK_EQ(s.warningLevel, 0);
|
|
CHECK_EQ(s.startTurn, -1);
|
|
|
|
// 2 -> 1 -> 2 restarts the clock each time
|
|
BankruptcyState r;
|
|
BankruptcyStep(r, 2, 20, t);
|
|
CHECK_EQ(r.startTurn, 20);
|
|
d = BankruptcyStep(r, 1, 22, t);
|
|
CHECK(d.costCutting);
|
|
CHECK_EQ(r.startTurn, 22);
|
|
d = BankruptcyStep(r, 2, 25, t);
|
|
CHECK(!d.eliminate); // old level was 1
|
|
CHECK_EQ(r.startTurn, 25);
|
|
d = BankruptcyStep(r, 2, 29, t);
|
|
CHECK(!d.eliminate);
|
|
d = BankruptcyStep(r, 2, 30, t);
|
|
CHECK(d.eliminate);
|
|
|
|
// even with a zero-turn limit, elimination happens the turn after level 2 is reached
|
|
TuningTable instant = t;
|
|
instant.BANKRUPTCY_ELIMINATION_TURNS = 0;
|
|
BankruptcyState q;
|
|
d = BankruptcyStep(q, 2, 5, instant);
|
|
CHECK(!d.eliminate);
|
|
d = BankruptcyStep(q, 2, 6, instant);
|
|
CHECK(d.eliminate);
|
|
}
|
|
|
|
int main() {
|
|
test_interest();
|
|
test_research_points();
|
|
test_expenses();
|
|
test_budget_hand_case();
|
|
test_budget_without_research_target();
|
|
test_budget_projected();
|
|
test_budget_debt();
|
|
test_budget_aid_and_bonus();
|
|
test_budget_edges();
|
|
test_trade();
|
|
test_bankruptcy();
|
|
return simtest::finish("test_economy");
|
|
}
|