Add design spec for probability-based space trading sim

This commit is contained in:
2026-06-12 13:26:23 +00:00
commit 7358ee322e
@@ -0,0 +1,161 @@
# Probability — design spec
A single-player space trading simulation in the browser. Inspired by Space Rangers.
The core conceit: NPC ships in systems the player cannot see exist as probability
clouds, not concrete positions. Entering a system collapses the clouds in it.
Probability is visible gameplay, not just an engine optimization: the player sees
clouds, stale prices, and aging intel, and trades on imperfect information — exactly
like every NPC does.
## Goals
- A complete, playable trading loop: fly, dock, buy low, sell high, reach a credit target.
- The probability mechanic is visible and legible: clouds on the map, intel that ages.
- A living galaxy of 100200 NPC traders at near-zero simulation cost while unobserved.
- Deterministic, headless-testable simulation core.
## Non-goals (v1)
- Combat, named pirate units, missions, factions, sound, save/load.
(Pirates exist only as hazard fields. localStorage save is a v1.5 candidate.)
- Free 2D flight. All movement is graph-based, including inside systems.
- Multiplayer, server, persistence beyond a session.
## World model
- **Galaxy**: an undirected graph of ~25 systems connected by jump lanes, generated
from a seed with a roughly planar layout.
- **System contents**: one star, 13 planets, one jump gate per lane. Inside a system,
ships move along straight legs between gate(s) and planets — still a graph
(nodes: gates, planets; edges: legs).
- **Commodities**: 6 goods. Each planet produces some and consumes others at fixed
rates. Price is a deterministic curve over current stock (low stock → high price).
- **Hazard fields**: each lane has a pirate danger rating in [0, 1]. Danger feeds the
probabilities of bad branches for ships using that lane: delay, cargo loss, or
destruction. No individual pirate units exist in v1.
## NPC model
NPC traders (100200) alternate between two states:
- **Docked (concrete decision point)**: the NPC picks the best arbitrage opportunity
it knows about and plans a route. Decisions use *stale news*, not live state:
price snapshots propagate through the galaxy at a fixed speed (a few lanes per
game-day). This bounds decision cascades — an NPC's plan depends only on committed
history at planning time — and makes NPCs fallible the same way the player is.
- **In transit (branch tree)**: the plan is a tree of possible histories. Branch
points: route choice (weighted by NPC caution vs. lane danger), hazard outcomes
per lane (safe / delayed / cargo lost / destroyed), and fuzzy arrival times
(a time distribution per leg whose spread grows with legs traveled since the NPC
was last observed).
**Hidden-variable determinism**: every NPC has a seed. Every branch outcome is drawn
deterministically from (seed, branch id). Outcomes are therefore fixed-but-unknown
from the moment a plan is made; observation merely reveals them. This guarantees
observation-order consistency (seeing a market first or the ship first always yields
the same history) and makes the sim reproducible for tests.
## Probability clouds
Because movement is graph-based, a cloud is one-dimensional: a weighted set of
lane/leg segments, derived analytically from the plan and elapsed time (no per-tick
integration). Properties:
- Total mass is 1 across all branches/segments (invariant, asserted).
- Spread grows with time/legs since last observation.
- Observation that excludes branches (e.g., "the ore never arrived at B, so the
safe-and-on-time branch is dead") renormalizes the remainder — observing a market
sharpens the cloud of every ship entangled with it.
## Markets and probabilistic trades
Markets in unobserved systems hold **pending probabilistic transactions**
("+50 ore, 85% likely, arriving t=1018") instead of applying trades on a schedule.
Anything needing a price meanwhile (NPC planning, news snapshots) uses the
expected (probability-weighted) market state. The concrete price exists only after
resolution.
## Observation and collapse
The player's current system is a **classical bubble** — fully concrete and simulated
in real time. All conversion happens at its boundary:
- **Player enters a system**: every cloud with mass inside is sampled (from its seed):
the ship spawns concrete, or is excluded and its remaining cloud renormalized
outside.
- **While the player stays**: an **arrival queue** holds potential arrival events for
this system — every branch in every NPC tree that routes through it, plus new
plans made elsewhere as they are created. When an event comes due, roll it: ship
materializes at the gate, or the branch silently didn't happen. Traffic volume
emerges from trade-route topology; nothing is scripted.
- **Docking at a planet**: resolve that market's pending transactions in
chronological order → concrete stock and price.
- **A concrete ship jumps out / the player leaves**: concrete state re-fuzzes into a
tight cloud anchored at the last known position and time, spreading thereafter.
## Time
Real-time with pause and ×1 / ×4 / ×16 speed. Internally: a global event queue
(arrivals, plan completions, news propagation) plus a fixed sim tick (~10 Hz,
scaled by game speed) for in-system ship motion. Clouds cost nothing between
observations.
## Player gameplay (v1)
- Fly between systems via gates, dock at planets, buy/sell on a market screen,
manage credits and cargo capacity. Win: reach a credit target (e.g., 100,000 cr).
- Player ship is subject to the same lane hazards (cargo loss / delay; destruction
= game over).
- **Galaxy map intel layer**:
- Lanes tinted by hazard rating.
- Last-known prices per planet, displayed with their age ("ore 12 cr — 3 days old").
The player's price knowledge updates by visiting or as news reaches their location.
- Probability clouds rendered as glowing smears along lanes — only for ships the
player has personally observed at least once.
## Rendering
- **PixiJS v8** on a single canvas, WebGL backend.
- **Two views**: galaxy map (systems = bright circles, lanes = dim lines, clouds =
bloomed smears) and system view (star = large bright circle, planets = circles,
ships = triangles sliding along legs, gates marked at the rim).
- **Neon look**: `AdvancedBloomFilter` (pixi-filters) over the world container;
simple flat-color geometry underneath.
- **UI as DOM overlay** (not canvas): market table, cargo panel, time controls,
hover tooltips.
## Architecture
```
src/
sim/ pure JS, zero rendering imports; deterministic given a seed
galaxy generation, economy, news propagation, NPC planning,
branch trees, clouds, observation/collapse, event queue, seeded RNG
render/ Pixi scene graph, two views, bloom, motion interpolation
ui/ DOM panels and tooltips
main.js game loop: advance sim, project state into render/ui
```
The sim exposes a small read API (system contents, cloud segments, market views,
player state) and a small command API (depart, dock, buy, sell, set speed). The
renderer and UI never mutate sim state directly.
Plain JavaScript (ES modules), Vite for dev server and bundling, Vitest for tests.
## Testing
Headless tests against `sim/`:
- Cloud invariants: mass sums to 1; renormalization after exclusion is correct.
- **Observation-order independence**: observing market-then-ship produces the same
committed history as ship-then-market (the per-seed hidden-variable guarantee).
- Economy sanity: prices respond to stock; NPC arbitrage narrows price gaps over time.
- Event queue: chronological resolution; arrival events match branch probabilities
over many seeds (statistical test).
- Determinism: same galaxy seed + same player inputs → identical end state.
## Error handling
Single-player browser game: invariant assertions in `sim/` (probability mass,
non-negative stock, queue ordering) that throw in dev builds. No network, no I/O
beyond the page.