270 lines
16 KiB
Markdown
270 lines
16 KiB
Markdown
# 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.
|