feat: seeded galaxy generation with lanes, hazards, routing

This commit is contained in:
2026-06-12 14:06:10 +00:00
parent 8e38d4c158
commit d712f9bc23
2 changed files with 225 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
import { describe, it, expect } from 'vitest';
import { generateGalaxy, hopMatrix, findRoute, otherEnd } from '../src/sim/galaxy.js';
import { GOODS } from '../src/sim/constants.js';
describe('galaxy', () => {
const g = generateGalaxy('test-seed', { systemCount: 25 });
it('is deterministic for the same seed', () => {
const g2 = generateGalaxy('test-seed', { systemCount: 25 });
expect(JSON.stringify(g2)).toBe(JSON.stringify(g));
const g3 = generateGalaxy('other-seed', { systemCount: 25 });
expect(JSON.stringify(g3)).not.toBe(JSON.stringify(g));
});
it('has the requested systems, each with 1-3 planets and at least one lane', () => {
expect(g.systems).toHaveLength(25);
for (const s of g.systems) {
expect(s.planets.length).toBeGreaterThanOrEqual(1);
expect(s.planets.length).toBeLessThanOrEqual(3);
expect(s.lanes.length).toBeGreaterThanOrEqual(1);
}
});
it('is fully connected (every system reachable from system 0)', () => {
const hops = hopMatrix(g);
for (let i = 0; i < g.systems.length; i++) {
expect(hops[0][i]).toBeLessThan(Infinity);
}
});
it('lanes have sane travel days and hazard, and back-reference systems', () => {
for (const lane of g.lanes) {
expect(lane.days).toBeGreaterThanOrEqual(1);
expect(lane.days).toBeLessThanOrEqual(4);
expect(lane.hazard).toBeGreaterThanOrEqual(0);
expect(lane.hazard).toBeLessThanOrEqual(0.7);
expect(g.systems[lane.a].lanes).toContain(lane.id);
expect(g.systems[lane.b].lanes).toContain(lane.id);
}
});
it('planets have profiles, stocks for every good, and an orbit for rendering', () => {
for (const p of g.planets) {
expect(Object.keys(p.produces).length).toBeGreaterThan(0);
expect(Object.keys(p.consumes).length).toBeGreaterThan(0);
for (const good of GOODS) expect(p.stock[good]).toBeGreaterThanOrEqual(0);
expect(p.stockTime).toBe(0);
expect(p.orbit.r).toBeGreaterThan(0);
}
});
it('findRoute returns a connected lane path with summed days', () => {
const from = 0, to = g.systems.length - 1;
const route = findRoute(g, from, to);
expect(route).not.toBeNull();
let sys = from, days = 0;
for (const laneId of route.lanes) {
const lane = g.lanes[laneId];
expect([lane.a, lane.b]).toContain(sys);
sys = otherEnd(lane, sys);
days += lane.days;
}
expect(sys).toBe(to);
expect(route.days).toBeCloseTo(days, 5);
expect(findRoute(g, 3, 3)).toEqual({ lanes: [], days: 0 });
});
});