Initial scaffold: AI Tycoon monorepo with core game loop
Turborepo monorepo with three packages: - packages/shared: TypeScript types for all 14 game systems + balance constants + formatting utils - packages/game-engine: Pure TS simulation engine with tick processor, economy, infrastructure, compute, research, market, and reputation systems - apps/web: React + Vite + Tailwind + Zustand frontend with sidebar dashboard layout, new game screen, dashboard with charts, infrastructure management, and model training pages Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
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;
|
||||
}
|
||||
|
||||
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');
|
||||
|
||||
const consumers = { ...state.market.consumers };
|
||||
if (chatProduct?.isActive && bestModel) {
|
||||
const growthRate = CONSUMER_BASE_GROWTH + modelQuality * CONSUMER_QUALITY_GROWTH_MULTIPLIER;
|
||||
const churnRate = CONSUMER_BASE_CHURN * (1 + (1 - consumers.satisfaction));
|
||||
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 < 10 && modelQuality > 0) {
|
||||
consumers.totalSubscribers += 1;
|
||||
}
|
||||
|
||||
consumers.satisfaction = Math.min(1, Math.max(0,
|
||||
0.3 + modelQuality * 0.5 + (1 - compute.inferenceUtilization) * 0.2,
|
||||
));
|
||||
}
|
||||
|
||||
const subscriptionRevenue = chatProduct?.isActive
|
||||
? consumers.totalSubscribers * (chatProduct.pricing.subscriptionPrice / 30 / 24 / 3600)
|
||||
: 0;
|
||||
|
||||
const enterprise = { ...state.market.enterprise };
|
||||
let apiRevenue = 0;
|
||||
if (textApi?.isActive && bestModel) {
|
||||
let totalTokens = 0;
|
||||
for (const contract of enterprise.activeContracts) {
|
||||
totalTokens += contract.tokensPerTick;
|
||||
apiRevenue += (contract.tokensPerTick / 1_000_000) * contract.pricePerMToken;
|
||||
}
|
||||
enterprise.totalApiCallsPerTick = totalTokens / API_TOKENS_PER_REQUEST;
|
||||
}
|
||||
|
||||
return {
|
||||
marketState: {
|
||||
...state.market,
|
||||
consumers,
|
||||
enterprise,
|
||||
},
|
||||
apiRevenue,
|
||||
subscriptionRevenue,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user