From 381251943dbe0ca31d4c6f66d9b349915ab96289 Mon Sep 17 00:00:00 2001 From: ruggsea Date: Sat, 3 Oct 2026 01:29:49 +0000 Subject: [PATCH 1/2] Fix NaN when province admin cost is exactly zero For control_ratio < 0.01 the factor 1/(1.01 - control) - 1 in the admin cost is negative, so (pop * conc + area * 100) * factor + 100 can land on exactly 0. update_administrative_efficiency then computes desire = prize / 0 = inf and control_scale * desire = 0 * inf = NaN, so control_ratio becomes NaN. The NaN reaches tax income, the owner's treasury and then every market price, and the game later aborts. Seen in a headless AI-only campaign (seed 97): province 593 (25,234 people, 3,322.6 km2, control 0.00972) hit mass == 0 on 1869-10-13; the abort followed in 1871. With this change control is left unchanged on such a day; every other case computes exactly the same values (other seeds stay bit-identical over 100 years). --- src/nations/nations.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/nations/nations.cpp b/src/nations/nations.cpp index 5e351b643..15da8641e 100644 --- a/src/nations/nations.cpp +++ b/src/nations/nations.cpp @@ -1112,13 +1112,16 @@ void update_administrative_efficiency(sys::state& state) { auto mass = ve_admin_cost_of_province(state, pids); auto prize = state.world.province_get_demographics(pids, demographics::total); // Higher population relative to admin cost = more desirable to control provinces - auto desire = ve::max(0.f, (prize / mass - 0.1f)); + // for control below 0.01 the admin cost factor is negative and mass can land on exactly 0: + // prize / 0 = inf, then 0 * inf = NaN, which spreads into taxes, treasuries and prices. Skip the update then. + auto mass_ok = mass != 0.f; + auto desire = ve::select(mass_ok, ve::max(0.f, (prize / mass - 0.1f)), 0.f); auto control_scale = ve::max(0.f, state.world.province_get_control_scale(pids)); // Bureaucratic capacity assigned to the province // as we expand control over local land, it requires much higher levels of administrative work to increase it auto available_control = ve::min(control_scale * desire * 5.f, mass); // How much control can be established this tick capped at mass (can't exceed admin capacity needed) - auto speed = (available_control / mass - current_control); // Difference between potential and current control. Control grows slowly at 1% per tick to avoid sudden drops in taxes + auto speed = ve::select(mass_ok, available_control / mass - current_control, 0.f); // Difference between potential and current control. Control grows slowly at 1% per tick to avoid sudden drops in taxes state.world.province_set_control_ratio( pids, From 6d8a92351f0cc951102ab89f4cf73e0495cf880c Mon Sep 17 00:00:00 2001 From: ruggsea Date: Sat, 3 Oct 2026 11:20:16 +0000 Subject: [PATCH 2/2] Clamp the admin cost control factor at 0 instead of skipping the control update For control below 0.01 the factor 1/(1.01-control)-1 is negative, so the admin cost could reach exactly 0 and produce inf/NaN. Clamping keeps the cost >= 100. --- src/nations/nations.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/nations/nations.cpp b/src/nations/nations.cpp index 15da8641e..b2b96c384 100644 --- a/src/nations/nations.cpp +++ b/src/nations/nations.cpp @@ -829,7 +829,7 @@ float admin_cost_of_province(sys::state const& state, dcon::province_id pid) { population_concentration *= 0.5f; } auto current_control = state.world.province_get_control_ratio(pid); - return (population * population_concentration + area * 100.f) * (1.f / (1.01f - current_control) - 1.f) + 100.f; + return (population * population_concentration + area * 100.f) * std::max(0.f, 1.f / (1.01f - current_control) - 1.f) + 100.f; } template ve::fp_vector ve_admin_cost_of_province(sys::state& state, T pid) { @@ -843,7 +843,7 @@ ve::fp_vector ve_admin_cost_of_province(sys::state& state, T pid) { population_concentration = ve::select(is_coastal, population_concentration * 0.5f, population_concentration); population_concentration = ve::select(has_major_river, population_concentration * 0.5f, population_concentration); auto current_control = state.world.province_get_control_ratio(pid); - return (population * population_concentration + area * 100.f) * (1.f / (1.01f - current_control) - 1.f) + 100.f; + return (population * population_concentration + area * 100.f) * ve::max(0.f, 1.f / (1.01f - current_control) - 1.f) + 100.f; } float desire_score_province(sys::state const& state, dcon::province_id pid) { @@ -1112,16 +1112,13 @@ void update_administrative_efficiency(sys::state& state) { auto mass = ve_admin_cost_of_province(state, pids); auto prize = state.world.province_get_demographics(pids, demographics::total); // Higher population relative to admin cost = more desirable to control provinces - // for control below 0.01 the admin cost factor is negative and mass can land on exactly 0: - // prize / 0 = inf, then 0 * inf = NaN, which spreads into taxes, treasuries and prices. Skip the update then. - auto mass_ok = mass != 0.f; - auto desire = ve::select(mass_ok, ve::max(0.f, (prize / mass - 0.1f)), 0.f); + auto desire = ve::max(0.f, (prize / mass - 0.1f)); auto control_scale = ve::max(0.f, state.world.province_get_control_scale(pids)); // Bureaucratic capacity assigned to the province // as we expand control over local land, it requires much higher levels of administrative work to increase it auto available_control = ve::min(control_scale * desire * 5.f, mass); // How much control can be established this tick capped at mass (can't exceed admin capacity needed) - auto speed = ve::select(mass_ok, available_control / mass - current_control, 0.f); // Difference between potential and current control. Control grows slowly at 1% per tick to avoid sudden drops in taxes + auto speed = (available_control / mass - current_control); // Difference between potential and current control. Control grows slowly at 1% per tick to avoid sudden drops in taxes state.world.province_set_control_ratio( pids,