import { integer, real, sqliteTable, text } from 'drizzle-orm/sqlite-core'; // Drizzle schema for the game state. Column names/types here are the source of // truth; the matching CREATE TABLE statements live in lib/db/client.ts (DDL) and // must stay in sync with this file. export const planes = sqliteTable('planes', { id: text('id').primaryKey().notNull(), model: text('model').notNull(), status: text('status', { enum: ['available', 'flying'] }).notNull(), fuel: real('fuel').notNull(), maxFuel: real('max_fuel').notNull(), locationIcao: text('location_icao').notNull(), // Current flight destination (v0.12.0) — set by fly, cleared on arrival. The // plane's origin is always its locationIcao while flying. destIcao: text('dest_icao'), lat: real('lat').notNull(), lon: real('lon').notNull(), // Sim-clock departure time of the current flight (null when available). While // flying, `fuel` is frozen at its departure value — live position/fuel are pure // functions of the clock (lib/sim/movement.ts), recomputed on read. departureSimTime: integer('departure_sim_time'), // Queue of upcoming legs NOT yet started (v0.25.0, ISS-043) — ordered JSON array // of ICAO strings ('[]' when empty). The ACTIVE leg stays destIcao + // departureSimTime; on landing the engine auto-chains queue[0] if fuel covers it. queue: text('queue').notNull().default('[]'), }); // Cargo packages (v0.12.0) replace the old contracts table end-to-end: a package // is one cargo unit origin → dest with a reward and deadline. 'loaded' = bound to // a plane's slot; it stays bound ('expired') or pays out ('delivered') when the // plane arrives at its destination airport. export const packages = sqliteTable('packages', { id: text('id').primaryKey().notNull(), originIcao: text('origin_icao').notNull(), destIcao: text('dest_icao').notNull(), reward: real('reward').notNull(), cargoType: text('cargo_type').notNull(), cargoCount: integer('cargo_count').notNull(), // Package weight in kg (v0.27.0, ISS-045) — drives load validation (plane payload) // and the per-kg part of the payout. Pre-existing rows backfill to the default. weightKg: real('weight_kg').notNull().default(2000), deadlineSimTime: integer('deadline_sim_time').notNull(), // 'expired' = an open OR loaded package whose deadline passed on a tick. Loaded // expired packages keep occupying their slot, deliver no reward, and abandon free. status: text('status', { enum: ['open', 'loaded', 'delivered', 'expired', 'abandoned'] }).notNull(), planeId: text('plane_id'), createdSimTime: integer('created_sim_time').notNull(), }); export const account = sqliteTable('account', { cash: real('cash').notNull(), reputation: real('reputation').notNull(), }); export const simState = sqliteTable('sim_state', { clockMs: integer('clock_ms').notNull(), timeScale: real('time_scale').notNull(), paused: integer('paused', { mode: 'boolean' }).notNull(), // Real-time (Date.now()) of the last tick — the tick-on-read engine advances // clockMs by (now - lastRealTickMs) × timeScale, clamped per tick. lastRealTickMs: integer('last_real_tick_ms').notNull(), }); export type PlaneRow = typeof planes.$inferSelect; export type PackageRow = typeof packages.$inferSelect; export type AccountRow = typeof account.$inferSelect; export type SimStateRow = typeof simState.$inferSelect;