import { describe, it, expect } from 'vitest'; import { priceOf, advancePlanet, marketView, applyTrade } from '../src/sim/economy.js'; import { BASE_PRICE, GOODS, TYPICAL_STOCK } from '../src/sim/constants.js'; function makePlanet() { return { id: 0, systemId: 0, name: 'Test Prime', produces: { food: 10 }, consumes: { fuel: 5 }, stock: { ore: 100, food: 100, fuel: 100, tech: 100, meds: 100, luxury: 100 }, stockTime: 0, }; } describe('economy', () => { it('price falls as stock rises, clamped to [0.25x, 4x] base', () => { expect(priceOf('ore', TYPICAL_STOCK)).toBeCloseTo(BASE_PRICE.ore, 1); expect(priceOf('ore', 10)).toBeGreaterThan(priceOf('ore', 500)); expect(priceOf('ore', 0)).toBe(BASE_PRICE.ore * 4); expect(priceOf('ore', 1e9)).toBe(BASE_PRICE.ore * 0.25); }); it('advancePlanet applies production/consumption over elapsed time', () => { const p = makePlanet(); advancePlanet(p, 10); expect(p.stock.food).toBeCloseTo(200, 5); // +10/day expect(p.stock.fuel).toBeCloseTo(50, 5); // -5/day expect(p.stock.ore).toBeCloseTo(100, 5); // untouched expect(p.stockTime).toBe(10); }); it('advancePlanet never goes negative and is idempotent', () => { const p = makePlanet(); advancePlanet(p, 100); expect(p.stock.fuel).toBe(0); const snapshot = { ...p.stock }; advancePlanet(p, 100); // same time again: no change expect(p.stock).toEqual(snapshot); }); it('applyTrade returns pre-trade price and mutates stock', () => { const p = makePlanet(); const price = applyTrade(p, 0, 'ore', -20); // a buyer removes 20 expect(price).toBeCloseTo(priceOf('ore', 100), 5); expect(p.stock.ore).toBe(80); applyTrade(p, 0, 'ore', 50); // a seller delivers 50 expect(p.stock.ore).toBe(130); }); it('marketView lists all goods with integer stock and price', () => { const p = makePlanet(); const view = marketView(p, 5); expect(view.map((r) => r.good)).toEqual(GOODS); for (const row of view) { expect(Number.isInteger(row.stock)).toBe(true); expect(row.price).toBeGreaterThan(0); } }); });