Add planet environment-extra materials to capex calcs; buildcost.py tool
Every building needs a planet-hazard material bundle on top of its normal BuildingCosts (SEA for low pressure, HSE for high pressure, MGC for low gravity, BL for high gravity, INS for cold, TSH for heat - area-scaled where applicable), confirmed against live in-game build panels on Nike. FIO's own BuildRequirements field is an unreliable superset across hazard types, not a direct per-building list - don't use it directly. - puga/planet.py::environment_extras(): the real threshold table. - tools/buildcost.py: full construction material list for one building, optionally on a given planet. - Wired into whatif.py/buy.py (via simulate(planet=...)) and scan.py's own bcost(), so their capex numbers stop silently omitting this. - docs/mechanics.md: the rule, plus a separate forum-sourced note on the CX order-cancellation penalty. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Full construction material list for one building, optionally on a specific planet (adds its
|
||||
environment-extra materials via puga.planet.environment_extras() - SEA/HSE/MGC/BL/INS/TSH).
|
||||
|
||||
puga buildcost HB1
|
||||
puga buildcost EXT --planet ZV-194a
|
||||
puga buildcost PP1 --planet ZV-194a --amount 1
|
||||
"""
|
||||
import argparse, sys
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from puga import config, fio, market, prunplanner as pp, planet as planet_mod
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("building", help="building ticker, e.g. HB1, EXT, PP1")
|
||||
ap.add_argument("--planet", help="planet natural id - adds its environment-extra materials")
|
||||
ap.add_argument("--amount", type=int, default=1)
|
||||
ap.add_argument("--cx", default=config.DEFAULT_CX)
|
||||
a = ap.parse_args()
|
||||
|
||||
blds = {b["Ticker"]: b for b in fio.buildings()}
|
||||
b = blds.get(a.building.upper())
|
||||
if not b:
|
||||
sys.exit(f"unknown building {a.building!r}")
|
||||
|
||||
need: dict[str, float] = {c["CommodityTicker"]: c["Amount"] for c in b["BuildingCosts"]}
|
||||
need["MCG"] = need.get("MCG", 0) + 4 * b["AreaCost"]
|
||||
|
||||
if a.planet:
|
||||
planet = pp._g(f"/data/planet/{a.planet}/", 3600)
|
||||
for tk, qty in planet_mod.environment_extras(planet, b["AreaCost"]).items():
|
||||
need[tk] = need.get(tk, 0) + qty
|
||||
print(f"# {b['Ticker']} on {planet['planet_name']} ({a.planet}): pressure {planet['pressure']:.3f}, "
|
||||
f"gravity {planet['gravity']:.2f}, temp {planet['temperature']:.1f}C")
|
||||
|
||||
total = 0.0
|
||||
for tk, qty in sorted(need.items()):
|
||||
qty *= a.amount
|
||||
cost = market.walk(tk, a.cx, qty, "buy")
|
||||
px = cost["total"] if not cost["short"] else None
|
||||
total += px or 0
|
||||
print(f"{tk:4} {qty:8.1f} {px:>10,.0f} AIC" if px is not None else f"{tk:4} {qty:8.1f} (short on {a.cx})")
|
||||
print(f"total: {total:,.0f} AIC" if total else "")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+9
-4
@@ -20,14 +20,16 @@ from pathlib import Path
|
||||
import yaml
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from puga import config, fio, market, prunplanner as pp
|
||||
from puga import planet as planet_mod
|
||||
from puga.simulate import simulate
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
import plan_push
|
||||
|
||||
|
||||
def construction_items(plan: dict, buildings: list, built: dict) -> dict:
|
||||
"""Materials needed for the plan's buildings/infra beyond what `built` (ticker -> count) already has."""
|
||||
def construction_items(plan: dict, buildings: list, built: dict, planet: dict | None = None) -> dict:
|
||||
"""Materials needed for the plan's buildings/infra beyond what `built` (ticker -> count) already has.
|
||||
planet: optional, adds environment-extra materials (SEA/HSE/MGC/BL/INS/TSH) via planet.environment_extras()."""
|
||||
bl = {b["building_ticker"]: b for b in buildings}
|
||||
d = plan["plan_data"]
|
||||
need: dict[str, float] = {}
|
||||
@@ -39,6 +41,9 @@ def construction_items(plan: dict, buildings: list, built: dict) -> dict:
|
||||
for c in info["costs"]:
|
||||
need[c["material_ticker"]] = need.get(c["material_ticker"], 0) + c["material_amount"] * gap
|
||||
need["MCG"] = need.get("MCG", 0) + 4 * info["area_cost"] * gap
|
||||
if planet is not None:
|
||||
for etk, qty in planet_mod.environment_extras(planet, info["area_cost"]).items():
|
||||
need[etk] = need.get(etk, 0) + qty * gap
|
||||
return need
|
||||
|
||||
|
||||
@@ -101,9 +106,9 @@ def main():
|
||||
perm = (st.get("permits", {}).get("used", 1), st.get("permits", {}).get("total", 2))
|
||||
planet = pp._g(f"/data/planet/{plan['planet_natural_id']}/", 3600)
|
||||
price_ask = lambda t, side="buy": (lambda q: q.ask if q else None)(market.snapshot().get((t, a.cx)))
|
||||
r = simulate(plan, recipes, blds, planet["resources"], planet["fertility"], price_ask, faction, perm)
|
||||
r = simulate(plan, recipes, blds, planet["resources"], planet["fertility"], price_ask, faction, perm, planet=planet)
|
||||
|
||||
con = net_of_on_hand(construction_items(plan, blds, built), on_hand)
|
||||
con = net_of_on_hand(construction_items(plan, blds, built, planet), on_hand)
|
||||
stk = net_of_on_hand(stock_items(r["flows"], a.days), on_hand)
|
||||
con_rows, con_total, con_w, con_v = price(con, a.cx)
|
||||
stk_rows, stk_total, stk_w, stk_v = price(stk, a.cx)
|
||||
|
||||
+9
-1
@@ -13,6 +13,7 @@ from pathlib import Path
|
||||
import yaml
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from puga import ROOT, config, econ, fio, market, prunplanner as pp, saturation as sat
|
||||
from puga import planet as pl
|
||||
|
||||
TIER_CODE = {"pioneer": "P", "settler": "S", "technician": "T", "engineer": "E", "scientist": "Sc"}
|
||||
TIER_RANK = {"P": 0, "S": 1, "T": 2, "E": 3, "Sc": 4}
|
||||
@@ -83,7 +84,14 @@ def main():
|
||||
parts = [(x["Amount"], ask(x["CommodityTicker"])) for x in b["BuildingCosts"]]
|
||||
if any(p is None for _, p in parts):
|
||||
return None
|
||||
return sum(n * p for n, p in parts) + a.mcg * b["AreaCost"] * mcg
|
||||
cost = sum(n * p for n, p in parts) + a.mcg * b["AreaCost"] * mcg
|
||||
if planet is not None:
|
||||
extras = pl.environment_extras(planet, b["AreaCost"])
|
||||
prices = {etk: ask(etk) for etk in extras}
|
||||
if any(p is None for p in prices.values()):
|
||||
return None
|
||||
cost += sum(qty * prices[etk] for etk, qty in extras.items())
|
||||
return cost
|
||||
|
||||
hab_head, hab_area = {}, {}
|
||||
for tier, hab in (("pioneer", "HB1"), ("settler", "HB2"), ("technician", "HB3"), ("engineer", "HB4"), ("scientist", "HB5")):
|
||||
|
||||
+1
-1
@@ -127,7 +127,7 @@ def marginal_freight(r_b, r_a, planet_id, cx, sf_price, ff_price, mats):
|
||||
def run(spec, recipes, blds, price, faction, perm, built):
|
||||
plan = plan_push.build_payload(spec, recipes, {b["building_ticker"] for b in blds})
|
||||
planet = pp._g(f"/data/planet/{plan['planet_natural_id']}/", 3600)
|
||||
return plan, simulate(plan, recipes, blds, planet["resources"], planet["fertility"], price, faction, perm, built=built)
|
||||
return plan, simulate(plan, recipes, blds, planet["resources"], planet["fertility"], price, faction, perm, built=built, planet=planet)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
Reference in New Issue
Block a user