d1d3eb4bf2
Add reusable Tooltip component and rich tooltips on all TopBar KPIs (cash breakdown, compute utilization, reputation context). Add save import button to Settings page. Fix game balance: reduce GPU maintenance 100x, increase organic API demand 200x, accelerate subscription revenue timescale, boost early subscriber seeding, use sqrt scaling for model compute factor, simplify deploy to activate all product lines at once. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
98 lines
3.5 KiB
TypeScript
98 lines
3.5 KiB
TypeScript
import type { GameState, MarketState, ComputeState } from '@ai-tycoon/shared';
|
|
import {
|
|
CONSUMER_BASE_GROWTH,
|
|
CONSUMER_QUALITY_GROWTH_MULTIPLIER,
|
|
CONSUMER_BASE_CHURN,
|
|
API_TOKENS_PER_REQUEST,
|
|
} from '@ai-tycoon/shared';
|
|
|
|
export interface MarketTickResult {
|
|
marketState: MarketState;
|
|
apiRevenue: number;
|
|
subscriptionRevenue: number;
|
|
totalTokenDemand: number;
|
|
}
|
|
|
|
export function processMarket(state: GameState, compute: ComputeState): MarketTickResult {
|
|
const bestModel = state.models.trainedModels
|
|
.filter(m => m.isDeployed)
|
|
.sort((a, b) => b.benchmarkScore - a.benchmarkScore)[0];
|
|
|
|
const modelQuality = bestModel ? bestModel.benchmarkScore / 100 : 0;
|
|
const chatProduct = state.models.productLines.find(p => p.type === 'chat-product');
|
|
const textApi = state.models.productLines.find(p => p.type === 'text-api');
|
|
|
|
// --- Consumer market (subscription product) ---
|
|
const consumers = { ...state.market.consumers };
|
|
let subscriptionRevenue = 0;
|
|
|
|
if (chatProduct?.isActive && bestModel) {
|
|
const priceAttractiveness = Math.max(0, 1 - chatProduct.pricing.subscriptionPrice / 100);
|
|
const growthRate = (CONSUMER_BASE_GROWTH + modelQuality * CONSUMER_QUALITY_GROWTH_MULTIPLIER) * (0.5 + priceAttractiveness * 0.5);
|
|
const churnRate = CONSUMER_BASE_CHURN * (1 + (1 - consumers.satisfaction) * 2);
|
|
|
|
consumers.growthRatePerTick = growthRate;
|
|
consumers.churnRatePerTick = churnRate;
|
|
|
|
const newSubs = consumers.totalSubscribers * growthRate;
|
|
const lostSubs = consumers.totalSubscribers * churnRate;
|
|
consumers.totalSubscribers = Math.max(0, consumers.totalSubscribers + newSubs - lostSubs);
|
|
|
|
if (consumers.totalSubscribers < 100 && modelQuality > 0.1) {
|
|
consumers.totalSubscribers += 5 + modelQuality * 20;
|
|
}
|
|
|
|
const loadPenalty = compute.inferenceUtilization > 0.9
|
|
? (compute.inferenceUtilization - 0.9) * 5
|
|
: 0;
|
|
consumers.satisfaction = Math.min(1, Math.max(0,
|
|
0.3 + modelQuality * 0.5 + (1 - Math.min(1, compute.inferenceUtilization)) * 0.2 - loadPenalty,
|
|
));
|
|
|
|
consumers.viralCoefficient = modelQuality > 0.5 ? 1 + (modelQuality - 0.5) * 2 : 0;
|
|
|
|
subscriptionRevenue = consumers.totalSubscribers * (chatProduct.pricing.subscriptionPrice / 86400);
|
|
}
|
|
|
|
// --- B2B API market (organic demand based on model quality + reputation) ---
|
|
const enterprise = { ...state.market.enterprise };
|
|
let apiRevenue = 0;
|
|
let organicApiTokens = 0;
|
|
|
|
if (textApi?.isActive && bestModel) {
|
|
const reputationFactor = state.reputation.score / 100;
|
|
const qualityFactor = modelQuality;
|
|
const priceFactor = Math.max(0.1, 1 - (textApi.pricing.outputTokenPrice / 20));
|
|
|
|
organicApiTokens = Math.floor(
|
|
qualityFactor * reputationFactor * priceFactor * 10_000_000 * (1 + state.meta.tickCount * 0.0001),
|
|
);
|
|
|
|
let contractTokens = 0;
|
|
for (const contract of enterprise.activeContracts) {
|
|
contractTokens += contract.tokensPerTick;
|
|
apiRevenue += (contract.tokensPerTick / 1_000_000) * contract.pricePerMToken;
|
|
}
|
|
|
|
const totalApiTokens = organicApiTokens + contractTokens;
|
|
apiRevenue += (organicApiTokens / 1_000_000) * textApi.pricing.outputTokenPrice;
|
|
|
|
enterprise.totalApiCallsPerTick = totalApiTokens / API_TOKENS_PER_REQUEST;
|
|
}
|
|
|
|
const totalTokenDemand = organicApiTokens +
|
|
consumers.totalSubscribers * 100 +
|
|
enterprise.activeContracts.reduce((s, c) => s + c.tokensPerTick, 0);
|
|
|
|
return {
|
|
marketState: {
|
|
...state.market,
|
|
consumers,
|
|
enterprise,
|
|
},
|
|
apiRevenue,
|
|
subscriptionRevenue,
|
|
totalTokenDemand,
|
|
};
|
|
}
|