From 395c0e851da866950835630df3ab4ba8e60eaefe Mon Sep 17 00:00:00 2001 From: EugeneTes Date: Fri, 12 Jun 2026 15:18:48 +0000 Subject: [PATCH] feat: balance checks and README - v1 complete --- README.md | 29 +++++++++++++++++++++++++++++ tests/balance.test.js | 31 +++++++++++++++++++++++++++++++ vite.config.js | 5 +++++ 3 files changed, 65 insertions(+) create mode 100644 README.md create mode 100644 tests/balance.test.js create mode 100644 vite.config.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..0fab21c --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# Probability + +A neon space-trading sim where unobserved ships are probability waves. + +Inspired by Space Rangers. Every NPC trader plans real routes and real trades, +but between observations its position is a probability cloud smeared along the +jump lanes — and so is your knowledge of every market more than a news-day away. +Observation collapses the cloud: enter a system and ships condense out of the +static; dock at a planet and its prices become real. + +Under the hood the universe is deterministic ("hidden variables" drawn lazily +from per-ship seeds) — the probability is *yours*, not the universe's. + +## Run + + npm install + npm run dev # play at http://localhost:5173 (add ?seed=anything for a new galaxy) + npm test # headless sim test suite + +## How to play + +- You start docked. Buy what a planet produces cheap, sell where it's consumed. +- Sidebar: navigation, market (when docked), cargo, intel, log. Space = pause, + Tab = map/system view. Click planets/gates/systems to move; click systems on + the map for price intel — note the age tag, old news lies. +- Lanes are tinted by pirate danger. Dangerous shortcuts are profitable until + they aren't. +- Orange smears on the map are ships you've met: where they probably are now. +- Reach 100,000 credits to win. Lose your ship and it's over. diff --git a/tests/balance.test.js b/tests/balance.test.js new file mode 100644 index 0000000..d33c1c5 --- /dev/null +++ b/tests/balance.test.js @@ -0,0 +1,31 @@ +import { describe, it, expect } from 'vitest'; +import { createWorld, advance } from '../src/sim/world.js'; +import { priceOf, advancePlanet } from '../src/sim/economy.js'; +import { GOODS, BASE_PRICE } from '../src/sim/constants.js'; + +describe('balance', () => { + it('the NPC economy stays alive over a long horizon', () => { + const w = createWorld('balance-1'); + advance(w, 120); + const alive = w.npcs.filter((n) => n.alive); + expect(alive.length).toBeGreaterThan(w.npcs.length * 0.5); + expect(w.stats.trades).toBeGreaterThan(w.npcs.length * 3); + }); + + it('price gradients survive NPC arbitrage (the player can still profit)', () => { + const w = createWorld('balance-1'); + advance(w, 60); + let bestSpread = 0; + for (const good of GOODS) { + let lo = Infinity, hi = 0; + for (const planet of w.galaxy.planets) { + advancePlanet(planet, w.t); + const price = priceOf(good, planet.stock[good]); + lo = Math.min(lo, price); + hi = Math.max(hi, price); + } + bestSpread = Math.max(bestSpread, (hi - lo) / BASE_PRICE[good]); + } + expect(bestSpread).toBeGreaterThan(0.5); // at least one good still has a fat margin somewhere + }); +}); diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..25b8fe5 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,5 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + build: { target: 'esnext' }, // top-level await in src/main.js +});