Add Week 2 depth systems: research, events, competitors, talent, data

Tech tree with 21 research nodes across 5 categories (infrastructure,
efficiency, generation, specialization, safety). Research page with
category-grouped cards, progress tracking, prerequisite gating.

Event engine with 34 events across industry/regulatory/PR/internal/market
categories, weighted random firing, cooldowns, expiry, and choice modal
with consequence preview. Events auto-expire with default choice.

Competitor system with 3 rival AI labs (Prometheus AI, Nexus Labs, Titan
Computing), personality-driven milestone progression, and comparison UI.

Talent page with department hiring, headcount management, and key hire
recruitment from a pool of 10 named characters with special abilities.

Data marketplace with 8 purchasable datasets, user data flywheel from
subscribers, and data system processing in tick loop.

Era transition system checks revenue/capability/reputation thresholds.
All new systems integrated into tick processor with notifications.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-24 17:30:24 -04:00
parent d1d3eb4bf2
commit 8c9555bc08
19 changed files with 3166 additions and 21 deletions
+88
View File
@@ -7,6 +7,7 @@ import type {
CompetitorState, TalentState, DataState,
ReputationState, EventState, AchievementState,
DataCenter, GpuType, GpuInventory, TrainingJob,
ActiveResearch, EventConsequence, OwnedDataset,
} from '@ai-tycoon/shared';
import {
INITIAL_SETTINGS, SAVE_VERSION,
@@ -16,6 +17,7 @@ import {
INITIAL_REPUTATION, INITIAL_EVENTS, INITIAL_ACHIEVEMENTS,
GPU_CONFIGS,
} from '@ai-tycoon/shared';
import { INITIAL_RIVALS } from '@ai-tycoon/game-engine';
export type ActivePage = 'dashboard' | 'infrastructure' | 'research' | 'models'
| 'market' | 'talent' | 'data' | 'competitors' | 'finance' | 'settings';
@@ -48,6 +50,10 @@ interface Actions {
deployModel: (modelId: string) => void;
setProductPricing: (productLineId: string, field: string, value: number) => void;
toggleProductLine: (productLineId: string) => void;
startResearch: (research: ActiveResearch) => void;
resolveEvent: (instanceId: string, choiceIndex: number) => void;
hireDepartment: (departmentId: string, count: number) => void;
purchaseDataset: (dataset: OwnedDataset, cost: number) => void;
updateState: (partial: Partial<GameState>) => void;
}
@@ -111,6 +117,10 @@ export const useGameStore = create<Store>()(
createdAt: Date.now(),
lastTickTimestamp: Date.now(),
},
competitors: {
rivals: INITIAL_RIVALS,
industryBenchmark: 0,
},
activePage: 'dashboard',
notifications: [],
}),
@@ -218,6 +228,84 @@ export const useGameStore = create<Store>()(
},
})),
startResearch: (research) => set((s) => {
if (s.research.activeResearch) return s;
return {
research: { ...s.research, activeResearch: research },
};
}),
resolveEvent: (instanceId, choiceIndex) => set((s) => {
const event = s.events.activeEvents.find(e => e.instanceId === instanceId);
if (!event) return s;
const choice = event.choices[choiceIndex];
if (!choice) return s;
let money = s.economy.money;
let reputation = { ...s.reputation };
const consequences = choice.consequences;
for (const c of consequences) {
switch (c.type) {
case 'money': money += c.value; break;
case 'reputation': reputation = { ...reputation, score: Math.min(100, Math.max(0, reputation.score + c.value)), publicPerception: Math.min(100, Math.max(0, reputation.publicPerception + c.value)) }; break;
}
}
return {
economy: { ...s.economy, money: Math.max(0, money) },
reputation,
events: {
...s.events,
activeEvents: s.events.activeEvents.filter(e => e.instanceId !== instanceId),
eventHistory: [
...s.events.eventHistory,
{
eventId: event.eventId,
instanceId,
title: event.title,
category: event.category,
tick: s.meta.tickCount,
chosenOptionIndex: choiceIndex,
},
],
},
};
}),
hireDepartment: (departmentId, count) => set((s) => {
const costPerHire = 2000;
const totalCost = costPerHire * count;
if (s.economy.money < totalCost) return s;
return {
economy: { ...s.economy, money: s.economy.money - totalCost },
talent: {
...s.talent,
departments: {
...s.talent.departments,
[departmentId]: {
...s.talent.departments[departmentId as keyof typeof s.talent.departments],
headcount: s.talent.departments[departmentId as keyof typeof s.talent.departments].headcount + count,
},
},
},
};
}),
purchaseDataset: (dataset, cost) => set((s) => {
if (s.economy.money < cost) return s;
return {
economy: { ...s.economy, money: s.economy.money - cost },
data: {
...s.data,
ownedDatasets: [...s.data.ownedDatasets, dataset],
totalTrainingTokens: s.data.totalTrainingTokens + dataset.sizeTokens,
},
};
}),
updateState: (partial) => set((s) => {
const newState: Partial<Store> = {};
for (const key of Object.keys(partial) as (keyof GameState)[]) {