import type { GameState, AchievementState, AchievementDefinition } from '@ai-tycoon/shared'; export interface AchievementTickResult { achievements: AchievementState; newAchievements: string[]; } const ERA_INDEX: Record = { startup: 0, scaleup: 1, bigtech: 2, agi: 3 }; function getFieldValue(state: GameState, field: string): number { if (field === 'meta._eraIndex') return ERA_INDEX[state.meta.currentEra] ?? 0; if (field === 'meta._deployedModelCount') return state.models.baseModels.filter(m => m.isDeployed).length; const parts = field.split('.'); let current: unknown = state; for (const part of parts) { if (current == null || typeof current !== 'object') return 0; current = (current as Record)[part]; } return typeof current === 'number' ? current : 0; } function checkCondition(state: GameState, def: AchievementDefinition): boolean { const value = getFieldValue(state, def.condition.field); switch (def.condition.operator) { case 'gt': return value > def.condition.value; case 'gte': return value >= def.condition.value; case 'eq': return value === def.condition.value; default: return false; } } export function processAchievements( state: GameState, definitions: AchievementDefinition[], ): AchievementTickResult { if (state.meta.tickCount % 10 !== 0) { return { achievements: state.achievements, newAchievements: [] }; } const unlockedIds = new Set(state.achievements.unlocked.map(a => a.id)); const newAchievements: string[] = []; const unlocked = [...state.achievements.unlocked]; for (const def of definitions) { if (unlockedIds.has(def.id)) continue; if (checkCondition(state, def)) { unlocked.push({ id: def.id, unlockedAtTick: state.meta.tickCount }); newAchievements.push(def.name); } } return { achievements: { ...state.achievements, unlocked }, newAchievements, }; }