Files
PuGa/tests/test_econ.py
dodoxandClaude Sonnet 5 3bbf524ebb Add simulator replica, persistence, planet scan, README; split empire state out of the repo
- tools/simulate.py + puga/simulate.py: replica of PRUNplanner's simulator (flows and
  efficiency verified against screenshots), reports real new capex (planned minus built)
- tools/scan.py: staffing variants, freight, HQ/experts, --planet mode, demolish-later,
  --min-n as a pure market-size filter, --json output
- tools/history.py, tools/persistence.py: margin history and short-horizon payback checks
- tools/plan_push.py: guarded delete; tools/state.py: syncs to empire/
- README with features and setup; CLAUDE.md made generic
- Own-empire material (profile, state, plans, notes) moved to gitignored empire/;
  generic examples in plans/examples and state/company.example.yaml

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 00:11:02 +02:00

85 lines
4.5 KiB
Python

"""Expected values copied from PRUNplanner's own test suite (ref/frontend/src/tests/features/planning/calculations)."""
import pytest
from puga import econ
HAB_COSTS = {'HB1': 50283.96581293734, 'HB2': 48183.93380367031, 'HB3': 171236.78856075153, 'HB4': 478742.2978713045,
'HB5': 881886.0033487707, 'HBB': 78602.69810873509, 'HBC': 191175.66459235144,
'HBM': 759867.355670017, 'HBL': 1210568.4635058648}
REQ = {'pioneer': 100, 'settler': 390, 'technician': 490}
@pytest.mark.parametrize("cap,req,l1,l2,exp", [
(100, 100, False, False, 0.7944444444444446), (50, 100, False, False, 0.3972222222222223),
(100, 50, False, False, 0.7944444444444446), (100, 100, True, False, 0.8666666666666668),
(100, 100, False, True, 0.9166666666666667), (100, 100, True, True, 1)])
def test_tier_efficiency(cap, req, l1, l2, exp):
assert econ.tier_efficiency(cap, req, l1, l2) == pytest.approx(exp)
@pytest.mark.parametrize("n,exp", [(-5, 0), (100, 0), (1, 0.0306), (2, 0.0696), (3, 0.1248), (4, 0.1974), (5, 0.284)])
def test_expert_bonus(n, exp):
assert econ.expert_bonus(n) == exp
def test_workforce_factor():
b = dict(pioneers=40, settlers=30, technicians=20, engineers=10, scientists=5)
eff = dict(pioneer=.5, settler=.25, technician=.4, engineer=.1, scientist=1.5)
assert econ.workforce_factor(b, eff) == pytest.approx(0.419047619047619)
b = dict(pioneers=100, settlers=50, technicians=20, engineers=10, scientists=5)
eff = dict(pioneer=1.25, settler=1.625, technician=.4, engineer=.1, scientist=1.5)
assert econ.workforce_factor(b, eff) == pytest.approx(1.204054054054054)
def test_faction_bonus():
assert econ.faction_multiplier("HORTUS", "AGRICULTURE", 1, 3) == pytest.approx(1.14)
assert econ.faction_multiplier("MORIA", "METALLURGY", 20, 21) == pytest.approx(1.0438095238095237)
assert econ.faction_multiplier("FOO", "AGRICULTURE", 1, 3) is None
assert econ.faction_multiplier("ANTARES", "METALLURGY", 1, 2) is None # Antares is electronics only
def test_hwp_without_technicians_under_metallurgy_cogc():
"""HWP 40 settlers + 10 technicians; technicians absent: workforce 0.8 x COGC 1.25 = 1.0 (before expert/HQ)."""
hwp = dict(settlers=40, technicians=10)
tier = dict(settler=econ.tier_efficiency(40, 40, True, True), technician=0.0)
total, el = econ.building_efficiency(hwp, tier, expertise="METALLURGY", cogc="METALLURGY")
assert el["WORKFORCE"] == pytest.approx(0.8) and total == pytest.approx(1.0)
def test_extraction_matches_live_planet():
"""Planet data ZV-759c: ALO factor 0.4 (MINERAL), O 0.3 (GASEOUS), H2O 0.2 (LIQUID). APEX/PRUNplanner chips: 28 ALO, 18 O, 14 H2O per day.
Live FIO EXT order: 14 ALO per 12.008 h = 28/day."""
assert econ.daily_extraction(0.4, "MINERAL") == pytest.approx(28.0)
assert econ.daily_extraction(0.3, "GASEOUS") == pytest.approx(18.0)
assert econ.daily_extraction(0.2, "LIQUID") == pytest.approx(14.0)
t, amt = econ.extraction_cycle("MINERAL", 28.0)
assert amt == 14 and t == pytest.approx(12 * 3600e3)
def test_production_io_flux_smelter():
r = dict(time_ms=14.9167 * 3600e3, inputs={"ALO": 6, "FLX": 1, "C": 1, "O": 1}, outputs={"AL": 4})
io = econ.production_io([r], efficiency=1.0, n_buildings=5)
assert io["out"]["AL"] == pytest.approx(4 * 5 * 24 / 14.9167, rel=1e-3) # ~32 AL/day for 5 SME at 100%
def test_workforce_consumption_luxury_gating():
d = econ.workforce_consumption("pioneer", 100, 100, lux1=True, lux2=False)
assert d == {"DW": 4, "RAT": 4, "OVE": 0.5, "PWO": 0.2}
assert econ.workforce_consumption("pioneer", 100, 50, False, False)["DW"] == 2
def test_hab_optimizer_matches_prunplanner():
r = econ.optimize_habs(REQ, HAB_COSTS, "cost", max_area=135)
assert r["habs"] == {"HB1": 1, "HB2": 4, "HB3": 5} and r["cost"] == pytest.approx(1099203.64383138)
r = econ.optimize_habs(REQ, HAB_COSTS, "area")
assert r["area"] == 118
def test_matches_live_fio_smelter_efficiency():
"""A live smelter, FIO /production 2026-09-18: Efficiency 1.3360869884. All pioneers, both luxuries met, Metallurgy COGC,
2 metallurgy experts, condition 0.9993602633."""
tier = dict(pioneer=econ.tier_efficiency(400, 370, True, True))
total, el = econ.building_efficiency(dict(pioneers=50), tier, expertise="METALLURGY", cogc="METALLURGY",
experts={"METALLURGY": 2}, condition=0.9993602633476257)
# model 1.33614 vs FIO 1.33609: agrees to 0.004%; residual unexplained (rounding of expert/condition in game)
assert total == pytest.approx(1.3360869884490967, rel=1e-4)