import type { GameState, CompetitorState, Competitor } from '@ai-tycoon/shared'; import { COMPETITOR_PRODUCT_THRESHOLDS, COMPETITOR_CATCHUP_SHARE_THRESHOLD, COMPETITOR_CATCHUP_PRICE_CUT, FRESHNESS_DECAY_RATE, } from '@ai-tycoon/shared'; function updateCompetitorProducts(rival: Competitor): Competitor['products'] { const cap = rival.estimatedCapability; const p = rival.products; const pricing = rival.pricingStrategy; const baseChatPrice = 20 * (1 + pricing.premiumPositioning * 0.5 - pricing.aggressiveness * 0.3); const baseApiOut = 3.0 * (1 + pricing.premiumPositioning * 0.3 - pricing.aggressiveness * 0.4); return { hasFreeTier: cap >= COMPETITOR_PRODUCT_THRESHOLDS.freeTierAndChat, chatPrice: cap >= COMPETITOR_PRODUCT_THRESHOLDS.freeTierAndChat ? Math.max(5, baseChatPrice) : p.chatPrice, apiInputPrice: cap >= COMPETITOR_PRODUCT_THRESHOLDS.apiAndCodeAssistant ? Math.max(0.2, baseApiOut * 0.33) : p.apiInputPrice, apiOutputPrice: cap >= COMPETITOR_PRODUCT_THRESHOLDS.apiAndCodeAssistant ? Math.max(0.5, baseApiOut) : p.apiOutputPrice, hasCodeAssistant: cap >= COMPETITOR_PRODUCT_THRESHOLDS.apiAndCodeAssistant, codeAssistantPrice: cap >= COMPETITOR_PRODUCT_THRESHOLDS.apiAndCodeAssistant ? Math.max(10, 20 * (1 - pricing.aggressiveness * 0.3)) : 0, hasAgentsPlatform: cap >= COMPETITOR_PRODUCT_THRESHOLDS.agentsPlatform, agentsPlatformPrice: cap >= COMPETITOR_PRODUCT_THRESHOLDS.agentsPlatform ? Math.max(50, 100 * (1 - pricing.aggressiveness * 0.2)) : 0, }; } export function processCompetitors(state: GameState): CompetitorState { const tick = state.meta.tickCount; const rivals = state.competitors.rivals.map(rival => { if (rival.status !== 'active') return rival; const updated = { ...rival }; // Freshness decay each tick updated.modelFreshness = Math.max(0, updated.modelFreshness - FRESHNESS_DECAY_RATE); // Developer ecosystem growth based on personality const ecoGrowth = rival.personality.openSourceTendency * 0.1 + rival.personality.marketingFocus * 0.05; updated.developerEcosystemScore = Math.min(100, updated.developerEcosystemScore + ecoGrowth * 0.01, ); // Catch-up: if any market share < threshold, cut prices const minShare = Math.min(...Object.values(updated.marketShares)); if (minShare < COMPETITOR_CATCHUP_SHARE_THRESHOLD) { updated.pricingStrategy = { ...updated.pricingStrategy, aggressiveness: Math.min(1, updated.pricingStrategy.aggressiveness + COMPETITOR_CATCHUP_PRICE_CUT * 0.1), }; } if (tick < rival.nextMilestoneAtTick) { updated.products = updateCompetitorProducts(updated); return updated; } // Milestone reached — capability jump + model release const { personality } = rival; const capGrowth = (2 + personality.researchFocus * 5 + personality.riskTolerance * 3) * (1 + tick * 0.00005); const revenueGrowth = rival.estimatedRevenue * (0.02 + personality.marketingFocus * 0.03); const userGrowth = rival.estimatedUsers * (0.01 + personality.marketingFocus * 0.02); updated.estimatedCapability = Math.min(95, rival.estimatedCapability + capGrowth); updated.estimatedRevenue = rival.estimatedRevenue + revenueGrowth + 50; updated.estimatedUsers = Math.floor(rival.estimatedUsers + userGrowth + 100); const repChange = personality.safetyFocus > 0.6 ? 1 : personality.riskTolerance > 0.7 ? -1 : 0; updated.reputation = Math.min(100, Math.max(0, rival.reputation + repChange)); const modelNames = [ 'Alpha', 'Beta', 'Gamma', 'Delta', 'Epsilon', 'Nova', 'Quantum', 'Nexus', 'Apex', 'Zenith', ]; const modelIdx = Math.floor(updated.estimatedCapability / 10); updated.latestModelName = `${rival.name.split(' ')[0]}-${modelNames[Math.min(modelIdx, modelNames.length - 1)]}`; // Model release resets freshness updated.modelFreshness = 1.0; updated.lastModelReleaseTick = tick; updated.products = updateCompetitorProducts(updated); const milestoneInterval = 200 + Math.floor(Math.random() * 200); updated.nextMilestoneAtTick = tick + milestoneInterval; return updated; }); const allCaps = [ ...rivals.filter(r => r.status === 'active').map(r => r.estimatedCapability), state.models.bestDeployedModelScore, ]; const industryBenchmark = allCaps.length > 0 ? Math.max(...allCaps) : 0; return { rivals, industryBenchmark }; }