Files
AIHostingTycoon/packages/game-engine/src/systems/marketSystem.ts
T
josh d1d3eb4bf2 Polish Week 1: tooltips, save import, game balance tuning
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>
2026-04-24 17:17:58 -04:00

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,
};
}