94 lines
3.7 KiB
JavaScript
94 lines
3.7 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
|
import { createWorld, advance } from '../src/sim/world.js';
|
|
import { goTo, buyGood, sellGood, playerSystem, cargoUsed } from '../src/sim/player.js';
|
|
import { INSYS_LEG_DAYS } from '../src/sim/constants.js';
|
|
|
|
function freshWorld() {
|
|
return createWorld('player-test', { systemCount: 10, npcCount: 5 });
|
|
}
|
|
|
|
describe('player', () => {
|
|
it('buys and sells at the docked planet, conserving value through stock', () => {
|
|
const w = freshWorld();
|
|
const planet = w.galaxy.planets[w.player.loc.planetId];
|
|
const before = w.player.credits;
|
|
const stockBefore = Math.floor(planet.stock.food);
|
|
expect(buyGood(w, 'food', 5)).toBe(true);
|
|
expect(w.player.cargo.food).toBe(5);
|
|
expect(w.player.credits).toBeLessThan(before);
|
|
expect(Math.floor(planet.stock.food)).toBe(stockBefore - 5);
|
|
expect(sellGood(w, 'food', 5)).toBe(true);
|
|
expect(w.player.cargo.food ?? 0).toBe(0);
|
|
});
|
|
|
|
it('rejects buys beyond capacity or credits', () => {
|
|
const w = freshWorld();
|
|
expect(buyGood(w, 'food', 9999)).toBe(true); // clamps to capacity/credits
|
|
expect(cargoUsed(w.player)).toBeLessThanOrEqual(w.player.capacity);
|
|
const w2 = freshWorld();
|
|
w2.player.credits = 0;
|
|
expect(buyGood(w2, 'food', 1)).toBe(false);
|
|
});
|
|
|
|
it('travels to another planet in-system after INSYS_LEG_DAYS', () => {
|
|
const w = freshWorld();
|
|
const sys = playerSystem(w);
|
|
const other = w.galaxy.planets.find((p) => p.systemId === sys && p.id !== w.player.loc.planetId);
|
|
if (!other) return; // single-planet start system on this seed: nothing to assert
|
|
expect(goTo(w, { planetId: other.id })).toBe(true);
|
|
expect(w.player.loc.kind).toBe('insys-leg');
|
|
advance(w, w.t + INSYS_LEG_DAYS + 0.01);
|
|
expect(w.player.loc.kind).toBe('docked');
|
|
expect(w.player.loc.planetId).toBe(other.id);
|
|
});
|
|
|
|
it('jumps to a neighboring system via gate and lane', () => {
|
|
const w = freshWorld();
|
|
const sys = playerSystem(w);
|
|
const laneId = w.galaxy.systems[sys].lanes[0];
|
|
const lane = w.galaxy.lanes[laneId];
|
|
const destSys = lane.a === sys ? lane.b : lane.a;
|
|
expect(goTo(w, { laneId })).toBe(true);
|
|
advance(w, w.t + INSYS_LEG_DAYS + lane.days * 1.01 + 0.01);
|
|
expect(playerSystem(w)).toBe(destSys);
|
|
expect(w.player.loc.kind).toBe('at-gate');
|
|
});
|
|
|
|
it('refuses movement while already moving, and trades while undocked', () => {
|
|
const w = freshWorld();
|
|
const sys = playerSystem(w);
|
|
const laneId = w.galaxy.systems[sys].lanes[0];
|
|
expect(goTo(w, { laneId })).toBe(true);
|
|
expect(goTo(w, { laneId })).toBe(false);
|
|
expect(buyGood(w, 'food', 1)).toBe(false);
|
|
});
|
|
|
|
it('docking refreshes the player price book', () => {
|
|
const w = freshWorld();
|
|
const sys = playerSystem(w);
|
|
const other = w.galaxy.planets.find((p) => p.systemId === sys && p.id !== w.player.loc.planetId);
|
|
if (!other) return;
|
|
goTo(w, { planetId: other.id });
|
|
advance(w, w.t + INSYS_LEG_DAYS + 0.01);
|
|
expect(w.player.priceBook[other.id].time).toBeCloseTo(w.t, 1);
|
|
});
|
|
|
|
it('hazard rolls on player lanes are deterministic per world seed', () => {
|
|
const run = () => {
|
|
const w = freshWorld();
|
|
// brute-force the player across the most dangerous lane repeatedly
|
|
const sys = playerSystem(w);
|
|
const laneId = w.galaxy.systems[sys].lanes[0];
|
|
for (let i = 0; i < 5 && w.player.alive; i++) {
|
|
const cur = playerSystem(w);
|
|
const lane = w.galaxy.lanes[laneId];
|
|
if (lane.a !== cur && lane.b !== cur) break;
|
|
goTo(w, { laneId });
|
|
advance(w, w.t + INSYS_LEG_DAYS + lane.days * 1.01 + 0.05);
|
|
}
|
|
return [w.player.alive, w.player.tripCount, Object.entries(w.player.cargo)];
|
|
};
|
|
expect(JSON.stringify(run())).toBe(JSON.stringify(run()));
|
|
});
|
|
});
|