db+game: ship_systems table + starting ship seed (new game and v4 backfill step)

This commit is contained in:
2026-09-28 23:32:04 +00:00
parent c7580d92bd
commit 0910891995
5 changed files with 108 additions and 3 deletions
+15 -1
View File
@@ -3,6 +3,7 @@ import type { GameDb } from '@/lib/db/client'
import { persist } from '@/lib/db/client'
import * as schema from '@/lib/db/schema'
import { generateStartingCrew, rollCrewMember, startingCrewRng, type CrewMember } from './crew'
import { BASE_CAPACITY, BASE_LEVEL, SHIP_TYPES } from './ship-costs'
import { bodyRng, explorationRng, generatePlanets, generateRing, generateStartRegion, mulberry32, rollExploration, type FoundItem } from './world'
// Save lifecycle: auto-init on first hit, world state reads, and the jump action.
@@ -95,7 +96,15 @@ async function seedStartingCrew(db: GameDb, saveId: number, seed: string): Promi
// versions seed at new-game time, so ensureSave runs these steps in order for anything below
// current — each step is idempotent (a no-op when its data already exists). When #9/#10 add
// seeding, bump SCHEMA_VERSION and append a step here.
export const SCHEMA_VERSION = 3 // v1: save + start region only · v2: bodies for every system · v3: starting crew
export const SCHEMA_VERSION = 4 // v1: save + start region only · v2: bodies for every system · v3: starting crew · v4: starting ship
// Seeds the starting ship: one row per known type — core types built at base values, the rest
// unbuilt (level/capacity 0) until a BUILD action.
async function seedStartingShip(db: GameDb, saveId: number): Promise<void> {
for (const def of SHIP_TYPES) {
await db.db.insert(schema.shipSystems).values({ saveId, type: def.type, level: def.builtAtStart ? BASE_LEVEL : 0, capacity: def.builtAtStart ? BASE_CAPACITY : 0 })
}
}
// Creates the save + starting region when the DB has none; backfills older saves otherwise.
// Safe to call on every request (single player, one process: check-then-insert cannot race).
@@ -123,6 +132,7 @@ async function createNewGame(db: GameDb): Promise<void> {
const resolve = (index: number) => (index === 0 ? startId : neighborIds[index - 1])
await insertRoutes(db, region.edges, resolve)
await seedStartingCrew(db, save.id, seed)
await seedStartingShip(db, save.id)
await db.db.insert(schema.events).values({ saveId: save.id, turn: 0, type: 'new_game', message: `New game — seed ${seed}, starting system ${region.start.name}`, createdAt: nowIso })
await persist(db)
@@ -141,6 +151,10 @@ async function backfillSave(db: GameDb, save: { id: number; seed: string; schema
if ((await db.db.select().from(schema.crew)).length === 0) await seedStartingCrew(db, save.id, save.seed)
version = 3
}
if (version < 4) {
if ((await db.db.select().from(schema.shipSystems)).length === 0) await seedStartingShip(db, save.id)
version = 4
}
if (from !== SCHEMA_VERSION) {
await db.db.update(schema.saves).set({ schemaVersion: version }).where(eq(schema.saves.id, save.id))