09a5cb69a7
CI / build-and-push (push) Successful in 42s
Replaces the simplified single-subscriber market with a full competitive simulation: shared TAM with softmax market shares across 4 segments, multi-tier consumer subscriptions (Free/Plus/Pro/Team) and API tiers (Free/PAYG/Scale/Enterprise), enterprise sales pipeline (Lead→Qualification→POC→Negotiation→Active→Renewal) with SLA tracking, developer ecosystem flywheel, technology obsolescence pressure, seasonal demand cycles, and two new product lines (Code Assistant, AI Agents Platform). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
107 lines
4.4 KiB
TypeScript
107 lines
4.4 KiB
TypeScript
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 };
|
|
}
|