game: seeded planet/moon generation per system + deterministic exploration roll

This commit is contained in:
2026-09-28 22:04:23 +00:00
parent 172f167a15
commit cbab17047b
2 changed files with 158 additions and 1 deletions
+56 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { generateRing, generateStartRegion, makeName, mulberry32 } from './world'
import { bodyRng, explorationRng, generatePlanets, generateRing, generateStartRegion, makeName, mulberry32, rollExploration } from './world'
describe('generateStartRegion', () => {
const region = generateStartRegion(mulberry32(42))
@@ -77,3 +77,58 @@ describe('generateRing (expansion)', () => {
expect(taken.size).toBe(40)
})
})
describe('generatePlanets', () => {
it('gives every system 2–5 planets and keeps body names unique within the system', () => {
for (const seed of [1, 7, 42, 999]) {
const planets = generatePlanets(bodyRng(String(seed), seed))
expect(planets.length).toBeGreaterThanOrEqual(2)
expect(planets.length).toBeLessThanOrEqual(5)
const names: string[] = []
for (const p of planets) {
names.push(p.name, ...p.moons.map((m) => m.name))
expect(['TERRESTRIAL', 'GAS GIANT', 'ROCKY', 'ICE WORLD', 'VOLCANIC', 'OCEAN', 'ASTEROID FIELD']).toContain(p.type)
for (const m of p.moons) {
expect(['MOON', 'ICE MOON', 'VOLCANIC MOON']).toContain(m.type)
}
}
expect(new Set(names).size).toBe(names.length) // planets + moons share one name pool per system
}
})
it('is deterministic per (seed, system id) and differs across systems', () => {
const a = generatePlanets(bodyRng('42', 7))
expect(generatePlanets(bodyRng('42', 7))).toEqual(a) // same save+system → same bodies
const b = generatePlanets(bodyRng('43', 7))
expect(JSON.stringify(b)).not.toBe(JSON.stringify(a)) // different seed → different world
})
})
describe('rollExploration', () => {
it('is deterministic for the same save+planet and yields valid items', () => {
const first = rollExploration(explorationRng('42', 13))
expect(rollExploration(explorationRng('42', 13))).toEqual(first)
for (const item of first) {
expect(['resource', 'part']).toContain(item.kind)
if (item.kind === 'resource') {
expect(item.qty).toBeGreaterThanOrEqual(1)
expect(item.qty).toBeLessThanOrEqual(4)
} else {
expect(item.qty).toBe(1)
}
}
})
it('across many planets produces every outcome shape: resource, part, both, or nothing', () => {
const shapes = new Set<string>()
for (let planetId = 1; planetId <= 200; planetId++) {
const found = rollExploration(explorationRng('42', planetId))
const hasResource = found.some((f) => f.kind === 'resource')
const hasPart = found.some((f) => f.kind === 'part')
shapes.add(`${hasResource ? 'r' : '-'}${hasPart ? 'p' : '-'}`)
}
expect(shapes).toEqual(new Set(['rp', 'r-', '-p', '--']))
})
})