Add Week 3 polish and late-game features

VC funding system (seed through IPO with requirements gating), 15
achievements with engine checker, model tuning presets and unlockable
sliders, overload policy controls, open-source mechanic with reputation
boost, enhanced Recharts analytics (subscriber/reputation/revenue vs
expenses charts), M&A acquisition system, sidebar NEW badges on era
transitions, tutorial hints, and wired-up settings toggles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-24 17:56:40 -04:00
parent 8ea6c771a1
commit 8a8b49d934
20 changed files with 907 additions and 75 deletions
+87 -1
View File
@@ -9,6 +9,7 @@ import type {
DataCenter, GpuType, GpuInventory, TrainingJob,
ActiveResearch, EventConsequence, OwnedDataset,
} from '@ai-tycoon/shared';
import type { FundingRoundType, OverloadPolicy, TuningPreset, ModelTuning } from '@ai-tycoon/shared';
import {
INITIAL_SETTINGS, SAVE_VERSION,
INITIAL_ECONOMY, INITIAL_INFRASTRUCTURE, INITIAL_COMPUTE,
@@ -16,11 +17,13 @@ import {
INITIAL_COMPETITORS, INITIAL_TALENT, INITIAL_DATA,
INITIAL_REPUTATION, INITIAL_EVENTS, INITIAL_ACHIEVEMENTS,
GPU_CONFIGS,
FUNDING_ROUNDS,
OPEN_SOURCE_REPUTATION_BOOST,
} 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';
| 'market' | 'talent' | 'data' | 'competitors' | 'finance' | 'achievements' | 'settings';
interface UIState {
activePage: ActivePage;
@@ -54,6 +57,11 @@ interface Actions {
resolveEvent: (instanceId: string, choiceIndex: number) => void;
hireDepartment: (departmentId: string, count: number) => void;
purchaseDataset: (dataset: OwnedDataset, cost: number) => void;
raiseFunding: (roundType: FundingRoundType) => void;
openSourceModel: (modelId: string) => void;
setOverloadPolicy: (policy: Partial<OverloadPolicy>) => void;
setModelTuning: (modelId: string, tuning: Partial<ModelTuning>) => void;
acquireCompetitor: (competitorId: string) => void;
updateState: (partial: Partial<GameState>) => void;
}
@@ -306,6 +314,84 @@ export const useGameStore = create<Store>()(
};
}),
raiseFunding: (roundType) => set((s) => {
const config = FUNDING_ROUNDS[roundType];
if (!config) return s;
const amount = config.amount;
const dilution = config.dilution;
return {
economy: {
...s.economy,
money: s.economy.money + amount,
funding: {
...s.economy.funding,
totalRaised: s.economy.funding.totalRaised + amount,
founderEquity: s.economy.funding.founderEquity * (1 - dilution),
completedRounds: [
...s.economy.funding.completedRounds,
{ type: roundType, amount, dilution, completedAtTick: s.meta.tickCount },
],
isPublic: roundType === 'ipo',
},
},
};
}),
openSourceModel: (modelId) => set((s) => {
if (s.market.openSourcedModels.includes(modelId)) return s;
return {
market: {
...s.market,
openSourcedModels: [...s.market.openSourcedModels, modelId],
},
reputation: {
...s.reputation,
score: Math.min(100, s.reputation.score + OPEN_SOURCE_REPUTATION_BOOST),
publicPerception: Math.min(100, s.reputation.publicPerception + OPEN_SOURCE_REPUTATION_BOOST),
},
};
}),
setOverloadPolicy: (policy) => set((s) => ({
market: {
...s.market,
overloadPolicy: { ...s.market.overloadPolicy, ...policy },
},
})),
setModelTuning: (modelId, tuning) => set((s) => ({
models: {
...s.models,
trainedModels: s.models.trainedModels.map(m =>
m.id === modelId ? { ...m, tuning: { ...m.tuning, ...tuning } } : m,
),
},
})),
acquireCompetitor: (competitorId) => set((s) => {
const rival = s.competitors.rivals.find(r => r.id === competitorId);
if (!rival || rival.status === 'acquired') return s;
const cost = rival.estimatedRevenue * 500 + rival.estimatedCapability * 100_000;
if (s.economy.money < cost) return s;
return {
economy: { ...s.economy, money: s.economy.money - cost },
competitors: {
...s.competitors,
rivals: s.competitors.rivals.map(r =>
r.id === competitorId ? { ...r, status: 'acquired' as const } : r,
),
},
talent: {
...s.talent,
departments: {
...s.talent.departments,
research: { ...s.talent.departments.research, headcount: s.talent.departments.research.headcount + 5 },
engineering: { ...s.talent.departments.engineering, headcount: s.talent.departments.engineering.headcount + 3 },
},
},
};
}),
updateState: (partial) => set((s) => {
const newState: Partial<Store> = {};
for (const key of Object.keys(partial) as (keyof GameState)[]) {