Overhaul market system with shared TAM competition, multi-tier pricing, enterprise pipeline, and developer ecosystem
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>
This commit is contained in:
2026-04-25 08:30:24 -04:00
parent 4c1c0e9ff2
commit 09a5cb69a7
34 changed files with 2851 additions and 408 deletions
@@ -1,44 +1,99 @@
import type { GameState, CompetitorState } from '@ai-tycoon/shared';
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;
if (tick < rival.nextMilestoneAtTick) 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);
const newCapability = Math.min(95, rival.estimatedCapability + capGrowth);
const newRevenue = rival.estimatedRevenue + revenueGrowth + 50;
const newUsers = rival.estimatedUsers + userGrowth + 100;
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(newCapability / 10);
const latestModelName = `${rival.name.split(' ')[0]}-${modelNames[Math.min(modelIdx, modelNames.length - 1)]}`;
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 {
...rival,
estimatedCapability: newCapability,
estimatedRevenue: newRevenue,
estimatedUsers: Math.floor(newUsers),
reputation: Math.min(100, Math.max(0, rival.reputation + repChange)),
latestModelName,
nextMilestoneAtTick: tick + milestoneInterval,
};
return updated;
});
const allCaps = [