feat(missions): mission board — schema, deterministic generation, top-up + v6 backfill
New missions table (schema + DDL). Pure model in lib/game/missions.ts: DELIVER / EXPLORE / FIND contracts generated from missionRng(seed, turn), board always topped up to >=3 AVAILABLE after every action. save.ts wires SCHEMA_VERSION 5 -> 6 with a v6 backfill step and completion hooks inside jumpToSystem/explorePlanet (one atomic persist). Unit tests cover generation determinism, top-up idempotency, backfill, accept validation and per-type completion incl. reward accumulation.
This commit is contained in:
@@ -108,6 +108,24 @@ export const tech = sqliteTable(
|
||||
(t) => [uniqueIndex('tech_save_slug_unique').on(t.saveId, t.slug)],
|
||||
)
|
||||
|
||||
// The mission board (#10): contracts generated deterministically from the save seed, one ACTIVE
|
||||
// at a time. `type` drives completion semantics (see advanceMission in lib/game/missions.ts):
|
||||
// DELIVER jumps to target_system_id, EXPLORE counts scans up to progress_target, FIND completes
|
||||
// when a scan's haul contains condition_kind. The reward lands in inventory_items on completion.
|
||||
export const missions = sqliteTable('missions', {
|
||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||
saveId: integer('save_id').notNull().references(() => saves.id),
|
||||
type: text('type').notNull(), // 'DELIVER' | 'EXPLORE' | 'FIND'
|
||||
description: text('description').notNull(), // human-readable objective, e.g. "Jump to Kepler Drift"
|
||||
targetSystemId: integer('target_system_id'), // DELIVER only — destination system (NULL otherwise)
|
||||
conditionKind: text('condition_kind'), // FIND only — 'resource' | 'part' (NULL otherwise)
|
||||
progressTarget: integer('progress_target').notNull().default(1), // EXPLORE: scans required; 1 for the others
|
||||
progress: integer('progress').notNull().default(0), // scans counted since acceptance; 0/1 for DELIVER/FIND
|
||||
rewardKind: text('reward_kind').notNull(), // 'resource' | 'part'
|
||||
rewardQty: integer('reward_qty').notNull().default(1),
|
||||
state: text('state').notNull().default('AVAILABLE'), // 'AVAILABLE' | 'ACTIVE' | 'DONE'
|
||||
})
|
||||
|
||||
// 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', {
|
||||
|
||||
Reference in New Issue
Block a user