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>
- puga/contd.py, tools/contd.py: price open CONTD contract offers against real
buy-fresh cost, flagging conditions that can't be priced from the API alone.
- tools/buyat.py: cheapest bid price likely to fill by a hard deadline
(stockout risk), using the empirical first-passage model from history.py.
- puga/actpkg.py, tools/actpkg.py: Refined PrUn XIT ACT buy-list JSON packages.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A resting order at the same price was posted first and has time
priority, so it should count as ahead too (p <= r["price"], not p <).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CLAUDE.md/docs/library.md updated to match the sell.py/parkcash.py/
scan.py changes in the preceding commits. docs/decisions.md gets the
full reasoning trail: the Black-Scholes closed form marked superseded,
the actual backtest entry (BHP/AL touch-probability table) that both
sell.py and library.md already cited but was never written, the
discretization-bias caveat on that backtest, the price-impact-cap and
demolition-value decisions. docs/roadmap.md pruned back to open items
only - finished work is discoverable from CLAUDE.md/library.md/
decisions.md instead of staying listed as a done backlog entry.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- marginal_freight()'s docstring claimed small additions "cost ~0" if
they fit in slack - the code is actually a continuous amortized rate
with no such threshold. Fixed the wording to match the real behavior.
- The one known failing test asserted fixed money values from a 2026-09-18
screenshot against live, drifting market prices - removed that
assertion (it was testing market drift, not code correctness) but kept
the exact area check. Replaced with a structural, price-independent
regression check instead of just deleting: degradation == prod_cost/180
and plan_cost == prod_cost+infra_cost+cm_cost, asserted directly from
simulate()'s own fields, holds at any price.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- max_units_at_impact(): walks the LIVE bid book for a genuine, ticker-
specific instant-exit liquidity check, replacing a flat 25%-of-flow
guess (a price-vs-volume elasticity regression was tried first and
abandoned - confounded by demand-driven volume on real data). Surfaced
as an [exit-thin] WARNING flag, not a hard cap - a single order-book
snapshot is less reliable than the 30-day flow aggregate that still
drives n_lim/filtering (using it as a hard cap once silently zeroed
out a real recipe from one noisy snapshot).
- tref_precise(): real variance of daily traded volume instead of a
Poisson unit-independence bound, wired into stage 2 only (bounded
candidate set, cached). Also fixed: was averaging only over trading
days, overstating flow ~3x for sparse tickers.
- n_out(): dropped the demand-coverage term, which penalized thin
resting-bid books even though production sells via patient asks taken
by walk-up buyers with no resting bid at all.
- --sort area: profit/day per tile of TOTAL footprint, now including the
single-tier hab area a building's workforce drags along (was building
area only).
- --deprec/--reclaim-frac: amortizes unreclaimed capex, not full capex -
real FIO reclaim data showed 50-86% returned even at near-100%
condition, contradicting a 100%->0 decay-curve assumption.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Where to park idle cash in a buy-low/sell-high position while it has no
better use - scans every ticker for real bid/ask spread and liquidity,
scoring round-trip (buy-dip, sell-recovery) via the same empirical
first-passage model as sell.py's optimal-ask fix, instead of an assumed
random walk. Dropped the universe's top pick from ~94%/day to ~1%/day
with real waits of 19-194+ days under the corrected model. Default
--rate 0.0 matches the tool's own idle-cash premise (sell.py's rate of
0.05 assumes cash is earmarked, which isn't this tool's case).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A driftless-random-walk closed form was tried first and backtested wrong
against 3 years of real exchange history (overstated touch probability
2-4x, confounded by discrete daily-VWAP sampling and real mean-reversion).
Replaced with puga.history.empirical_touch(): a non-parametric estimator
measuring first-passage directly from a ticker's own history, conditioned
on current state (trailing-60d z-score) since mean reversion's effect
depends on where price is now, not just move size. Queue-depth (existing
resting quantity) folds in as a deterministic shift of the hitting time,
not a max, ranking by the resulting hit_factor rather than a point
estimate of median days (which created a false cliff at the 50%-touched
threshold). Optional fixed-horizon right-censoring avoids recency bias
in "never reached" samples.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirmed live: placing a BUY order at a remote exchange (tested NC2/Hubur)
works with no local base, storage, or warehouse rental there beforehand -
the default "Storage Location: Warehouse" option is usable without a
prerequisite rental. Storage fee and FX conversion fee size remain unconfirmed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
README keeps the one-paragraph pitch and name origin, adds example
questions and a quick start. The tool index, usage examples, .env table
and layout move to docs/usage.md (now also lists sell tiers, parkcash,
undervalued, ships, lmscan). CLAUDE.md layout line points at it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
CLAUDE.md/docs/roadmap.md/docs/library.md: register tools/shiptrip.py,
tools/shipcost.py, tools/lmscan.py, and puga/shipping.py/puga/contracts.py
per this project's usual pattern of recording new tools where every other
tool is listed.
docs/mechanics.md: new "Gateway (GTW) jump transit time" section - our own
measured GTW segment durations scale ~linearly with parsec distance
(~20 min/parsec across 3 distinct distances), contradicting a 2024
pre-launch forum comment about a possibly-fixed transit time. Also notes
the handbook-confirmed facts not modelled anywhere yet: one-way gateway
links and per-gateway max jump volume.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
network.py: tools/network.py now computes freight cost from the real fuel
model (puga.shipping.round_trip_cost) for direct-jump transfers instead of
the flat 9250 AIC placeholder; gateway transfers or an explicit trip_cost
in the chain YAML still use that as a fallback. puga/network.py's combine()
docstring now flags that its "flows cancel, only freight matters" claim
only holds under a symmetric-price basis (uni30, the default) - under
--basis real it understates the benefit of physical transfer by the
bid-ask spread on the moved quantity.
whatif.py: now charges marginal freight too - only the EXTRA round trips
an addition needs beyond the base's existing traffic, so a small addition
riding along on already-necessary hauls costs ~0, while one that adds
meaningfully to shipped tonnage doesn't get a free pass.
Terminology: "profit/day ÷ capex" was being reported as "ROI/day X%",
which invites reading it as a compoundable rate. It's just 1/payback_days -
there's no reinvestment mechanism and every opportunity here is
capacity/market-limited, so it can't be annualized like a real yield.
Payback in days is now the primary number everywhere (scan.py, simulate.py,
network.py, whatif.py); see docs/decisions.md for the reasoning.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Pulls real player-posted freight jobs from FIO's /localmarket/shipping
endpoints (public, no auth - distinct from the CONTD player-contract
system, which isn't exposed via any API). Ranks by margin after real
one-way fuel cost (puga.shipping.leg_cost), flagging ads that exceed the
account's ship hold or route through an unmeasured gateway.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Calibrated from a controlled slider sweep across 5 real destinations, read
from the live APEX Ship Flight Control DOM. Covers STL cruise, FTL
jumps/charges, gateway hops (measured LOCK fees for ANT<->HRT/BEN), and
per-planet landing fuel. Scoped to this account's one hull/reactor; open
gaps (gravity dependence, hull generalization, gateway one-way links,
per-gateway volume caps) are documented in the module docstring.
tools/shiptrip.py is the simple entry point (ship + from + to, works out
jumps/charges/landing itself); tools/shipcost.py takes explicit physics
params for when the breakdown is already known. puga/route.py now also
resolves CX station codes (ANT, HRT, ...), not just planets/systems - this
was needed for the gateway-aware lookups to work at all.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Was wrong earlier this session claiming demolition gives nothing back.
FIO's /rain/userplanetbuildingreclaimables confirms partial material
return (observed 50-90% depending on material, not a fixed fraction -
looked up live, not approximated). --remove now prices the real
reclaim for anything actually built and nets it against new capex,
so a demolish-and-reinvest question gets the true net cash picture
instead of double-penalizing the removed building.
Used this to check "demolish BHP's HWP, build a 2nd CHP or POL
instead" - both are clearly net losers even counting the ~57,750 AIC
reclaim credit (net capex still positive, and marginal profit is
negative either way) - dominated by just adding the new building
without removing anything, since nothing scarce actually forces the
swap.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Mirrors --add - removes AMOUNT (default: all) of a building from the
copied spec. Used this to check whether BHP's HWP is still worth
running after its price drop (still +19.8k-28.4k/day marginal,
nowhere near a teardown call) without hand-writing two spec files.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Finds manufactured goods currently selling below their implied
production cost (live input cost vs output price) - candidates to buy
and hold for a price-reversion bounce, the mirror of the "is this
worth building" question saturation.py already answers.
Backtested first (326 output tickers, 287 deduplicated dip-onset
episodes across 112 distinct tickers): +3mo forward return mean
+21.3%/median +8.9%/73.2% hit rate at >=10 units/day volume - real
signal, not noise, but only ~0.10-0.24%/day, far below the 14-29%/day
marginal ROI this session's production builds found. Documented
prominently in the tool's own docstring and CLAUDE.md so it doesn't
get mistaken for a primary strategy - it's for excess idle capital
once better-yielding builds are exhausted, not before.
Promoted daily_prices() out of tools/history.py into puga/history.py
so both tools share it, per the self-improve rule.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
sell, chain --consumers, planet, route, whatif, buy, runway and
network existed in the codebase but were never added to the README -
anyone reading it would have missed most of what the toolkit does.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AIC is this player's own home currency, not universal across the
game's regions - keep the README's phrasing currency-agnostic.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Swap the generic example questions for ones that show what actually
happened this session - simulating a proposed plan before spending on
it, and scanning up 25%/day marginal ROIs that were sitting unbuilt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Slogan now frames PuGa as a harness that gets sharper through use, not
just a static set of tools - matches how route/whatif/chain-consumers/
planet.py actually got built this session (noticing repeated ad-hoc
work and promoting it into a tool). Codifies that loop as a standing
instruction so it keeps happening without being asked each time.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ranks every planet with a given extraction factor using FIO's bulk
planet data, pricing founding cost straight from FIO's own
BuildRequirements (real environment-extras materials for that specific
planet, not guessed temperature/pressure thresholds - PRUNplanner's own
calculations don't model those either). Takes --from an existing base
to report jump distance via tools/route.py, since pure-factor ranking
alone is misleading: the best-factor planets found for FEO were all
500-2600+ ly away, confirming Vulcan (merely decent factor, same system
as Deimos) was still the right practical pick.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reverse of the existing make-vs-buy tree: given a ticker, find every
recipe that consumes it as an input, with each output's live market
depth. Answers "is there a deeper downstream product worth chaining
into" without an ad-hoc fio.recipes() scan each time (did this by hand
for BOR, BSE and BBH this session).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Formalizes a pattern repeated by hand all session: copy the current
whole-base plan, add a building/recipe, diff simulate() against the
baseline for the true marginal profit and capex (not PRUNplanner's
plan-level ROI, which divides whole-plan profit by only the new capex
and badly overstates once a base exists).
Also flags any workforce tier the addition newly overloads, distinct
from a pre-existing overload it didn't cause - this is what caught the
PP2/pioneer-housing and 2nd-CHP/pioneer-housing surprises by hand
before this tool existed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PRUNplanner doesn't model travel, so nothing in the project could answer
"how far is planet X" until now. Builds a graph from /systemstars (698
systems, real jump connections + 3D coordinates) and runs Dijkstra
weighted by straight-line ly (what FTL fuel/time actually scale with),
reporting jump count alongside.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- 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>
- puga/network.py + tools/network.py: model a multi-base chain (e.g. mine LST at one base,
ship it, consume it at another). Simulates each base independently, nets a transferred
material's producer-surplus against consumer-need, charges real freight only on what's
moved, cm_free for a founding covered by a Core Module Kit. Worked example in plans/chains/.
- puga/saturation.py: real price-band filtering. FIO's order_book NarrowPriceBandLow/High and
WidePriceBandLow/High match APEX's own displayed Price Band exactly (verified live) - an
order outside it is a stale artifact, not just uncompetitive. Added in_band() and
effective_demand(); effective_supply() gained the same hard band filter alongside its
existing soft vwap-proximity filter (renamed that param mult to free up `band`). Wired into
tools/scan.py stage 2 in place of the raw, unfiltered demand figure. Was flagged as an
unimplemented refinement in saturation-design.md since the original design review.
- tools/sell.py: self-contained tranche split (aggressive tranche capped at a volume quantile,
median normally or 80th pct with --tight when a payment is imminent and stockout risk
outweighs margin; patient tranche priced under the next competitor tier). Instant-bid
comparison moved behind --show-bid (off by default).
- Docs and roadmap updated accordingly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- tools/buy.py: cash-aware shopping list (construction gap vs empire state's built
buildings, plus N days of NET operating stock via simulate() so self-produced
inputs net against consumption), priced at real order-book fill cost, checked
against cash/reserve, flags multi-trip, binary-searches an affordable size when short
- tools/sell.py: where to post an ask vs hitting the bids now, with expected
clear time from 30-day traded-volume percentiles
- docs/library.md: module map and patterns for using puga.* directly instead of
the CLI, plus the "simulate() only knows what the plan lists" gotcha that a
first version of buy.py hit
- tools/simulate.py: fix --no-hq being a no-op; tools/state.py: permits_total_override
for when the in-game HQ screen disagrees with FIO's MaximumPermits
- docs/mechanics.md: HQ display vs PRUNplanner's HQ flag, workforce arrival sources
- CLAUDE.md, docs/roadmap.md updated
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- pyproject.toml + puga/cli.py: `pip install -e .` installs a `puga <tool>` command, so no
venv path is needed (replaces requirements.txt)
- tools/plan_push.py: `pull <uuid>` reads a plan (incl. UI edits) back into a YAML spec;
recipe entries may carry an amount so switched-off recipes round trip; fixes a variable
that overwrote the plan name in build_payload, with a regression test
- README: usage with the puga command, and a section on working on the same plans in the
PRUNplanner UI and from Claude (write, edit in UI, read back)
- CLAUDE.md updated accordingly
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs/mechanics.md: workers are distributed weekly at the population report; a habitation
change or a new production building sends a request that is filled at the report; a new
production building can instantly recruit a small share of the planet's reserve pool
(shown in brackets in APEX). Cites the official dev log #239 and the APEX handbook.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- 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>