Overhaul market system with shared TAM competition, multi-tier pricing, enterprise pipeline, and developer ecosystem
CI / build-and-push (push) Successful in 42s
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:
@@ -0,0 +1,127 @@
|
||||
import type { ConsumerTierState, ConsumerTierId } from '@ai-tycoon/shared';
|
||||
import {
|
||||
CONSUMER_TIER_ORDER,
|
||||
CONVERSION_RATES,
|
||||
TIER_CHURN_RATES,
|
||||
FREE_TIER_ADOPTION_RATE,
|
||||
CONSUMER_TOKENS_PER_SUBSCRIBER,
|
||||
OVERLOAD_PENALTY_EXPONENT,
|
||||
NETWORK_DEGRADATION,
|
||||
} from '@ai-tycoon/shared';
|
||||
|
||||
export interface ConsumerTickResult {
|
||||
consumerTiers: ConsumerTierState;
|
||||
subscriptionRevenue: number;
|
||||
totalConsumerTokenDemand: number;
|
||||
}
|
||||
|
||||
export function processConsumerTiers(
|
||||
tiers: ConsumerTierState,
|
||||
playerConsumerCustomers: number,
|
||||
modelQuality: number,
|
||||
seasonalConsumerMultiplier: number,
|
||||
demandCapacityRatio: number,
|
||||
networkLatencyPenalty: number,
|
||||
overloadPolicy: { degradeQualityUnderLoad: boolean; prioritizeEnterprise: boolean },
|
||||
): ConsumerTickResult {
|
||||
const updated = {
|
||||
tiers: { ...tiers.tiers },
|
||||
totalUsers: 0,
|
||||
satisfaction: tiers.satisfaction,
|
||||
viralCoefficient: tiers.viralCoefficient,
|
||||
};
|
||||
|
||||
for (const id of CONSUMER_TIER_ORDER) {
|
||||
updated.tiers[id] = { ...tiers.tiers[id], config: { ...tiers.tiers[id].config } };
|
||||
}
|
||||
|
||||
if (modelQuality <= 0) {
|
||||
return { consumerTiers: updated, subscriptionRevenue: 0, totalConsumerTokenDemand: 0 };
|
||||
}
|
||||
|
||||
const qualityFactor = Math.max(0.1, modelQuality);
|
||||
const freeAdoption = playerConsumerCustomers * FREE_TIER_ADOPTION_RATE * seasonalConsumerMultiplier;
|
||||
const targetFreeUsers = Math.max(updated.tiers.free.userCount, freeAdoption);
|
||||
|
||||
const freeGrowth = (targetFreeUsers - updated.tiers.free.userCount) * 0.05;
|
||||
updated.tiers.free.userCount = Math.max(0, updated.tiers.free.userCount + freeGrowth);
|
||||
updated.tiers.free.churnRate = TIER_CHURN_RATES.free;
|
||||
const freeChurn = updated.tiers.free.userCount * TIER_CHURN_RATES.free;
|
||||
updated.tiers.free.userCount = Math.max(0, updated.tiers.free.userCount - freeChurn);
|
||||
|
||||
const prevTierMap: Record<ConsumerTierId, ConsumerTierId | null> = {
|
||||
free: null,
|
||||
plus: 'free',
|
||||
pro: 'plus',
|
||||
team: 'pro',
|
||||
};
|
||||
|
||||
for (const id of CONSUMER_TIER_ORDER) {
|
||||
if (id === 'free') continue;
|
||||
const tier = updated.tiers[id];
|
||||
if (!tier.config.isActive) continue;
|
||||
if (modelQuality * 100 < tier.config.requiredModelQuality) continue;
|
||||
|
||||
const prevId = prevTierMap[id];
|
||||
if (!prevId) continue;
|
||||
const prevTier = updated.tiers[prevId];
|
||||
|
||||
const conversionKey = `${prevId}->${id}`;
|
||||
const baseRate = CONVERSION_RATES[conversionKey] ?? 0;
|
||||
const priceAttr = tier.config.price > 0
|
||||
? Math.max(0.1, 1 - tier.config.price / 100)
|
||||
: 1;
|
||||
const convRate = baseRate * qualityFactor * priceAttr * seasonalConsumerMultiplier;
|
||||
|
||||
const converting = prevTier.userCount * convRate;
|
||||
prevTier.userCount = Math.max(0, prevTier.userCount - converting);
|
||||
tier.userCount += converting;
|
||||
tier.conversionRateFromBelow = convRate;
|
||||
|
||||
tier.churnRate = TIER_CHURN_RATES[id];
|
||||
const churnMultiplier = 1 + (1 - updated.satisfaction) * 2;
|
||||
const churned = tier.userCount * tier.churnRate * churnMultiplier;
|
||||
tier.userCount = Math.max(0, tier.userCount - churned);
|
||||
}
|
||||
|
||||
let totalUsers = 0;
|
||||
let subscriptionRevenue = 0;
|
||||
let totalTokenDemand = 0;
|
||||
|
||||
for (const id of CONSUMER_TIER_ORDER) {
|
||||
const tier = updated.tiers[id];
|
||||
totalUsers += tier.userCount;
|
||||
subscriptionRevenue += tier.userCount * (tier.config.price / 86400);
|
||||
totalTokenDemand += tier.userCount * CONSUMER_TOKENS_PER_SUBSCRIBER;
|
||||
}
|
||||
|
||||
updated.totalUsers = totalUsers;
|
||||
|
||||
let headroomBonus = 0;
|
||||
let overloadPenalty = 0;
|
||||
if (demandCapacityRatio <= 1) {
|
||||
headroomBonus = (1 - demandCapacityRatio) * 0.2;
|
||||
} else {
|
||||
overloadPenalty = Math.min(1, Math.pow(demandCapacityRatio - 1, OVERLOAD_PENALTY_EXPONENT));
|
||||
}
|
||||
|
||||
const netLatencyPenalty = networkLatencyPenalty * NETWORK_DEGRADATION.satisfactionPenaltyPerLatency;
|
||||
updated.satisfaction = Math.min(1, Math.max(0,
|
||||
0.3 + modelQuality * 0.5 + headroomBonus - overloadPenalty - netLatencyPenalty,
|
||||
));
|
||||
|
||||
if (overloadPolicy.degradeQualityUnderLoad && demandCapacityRatio > 0.85) {
|
||||
updated.satisfaction = Math.max(0, updated.satisfaction - 0.02);
|
||||
}
|
||||
if (overloadPolicy.prioritizeEnterprise && demandCapacityRatio > 0.9) {
|
||||
updated.satisfaction = Math.max(0, updated.satisfaction - 0.01);
|
||||
}
|
||||
|
||||
updated.viralCoefficient = modelQuality > 0.5 ? 1 + (modelQuality - 0.5) * 2 : 0;
|
||||
|
||||
return {
|
||||
consumerTiers: updated,
|
||||
subscriptionRevenue,
|
||||
totalConsumerTokenDemand: totalTokenDemand,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user