// Pure technology model: the known techs, their costs/prerequisites and what each unlock does. // No DB imports — both lib/game/research.ts + lib/game/ship.ts (server) and the frontend import // from here so research/cap/gate logic can never drift from what the API enforces. import { MAX_CAPACITY, MAX_LEVEL } from './ship-costs' export interface TechDef { slug: string // stable key used in DB + prerequisites name: string // display name costKind: 'resource' | 'part' costQty: number prereqs: string[] // slugs that must be researched first (short chains only) effect: string // effect slug — see EFFECTS below } // The technology tree, in display order. Roots have no prerequisites; the two chains are // WEAPONS SYSTEMS → PLASMA CANNON and REACTOR OVERCLOCK → FUSION CORE. Costs stay small so a // handful of scans makes something affordable (the e2e scan loop relies on that). export const TECHS: TechDef[] = [ { slug: 'WEAPONS_SYSTEMS', name: 'WEAPONS SYSTEMS', costKind: 'part', costQty: 2, prereqs: [], effect: 'BUILD_WEAPONS' }, { slug: 'REACTOR_OVERCLOCK', name: 'REACTOR OVERCLOCK', costKind: 'resource', costQty: 3, prereqs: [], effect: 'REACTOR_LEVEL_CAP' }, { slug: 'HULL_PLATING', name: 'HULL PLATING', costKind: 'resource', costQty: 3, prereqs: [], effect: 'HULL_CAPACITY_CAP' }, { slug: 'NAV_AI', name: 'NAVIGATION AI', costKind: 'resource', costQty: 2, prereqs: [], effect: 'NAV_LEVEL_CAP' }, { slug: 'PLASMA_CANNON', name: 'PLASMA CANNON', costKind: 'part', costQty: 3, prereqs: ['WEAPONS_SYSTEMS'], effect: 'WEAPONS_LEVEL_CAP' }, { slug: 'FUSION_CORE', name: 'FUSION CORE', costKind: 'resource', costQty: 4, prereqs: ['REACTOR_OVERCLOCK'], effect: 'REACTOR_CAPACITY_CAP' }, ] // What each researched tech changes on the ship. `unlock_build` gates a BUILD action; the cap // effects raise the UPGRADE/EXPAND ceiling for one system type (stacked over the base caps). export interface TechEffect { kind: 'unlock_build' | 'raise_level_cap' | 'raise_capacity_cap' shipType: string bonus?: number // cap effects only } export const EFFECTS: Record = { BUILD_WEAPONS: { kind: 'unlock_build', shipType: 'WEAPONS', description: 'Unlocks building the WEAPONS system' }, REACTOR_LEVEL_CAP: { kind: 'raise_level_cap', shipType: 'REACTOR', bonus: 2, description: `Raises REACTOR max level by 2 (${MAX_LEVEL} → ${MAX_LEVEL + 2})` }, HULL_CAPACITY_CAP: { kind: 'raise_capacity_cap', shipType: 'HULL', bonus: 3, description: `Raises HULL max capacity by 3 (${MAX_CAPACITY} → ${MAX_CAPACITY + 3})` }, NAV_LEVEL_CAP: { kind: 'raise_level_cap', shipType: 'NAV COMPUTER', bonus: 2, description: `Raises NAV COMPUTER max level by 2 (${MAX_LEVEL} → ${MAX_LEVEL + 2})` }, WEAPONS_LEVEL_CAP: { kind: 'raise_level_cap', shipType: 'WEAPONS', bonus: 2, description: `Raises WEAPONS max level by 2 (once built)` }, REACTOR_CAPACITY_CAP: { kind: 'raise_capacity_cap', shipType: 'REACTOR', bonus: 3, description: `Raises REACTOR max capacity by 3 (${MAX_CAPACITY} → ${MAX_CAPACITY + 3})` }, } export type TechStateName = 'RESEARCHED' | 'AVAILABLE' | 'LOCKED' // One tech as served to the UI (GET /api/tech) and consumed by both tabs. export interface TechDto { id: number slug: string name: string costKind: 'resource' | 'part' costQty: number prereqs: string[] // prerequisite NAMES (for display), in TECHS order effect: string // human-readable unlock description state: TechStateName missingPrereqs: string[] // unmet prerequisite names — non-empty only when LOCKED } export function techBySlug(slug: string): TechDef | undefined { return TECHS.find((t) => t.slug === slug) } // RESEARCHED if done, AVAILABLE if all prerequisites are researched, LOCKED otherwise (with the // missing prerequisite names so the UI can say which one is blocking it). export function techStatus(def: TechDef, researchedSlugs: Set): { state: TechStateName; missingPrereqs: string[] } { if (researchedSlugs.has(def.slug)) return { state: 'RESEARCHED', missingPrereqs: [] } const missing = def.prereqs.filter((p) => !researchedSlugs.has(p)).map((slug) => techBySlug(slug)?.name ?? slug) return missing.length === 0 ? { state: 'AVAILABLE', missingPrereqs: [] } : { state: 'LOCKED', missingPrereqs: missing } } // The UPGRADE ceiling for a system type given what is researched (base cap + bonuses). export function effectiveLevelCap(shipType: string, researchedSlugs: Set): number { let cap = MAX_LEVEL for (const def of TECHS) { if (!researchedSlugs.has(def.slug)) continue const effect = EFFECTS[def.effect] if (effect?.kind === 'raise_level_cap' && effect.shipType === shipType) cap += effect.bonus ?? 0 } return cap } // The EXPAND ceiling for a system type given what is researched. export function effectiveCapacityCap(shipType: string, researchedSlugs: Set): number { let cap = MAX_CAPACITY for (const def of TECHS) { if (!researchedSlugs.has(def.slug)) continue const effect = EFFECTS[def.effect] if (effect?.kind === 'raise_capacity_cap' && effect.shipType === shipType) cap += effect.bonus ?? 0 } return cap } // The name of the tech that must be researched before a system type can be BUILT, or null when // building it is not gated (or the gate is already researched). export function buildGate(shipType: string, researchedSlugs: Set): string | null { for (const def of TECHS) { const effect = EFFECTS[def.effect] if (effect?.kind === 'unlock_build' && effect.shipType === shipType && !researchedSlugs.has(def.slug)) return def.name } return null }