Add complete game loop: training, revenue, market, offline catch-up

- Model training system: training jobs produce TrainedModels with
  calculated capabilities based on compute, data, and research
- Market system: organic API demand and consumer subscriptions now
  generate real revenue from deployed models
- Talent system: salary costs calculated from department headcount
- Toast notification system for game events (training complete, etc.)
- Offline catch-up: progress bar + summary screen when returning
- Market page: pricing controls for API and subscription products
- Finance page: income statement, cash charts, funding history
- Tick processor now runs all 7 systems in correct dependency order

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-24 17:02:58 -04:00
parent fdc8e544ae
commit 9a48c188ad
12 changed files with 757 additions and 21 deletions
+19 -3
View File
@@ -1,14 +1,15 @@
import { useEffect, useRef } from 'react';
import { GameEngine } from '@ai-tycoon/game-engine';
import type { TickNotification } from '@ai-tycoon/game-engine';
import { useGameStore } from '@/store';
export function useGameLoop() {
export function useGameLoop(skip = false) {
const engineRef = useRef<GameEngine | null>(null);
const companyName = useGameStore((s) => s.meta.companyName);
const gameSpeed = useGameStore((s) => s.meta.gameSpeed);
useEffect(() => {
if (!companyName) return;
if (!companyName || skip) return;
const engine = new GameEngine({
getState: () => {
@@ -30,7 +31,22 @@ export function useGameLoop() {
};
},
setState: (partial) => {
const notifications = (partial as Record<string, unknown>)['_notifications'] as TickNotification[] | undefined;
delete (partial as Record<string, unknown>)['_notifications'];
useGameStore.getState().updateState(partial);
if (notifications?.length) {
const store = useGameStore.getState();
for (const n of notifications) {
store.addNotification({
title: n.title,
message: n.message,
type: n.type,
tick: store.meta.tickCount,
});
}
}
},
});
@@ -41,7 +57,7 @@ export function useGameLoop() {
engine.stop();
engineRef.current = null;
};
}, [companyName]);
}, [companyName, skip]);
useEffect(() => {
if (engineRef.current) {