Files
AIHostingTycoon/packages/game-engine/src/tick.ts
T
josh 900d1d5190
CI / build-and-push (push) Successful in 34s
Fix compute utilization bug and add subscriber saturation cap
Three intertwined fixes:

1. Zero-capacity utilization: when inference allocation was 0%, the
   guard clause returned 0% utilization instead of 100%, so the market
   system never penalized satisfaction and subscribers never churned.

2. Stale compute in market: restructured tick order so capacity is
   computed before market runs, giving satisfaction calculations
   current-tick demand/capacity ratio instead of previous tick's.

3. Subscriber growth: replaced pure compound growth (reached billions
   in minutes) with logistic saturation curve. Era-based market caps:
   startup 10K, scaleup 1M, bigtech 20M, agi 100M. Quality and
   reputation expand the effective cap.

Also tuned FLOPS-to-tokens multiplier (10 → 26) for balanced
demand/capacity feel across all eras, and added market saturation
indicator to the Market page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-24 20:50:26 -04:00

153 lines
5.0 KiB
TypeScript

import type { GameState, AchievementDefinition } from '@ai-tycoon/shared';
import { processEconomy } from './systems/economySystem';
import { processInfrastructure } from './systems/infrastructureSystem';
import { computeCapacity, finalizeCompute } from './systems/computeSystem';
import { processResearch } from './systems/researchSystem';
import { processModels } from './systems/modelSystem';
import { processMarket } from './systems/marketSystem';
import { processReputation } from './systems/reputationSystem';
import { processTalent } from './systems/talentSystem';
import { processCompetitors } from './systems/competitorSystem';
import { processData } from './systems/dataSystem';
import { checkEraTransition } from './systems/eraSystem';
import { processAchievements } from './systems/achievementSystem';
import { computeValuation } from './systems/fundingSystem';
export interface TickResult {
state: Partial<GameState>;
notifications: TickNotification[];
}
export interface TickNotification {
title: string;
message: string;
type: 'info' | 'success' | 'warning' | 'danger';
}
let cachedAchievementDefs: AchievementDefinition[] | null = null;
export function setAchievementDefinitions(defs: AchievementDefinition[]) {
cachedAchievementDefs = defs;
}
export function processTick(state: GameState): Partial<GameState> {
const notifications: TickNotification[] = [];
const infraResult = processInfrastructure(state);
const infrastructure = infraResult.infrastructure;
notifications.push(...infraResult.notifications);
const stateWithInfra = { ...state, infrastructure };
const modelResult = processModels(stateWithInfra);
if (modelResult.modelCompleted) {
notifications.push({
title: 'Training Complete',
message: `${modelResult.modelCompleted.name} is ready! Benchmark: ${modelResult.modelCompleted.benchmarkScore.toFixed(1)}/100`,
type: 'success',
});
}
const stateWithModels = { ...stateWithInfra, models: modelResult.modelsState };
const capacity = computeCapacity(state, infrastructure);
const market = processMarket(stateWithModels, capacity.tokensPerSecondCapacity);
const compute = finalizeCompute(capacity, market.totalTokenDemand);
const talent = processTalent(stateWithModels);
const stateWithTalent = { ...stateWithModels, talent };
const researchResult = processResearch(stateWithTalent, compute);
if (researchResult.researchCompleted) {
notifications.push({
title: 'Research Complete',
message: `${researchResult.researchCompleted} has been unlocked!`,
type: 'success',
});
}
const reputationResult = processReputation(stateWithTalent);
const { _safetyIncident, ...reputation } = reputationResult;
if (_safetyIncident) {
notifications.push({
title: 'Safety Incident!',
message: 'Your AI model caused a safety incident. Public trust and safety record damaged.',
type: 'danger',
});
}
const economy = processEconomy(stateWithTalent, market, infrastructure, infraResult.repairCosts);
const data = processData(stateWithTalent);
const competitors = processCompetitors(stateWithTalent);
const tickCount = state.meta.tickCount + 1;
let meta = {
...state.meta,
tickCount,
lastTickTimestamp: Date.now(),
totalPlayTime: state.meta.totalPlayTime + 1,
};
const newEra = checkEraTransition({ ...stateWithTalent, economy, reputation, research: researchResult.research });
if (newEra) {
meta = { ...meta, currentEra: newEra };
notifications.push({
title: 'Era Transition!',
message: `Your company has entered the ${newEra === 'scaleup' ? 'Scale-up' : newEra === 'bigtech' ? 'Big Tech' : 'AGI'} era!`,
type: 'success',
});
}
const valuation = computeValuation({ ...stateWithTalent, economy, reputation, research: researchResult.research });
const updatedEconomy = {
...economy,
funding: { ...economy.funding, valuation },
};
const stateForAchievements: GameState = {
...stateWithTalent,
meta,
economy: updatedEconomy,
infrastructure,
compute,
research: researchResult.research,
models: modelResult.modelsState,
market: market.marketState,
reputation,
data,
competitors,
achievements: state.achievements,
};
const achievementResult = cachedAchievementDefs
? processAchievements(stateForAchievements, cachedAchievementDefs)
: { achievements: state.achievements, newAchievements: [] };
for (const name of achievementResult.newAchievements) {
notifications.push({
title: 'Achievement Unlocked!',
message: name,
type: 'success',
});
}
const result: Partial<GameState> = {
meta,
economy: updatedEconomy,
infrastructure,
compute,
research: researchResult.research,
models: modelResult.modelsState,
market: market.marketState,
talent,
reputation,
data,
competitors,
achievements: achievementResult.achievements,
};
(result as Record<string, unknown>)['_notifications'] = notifications;
return result;
}