84 lines
3.9 KiB
TypeScript
84 lines
3.9 KiB
TypeScript
import { integer, primaryKey, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
||
|
||
// Drizzle schema for game state. Single-player by design: exactly ONE save row ever exists
|
||
// (no auth/accounts), so every table hangs off saves.id but only one save is in play.
|
||
// Column names/types here are the source of truth; matching CREATE TABLE statements live
|
||
// in lib/db/client.ts (DDL) and must stay in sync with this file.
|
||
|
||
export const saves = sqliteTable('saves', {
|
||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||
// Seed for the deterministic world generator — same seed always regenerates the same world.
|
||
seed: text('seed').notNull(),
|
||
turn: integer('turn').notNull().default(0),
|
||
createdAt: text('created_at').notNull(),
|
||
})
|
||
|
||
export const solarSystems = sqliteTable('solar_systems', {
|
||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||
saveId: integer('save_id').notNull().references(() => saves.id),
|
||
name: text('name').notNull(),
|
||
// 0–100 coordinate space, rendered directly by the NodeGraph component.
|
||
x: real('x').notNull(),
|
||
y: real('y').notNull(),
|
||
discovered: integer('discovered', { mode: 'boolean' }).notNull().default(false),
|
||
isCurrent: integer('is_current', { mode: 'boolean' }).notNull().default(false),
|
||
})
|
||
|
||
// Jump routes, stored as directed pairs (both directions of each route) so adjacency and
|
||
// "reachable from current" are single-row lookups. Read side dedupes for display.
|
||
export const jumpRoutes = sqliteTable(
|
||
'jump_routes',
|
||
{
|
||
fromSystem: integer('from_system').notNull().references(() => solarSystems.id),
|
||
toSystem: integer('to_system').notNull().references(() => solarSystems.id),
|
||
},
|
||
(t) => [primaryKey({ columns: [t.fromSystem, t.toSystem] })],
|
||
)
|
||
|
||
// Bodies of a solar system: planets (parent_id NULL) and moons (parent_id = their planet's
|
||
// id). Generated when the system is generated, so every system has 2–5 planets, some with
|
||
// 1–3 moons. Insertion order is display order — a moon always follows its parent row.
|
||
export const planets = sqliteTable('planets', {
|
||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||
systemId: integer('system_id').notNull().references(() => solarSystems.id),
|
||
parentId: integer('parent_id'), // NULL for a planet, set for a moon
|
||
name: text('name').notNull(),
|
||
type: text('type').notNull(),
|
||
hazard: integer('hazard', { mode: 'boolean' }).notNull().default(false),
|
||
explored: integer('explored', { mode: 'boolean' }).notNull().default(false),
|
||
})
|
||
|
||
// Dumb-simple cargo hold on the save: what exploration has found so far. SHIP SYSTEMS (#8)
|
||
// will spend parts from here later; nothing consumes it yet.
|
||
export const inventoryItems = sqliteTable('inventory_items', {
|
||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||
saveId: integer('save_id').notNull().references(() => saves.id),
|
||
kind: text('kind').notNull(), // 'resource' | 'part'
|
||
name: text('name').notNull(),
|
||
qty: integer('qty').notNull().default(1),
|
||
})
|
||
|
||
// The ship's crew roster: the starting crew (seeded on new game) plus members found while
|
||
// exploring planets/moons. Display-only for now — no dismiss/assign actions yet (#7 scope).
|
||
export const crew = sqliteTable('crew', {
|
||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||
saveId: integer('save_id').notNull().references(() => saves.id),
|
||
name: text('name').notNull(),
|
||
role: text('role').notNull(),
|
||
traitGood: text('trait_good').notNull(),
|
||
traitBad: text('trait_bad').notNull(),
|
||
// Where the member came from: 'STARTING CREW' or '<planet>, <system>' for found members.
|
||
origin: text('origin').notNull(),
|
||
})
|
||
|
||
// Append-only game log. Writers so far: new-game + jump events; later issues add more
|
||
// writers and a read endpoint.
|
||
export const events = sqliteTable('events', {
|
||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||
saveId: integer('save_id').notNull().references(() => saves.id),
|
||
turn: integer('turn').notNull(),
|
||
type: text('type').notNull(),
|
||
message: text('message').notNull(),
|
||
createdAt: text('created_at').notNull(),
|
||
})
|