db: game save schema — saves, solar_systems, jump_routes, events (single-player, one save)
This commit is contained in:
+35
-4
@@ -14,12 +14,43 @@ export interface GameDb {
|
|||||||
file: string;
|
file: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// CREATE TABLE statements mirroring lib/db/schema.ts — keep in sync. Game tables land with
|
// CREATE TABLE statements mirroring lib/db/schema.ts — keep in sync. _meta is DB plumbing
|
||||||
// the turn engine; _meta is DB plumbing (not game state): a brand-new sql.js database exports
|
// (not game state): a brand-new sql.js database exports as ZERO bytes, so without at least
|
||||||
// as ZERO bytes, so without at least one real table there would be nothing valid to persist
|
// one real table there would be nothing valid to persist on the ./db volume from first boot.
|
||||||
// on the ./db volume from first boot.
|
|
||||||
const DDL = `
|
const DDL = `
|
||||||
CREATE TABLE IF NOT EXISTS _meta (key TEXT PRIMARY KEY, value TEXT);
|
CREATE TABLE IF NOT EXISTS _meta (key TEXT PRIMARY KEY, value TEXT);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS saves (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
seed TEXT NOT NULL,
|
||||||
|
turn INTEGER NOT NULL DEFAULT 0,
|
||||||
|
created_at TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS solar_systems (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
save_id INTEGER NOT NULL REFERENCES saves(id),
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
x REAL NOT NULL,
|
||||||
|
y REAL NOT NULL,
|
||||||
|
discovered INTEGER NOT NULL DEFAULT 0,
|
||||||
|
is_current INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS jump_routes (
|
||||||
|
from_system INTEGER NOT NULL REFERENCES solar_systems(id),
|
||||||
|
to_system INTEGER NOT NULL REFERENCES solar_systems(id),
|
||||||
|
PRIMARY KEY (from_system, to_system)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS events (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
save_id INTEGER NOT NULL REFERENCES saves(id),
|
||||||
|
turn INTEGER NOT NULL,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
message TEXT NOT NULL,
|
||||||
|
created_at TEXT NOT NULL
|
||||||
|
);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// SPACE_WANDERER_DB_PATH override exists so unit tests can point at a temp file instead
|
// SPACE_WANDERER_DB_PATH override exists so unit tests can point at a temp file instead
|
||||||
|
|||||||
+44
-3
@@ -1,6 +1,47 @@
|
|||||||
// Drizzle schema for game state. EMPTY on purpose at scaffold stage (v0.1.0) — no game
|
import { integer, primaryKey, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
||||||
// tables yet; the first turn-engine/ship/crew tables land with the first game systems.
|
|
||||||
|
// 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
|
// 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.
|
// in lib/db/client.ts (DDL) and must stay in sync with this file.
|
||||||
|
|
||||||
export {};
|
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] })],
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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(),
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user