- 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>
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>
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>
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>
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>
- 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>