game: starting crew seeded on new game; exploration rolls for a joining crew member (same per-planet stream)
This commit is contained in:
+73
-1
@@ -4,7 +4,8 @@ import path from 'node:path'
|
||||
import { afterAll, describe, expect, it } from 'vitest'
|
||||
import { openGameDb, type GameDb } from '@/lib/db/client'
|
||||
import * as schema from '@/lib/db/schema'
|
||||
import { getSystemState, explorePlanet, getWorldState, jumpToSystem, type WorldState } from './save'
|
||||
import { generateStartingCrew, rollCrewMember, startingCrewRng } from './crew'
|
||||
import { getCrewState, getSystemState, explorePlanet, getWorldState, jumpToSystem, type WorldState } from './save'
|
||||
import { bodyRng, explorationRng, generatePlanets, rollExploration } from './world'
|
||||
|
||||
const dirs: string[] = []
|
||||
@@ -228,3 +229,74 @@ describe('explorePlanet', () => {
|
||||
expect(state.turn).toBe(planetIds.length) // one turn per scan
|
||||
})
|
||||
})
|
||||
|
||||
describe('getCrewState', () => {
|
||||
it('seeds a starting crew of 2–3 members derived from the save seed', async () => {
|
||||
const { db } = await freshFile()
|
||||
const crew = await getCrewState(db) // ensureSave runs inside, so this also seeds the crew
|
||||
|
||||
expect(crew.length).toBeGreaterThanOrEqual(2)
|
||||
expect(crew.length).toBeLessThanOrEqual(3)
|
||||
for (const member of crew) {
|
||||
expect(member.origin).toBe('STARTING CREW')
|
||||
expect(member.name.trim()).not.toBe('')
|
||||
expect(member.role.trim()).not.toBe('')
|
||||
expect(member.traitGood.trim()).not.toBe('')
|
||||
expect(member.traitBad.trim()).not.toBe('')
|
||||
}
|
||||
|
||||
// The roster is exactly the pure generation for this save's seed.
|
||||
const [save] = await db.db.select().from(schema.saves)
|
||||
const expected = generateStartingCrew(startingCrewRng(save.seed))
|
||||
expect(crew.map((c) => c.name)).toEqual(expected.map((m) => m.name))
|
||||
expect(crew.map((c) => c.role)).toEqual(expected.map((m) => m.role))
|
||||
expect(crew.map((c) => [c.traitGood, c.traitBad])).toEqual(expected.map((m) => [m.traitGood, m.traitBad]))
|
||||
})
|
||||
})
|
||||
|
||||
describe('explorePlanet crew finds', () => {
|
||||
it('lands a found member in the roster with the planet+system origin and never joins twice (known seed)', async () => {
|
||||
// Hand-built save with a known seed so the crew roll is recomputable offline.
|
||||
const { db } = await freshFile()
|
||||
const [save] = await db.db.insert(schema.saves).values({ seed: '42', turn: 0, createdAt: new Date().toISOString() }).returning()
|
||||
const [sys] = await db.db
|
||||
.insert(schema.solarSystems)
|
||||
.values({ saveId: save.id, name: 'Test System', x: 50, y: 50, discovered: true, isCurrent: true })
|
||||
.returning({ id: schema.solarSystems.id })
|
||||
|
||||
// Find a small planet id whose exploration roll finds crew (deterministic — recomputed here,
|
||||
// mirroring save.ts: the item haul draws first, then the crew roll on the same stream).
|
||||
let crewPlanetId = -1
|
||||
for (let id = 1; id <= 50 && crewPlanetId === -1; id++) {
|
||||
const rng = explorationRng('42', id)
|
||||
rollExploration(rng)
|
||||
if (rollCrewMember(rng, new Set<string>())) crewPlanetId = id
|
||||
}
|
||||
expect(crewPlanetId).toBeGreaterThan(0)
|
||||
|
||||
const planetName = `P${crewPlanetId}`
|
||||
for (let i = 1; i <= crewPlanetId; i++) {
|
||||
await db.db.insert(schema.planets).values({ systemId: sys.id, parentId: null, name: `P${i}`, type: 'ROCKY', hazard: false })
|
||||
}
|
||||
|
||||
const before = await getCrewState(db) // empty — this save was hand-built, no starting crew seeded
|
||||
const result = await explorePlanet(db, crewPlanetId)
|
||||
|
||||
expect(result.crewFound).not.toBeNull()
|
||||
const expectedRng = explorationRng('42', crewPlanetId)
|
||||
rollExploration(expectedRng) // consume the item haul draws first, exactly as save.ts does
|
||||
const expectedMember = rollCrewMember(expectedRng, new Set<string>())! // same save+planet → same member
|
||||
expect(result.crewFound!.name).toBe(expectedMember.name)
|
||||
expect(result.crewFound!.role).toBe(expectedMember.role)
|
||||
expect([result.crewFound!.traitGood, result.crewFound!.traitBad]).toEqual([expectedMember.traitGood, expectedMember.traitBad])
|
||||
expect(result.crewFound!.origin).toBe(`${planetName}, Test System`)
|
||||
|
||||
const after = await getCrewState(db)
|
||||
expect(after.length).toBe(before.length + 1) // exactly one join
|
||||
expect(after.find((c) => c.id === result.crewFound!.id)).toMatchObject({ name: expectedMember.name, origin: `${planetName}, Test System` })
|
||||
|
||||
// The body is already explored → a second scan is rejected and no duplicate joins.
|
||||
await expect(explorePlanet(db, crewPlanetId)).rejects.toMatchObject({ status: 409 })
|
||||
expect((await getCrewState(db)).length).toBe(before.length + 1)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user