Files
PuGa/tests/test_simulate.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.3 KiB
Python

"""Test vector: screenshots of PRUNplanner's simulator for plans/examples/base_plus_hwp.yaml (2026-09-18). Needs cached/online PRUNplanner data."""
import importlib.util
from pathlib import Path
import pytest, yaml
ROOT = Path(__file__).resolve().parent.parent
spec = importlib.util.spec_from_file_location("plan_push", ROOT / "tools" / "plan_push.py")
plan_push = importlib.util.module_from_spec(spec)
spec.loader.exec_module(plan_push)
from puga import prunplanner as pp
from puga.simulate import simulate
@pytest.fixture(scope="module")
def sim():
try:
recipes, blds = pp.recipes(), pp.buildings()
planet = pp._g("/data/planet/ZV-759c/", 3600)
except Exception as e: # offline
pytest.skip(f"no PRUNplanner data: {e}")
plan = plan_push.build_payload(yaml.safe_load((ROOT / "plans" / "examples" / "base_plus_hwp.yaml").read_text()), recipes, {b["building_ticker"] for b in blds})
plan["plan_corphq"] = False # screenshots 1-3 were taken without HQ; plan files now say hq: true
return lambda off=(): _run(plan, recipes, blds, planet, off)
def _run(plan, recipes, blds, planet, off):
import copy
plan = copy.deepcopy(plan)
for b in plan["plan_data"]["buildings"]:
if b["name"] in off:
for ar in b["active_recipes"]:
ar["amount"] = 0
return simulate(plan, recipes, blds, planet["resources"], planet["fertility"], lambda t, side='both': 100.0)
def test_efficiencies_and_workforce_match_screenshot(sim):
r = sim()
eff = {b["building"]: b["efficiency"] for b in r["buildings"]}
assert eff["EXT"] == pytest.approx(1.0) and eff["SME"] == pytest.approx(1.337, abs=1e-4) and eff["HWP"] == pytest.approx(1.0696, abs=1e-4)
wf = r["workforce"]
assert (wf["pioneer"]["need"], wf["pioneer"]["supply"], wf["settler"]["supply"], wf["technician"]["open"]) == (370, 400, 100, -10)
def test_material_flows_match_screenshot(sim):
f = sim()["flows"]
exp = {"AL": (42.78, 44.57), "ALO": (66.85, 56.00), "BHP": (0, 14.26), "C": (11.14, 0), "COF": (1.85, 0), "DW": (16.80, 0),
"EXO": (0.20, 0), "FLX": (11.14, 0), "HE": (3.57, 0), "KOM": (0.40, 0), "O": (11.14, 0), "OVE": (1.85, 0),
"PT": (0.20, 0), "PWO": (0.74, 0), "RAT": (17.20, 0), "REP": (0.08, 0), "STL": (3.57, 0)}
for tk, (i, o) in exp.items():
assert f[tk]["inp"] == pytest.approx(i, abs=0.01) and f[tk]["out"] == pytest.approx(o, abs=0.01), tk
def test_switched_off_recipes_produce_nothing_but_keep_workforce(sim):
r = sim(off=("EXT", "SME"))
assert r["flows"]["AL"]["out"] == 0 and "ALO" not in r["flows"] # no ALO row at all, as in the screenshot
assert r["flows"]["DW"]["inp"] == pytest.approx(16.80, abs=0.01) # idle pioneers still consume (screenshot 2)
assert r["flows"]["AL"]["inp"] == pytest.approx(42.78, abs=0.01)
def test_cost_degradation_area_match_screenshots():
"""Screenshots: full plan area 237, degradation 2,141.74, plan cost 737,076; HWP-only area 102, degradation 373.60, cost 418,812.
Prices drift, so 3% tolerance on money values; area is exact."""
from puga import market
try:
recipes, blds = pp.recipes(), pp.buildings()
planet = pp._g("/data/planet/ZV-759c/", 3600)
snap = market.snapshot()
except Exception as e:
pytest.skip(f"offline: {e}")
price = lambda t, side='both': market.uni30(snap, t)
for fname, area, degr, cost in (("base_plus_hwp.yaml", 237, 2141.74, 737076.53), ("hwp_only.yaml", 102, 373.60, 418811.91)):
plan = plan_push.build_payload(yaml.safe_load((ROOT / "plans" / "examples" / fname).read_text()), recipes, {b["building_ticker"] for b in blds})
plan["plan_corphq"] = False
r = simulate(plan, recipes, blds, planet["resources"], planet["fertility"], price)
assert r["area"] == area
assert r["degradation"] == pytest.approx(degr, rel=0.03) and r["plan_cost"] == pytest.approx(cost, rel=0.03)
def test_hq_multiplies_every_building_by_1_1(sim):
"""Screenshot 6: HWP 117.66% with HQ ticked (0.8 x 1.25 x 1.0696 x 1.1)."""
import copy
base = sim()
assert next(b for b in base["buildings"] if b["building"] == "HWP")["efficiency"] == pytest.approx(1.0696, abs=1e-4)
assert 1.0696 * 1.1 == pytest.approx(1.1766, abs=1e-4)