- 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>
85 lines
4.3 KiB
Python
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)
|