Add runway tool with auto-detected inbound ships; fix multi-base state tagging

- tools/runway.py: days of runway per material before a base stalls, from simulate()'s real
  net consumption, base storage, plus (by default) any ship currently flying to that planet
- tools/state.py: tag each STORE entry with its planet (FIO AddressableId -> site SiteId) and
  each ship with its live flight origin/destination (keyed by ShipId, not StlFuelStoreId).
  Needed now that a 2nd base exists - storage/ship data was previously unscoped, which would
  have silently mixed bases' inventories together
- tests, docs updated (docs/library.md notes the multi-base tagging as a reusable pattern)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-28 21:56:18 +02:00
co-authored by Claude Sonnet 5
parent 58db08b921
commit b24f132210
6 changed files with 155 additions and 2 deletions
+11 -2
View File
@@ -29,6 +29,8 @@ def sync():
prod = fio.own(f"/production/{u}", ttl=0)
stores = fio.own(f"/storage/{u}", ttl=0)
ships = fio.own(f"/ship/ships/{u}", ttl=0)
flights = fio.own(f"/ship/flights/{u}", ttl=0)
by_ship = {f["ShipId"]: f for f in flights}
st["permits"] = {"used": sites[0]["InvestedPermits"] if sites else 0, "total": sites[0]["MaximumPermits"] if sites else 0}
if st.get("permits_total_override"): # e.g. the HQ screen (base permits x / y) disagrees with FIO's MaximumPermits
st["permits"]["total"] = st["permits_total_override"]
@@ -42,10 +44,17 @@ def sync():
for l in prod if l["SiteId"] == s["SiteId"]]
bases.append(b)
st["bases"] = bases
st["storage"] = [dict(type=x["Type"], name=x["Name"], weight=f"{x['WeightLoad']:.0f}/{x['WeightCapacity']:.0f}t",
site_planet = {s["SiteId"]: s["PlanetIdentifier"] for s in sites} # STORE.AddressableId == a site's SiteId
st["storage"] = [dict(type=x["Type"], name=x["Name"], planet=site_planet.get(x.get("AddressableId")),
weight=f"{x['WeightLoad']:.0f}/{x['WeightCapacity']:.0f}t",
items={i["MaterialTicker"]: i["MaterialAmount"] for i in x["StorageItems"] if i.get("MaterialTicker")})
for x in stores if x["StorageItems"] or x["Type"] == "SHIP_STORE"]
st["ships"] = [dict(name=x["Name"], reg=x["Registration"], mass=x["Mass"], in_flight=bool(x.get("FlightId"))) for x in ships]
def ship_dest(x):
f = by_ship.get(x.get("ShipId"))
return None if not f else {"origin": f.get("Origin"), "destination": f.get("Destination"), "eta_ms": f.get("ArrivalTimeEpochMs")}
st["ships"] = [dict(name=x["Name"], reg=x["Registration"], mass=x["Mass"], in_flight=bool(x.get("FlightId")), flight=ship_dest(x)) for x in ships]
P.write_text(yaml.safe_dump(st, sort_keys=False, default_flow_style=None, width=140))
print("synced", st["as_of"])