Files
PuGa/CLAUDE.md
T
dodoxandClaude Sonnet 5 bf15c709ea 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>
2026-10-17 19:03:51 +02:00

87 lines
18 KiB
Markdown

# PuGa: Prosperous Universe advisory toolkit
Agent-first toolkit for advising a Prosperous Universe player. The player asks questions; you answer by running the tools in `tools/` against live data, not by guessing.
## Read first
1. `empire/PROFILE.md` if it exists: who the player is, how they want answers, their company and current plans. `empire/` is gitignored and holds everything specific to this player's empire (see Layout). If it does not exist, ask the player the basics and create it.
2. `docs/mechanics.md` for game mechanics (source of truth order below).
3. `empire/state/company.yaml` for the current position; refresh it with `tools/state.py sync` before answering anything about it.
## Working rules
- Numbers, tables, ROI per day. Short answers, no fluff. Match the style in the player's profile.
- The player plays on browser APEX and may send screenshots; read them carefully, they override assumptions.
- Never guess APEX commands or numbers. Pull data (tools, FIO, PRUNplanner) or search.
- Label every number: live (tool output), state (company.yaml), or estimate; say which in-game command would confirm an estimate.
- Prices move; re-run tools before quoting. Anything older than a day is stale.
- Do not commit or push unless asked. Writes to the player's PRUNplanner account follow the guardrails in `docs/decisions.md`.
## Source-of-truth rules
1. **PRUNplanner code wins** over our own docs for game mechanics (`ref/frontend/src/features/planning/calculations/`, `ref/backend/`). If any older handoff or note conflicts with it, the note is wrong; fix `docs/mechanics.md`.
2. In-game numbers the player reports beat both, especially for planet resource factors.
3. `ref/` is a gitignored copy of the PRUNplanner repos; refresh with `tools/refresh_refs.sh`.
## Setup
- Python venv at `.venv`, package installed editable (`pip install -e ".[dev]"`), which provides the `puga` command: `puga <tool> [args]` (`.venv/bin/puga` if the venv is not activated). `tools/*.py` also run directly with `.venv/bin/python`. Dependencies are in `pyproject.toml`.
- Secrets in `.env` (gitignored; template `.env.example`): FIO REST key, FIO API key, PRUNplanner key, FIO username, company code. Never print or commit them; do not ask the player to paste keys into chat.
- Scope: whole universe supported; default exchange from `DEFAULT_CX` (AI1 = Antares), `--cx` to change.
## Layout
- `CLAUDE.md` this file; `README.md` for humans (short pitch), `docs/usage.md` the human tool/setup reference.
- `docs/` GAME and toolkit knowledge, safe to publish: `mechanics.md` (verified rules), `saturation-design.md` (market model), `decisions.md` (toolkit decisions and guardrails), `roadmap.md`.
- `empire/` (**gitignored**) OUR game state, nothing here goes public: `PROFILE.md`, `state/company.yaml` (synced from FIO), `plans/` (own plan specs), `docs/` (handoffs, build plans, personal notes).
- `puga/` library; `tools/` CLIs; `plans/examples/` generic plan specs (also test fixtures); `state/company.example.yaml` example state; `tests/`; `data/` (gitignored fetched data cache); `ref/` (gitignored PRUNplanner source).
- Rule: game facts and generic methods go in `docs/`; anything about the player's own bases, cash, decisions or preferences goes in `empire/`.
## Library use (not just CLI)
For anything the CLI tools don't expose (custom what-ifs, cost breakdowns with wages/capex, ad-hoc market checks) import `puga.*` directly in a short inline script instead of stretching a tool's flags. See `docs/library.md` for the module map and patterns; most non-trivial answers in this project came from a `python - <<EOF` block, not a CLI flag.
## Self-improve the harness
If you catch yourself writing similar ad-hoc `python - <<EOF` code for the same kind of workload more than once in a session (a reverse recipe lookup, a planet/resource search, a marginal-ROI diff, a jump-distance check), stop and abstract it into a `puga/` function plus a `tools/*.py` CLI instead of repeating it by hand again. This is exactly how `tools/route.py`, `tools/whatif.py`, `tools/chain.py --consumers` and `tools/planet.py` got built - noticing the repeated-by-hand pattern, then promoting it. Register the new tool in this file's Tools list and in `docs/roadmap.md`; add a matching entry to `docs/library.md` if it's a new `puga/` module. This is the harness improving itself through use, not a one-off - keep doing it.
## Tools
Run as `puga <tool> ...` (e.g. `puga scan --min-n 3`, `puga plan pull <uuid>`); the `tools/x.py` paths below are the same programs.
- `tools/state.py sync|show` refreshes `empire/state/company.yaml` from live FIO (cash, permits, buildings, real production efficiency, storage, ships).
- `tools/plan_push.py` builds/validates PRUNplanner plans from YAML specs (`plans/examples/`, `empire/plans/`). Dry run by default; `--apply` only after the player says yes; only `[PuGa]`-named plans are created/updated/deleted (`delete <uuid>`, only when asked); `list` shows the account's plans, `pull <uuid> -o file.yaml` reads a plan back into a YAML spec including edits made in the PRUNplanner UI. Workflow: push a plan, the player refines it in the UI and saves, then `pull` / `simulate --uuid` read it back and you continue from there.
- `tools/simulate.py <spec.yaml | --uuid U> [--off EXT,SME] [--basis real|uni30|vwap30|ask|...]` replica of PRUNplanner's simulator (efficiency, workforce, material I/O, profit). Flows and efficiency verified exact vs screenshots; profit within ~2%. The NEW CAPEX line = planned minus already built (from state), the honest payback; ignore the plan-level ROI (PRUNplanner always adds a core module). `--uuid` reads the SAVED plan from the account (UI edits must be saved first).
- `tools/scan.py` DEPTH-AWARE recipe scan (use this): buildings the market could absorb, ROI for our own size, patient prices, ask-walked inputs. Fully staffed AND understaffed variants by default (`--staffing`), freight netted out (`--trip-cost`, `--cargo`), HQ/COGC/experts/faction, `--planet ID` adds that planet's extraction, fertility and COGC (a planet not in state is a new base: no HQ, permits+1), `--deprec 60 [--reclaim-frac 0.65]` prices demolish-later: amortizes the UNRECLAIMED capex (not full capex - real FIO reclaim data shows 50-86% comes back even at near-100% condition, see `docs/decisions.md`) over the holding period. `--min-n` is ONLY a noise filter on market capacity (use 3, ideally 10); ROI is always for our own size (`--own`, default 1 building), never at the filtered scale. Below 1 lets sub-building junk in. `--json rows.json` feeds `persistence.py`. `--sort area` ranks by profit/day per tile of BUILDING area (FIO `AreaCost`, not hab area) - once an existing base's area cap binds, this is the real allocation criterion, not %-yield/payback (a higher-%% recipe needing much more area can lose to a lower-%% one that fits more densely). `--max-impact 10` (stage 2 only) flags `[exit-thin]` when the LIVE bid book can't absorb even 1 day of 1 building's output within that %% (`puga.saturation.max_units_at_impact`) - an INSTANT-DUMP/panic-exit liquidity check, not production price impact (that's what `market`/`lim` already measure via real 30-day flow); a single snapshot (orders change constantly), so it's a WARNING, not folded into `market`/`lim`. Library: `puga/saturation.py`.
- `tools/history.py TICKER` monthly margin history of the recipe producing TICKER; `tools/persistence.py rows.json` re-prices scan rows over history (mean ROI 7/14/30/90/180d, payback, net gain over 7 and 14 days, % days profitable). ALWAYS check persistence before recommending: current margins are often a spike.
- `tools/buy.py <spec.yaml|--uuid U> --days N` cash-aware shopping list: construction gap (plan vs empire state's built buildings) plus N days of the plan's NET operating stock (via `simulate()`, so self-produced inputs like AL net against consumption), priced at real fill cost, checked against cash/reserve. Pass a plan covering the WHOLE base for restocking, not just a new addition (see `docs/library.md`).
- `tools/sell.py TICKER QTY [--tight]` where to post an ask, expected hours to clear, and a self-contained tranche split: aggressive (undercut) tranche capped at the median 30d-volume quantile (or 80th pct with `--tight`, for when a payment is imminent and stockout risk outweighs a few points of margin), patient tranche priced just under the next competitor tier. `--show-bid` for the instant-bid comparison (off by default; usually worse). `--tiers [--rate R]` answers "is it ever worth pricing above the current market": combines queue-depth days (real resting quantity, within the traded range) with an EMPIRICAL first-passage estimate from the ticker's own multi-year price history (no random-walk assumption - a driftless-walk closed form was tried first and backtested wrong, see `docs/decisions.md`'s "Optimal ask price" entry), via max (the binding constraint). `rate` should be the player's REALIZED capital growth rate (read off their equity-growth chart, default 0.05), not a theoretical build ROI. Library: `puga/history.py::empirical_touch()`.
- `tools/network.py <chain.yaml>` models a MULTI-BASE chain (e.g. mine LST at Nike, ship it, make BSE at Deimos): simulates each base, nets a transferred material's producer-surplus against consumer-need, charges real freight only on what's moved (`puga/network.py`). Spec and worked example in `plans/chains/`. `cm_free: true` per base for a new founding covered by a Core Module Kit. profit/day per base is the WHOLE plan, not the marginal addition - diff against the base's plan without the addition for a true marginal ROI.
- `tools/whatif.py <before.yaml> [--add BUILDING:AMOUNT[:RECIPE]] [--infra HB1:+1] [--after after.yaml]` marginal payback of adding to a base you already run (whole-plan diff, same rule as buy/runway - pass the WHOLE base, not just the addition). `--add` mutates a copy of `before` in-memory (reuses an existing building's recipe if omitted, e.g. `--add POL:1`); `--after` takes a second full spec instead. Flags any workforce tier the addition newly overloads (vs. a pre-existing one, which it won't flag) - this is what caught the PP2/pioneer-housing and 2nd-CHP/pioneer-housing surprises by hand before this tool existed. Charges marginal freight too (real fuel model, CX-to-planet, via `puga.shipping`): a CONTINUOUS amortized rate (extra tonnage/cargo-hold x round-trip cost), scaling with the addition's own tonnage, not a step function - a small addition adds a small continuous freight cost, not exactly zero, and a recipe whose ROI looks great only because its freight cost was left out still gets caught.
- `tools/runway.py <spec.yaml|--uuid U> [--no-inbound]` days of runway per material before a base stalls: real NET consumption via `simulate()` (self-produced inputs excluded), base storage, PLUS (by default) any ship currently flying to that planet, auto-detected from live flight data (`tools/state.py` tags each ship with its flight origin/destination). Pass the WHOLE base plan (see `docs/library.md`).
- `tools/undervalued.py [--cx AI1] [--min-vol 5] [--top 20]` manufactured goods currently priced below implied production cost - buy-and-hold-for-reversion candidates, not build candidates. **Expected ROI is much lower than production builds** (~0.10-0.24%/day backtested, vs 14-29%/day for the builds this toolkit recommends) - only worth it for excess idle capital once better-yielding builds are exhausted. Library: `puga/undervalued.py`.
- `tools/chain.py TICKER` make-vs-buy cost tree plus sourcing depth of inputs. `--consumers` flips it: what recipes EAT this ticker as an input, with each output's market depth - for "is there a deeper downstream product worth chaining into" questions (e.g. checking BOR/BSE/BBH for further processing).
- `tools/planet.py RESOURCE [--min-factor X] [--from PLANET] [--top N]` ranks planets for a raw resource by extraction factor, with founding cost straight from FIO's own `BuildRequirements` (real environment-extras materials, not guessed temperature/pressure thresholds - PRUNplanner's own calculations don't model those either) and optional jump distance via `tools/route.py`. Pure-factor ranking alone is misleading - the best-factor planets are often thousands of ly away; always pass `--from` an existing base. Library: `puga/planet.py`.
- `tools/route.py <from planet/system> <to planet/system>` jump-distance route (not a PRUNplanner feature - built from FIO's `/systemstars` graph, Dijkstra by straight-line ly since that's what FTL fuel/time scale with, jump count reported alongside). Accepts planet natural IDs, system natural IDs, or names. Library: `puga/route.py`.
- `tools/shiptrip.py SHIP FROM TO [--v0 X --v1 Y]` the simple ship-trip tool: give a ship registration + start + stop, it works out jumps/charges/landing fuel itself via `tools/route.py`'s jump graph and reports fuel cost (defaults to max sliders). Direct-jump routes only - an unconnected pair means gateway-only, and it tells you exactly what to read off the live APEX Ship Flight Control screen (the LOCK fee - genuinely not available via FIO/PRUNplanner, confirmed by inspecting both) and what to run instead (`tools/shipcost.py`). Warns if the given ship's live specs (FIO `/ship/ships/{user}`) don't match the calibrated hull/reactor.
- `tools/shipcost.py [--jumps N --charges N] [--v0 X --v1 Y] [--gateways N --lock-fee AMOUNT:CURRENCY] [--to PLANET]` the explicit/manual version: fuel/AIC cost of a ship trip from physics parameters you already know (jump count, slider values, gateway LOCK fees read off the live game) - use `tools/shiptrip.py` instead if you just want "ship X from A to B". Landing fuel is a measured-planet lookup via `--to`, independent of whether the route used jumps or gateways. Both tools scoped to this account's one hull/reactor (blueprint BP-STRT-0000, shared by both ships AVI-07JFB/AVI-07JFC); open caveats (v0 relative to current fuel load, landing fuel confounded with gravity/distance, no second hull to cross-check) are in `puga/shipping.py`'s module docstring - don't trust magnitudes beyond what's there. Raw calibration data (APEX DOM reads, 2026-10-09): `empire/data/shipping_calibration/`. Also wired into `tools/network.py`'s freight cost (direct-jump transfers use the real model; gateway transfers need an explicit `trip_cost` in the chain YAML, else a 9250 AIC fallback). Library: `puga/shipping.py` (`trip_fuel()`, `round_trip_cost()`).
- `tools/parkcash.py [--min-spread X] [--min-trade-days N] [--rate R] [--top N]` where to park idle cash as a buy-low/sell-high position (not a build) while ships are busy or cash has no better use right now - scans every ticker for a big bid/ask spread AND real trading liquidity (trade-days/gap over a calendar window - a huge spread with few trading days is a stale-quote trap, confirmed by hand: AST showed a 130% spread but its resting ask had gone stale after a 60% real price crash). Round-trip wait/value is EMPIRICAL (`puga.history.empirical_touch`, same model as `tools/sell.py --tiers`), not an assumed random walk - a driftless-walk version was tried and backtested wrong (overstated reachability 2-4x vs 3 years of real data, see `docs/decisions.md`). This dropped the whole universe's top pick from ~94%/day to ~1%/day with real waits of 19-194+ days - treat any number from the old model as stale. `--rate` defaults to 0.0 (matches the idle-cash premise; pass your realized growth rate if this cash IS earmarked) and shows up as a separate `discounted_pct` column (margin% x real odds both legs ever happen) alongside the simple non-compounding `daily_pct` used for ranking. Library: `puga/parkcash.py`.
- `tools/contd.py [--cx AI1]` evaluates this account's OPEN CONTD contract offers (the NPC logistics-officer
pool in APEX's CONTD panel, usually 3 at a time, only one acceptable) - payment, each required
material priced at real buy-fresh cost, and net. Deliberately leaves conditions it can't price
(EXPLORATION, REPAIR_SHIP, HEADQUARTERS_UPGRADE, BASE_CONSTRUCTION, CONTRIBUTION, WAIT,
INCREASE_SATISFACTION - real cost depends on context the API can't see, e.g. a ship already flying
that route) as flagged, unpriced conditions, and doesn't account for materials you may already hold
in storage - it's a breakdown for the caller to judge, not a verdict. Library: `puga/contd.py`.
- `tools/lmscan.py PLANET [--direction source|destination] [--min-margin X]` live Local Market hauling-ad scan (FIO, public/no-auth - real player-posted freight jobs, not the CONTD player-contract system), ranked by margin after real fuel cost (`puga.shipping.leg_cost`). One-way cost only - margin assumes the next job is picked up near the destination, not an empty flight home. A small measured gateway-fee table covers ANT<->HRT/BEN; other gateway routes can't be priced (LOCK fee isn't available via any API). Library: `puga/contracts.py`.
- `tools/buildcost.py BUILDING [--planet ID] [--amount N] [--cx AI1]` full construction material
list for one building, priced at real fill cost; `--planet` adds that planet's environment-extra
materials (SEA/HSE/MGC/BL/INS/TSH - threshold table in `docs/mechanics.md`). Also wired into
`whatif.py`/`buy.py`/`scan.py`'s own capex numbers via `simulate(planet=...)` / `scan.py`'s
`bcost()`. Library: `puga/planet.py::environment_extras()`.
- `tools/actpkg.py NAME TICKER:QTY [TICKER:QTY ...] [--cx AI1] [-o file.json]` emits a Refined PrUn
(browser extension, github.com/refined-prun/refined-prun) `XIT ACT` action-package JSON for a buy
list - paste into the extension's Import Action Package to pre-fill the in-game CXPO buy tile per
material (player still clicks the real buy button - semi-auto, not full auto). NAME must be
letters/digits/spaces only; punctuation is stripped automatically. Library: `puga/actpkg.py`.
- `tools/price.py` prices across exchanges with VWAP, volume, fill price for a quantity; `tools/book.py` order-book ladder.
- `tools/prun_scan.py`, `tools/prun_cxarb.py` legacy top-of-book scans; overstate thin markets. Baseline only. `tools/arb.py` replacement is on the roadmap.
- `puga/econ.py` pure formulas ported from PRUNplanner (efficiency stack, workforce, extraction, production I/O, hab optimizer); `tests/test_econ.py` holds the reference values.
## Model policy
Sonnet builds; spawn a bigger model (Agent `model: opus|fable`) for the review points in `docs/decisions.md`. Roadmap: `docs/roadmap.md`.
## Key analytic rule: depth matters
PRUNplanner's ROI Overview ranks recipes as if the market absorbs unlimited output (e.g. 0.25 day ROI on a recipe whose whole market fits in 2 buildings). Every opportunity we report must include **saturation** (buildings the market can absorb) and, for anything we would act on, **persistence** (is the margin a spike?). Report ROI at realistic fill prices, not top-of-book.