400 lines
23 KiB
Markdown
400 lines
23 KiB
Markdown
# Orbit — Trade & Economy (design)
|
||
|
||
This is the design for the game's trade/economy. It hangs off the deterministic
|
||
galaxy Orbit already has — the jump network, factions, planet classes, settlements,
|
||
the SHOP button on the surface deck, and the `ship.stats.cargoHold` slot — so nothing
|
||
here requires new world-structure.
|
||
|
||
**Status: MODEL v2 — IMPLEMENTED.** The goods catalog (`data/goods.json`) and the
|
||
tuning knobs (`data/economy.json`) are the source of truth; the engine
|
||
(`js/economy/Economy.js`), the batch clock (`js/economy/Stock.js`), and the shop
|
||
UI (`js/ui/shop/*`) are built. Model v2 — **ONE WORLD, ONE GOOD** — was locked in
|
||
after v1's price model (producer/demand class discounts) turned out to make
|
||
**distance a weak signal** and, worse, left a **buy→sell→buyback infinite-credit
|
||
loop** at any shop. v2's invariants are enforced by `dev/economy.test.mjs`.
|
||
|
||
---
|
||
|
||
## 1. Design goals (v2)
|
||
|
||
1. **Distance is the dominant factor in profit.** A good's sell price grows with
|
||
its hop distance from its ORIGIN (the one world that makes it). Longer journey
|
||
⇒ meaningfully better reward. This is the business model.
|
||
2. **Planet type is a secondary modifier.** A world's class contributes a small
|
||
demand bonus (the class *wants* the good) and drives the THEMATIC FIT of the
|
||
origin assignment (a gas world is where fuel is made) — but it never overrides
|
||
distance.
|
||
3. **Goods are regionally produced — one world, one good.** Every tradeable good
|
||
has exactly ONE origin system (a seeded strict 1:1 assignment); every settled
|
||
system sells exactly ONE product — its signature good, cheap, in a daily batch.
|
||
The catalog's remainder is the **hub pool**, resold by stations/capitals at
|
||
standard price.
|
||
4. **Same-system trading earns money, but it is not the business model.** A
|
||
buy-local/sell-local loop in one system is *bounded* by the daily batch
|
||
(a few units a day) — pocket change. Long-haul SELLs are unlimited: the market
|
||
is a sink.
|
||
5. **Interact, don't control.** No market simulation, no stock you can
|
||
manipulate, no persistent shop state beyond the day's remaining batch. The
|
||
economy is a landscape you travel through and exploit.
|
||
6. **Local money is possible without crossing the galaxy.** You never *have* to
|
||
cross the map to make a living; you choose to, for much more.
|
||
|
||
---
|
||
|
||
## 2. The core loop
|
||
|
||
```
|
||
LAND on the ORIGIN world SHIP a good LAND far away
|
||
┌──────────────┐ (jump) ┌──────────────┐
|
||
│ open SHOP │ ◄──────────────────────────────── │ SELL for │
|
||
│ BUY the │ │ CREDITS │
|
||
│ signature │ ─────────────────────────────────► │ (dear — it │
|
||
│ (half price,│ carry in cargoHold │ is far from │
|
||
│ daily batch│ │ its home) │
|
||
└──────────────┘ └──────────────┘
|
||
▲ │
|
||
└──────────────── spend credits on builds/quests ◄──────┘
|
||
```
|
||
|
||
- **BUY** a world's signature good at its ORIGIN — the only place it is cheap
|
||
(half of base value), limited to the day's batch.
|
||
- **CARRY** it in your cargo hold (`ship.stats.cargoHold`).
|
||
- **SELL** it wherever a class wants it — the price rises with hop distance
|
||
from the origin (the banded term). The further you carry it, the more it's
|
||
worth.
|
||
- **PROFIT** = sell price − buy price. The gap is *dominated* by distance.
|
||
- **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.
|
||
|
||
---
|
||
|
||
## 3. The eight economic classes
|
||
|
||
`data/goods.json` defines them; they map straight onto existing structures:
|
||
|
||
| Class | Where it exists | Thematic producers (origin fit) | Strongly WANTS (+10% demand bonus) |
|
||
|---|---|---|---|
|
||
| **terran** | `terran` planets | food, water, timber, pharma, biology | rare metals, fuel, heat goods, high-end tech |
|
||
| **arid** | `arid` planets | salt, silica, solar, spice | water, humidity, coolants, food |
|
||
| **rocky** | `rocky` planets | ores, metal, stone | fuel, food, water, processed tech |
|
||
| **gas** | `gas` planets | fuel, gases, isotopes | solid metal, food, water, hull parts |
|
||
| **ice** | `ice` planets | water, ice, cryo, volatiles | heat, energy, food, fuel |
|
||
| **lava** | `lava` planets | heat, rare metals, volcanic glass | coolants, water, food, fuel |
|
||
| **station** | deep-space stations | (hubs — resellers, not producers) | raw ore, food, water, bulk fuel |
|
||
| **capital** | a faction's seat | (hubs — resellers, not producers) | everything, at a premium (the universal buyer) |
|
||
|
||
In v2 the classes play TWO roles, both secondary to distance:
|
||
|
||
- **Origin fit** — the seeded origin assignment prefers systems whose classes
|
||
*produce* the good (a gas world gets fuel, an ice world gets cryo). The
|
||
`producer` list is the fit signal; where the catalog leaves no fitting
|
||
system, the assignment falls back (a few goods are "anywhere" — the
|
||
deterministic seed still fixes exactly where).
|
||
- **Demand** — a class in the good's `demand` list pays a +10% bonus
|
||
(`economy.json → pricing.demandBonus`). The `capital` class wants everything:
|
||
the great sink, the universal buyer, the long-haul ceiling.
|
||
|
||
The opposition pairs (ice ⇄ lava, terran ⇄ arid, gas ⇄ rocky) still give the
|
||
catalog its flavor and keep the demand web symmetric — but the *price* no longer
|
||
derives from "produces here, wants there"; it derives from **how far from the
|
||
origin you sell it**.
|
||
|
||
---
|
||
|
||
## 4. One world, one good (the v2 identity)
|
||
|
||
v1 gave every shop a floor of 8–16 class-appropriate goods and a per-class
|
||
signature gem. v2 replaces both with a single rule:
|
||
|
||
> **Every settled system has exactly ONE signature good — and every signature
|
||
> good has exactly ONE origin system.**
|
||
|
||
- **The assignment is a seeded strict bijection** (the `origin-assign` pass in
|
||
`Economy.buildEconomy`, greedy most-constrained-first with zone→tier fit and
|
||
a seeded tie-break). 78 settled systems ↔ 78 signature goods out of the
|
||
114-goods catalog; the 12 jump-gate-only (barren) systems host nothing —
|
||
there is no world to sell from.
|
||
- **The catalog's remainder (36 goods) is the HUB POOL** — no origin world;
|
||
stations and capitals resell a seeded selection of it (standard price, wide
|
||
floors for variety).
|
||
- **Thematic fit is near-total** — the origin assignment prefers systems whose
|
||
classes produce the good (in the test seed, 78/78 fit; the fallback only
|
||
fires where the catalog leaves a good with no fitting system).
|
||
- **Gems are no longer special.** The eight gemstones are still in the catalog
|
||
(and can be a world's signature, earning half-price origin + the demand
|
||
bonus), but there is no more "one per class, always on every shelf" rule.
|
||
|
||
Why: one product per world makes each place *legible* — you can name a system
|
||
by what it makes ("the fuel world"), the buy menu is trivially small, and the
|
||
only economic question a player has to answer is **where does it go?**
|
||
|
||
---
|
||
|
||
## 5. Pricing — fixed per location, distance dominant
|
||
|
||
**Prices are FIXED per (good, location) pair — a pure function of the seed.**
|
||
They do not depend on where you got the good or what's in your hold. The shops
|
||
are stateless; the whole economy re-derives from `(seed, placeId, economyVersion)`.
|
||
The only state is the day's remaining batch (see §5.3).
|
||
|
||
For a good `g` at a system `S` (class `C`) there are TWO fixed prices:
|
||
|
||
### 5.1 The BUY price (what you pay)
|
||
|
||
```
|
||
origin sale (S is g's ORIGIN) = baseValue × (1 − originDiscount) × jitter
|
||
hub resale (hub-pool good, S is a hub) = baseValue × hubPriceFactor × jitter
|
||
everywhere else = NOT ON SALE (buyPrice = 0)
|
||
```
|
||
|
||
- **originDiscount** = 50% — the origin sells at ~half base. **The origin is the
|
||
galaxy's only cheap source of its good.**
|
||
- **hubPriceFactor** = 1.0 — hubs resell at standard (never below the origin —
|
||
there is exactly one cheap place).
|
||
- A world's floor is *only* its signature (its one product); a hub's floor is
|
||
the hub-pool selection **plus** its own system's signature (same day's batch,
|
||
same source — a station in the fuel system resells fuel at the origin price).
|
||
|
||
### 5.2 The SELL price (what you get paid)
|
||
|
||
```
|
||
sell(g, C, S) = baseValue(g)
|
||
× distanceBand(hops(S → origin(g))) ← THE dominant term
|
||
× (1 + demandBonus if C wants g) ← the secondary modifier
|
||
× jitter
|
||
, CAPPED at the shop's own BUY price for g ← the anti-loop rule
|
||
```
|
||
|
||
- **distanceBands** (banded, not linear — the trip pays in steps):
|
||
|
||
| hops from origin | 0 | 2 | 4 | 7 | 11+ |
|
||
|---|---|---|---|---|---|
|
||
| factor | ×0.75 | ×1.15 | ×1.85 | ×2.75 | ×3.75 |
|
||
| profit/unit (≈) | 0 → 0.25× | 0.65× | 1.35× | 2.25× | 3.25× |
|
||
|
||
Profit is in base-value multiples (≈ (factor − 0.5) × base, before the
|
||
+10% demand bonus and jitter); 0 hops nets ~0 — the anti-loop cap.
|
||
HALVED-MARGIN curve (f′ = 0.25 + f/2 on the original ×1.0 / ×1.8 / ×3.2 /
|
||
×5.0 / ×7.0): every band's per-unit profit is half the original, distance
|
||
stays the dominant term.
|
||
The jump tree's diameter (~17 hops) is the natural ceiling. No cap: the band
|
||
with the largest `minHops ≤ hops` wins.
|
||
- **demandBonus** = +10% when the class wants the good (`g.demand` contains `C`)
|
||
— planet type's entire price role.
|
||
- **jitter** = ±10% seeded per (good, place) — no two shops feel formulated.
|
||
- **THE ANTI-LOOP RULE** — a shop's sell price for a good is **capped at its own
|
||
buy price for that good**. No shop may ever be a positive same-shop spread.
|
||
This is the v1 exploit's tombstone (buy→sell→buyback at one shop used to print
|
||
infinite credits); `dev/economy.test.mjs` sweeps every system × class and
|
||
asserts **zero** positive same-shop spreads.
|
||
|
||
### 5.3 The daily batch (the economy's one stateful seam)
|
||
|
||
Each origin world's signature good comes to market as a **daily batch of 6
|
||
units** (`economy.json → stock`):
|
||
|
||
- `dayMs = 3 600 000` (an hour of wall-clock), `epoch = 2026-01-01T00:00:00Z` —
|
||
the batch regenerates while the player is away.
|
||
- **Buy side only** is batch-limited. The SELL side is unlimited (the market is
|
||
a sink — selling never "uses up" anything).
|
||
- This bounds the same-system loop: in the origin system you can buy at most 6
|
||
units a day at half price and flip them to the same system's station — a
|
||
*bounded pocket change*, not infinite money. Long-haul selling stays
|
||
unlimited, so the incentive clearly favors the trip.
|
||
- **Save state** is tiny and save-clone-safe:
|
||
`economyStock = { [systemId]: { day, left } }` — one integer pair per origin
|
||
the player has drawn from (`js/economy/Stock.js` is a pure module; `spend()`
|
||
returns a NEW map, `GameScene.spendStock` swaps the reference;
|
||
`SaveData.js` captures/restores/cleans it).
|
||
|
||
### 5.4 Credits are the go-between — no barter
|
||
|
||
Buying spends credits; selling earns them; profit = sell − buy. All fixed per
|
||
location; none changes based on your inventory.
|
||
|
||
**Concrete example (real numbers, test seed):** `Cryo Serum` (base 66) is
|
||
Keth's signature (an ice world — thematic fit). At Keth it sells for
|
||
`66 × 0.5 ≈ 31` (6 units a day). A rocky world 2 hops out, which wants cryo,
|
||
pays `66 × 1.15 × 1.1 ≈ 84` → **≈ 53/unit profit on a 2-hop run.** Seven hops
|
||
out: `66 × 2.75 × 1.1 ≈ 200` → **≈ 169/unit** — same hold-space, three times the
|
||
money, because it was carried further. The same-system flip (Keth world → Keth
|
||
station) nets ~0 per unit (the anti-loop cap) and is limited to 6 units a day —
|
||
pocket change by design.
|
||
|
||
---
|
||
|
||
## 6. Shop floors (v2)
|
||
|
||
| Shop | Floor |
|
||
|---|---|
|
||
| **A world** | EXACTLY its signature good — one row, origin price, the "N left" batch strip. |
|
||
| **A station** | a seeded 10–16 from the hub pool (standard price) + its own system's signature (origin price, batch-limited). |
|
||
| **A capital** | a seeded 12–16 from the hub pool (standard price) + its own system's signature. Its SELL list is the **entire catalog** (the universal sink). |
|
||
|
||
Every world's BUYBACK list is its class's wanted goods (the `demand` rule) — a
|
||
place that wants fuel buys back fuel; a capital buys back everything.
|
||
|
||
The scout panel (tap a buy row — `js/ui/shop/ScoutPanel.js` + `TradeScout.js`)
|
||
reports the good's **MADE IN** (its origin system, or "HUB RESALE"), its
|
||
**SOUGHT ON** classes, and the top-3 charted exits ranked by sell price — with
|
||
tap-to-**SET DESTINATION** on each exit. That is the player's strategy tool:
|
||
the economy is a landscape, and this is the map of it.
|
||
|
||
---
|
||
|
||
## 7. How it hangs off the existing galaxy
|
||
|
||
| Existing system | Economy uses it |
|
||
|---|---|
|
||
| **Jump network / hops** (`Galaxy.js`, `data/galaxy.json`) | the distance bands in pricing — hops ≈ map distance. 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 — origin-fit (assignment) + demand bonus (pricing). |
|
||
| **Settlements** (`data/settlements.json`, every planet settled) | each settled system is a shop; the origin assignment's candidate pool. |
|
||
| **Stations** (`data/stations.json`) | the `station` class (hub-pool resellers, wider floors). |
|
||
| **Zones** (`data/galaxy.json → zones`) | the origin assignment's tie-break (zone → tier fit). |
|
||
| **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 opens 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** (lazy and eager
|
||
generation agree; saves stay tiny — we store only your credits, your carried
|
||
goods, and the batch remainders, never the shops).
|
||
|
||
---
|
||
|
||
## 8. The credits currency (decided)
|
||
|
||
- **Displayed next to the minerals HUD** (upper-right). Minerals are *bulk
|
||
matter*; credits are *trade value*.
|
||
- **You start at 0** (`economy.json → credits.startingCredits`).
|
||
- **Earning credits** = selling goods + mission/quest credit rewards + the
|
||
bounded local batch loop. **Spending** = builds, upgrades, and buying goods.
|
||
- **The cargo bays are the first credit sink** — the hold expands 4 → 8 → 12 → 16
|
||
through three ship builds paid in credits + minerals
|
||
(`data/builds.json → cargo-bay-8/12/16`: 500 cr + 200 min / 800 cr + 350 min /
|
||
1200 cr + 500 min).
|
||
- **Trading is LOCKED until the third story quest.** Until then the economy
|
||
exists in the world (shops, prices, batches) but the buy/sell UI is gated.
|
||
|
||
---
|
||
|
||
## 9. Interact, don't control (your requirement)
|
||
|
||
What the player **does**: pick goods, buy at the origin, travel, sell far,
|
||
spend. Nothing else.
|
||
|
||
What the player **cannot do**: change prices, stock a shop, corner a market,
|
||
or make a good appear that isn't made. The only "control" surface that exists
|
||
is *light* and comes from the existing systems: reputation slightly moves a
|
||
place's prices, and (later) factions may patron/tax specific goods. That's
|
||
flavor, not management. The daily batch is the one place the economy has a
|
||
memory — and it only ever *refills* in the player's favor.
|
||
|
||
---
|
||
|
||
## 10. Files (v2)
|
||
|
||
| File | What it is |
|
||
|---|---|
|
||
| `data/goods.json` | **114 goods** across 9 categories, each with producer/demand/weight/tier/value/flavor. The catalog (unchanged by v2 — the assignment works on it). |
|
||
| `data/economy.json` | All the tuning knobs (model v2: originDiscount, hubPriceFactor, demandBonus, distanceBands, jitter, the daily batch, cargo, credits). `version: 2`. |
|
||
| `js/economy/Economy.js` | The pure price/stock engine: `originOf`/`signatureOf`/`isHubPool`, `buyPrice`/`sellPrice`/`shopFor`, `tradePreview` (with the stock-cap backstop), and the `buildEconomy` generation pass (classBySystem + the strict signatures/origins/hubPool assignment + producerHops). |
|
||
| `js/economy/Stock.js` | **NEW (v2)** — the time-gated daily batch: `dailyBatch`, `economyDay`, `batchRemaining`, `spend` (pure, reference-swap; state = `{ [systemId]: { day, left } }`). |
|
||
| `js/economy/TradeScout.js` | **The strategy layer** (pure): `scoutGood()` — the good's origin (MADE IN), demand, and the top-N charted exits ranked by sell price. |
|
||
| `js/economy/CargoHold.js` | The trade-goods hold — a pure, framework-free class. One good per bay. |
|
||
| `js/ui/shop/TradeGrid.js` | The shop's SELL side: a 4×4 grid of the player's cargo bays. |
|
||
| `js/ui/shop/BuyList.js` | The shop's BUY side: the floor as wide rows — icon + name + flavor + price + stepper, plus the **"N left" / "SOLD OUT" batch strip** (v2) and the stepper clamp at the remaining batch. |
|
||
| `js/ui/shop/ScoutPanel.js` | **The goods scout** (v2): the panel that replaces the cargo grid when a buy row is tapped — MADE IN / SOUGHT ON / BEST EXITS, with tap-to-set-destination rows. |
|
||
| `js/ui/shop/ShopView.js` | The buy/sell (and buyback) screen: the two grids + the scout + a live NET preview + Confirm/Clear. Owns the batch wiring (`stockRemaining`/`stockCapsFor`, `confirm()` spends the batch, `_afterTrade` re-renders the "N left" strip). |
|
||
| `js/galaxy/Galaxy.js` | Calls `buildEconomy` at the end of generation (the `galaxy.economy` seam). |
|
||
| `js/entities/Ship.js` | `ship.credits` + the ledger + `ship.cargo` (the `CargoHold`). |
|
||
| `js/save/SaveData.js` | Credits + cargo + **`economyStock`** (the batch remainders) captured/restored; `cleanEconomyStock` normalizes the shape; a new run clears it. |
|
||
| `js/scenes/GameScene.js` | Owns `economyStock` (registry-backed), `spendStock()` (the reference swap), the credits HUD, quest payouts. |
|
||
| `js/scenes/SurfaceScene.js` | Opens the `ShopView` over the shop clip; the SET DESTINATION offer from a scout exit. |
|
||
| `data/builds.json` | The **cargo bays** (`cargo-bay-8/12/16`) — the first credit sink (credits + minerals). |
|
||
| `dev/economy.test.mjs` | The engine's invariants: the strict 1:1 assignment, hub-pool completeness, determinism, origin-only sales, the loop, the distance bands, the shop floors, and the **anti-loop sweep** (zero positive same-shop spreads, every system × class). |
|
||
| `dev/cargo.test.mjs` | The hold's rules + the `tradePreview` caps (credits/cargo/**stock**, sell→buy in one confirm, the buyback exemption from the batch). |
|
||
| `dev/shop-ui.test.mjs` | The shop screen's LOGIC headlessly: sell, buy, the **daily batch** ("N left" → clamp → SOLD OUT → engine backstop), scroll/clip, the two caps, the buyback, the scout panel + set-destination. |
|
||
| `dev/trade-scout.test.mjs` | The scout report's invariants (origin field, exits ranked, determinism). |
|
||
| `dev/saves.test.mjs` | The save bank + the `economyStock` round-trip (capture/restore/reset/legacy/malformed). |
|
||
|
||
---
|
||
|
||
## 11. Decisions (locked)
|
||
|
||
### v2 decisions (this pass)
|
||
|
||
1. **ONE WORLD, ONE GOOD** — every tradeable good has exactly one origin
|
||
system; every settled system sells exactly one product (its signature).
|
||
Strict seeded 1:1; barren (jump-gate-only) systems host nothing.
|
||
2. **Distance is the dominant profit factor** — banded hops from the origin
|
||
(×0.75 / ×1.15 / ×1.85 / ×2.75 / ×3.75 — the halved-margin curve,
|
||
f′ = 0.25 + f/2), no cap (the tree's diameter is the ceiling).
|
||
Planet type contributes at most +10% (demand bonus).
|
||
3. **The daily batch bounds the local loop** — 6 units per origin per
|
||
wall-clock day (1h); sell side unlimited. Same-system trading is bounded
|
||
pocket change, never infinite money.
|
||
4. **The anti-loop cap** — sell price ≤ own buy price at the same shop; no shop
|
||
may ever be a positive same-shop spread (the v1 exploit, dead).
|
||
5. **The catalog is reused as-is** (114 goods) — the 36 remainder form the hub
|
||
pool; no new goods, no per-class gems.
|
||
|
||
### v1 decisions (still in force)
|
||
|
||
1. **Initial credits** — trading gated until the third story quest; first
|
||
credits from quest rewards + (later) asteroid gemstone mining.
|
||
2. **Cargo** — fixed bays, one good each; 4 unlocked → 16-slot grid.
|
||
3. **Reputation & faction prices** — deferred (hooks in `economy.json`).
|
||
4. **Zones** — hop-distance only (no per-zone price swing).
|
||
5. **Station vs. capital** — stations = wide variety (resellers); capitals =
|
||
the universal buyer.
|
||
6. **Credits are the go-between** — no barter.
|
||
|
||
---
|
||
|
||
## 12. The shop screen (player-facing UI)
|
||
|
||
Where the loop is actually played. You open it from the surface deck's **SHOP**
|
||
button; the screen sits **over** the shop clip. One component —
|
||
`js/ui/shop/ShopView.js` — composing a `TradeGrid` (the SELL side) + a `BuyList`
|
||
(the BUY side) + the `ScoutPanel` (the goods scout).
|
||
|
||
**Entry state (showcase the clip).** First open shows **only the tab sub-bar**
|
||
(`[ BUY/SELL | BUYBACK ] │ [ CLEAR | CONFIRM ] │ NET 0 cr`) above the command
|
||
deck. Clicking **Buy/Sell** reveals the trade panel: the **SELL grid** (your
|
||
cargo bays, tap to check off) and the **BUY list** side by side, spanning the
|
||
screen.
|
||
|
||
**BUY list (v2).** A world's list is ONE wide row — its signature good: icon,
|
||
name, one-line flavor, the **origin price**, a **"N left" strip** (the day's
|
||
remaining batch — the stepper can't exceed it; 0 reads **SOLD OUT** in magenta),
|
||
and the −/qty/+ stepper. A hub's list is its 10–16 hub-pool rows (unlimited —
|
||
no strip) + its own signature (with the strip). Tapping a row's body (not the
|
||
stepper) opens the **TRADE SCOUT**: MADE IN (the origin system, or HUB RESALE),
|
||
SOUGHT ON (the demand classes), and the **BEST EXITS** ranked by sell price —
|
||
tapping an exit row offers to **SET DESTINATION** to that system. Tapping
|
||
anywhere else closes the scout.
|
||
|
||
**NET readout** — the live credit delta on the sub-bar (green up, red spend,
|
||
dim at zero), the same `preview()` the Confirm button is gated on. **SELL → BUY
|
||
is atomic** — one Confirm executes credits `+ sale − buy`, cargo `− sold +
|
||
bought`. **BUYBACK** (second tab) lists what you sold this visit, buyable back
|
||
at the sale price — a *neutral* undo (sell→buyback nets zero; it never draws on
|
||
the batch).
|
||
|
||
**Determinism + the one stateful seam.** Prices and floors are **fixed** per
|
||
(good, location) — pure functions of the seed; reopening a shop re-derives the
|
||
same floor and prices. The **only** state is the origin's remaining daily batch
|
||
(`economyStock` — save/restore in `SaveData.js`), and it only ever *regenerates*
|
||
while you're away. So the economy stays "interact, don't control": you trade
|
||
against a fixed landscape, and the only clock in it runs in your favor.
|
||
|
||
**Tested headlessly** (`dev/shop-ui.test.mjs`, `dev/economy.test.mjs`,
|
||
`dev/cargo.test.mjs`): the full logic — sell, buy, the daily batch (clamp →
|
||
SOLD OUT → engine backstop), the two caps, the buyback neutral-undo, the scout
|
||
+ set-destination, the anti-loop sweep, and the save round-trip of the batch.
|
||
Pixel layout and the slide-in animation are verified in the browser.
|