orbit/docs/ECONOMY.md

16 KiB
Raw Blame History

Orbit — Trade & Economy (design)

This is the design for the game's trade/economy. It is written to be built on top of what Orbit already has — the jump network, factions, planet classes, settlements, the SHOP button that already exists on the surface deck, and the ship.stats.cargoHold slot that's already reserved for cargo. Nothing here requires new world-structure; it all hangs off the existing deterministic galaxy.

Status: DRAFT / design + data only. The goods catalog and tuning knobs are written to data/ so they can be iterated on. The JS plumbing (HUD, credits, shop logic, generator hook) is intentionally not written yet — the open questions at the bottom of this doc need your call first.


1. Design goals

  1. Trade is a thing the player does, not a thing the player manages. No market simulation, no stock you can manipulate, no persistent shop state. The economy is a landscape you travel through and exploit; it doesn't react to you (or only very lightly, via reputation).
  2. Every world has a market. Every settled planet and station gets a shop of 8–16 goods, priced so that what's local is cheap and what's foreign is dear.
  3. The trip is the reward. A good's profit scales with how far it is from where it's made. Local trade (buy low in-system, sell nearby) works and is safe; long-haul trade pays much more and costs jumps/time/risk. Longer journey ⇒ meaningfully better reward.
  4. Planet type drives production. A terran world makes food; a lava world makes heat and rare metals; a gas giant makes fuel. Opposing types want each other's signature goods — ice wants heat, lava wants cool, terran wants salt/solar, arid wants water/food, gas wants metal, rocky wants fuel.
  5. Local money is possible without crossing the galaxy. You never have to cross the map to make a living; you choose to, for more.
  6. Each planet type yields a gemstone that other types want (a universal premium good, and a later mining/loot hook).

2. The core loop

   LAND on a world            SHIP a good           LAND on another world
      ┌──────────────┐          (jump)               ┌──────────────┐
      │  open SHOP   │  ◄──────────────────────────  │  SELL for    │
      │  BUY cheap   │                               │  CREDITS     │
      │  (local/low) │  ──────────────────────────►  │  (dear/hi)   │
      └──────────────┘   carry in cargoHold          └──────────────┘
             ▲                                                        │
             └────────────────  spend credits on builds/quests ◄──────┘
  • BUY where a good is cheap (it's produced there, or it's local).
  • CARRY it in your cargo hold (ship.stats.cargoHold).
  • SELL where it's dear (it's wanted there, or it's far from its source).
  • PROFIT = sell price − buy price. The gap is bigger the further the good has travelled.
  • SPEND credits on ship builds, quest rewards, upgrades.

You are never balancing a market. You are finding an arbitrage the galaxy already set up and running it. That is the "interact, don't control" requirement made concrete.


3. The eight economic classes

data/goods.json defines them; they map straight onto existing structures:

Class Where it exists Naturally PRODUCES (cheap to buy) Strongly WANTS (dear to sell)
terran terran planets food, water, timber, pharma, biology, gems: Verdant Emerald rare metals, fuel, heat goods, high-end tech
arid arid planets salt, silica, solar, spice, gems: Dune Topaz water, humidity, coolants, food
rocky rocky planets ores, metal, stone, gems: Karnite fuel, food, water, processed tech
gas gas planets fuel, gases, isotopes, gems: Jovian Sapphire solid metal, food, water, hull parts
ice ice planets water, ice, cryo, volatiles, gems: Frost Diamond heat, energy, food, fuel
lava lava planets heat, rare metals, volcanic glass, gems: Magma Ruby coolants, water, food, fuel
station deep-space stations processed/refined goods, services, gems: Aether Crystal raw ore, food, water, bulk fuel
capital a faction's seat luxury, finance, high-end tech, gems: Imperial Diamond everything, at a premium (the universal buyer)

This is the supply/demand web. It's deliberately symmetric on the opposition pairs you asked for:

  • ice ⇄ lava — ice sells cool (coolants, cryo, water) to lava; lava sells heat (thermal batteries, plasma, volcanic energy) to ice.
  • terran ⇄ arid — terran sells water/food to arid; arid sells salt/solar/spice to terran.
  • gas ⇄ rocky — gas sells fuel to rocky; rocky sells metal to gas.

The capital class is the great sink: it buys almost everything at a premium, which is what makes a run to a faction seat lucrative and gives long-haul a ceiling.


4. The gemstone system

One signature gem per class, category gemstone. They are:

  • Universal demand — every gem is wanted by other classes (see each entry's demand), so there is always a premium outlet for a world's signature stone.
  • Higher value band (tier 3–4, baseValue 105–300) than most commodity goods — they are the "jewel" trade that makes even a short trip worthwhile.
  • A future mining/loot hook — right now they are tradeable goods; later they can also drop from mining/loot, as you noted. The catalog already treats them as goods, so a drop source just adds a second way to acquire the same id.

The eight gems: Verdant Emerald (terran), Dune Topaz (arid), Karnite (rocky), Jovian Sapphire (gas), Frost Diamond (ice), Magma Ruby (lava), Aether Crystal (station), Imperial Diamond (capital — the measure by which the rest are weighed).


5. Pricing — fixed per location, distance baked in by regionality

The prices are FIXED per (good, location) pair — decided once at generation, and they do NOT depend on where you got the good or what's in your hold. A good's sell price at a planet is the same whether you mined it two systems away or carried it across the galaxy. This is what makes the shops stateless (no per-player stock bookkeeping) and the whole economy a pure function of the seed.

For a good g at a place p (class C, system S) there are TWO fixed prices, both in credits:

  • BUY price (what you pay p): cheap where C produces the good (its home region), standard where a hub resells it.
  • SELL price (what p pays you): a premium where C wants the good, growing with the hops between S and the good's home region. That hop distance is the "trip is the reward" term.
price(g,p) = baseValue(g)
           × (1 + demandBonus  if p.class is in g.demand)     ← it's wanted here
           × (1 − producerDiscount if p.class is in g.producer)← it's made here (cheaper)
           × (1 + distancePerHop × min(hops, cap))            ← how far from its source
           × jitter
  • baseValue — the good's intrinsic worth (goods.json).

  • demandBonus (+12%/class in the draft) — a place that wants the good pays more.

  • producerDiscount (−35% in the draft) — a place that makes the good sells it cheap. This is the "local = cheap" rule.

  • distance (+22%/hop, capped at 6 hops) — the good is worth more the further it is from the nearest class that produces it. This is the "trip is the reward" rule. It uses the jump graph (hops), which PROJECT_NOTES already establishes as ≈ map distance, so it's consistent with how the galaxy is generated.

  • jitter (±10% seeded) — per-good, per-place wobble so no two shops are identical and no number feels "formulated."

  • Regional availability is how distance is enforced. A good is only offered for sale (cheap) where its class produces it — its HOME region. You must be in that region to buy it cheap; a far region doesn't carry it locally, so the only way to get it there is to carry it. That regional lock — not a live per-trade hop-multiplier — is the distance mechanism.

  • Credits are the go-between — there is no barter. Buying is one credit transaction (you spend); selling is a separate one (you earn). Your profit is simply SELL price (where you sell) − BUY price (where you bought).

Concrete example (draft numbers): Refined Fuel (base 14, gas producer) is on sale on a gas world for 14 × 0.65 ≈ 9. A rocky world 2 hops out, which wants fuel, buys it back for 14 × 1.12 × 1.44 ≈ 22. Profit ≈ 13 credits on a 2-hop run. Same good, 5 hops out: 14 × 1.12 × 2.10 ≈ 33 → profit ≈ 24 — same good, same hold-space, more than double, because it was carried further. Every one of those numbers is fixed for its location; none changes based on your inventory.


6. Shop stock rules (8–16 goods)

Each settlement gets a shop with a stock size from economy.json → shop.stockByKind (colonies/mining 8–12, stations/capitals 10–16). The stock is seeded, not random-at-open, so it's stable per place. The mix follows the class:

  • Locally produced goods appear in higher stock and lower price (the producer rule).
  • Goods the class wants appear at a premium or are absent — a place that needs fuel often won't sell fuel cheap; it sells it dear or not at all, so your imported fuel is what they're buying.
  • Signature gem of the local class is always present (the world's "jewel" is always on the shelf) — guarantees a premium good in every shop and a guaranteed local buy.
  • Stations/capitals carry wider, more exotic stock (they're resellers/finance), so they list more tier-3/4 and tech/luxury goods than a mining station does.

The exact pick is seed → (place class, distance profile) → weighted sample of 8–16 goods, weighted by the good's weight field and its relevance to the class. It's a pure function, so it's deterministic and re-derivable (see §8).


7. How it hangs off the existing galaxy

Existing system Economy uses it
Jump network / hops (Galaxy.js, data/galaxy.json) the distance term in pricing — hops ≈ map distance, per PROJECT_NOTES. Longer path ⇒ higher sell price.
Faction territories (data/factions.json) capital class = faction seats (universal premium buyers). Faction tariff/patron hooks (economy.json → faction).
Planet classes (data/planets.json) the six world classes + their producer/demand roles.
Settlements (data/settlements.json, every planet settled) each settlement = a shop. settledKindByClass already tells us the shop's "personality."
Stations (data/stations.json) the station class (refiner/reseller, wider stock).
Zones (data/galaxy.json → zones) optional regional price bands — far zones (Far Reach / Outer Verge) run wilder prices.
Reputation (js/reputation/Reputation.js) standing with a place's owner faction nudges its prices (hook, economy.json → reputation).
SHOP button (surface deck, data/actionbar.json) the existing surface shop action becomes the buy/sell UI.
cargoHold (data/ship.json) your carrying capacity for goods.
Credits (builds, quest rewards already reference credits) the currency. HUD next to minerals.

Determinism: all of this is a pure function of the seed. Shop stock and prices are derived from (seed, placeId, economyVersion) exactly like system contents are derived today (Rng.derive(seed, 'economy', …)). That means lazy and eager generation agree, saves stay tiny (we store only your credits + your carried goods, never the shops), and the economy can never drift or corrupt.


8. The credits currency (decided)

  • Displayed next to the minerals HUD (upper-right). Minerals are bulk matter; credits are trade value. They coexist because they're different resources: you mine minerals, you earn credits.
  • You start at 0 (economy.json → credits.startingCredits).
  • Earning credits = selling goods + mission/quest credit rewards. Spending = builds, upgrades, and buying goods.
  • Trading is LOCKED until the third story quest (not written yet). Until then the economy exists in the world (shops, prices, gems) but the player's buy/sell UI is gated. Initial credits come from mission/quest rewards (the quest reward object already has a credits field — data/quests.json → reward.credits, surfaced in _questRewardLabel) and, later, from mining-laser upgrades that periodically yield gemstones from asteroids — the gems are already tradeable goods, so selling them is the credit faucet. (Decision #1.)

9. Interact, don't control (your requirement)

What the player does: pick goods, buy low, travel, sell high, spend. Nothing else.

What the player cannot do: change prices, stock a shop, corner a market, set a price, or make a good appear that isn't produced. The economy is a fixed landscape generated from the seed. The only "control" surface that exists is light and comes from your existing systems: reputation slightly moves a place's prices, and (later) factions may patron/tax specific goods. That's flavor, not management.


10. Files

File What it is
data/goods.json 114 goods across 9 categories, each with producer/demand/weight/tier/value/flavor. The catalog.
data/economy.json All the tuning knobs (shop size, price multipliers, distance curve, cargo, credits start, trading lock, reputation/faction hooks).
data/creditshud.json The CREDITS readout styling (upper-right, stacked above the minerals HUD).
js/economy/Economy.js The pure price/stock engine (buyPrice/sellPrice/shopFor) + the buildEconomy generation pass (classBySystem + producerHops).
js/ui/CreditsHud.js The CREDITS line, upper-right, above the minerals HUD (count-up on change).
js/galaxy/Galaxy.js Calls buildEconomy at the end of generation (the galaxy.economy seam).
js/entities/Ship.js ship.credits + canAfford/addCredits/spendCredits/setCredits (mirrors the minerals seam).
js/save/SaveData.js Credits captured/restored with the ship record.
js/scenes/GameScene.js Builds the credits HUD (minerals HUD shifted below it), refreshes it on credit changes, pays quest reward.credits on claim.
dev/economy.test.mjs The engine's invariants: determinism, fixed prices, the loop, distance, shop floor, hubs.
docs/ECONOMY.md This document.

Implemented this pass: the price/stock engine, the generation pass, credits state + save + HUD, and quest-credit payout (the reward seam is live; no first-pass quest pays credits yet). The shop buy/sell UI panel and the goods spritesheet remain deferred — trading is locked until the third story quest.


11. Decisions (locked in this pass)

  1. Initial credits — trading is gated until the third story quest; first credits come from mission/quest rewards (the reward.credits field already exists in data/quests.json) and, later, asteroid gemstone mining (mining-laser upgrades yield gems you sell).
  2. Cargo weight — 1 hold unit per good (simplest to manage; cargoHold = 100).
  3. Reputation & faction prices — deferred (the hooks stay in economy.json; wired later).
  4. Zones / regional bands — hop-distance only (no extra per-zone price swing).
  5. Station vs. capital — stations = wide variety (resellers), capitals = wide premium (the universal buyer). Locked.
  6. Gems — purely tradeable this pass (the asteroid mining faucet is later).
  7. Catalog — the 114-goods list is good for the first pass.

Model confirmations (the two "before you code" points):

  • Prices are fixed per (good, location) — independent of where you got the good or your hold. Distance is enforced by regional availability (goods only cheap in their home region), not a live per-trade hop-multiplier.
  • Credits are the go-between — buy and sell are two separate credit transactions; there is no good-for-good barter.