db+game: tech table + starting tech list seeded on new game and v5 backfill step
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
// 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<string, TechEffect & { description: string }> = {
|
||||
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<string>): { 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<string>): 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<string>): 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>): 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
|
||||
}
|
||||
Reference in New Issue
Block a user