From 95f2f971219c64a6f2f7632e810a81998e4b628d Mon Sep 17 00:00:00 2001 From: josh Date: Fri, 24 Apr 2026 19:54:44 -0400 Subject: [PATCH] Remove events system entirely The random events (GPU shortages, regulatory hearings, PR crises, etc.) added interruption without enough gameplay value. Removed all event types, definitions (~1800 lines of event data), the event processor, EventModal UI, store actions, and tick integration. Updated docs to reflect the removal. Bundle size drops ~47kB. Co-Authored-By: Claude Opus 4.6 --- apps/web/src/components/game/EventModal.tsx | 84 - .../src/components/game/OfflineCatchUp.tsx | 2 +- apps/web/src/components/layout/MainLayout.tsx | 2 - apps/web/src/hooks/useGameLoop.ts | 4 +- apps/web/src/pages/FinancePage.tsx | 2 +- apps/web/src/store/index.ts | 57 +- docs/architecture.md | 26 +- docs/how-to-play.md | 14 - packages/game-engine/src/data/events.ts | 1795 ----------------- packages/game-engine/src/index.ts | 3 +- .../game-engine/src/systems/eventSystem.ts | 118 -- packages/game-engine/src/tick.ts | 22 +- packages/shared/src/constants/gameBalance.ts | 1 - packages/shared/src/index.ts | 1 - packages/shared/src/types/events.ts | 74 - packages/shared/src/types/gameState.ts | 2 - 16 files changed, 16 insertions(+), 2191 deletions(-) delete mode 100644 apps/web/src/components/game/EventModal.tsx delete mode 100644 packages/game-engine/src/data/events.ts delete mode 100644 packages/game-engine/src/systems/eventSystem.ts delete mode 100644 packages/shared/src/types/events.ts diff --git a/apps/web/src/components/game/EventModal.tsx b/apps/web/src/components/game/EventModal.tsx deleted file mode 100644 index 8f95119..0000000 --- a/apps/web/src/components/game/EventModal.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { AlertTriangle, Newspaper, Building2, Users, TrendingUp, X } from 'lucide-react'; -import { useGameStore } from '@/store'; -import type { ActiveEvent, EventCategory } from '@ai-tycoon/shared'; - -const CATEGORY_ICONS: Record = { - industry: Newspaper, - regulatory: Building2, - pr: Users, - internal: AlertTriangle, - market: TrendingUp, -}; - -const CATEGORY_COLORS: Record = { - industry: 'border-blue-500/50 bg-blue-500/5', - regulatory: 'border-yellow-500/50 bg-yellow-500/5', - pr: 'border-purple-500/50 bg-purple-500/5', - internal: 'border-orange-500/50 bg-orange-500/5', - market: 'border-green-500/50 bg-green-500/5', -}; - -export function EventModal() { - const activeEvents = useGameStore((s) => s.events.activeEvents); - const resolveEvent = useGameStore((s) => s.resolveEvent); - - if (activeEvents.length === 0) return null; - - const event = activeEvents[0]; - const Icon = CATEGORY_ICONS[event.category]; - - return ( -
-
-
-
-
- -

{event.title}

-
- {event.category} -
- -

{event.description}

- -
- {event.choices.map((choice, idx) => ( - - ))} -
-
-
-
- ); -} - -function ConsequenceTag({ type, value }: { type: string; value: number }) { - const isPositive = value > 0; - const label = type === 'money' ? `$${Math.abs(value).toLocaleString()}` - : type === 'reputation' ? `${Math.abs(value)} rep` - : type === 'talent' ? `${Math.abs(value)} talent` - : type === 'research_speed' ? `${Math.round(Math.abs(value) * 100)}% R&D` - : `${type}: ${value}`; - - return ( - - {isPositive ? '+' : '-'}{label} - - ); -} diff --git a/apps/web/src/components/game/OfflineCatchUp.tsx b/apps/web/src/components/game/OfflineCatchUp.tsx index cf92ea1..f0e9a04 100644 --- a/apps/web/src/components/game/OfflineCatchUp.tsx +++ b/apps/web/src/components/game/OfflineCatchUp.tsx @@ -32,7 +32,7 @@ export function OfflineCatchUp({ missedTicks, onComplete }: { missedTicks: numbe meta: s.meta, economy: s.economy, infrastructure: s.infrastructure, compute: s.compute, research: s.research, models: s.models, market: s.market, competitors: s.competitors, talent: s.talent, - data: s.data, reputation: s.reputation, events: s.events, + data: s.data, reputation: s.reputation, achievements: s.achievements, }; }, diff --git a/apps/web/src/components/layout/MainLayout.tsx b/apps/web/src/components/layout/MainLayout.tsx index 7b36633..0d46b60 100644 --- a/apps/web/src/components/layout/MainLayout.tsx +++ b/apps/web/src/components/layout/MainLayout.tsx @@ -1,7 +1,6 @@ import { Sidebar } from './Sidebar'; import { TopBar } from './TopBar'; import { ToastContainer } from '@/components/common/ToastContainer'; -import { EventModal } from '@/components/game/EventModal'; import { useGameStore } from '@/store'; import { DashboardPage } from '@/pages/DashboardPage'; import { InfrastructurePage } from '@/pages/InfrastructurePage'; @@ -29,7 +28,6 @@ export function MainLayout() { - ); } diff --git a/apps/web/src/hooks/useGameLoop.ts b/apps/web/src/hooks/useGameLoop.ts index 15b50c5..9720f9b 100644 --- a/apps/web/src/hooks/useGameLoop.ts +++ b/apps/web/src/hooks/useGameLoop.ts @@ -1,5 +1,5 @@ import { useEffect, useRef } from 'react'; -import { GameEngine, setEventDefinitions, setAchievementDefinitions, EVENT_DEFINITIONS, ACHIEVEMENT_DEFINITIONS } from '@ai-tycoon/game-engine'; +import { GameEngine, setAchievementDefinitions, ACHIEVEMENT_DEFINITIONS } from '@ai-tycoon/game-engine'; import type { TickNotification } from '@ai-tycoon/game-engine'; import { useGameStore } from '@/store'; @@ -11,7 +11,6 @@ export function useGameLoop(skip = false) { useEffect(() => { if (!companyName || skip) return; - setEventDefinitions(EVENT_DEFINITIONS); setAchievementDefinitions(ACHIEVEMENT_DEFINITIONS); const engine = new GameEngine({ @@ -29,7 +28,6 @@ export function useGameLoop(skip = false) { talent: state.talent, data: state.data, reputation: state.reputation, - events: state.events, achievements: state.achievements, }; }, diff --git a/apps/web/src/pages/FinancePage.tsx b/apps/web/src/pages/FinancePage.tsx index 9f694a7..64394a3 100644 --- a/apps/web/src/pages/FinancePage.tsx +++ b/apps/web/src/pages/FinancePage.tsx @@ -21,7 +21,7 @@ export function FinancePage() { meta: state.meta, economy: state.economy, infrastructure: state.infrastructure, compute: state.compute, research: state.research, models: state.models, market: state.market, competitors: state.competitors, talent: state.talent, - data: state.data, reputation: state.reputation, events: state.events, + data: state.data, reputation: state.reputation, achievements: state.achievements, }; const fundingStatus = canRaiseFunding(gameStateForFunding); diff --git a/apps/web/src/store/index.ts b/apps/web/src/store/index.ts index 7b29690..08a6618 100644 --- a/apps/web/src/store/index.ts +++ b/apps/web/src/store/index.ts @@ -5,9 +5,9 @@ import type { EconomyState, InfrastructureState, ComputeState, ResearchState, ModelsState, MarketState, CompetitorState, TalentState, DataState, - ReputationState, EventState, AchievementState, + ReputationState, AchievementState, DataCenter, DCTier, RackSkuId, TrainingJob, - ActiveResearch, EventConsequence, OwnedDataset, LocationId, + ActiveResearch, OwnedDataset, LocationId, } from '@ai-tycoon/shared'; import type { FundingRoundType, OverloadPolicy, TuningPreset, ModelTuning } from '@ai-tycoon/shared'; import { @@ -15,7 +15,7 @@ import { INITIAL_ECONOMY, INITIAL_INFRASTRUCTURE, INITIAL_COMPUTE, INITIAL_RESEARCH, INITIAL_MODELS, INITIAL_MARKET, INITIAL_COMPETITORS, INITIAL_TALENT, INITIAL_DATA, - INITIAL_REPUTATION, INITIAL_EVENTS, INITIAL_ACHIEVEMENTS, + INITIAL_REPUTATION, INITIAL_ACHIEVEMENTS, DC_TIER_CONFIGS, RACK_SKU_CONFIGS, PIPELINE_ORDER_BASE_TICKS, DC_UPGRADE_COST_FRACTION, DC_UPGRADE_INCREMENT, FUNDING_ROUNDS, @@ -57,7 +57,6 @@ interface Actions { 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; raiseFunding: (roundType: FundingRoundType) => void; @@ -93,7 +92,6 @@ const initialGameState: GameState = { talent: INITIAL_TALENT, data: INITIAL_DATA, reputation: INITIAL_REPUTATION, - events: INITIAL_EVENTS, achievements: INITIAL_ACHIEVEMENTS, }; @@ -293,55 +291,6 @@ export const useGameStore = create()( }; }), - 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 }; - let talent = { ...s.talent }; - 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; - case 'regulation': reputation = { ...reputation, regulatoryStanding: Math.min(100, Math.max(0, reputation.regulatoryStanding + c.value)) }; break; - case 'talent': { - const dept = c.target as keyof typeof talent.departments | undefined; - if (dept && talent.departments[dept]) { - talent = { ...talent, departments: { ...talent.departments, [dept]: { ...talent.departments[dept], headcount: Math.max(0, talent.departments[dept].headcount + c.value) } } }; - } - break; - } - } - } - - return { - economy: { ...s.economy, money: Math.max(0, money) }, - reputation, - talent, - 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; diff --git a/docs/architecture.md b/docs/architecture.md index 55de45f..610f8d4 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -24,7 +24,7 @@ ai-tycoon/ ├── apps/ │ ├── web/ # React frontend (Vite) │ │ └── src/ -│ │ ├── components/ # layout/, common/, charts/, events/, game/ +│ │ ├── components/ # layout/, common/, charts/, game/ │ │ ├── pages/ # One page per game system │ │ ├── store/ # Zustand store with slice pattern │ │ ├── hooks/ # useGameLoop, useOfflineCatchUp, etc. @@ -49,7 +49,7 @@ ai-tycoon/ ├── engine.ts # GameEngine class (tick scheduling) ├── tick.ts # Tick processor (orchestrates systems) ├── systems/ # One file per simulation system - └── data/ # Event definitions, tech tree, datasets + └── data/ # Tech tree, achievements, datasets ``` ## Tick System @@ -83,10 +83,9 @@ Each tick runs these systems sequentially, since later systems depend on earlier 8. Economy — Revenue, expenses, net cash flow 9. Data — Data acquisition, user data flywheel 10. Competitors — AI rival decisions and actions -11. Events — Random + condition-triggered events -12. Era check — Threshold-based era transitions -13. Valuation — Dynamic company valuation -14. Achievements — Milestone checks (every 10 ticks) +11. Era check — Threshold-based era transitions +12. Valuation — Dynamic company valuation +13. Achievements — Milestone checks (every 10 ticks) ``` ### Offline Catch-Up @@ -118,7 +117,6 @@ The store uses a slice pattern with 14 slices, each owning a portion of the game | `talentSlice` | Departments, headcount, morale, hiring | | `dataSlice` | Datasets, quality, user data generation | | `reputationSlice` | Safety record, public perception, regulatory standing | -| `eventSlice` | Active events, history, cooldowns | | `achievementSlice` | Unlocked achievements | | `uiSlice` | Active page, notifications, modals (not persisted) | @@ -188,20 +186,12 @@ Training a model: Composite score (0-100) from four factors: - **Safety record** (30%): Damaged by safety incidents -- **Public perception** (30%): Affected by events and incidents +- **Public perception** (30%): Affected by incidents and open-sourcing models - **Employee satisfaction** (20%): Driven by department morale averages - **Regulatory standing** (20%): `50 + safetyResearch * 8 - eraIndex * 5` Safety incidents trigger when deployed models have safety scores below `LOW_SAFETY_THRESHOLD` (40), checked every 60 ticks with probability `SAFETY_INCIDENT_PROBABILITY_BASE * (threshold - safetyLevel)`. -### Event System (`eventSystem.ts`) - -- 40+ event definitions across 6 categories -- Events have conditions (era, tick count, state thresholds), weights, cooldowns, and max occurrences -- Most events present 2-3 choices with consequences affecting money, reputation, compute, or other state -- Template system with variable interpolation prevents repetition -- Geopolitical events (export controls, energy crises, natural disasters) affect specific regions - ### Competitor System (`competitorSystem.ts`) - 3+ AI rival labs with archetypes (safety-first, move-fast, big tech, open-source) @@ -283,7 +273,7 @@ Anonymous-first: players get a UUID token on first visit. Optional email/passwor ## Performance Considerations - Game engine runs outside React's render cycle; single `setState` per tick -- Achievements check every 10 ticks, events every 30 ticks +- Achievements check every 10 ticks - History arrays use circular buffers with configurable max sizes - Snapshots sample at intervals (every 60-120 ticks), not every tick - All systems are bounded O(n) where n is small (number of GPUs, models, competitors) @@ -299,4 +289,4 @@ Anonymous-first: players get a UUID token on first visit. Optional email/passwor 4. **Notifications via `_notifications`**: The tick processor attaches notifications as a side-channel property on the result, keeping the return type clean while enabling the UI to show contextual alerts. -5. **Cached definitions**: Event and achievement definitions are set once at game start via `setEventDefinitions` / `setAchievementDefinitions`, avoiding repeated imports in the hot tick path. +5. **Cached definitions**: Achievement definitions are set once at game start via `setAchievementDefinitions`, avoiding repeated imports in the hot tick path. diff --git a/docs/how-to-play.md b/docs/how-to-play.md index 370cb9a..adc0f96 100644 --- a/docs/how-to-play.md +++ b/docs/how-to-play.md @@ -145,19 +145,6 @@ Three rival AI labs compete with you. Each has a personality: In later eras (Big Tech and AGI), you can **acquire** competitors, absorbing their talent and technology. -### Events - -Random and conditional events keep the game dynamic. Categories include: - -- **Industry**: Breakthroughs, open-source releases, benchmarks -- **Regulatory**: Hearings, compliance requirements, AI bills -- **PR/Cultural**: Media coverage, safety debates, public opinion shifts -- **Internal**: Employee issues, technical problems -- **Market**: Demand spikes, pricing pressure -- **Geopolitical**: Export controls, energy crises, natural disasters - -Most events present 2-3 choices with meaningful tradeoffs. Some trigger chain events with delayed consequences. - ### Funding Raise capital through VC rounds as you grow: @@ -215,7 +202,6 @@ Invest in multiple specializations and diverse products. Spread your compute acr - **Timing funding rounds matters.** Raise too early and you give up equity cheaply. Raise too late and you run out of runway. - **Safety research compounds.** Each safety project improves all future models. - **Check competitor activity.** If a rival just released a strong model, expect to lose some subscribers unless you respond. -- **Events have lasting consequences.** Read the options carefully — some choices trigger follow-up events. - **The data flywheel is real.** More users generate more data, which trains better models, which attract more users. - **Deploy your models.** A trained model sitting idle generates zero revenue. - **Use speed controls.** Pause when making big decisions. Speed up during waiting periods. diff --git a/packages/game-engine/src/data/events.ts b/packages/game-engine/src/data/events.ts deleted file mode 100644 index cc932e5..0000000 --- a/packages/game-engine/src/data/events.ts +++ /dev/null @@ -1,1795 +0,0 @@ -import type { EventDefinition } from '@ai-tycoon/shared'; - -export const EVENT_DEFINITIONS: EventDefinition[] = [ - // ============================================================ - // INDUSTRY EVENTS (8) - // ============================================================ - { - id: 'industry_gpu_shortage', - title: 'GPU Shortage Hits the Market', - descriptionTemplate: - 'NVIDIA announces allocation limits on H100s. Every AI lab is scrambling for compute. Your procurement team needs direction.', - category: 'industry', - eras: ['startup', 'scaleup', 'bigtech'], - weight: 8, - cooldownTicks: 900, - maxOccurrences: 2, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Panic-buy at scalper prices', - description: - 'Secure GPUs now at 3x markup before the shortage gets worse.', - consequences: [ - { type: 'money', value: -80000 }, - { type: 'compute', value: 50 }, - ], - }, - { - label: 'Negotiate a cloud provider deal', - description: - 'Lock in a long-term cloud contract for reserved instances instead of buying hardware.', - consequences: [ - { type: 'money', value: -40000 }, - { type: 'compute', value: 25 }, - { type: 'reputation', value: 3 }, - ], - }, - { - label: 'Optimize existing infrastructure', - description: - 'Invest engineering time into squeezing more out of what you have.', - consequences: [ - { type: 'money', value: -10000 }, - { type: 'research_speed', value: 0.15 }, - { type: 'talent', value: -1 }, - ], - }, - ], - defaultChoiceIndex: 1, - expiryTicks: 300, - }, - { - id: 'industry_new_benchmark', - title: 'New AI Benchmark Released', - descriptionTemplate: - 'Researchers at Stanford publish "MegaEval-2000", a comprehensive benchmark that makes existing evals look like kindergarten quizzes. The industry is racing to top the leaderboard.', - category: 'industry', - eras: ['startup', 'scaleup', 'bigtech', 'agi'], - weight: 7, - cooldownTicks: 600, - maxOccurrences: 3, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Prioritize benchmark performance', - description: - 'Redirect research to chase leaderboard glory. Great for marketing, questionable for real progress.', - consequences: [ - { type: 'reputation', value: 10 }, - { type: 'research_speed', value: -0.1 }, - { type: 'money', value: -15000 }, - ], - }, - { - label: 'Ignore the hype', - description: - 'Stay focused on your own research agenda. The benchmark will be irrelevant in six months anyway.', - consequences: [ - { type: 'research_speed', value: 0.1 }, - { type: 'reputation', value: -3 }, - ], - }, - ], - defaultChoiceIndex: 1, - expiryTicks: 240, - }, - { - id: 'industry_ai_winter_scare', - title: 'AI Winter Scare', - descriptionTemplate: - 'A prominent AI researcher publishes a paper titled "Scaling Laws Have Hit a Wall." Tech Twitter melts down. VCs start sweating. Your investors are calling.', - category: 'industry', - eras: ['scaleup', 'bigtech'], - weight: 5, - cooldownTicks: 1800, - maxOccurrences: 1, - prerequisites: [], - conditions: [ - { field: 'economy.money', operator: 'gte', value: 50000 }, - ], - choices: [ - { - label: 'Double down publicly', - description: - 'Publish a bold rebuttal and announce expanded research plans. High risk, high reward.', - consequences: [ - { type: 'money', value: -60000 }, - { type: 'reputation', value: 12 }, - { type: 'research_speed', value: 0.2 }, - ], - }, - { - label: 'Quietly diversify', - description: - 'Hedge by pivoting some resources toward applied AI products with near-term revenue.', - consequences: [ - { type: 'money', value: 30000 }, - { type: 'reputation', value: -2 }, - { type: 'research_speed', value: -0.1 }, - ], - }, - { - label: 'Stay the course', - description: - 'Issue a measured statement and keep doing what you are doing. Boring but stable.', - consequences: [ - { type: 'reputation', value: 2 }, - ], - }, - ], - defaultChoiceIndex: 2, - expiryTicks: 480, - }, - { - id: 'industry_opensource_breakthrough', - title: 'Open-Source Breakthrough by Competitor', - descriptionTemplate: - 'MetaBook AI just dropped "LLaMA-Next" with Apache 2.0 licensing. It matches your best model on most benchmarks. Reddit is celebrating. Your sales team is panicking.', - category: 'industry', - eras: ['scaleup', 'bigtech'], - weight: 6, - cooldownTicks: 1200, - maxOccurrences: 2, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Open-source your own model', - description: - 'If you can not beat them, join them. Release your model weights and become the community darling.', - consequences: [ - { type: 'money', value: -30000 }, - { type: 'reputation', value: 15 }, - { type: 'research_speed', value: 0.1 }, - ], - }, - { - label: 'Emphasize proprietary advantages', - description: - 'Double down on enterprise features, safety, and reliability that open-source cannot match.', - consequences: [ - { type: 'money', value: 20000 }, - { type: 'reputation', value: -5 }, - ], - }, - { - label: 'Build on top of their model', - description: - 'Use the open-source model as a base and fine-tune it. Pragmatic, if a bit shameless.', - consequences: [ - { type: 'money', value: -5000 }, - { type: 'compute', value: -10 }, - { type: 'research_speed', value: 0.2 }, - ], - }, - ], - defaultChoiceIndex: 1, - expiryTicks: 360, - }, - { - id: 'industry_cloud_partnership', - title: 'Cloud Provider Partnership Offer', - descriptionTemplate: - 'Amazure Web Services reaches out about a strategic partnership. They will give you discounted compute in exchange for making your models available on their platform exclusively.', - category: 'industry', - eras: ['startup', 'scaleup'], - weight: 6, - cooldownTicks: 1200, - maxOccurrences: 1, - prerequisites: [], - conditions: [ - { field: 'reputation.score', operator: 'gte', value: 20 }, - ], - choices: [ - { - label: 'Accept the exclusive deal', - description: - 'Cheap compute now, vendor lock-in later. A Faustian bargain wrapped in a term sheet.', - consequences: [ - { type: 'money', value: 100000 }, - { type: 'compute', value: 80 }, - { type: 'reputation', value: -5 }, - ], - }, - { - label: 'Counter with a non-exclusive offer', - description: - 'Negotiate a preferred-partner deal while keeping your options open.', - consequences: [ - { type: 'money', value: 40000 }, - { type: 'compute', value: 30 }, - { type: 'reputation', value: 3 }, - ], - }, - { - label: 'Decline politely', - description: - 'Maintain full independence. Your infra team will figure it out.', - consequences: [ - { type: 'reputation', value: 5 }, - { type: 'money', value: -10000 }, - ], - }, - ], - defaultChoiceIndex: 1, - expiryTicks: 480, - }, - { - id: 'industry_hardware_price_crash', - title: 'Hardware Price Crash', - descriptionTemplate: - 'A new chip fab comes online in Taiwan, flooding the market with GPUs. Prices drop 40% overnight. Time to go shopping.', - category: 'industry', - eras: ['startup', 'scaleup', 'bigtech'], - weight: 4, - cooldownTicks: 1500, - maxOccurrences: 1, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Buy aggressively', - description: - 'Stock up while prices are low. Fill every rack.', - consequences: [ - { type: 'money', value: -50000 }, - { type: 'compute', value: 100 }, - ], - }, - { - label: 'Buy modestly', - description: - 'Pick up some extra capacity at the discount, but do not overextend.', - consequences: [ - { type: 'money', value: -20000 }, - { type: 'compute', value: 40 }, - ], - }, - ], - defaultChoiceIndex: 1, - expiryTicks: 240, - }, - { - id: 'industry_power_outage', - title: 'Data Center Power Outage', - descriptionTemplate: - 'A freak ice storm knocks out power to your primary data center in Oregon. Training runs are interrupted. Customers are experiencing downtime.', - category: 'industry', - eras: ['scaleup', 'bigtech', 'agi'], - weight: 5, - cooldownTicks: 900, - maxOccurrences: 2, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Emergency failover to backup region', - description: - 'Spin up in your secondary region immediately. Expensive but fast.', - consequences: [ - { type: 'money', value: -35000 }, - { type: 'reputation', value: 5 }, - ], - }, - { - label: 'Wait for power restoration', - description: - 'The power company says 12-24 hours. Probably fine. Probably.', - consequences: [ - { type: 'money', value: -5000 }, - { type: 'reputation', value: -10 }, - { type: 'compute', value: -20 }, - ], - }, - { - label: 'Invest in redundancy for next time', - description: - 'Fix the immediate issue and build out multi-region redundancy so this never happens again.', - consequences: [ - { type: 'money', value: -80000 }, - { type: 'reputation', value: 3 }, - { type: 'compute', value: 30 }, - ], - }, - ], - defaultChoiceIndex: 0, - expiryTicks: 120, - }, - { - id: 'industry_chip_export_controls', - title: 'Chip Export Controls Tightened', - descriptionTemplate: - 'The Department of Commerce announces new export restrictions on advanced AI chips. Your supply chain team is evaluating the impact.', - category: 'industry', - eras: ['scaleup', 'bigtech', 'agi'], - weight: 5, - cooldownTicks: 1500, - maxOccurrences: 1, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Lobby for exemptions', - description: - 'Hire lobbyists and argue your chips are for "research purposes only." Classic.', - consequences: [ - { type: 'money', value: -40000 }, - { type: 'regulation', value: 5 }, - { type: 'reputation', value: -3 }, - ], - }, - { - label: 'Invest in domestic alternatives', - description: - 'Start working with US-based chip startups. More expensive but politically safer.', - consequences: [ - { type: 'money', value: -50000 }, - { type: 'compute', value: 20 }, - { type: 'reputation', value: 5 }, - ], - }, - { - label: 'Adapt training to lower-end hardware', - description: - 'Invest in algorithmic efficiency to work with whatever chips are available.', - consequences: [ - { type: 'money', value: -15000 }, - { type: 'research_speed', value: 0.15 }, - ], - }, - ], - defaultChoiceIndex: 2, - expiryTicks: 480, - }, - - // ============================================================ - // REGULATORY EVENTS (6) - // ============================================================ - { - id: 'regulatory_congressional_hearing', - title: 'Congressional Hearing on AI', - descriptionTemplate: - 'You have been invited to testify before the Senate Commerce Committee on "The Promises and Perils of Artificial Intelligence." Senator from Nebraska wants to know if your AI can feel pain.', - category: 'regulatory', - eras: ['scaleup', 'bigtech', 'agi'], - weight: 6, - cooldownTicks: 1200, - maxOccurrences: 2, - prerequisites: [], - conditions: [ - { field: 'reputation.score', operator: 'gte', value: 30 }, - ], - choices: [ - { - label: 'Send the CEO with a charm offensive', - description: - 'Wear a hoodie, speak in platitudes about "responsible innovation," promise self-regulation.', - consequences: [ - { type: 'reputation', value: 8 }, - { type: 'regulation', value: 3 }, - { type: 'money', value: -10000 }, - ], - }, - { - label: 'Send the legal team', - description: - 'Play it safe with carefully prepared statements that say absolutely nothing.', - consequences: [ - { type: 'reputation', value: 2 }, - { type: 'money', value: -20000 }, - ], - }, - { - label: 'Decline to testify', - description: - 'You are too busy building the future. Congress can wait.', - consequences: [ - { type: 'reputation', value: -8 }, - { type: 'regulation', value: -5 }, - ], - }, - ], - defaultChoiceIndex: 1, - expiryTicks: 360, - }, - { - id: 'regulatory_eu_ai_act', - title: 'EU AI Act Compliance Deadline', - descriptionTemplate: - 'The EU AI Act classifies your flagship model as "high-risk." You have 90 days to comply or face fines up to 6% of global revenue. The compliance checklist is 47 pages long.', - category: 'regulatory', - eras: ['scaleup', 'bigtech', 'agi'], - weight: 7, - cooldownTicks: 1800, - maxOccurrences: 1, - prerequisites: [], - conditions: [ - { field: 'economy.money', operator: 'gte', value: 100000 }, - ], - choices: [ - { - label: 'Full compliance effort', - description: - 'Hire a compliance team and do everything by the book. Expensive but opens the EU market.', - consequences: [ - { type: 'money', value: -80000 }, - { type: 'reputation', value: 10 }, - { type: 'regulation', value: 10 }, - { type: 'talent', value: -1 }, - ], - }, - { - label: 'Minimal compliance', - description: - 'Do the bare minimum. Check the boxes, ship the paperwork, hope for the best.', - consequences: [ - { type: 'money', value: -25000 }, - { type: 'reputation', value: -2 }, - { type: 'regulation', value: 3 }, - ], - }, - { - label: 'Exit the EU market', - description: - 'Pull out of Europe entirely. American AI for American customers.', - consequences: [ - { type: 'money', value: -40000 }, - { type: 'reputation', value: -10 }, - { type: 'regulation', value: -3 }, - ], - }, - ], - defaultChoiceIndex: 0, - expiryTicks: 600, - }, - { - id: 'regulatory_safety_audit', - title: 'Safety Audit Demanded', - descriptionTemplate: - 'A coalition of AI safety organizations publishes an open letter demanding an independent audit of your model. #AuditTheirAI is trending on Twitter.', - category: 'regulatory', - eras: ['scaleup', 'bigtech', 'agi'], - weight: 6, - cooldownTicks: 900, - maxOccurrences: 2, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Welcome the audit', - description: - 'Invite auditors in, publish the results transparently. Nothing to hide here.', - consequences: [ - { type: 'money', value: -30000 }, - { type: 'reputation', value: 12 }, - { type: 'regulation', value: 8 }, - { type: 'research_speed', value: -0.1 }, - ], - }, - { - label: 'Conduct an internal audit instead', - description: - 'We will audit ourselves, thanks. Publish a glossy safety report.', - consequences: [ - { type: 'money', value: -15000 }, - { type: 'reputation', value: -3 }, - { type: 'regulation', value: 2 }, - ], - }, - ], - defaultChoiceIndex: 0, - expiryTicks: 360, - }, - { - id: 'regulatory_privacy_lawsuit', - title: 'Data Privacy Lawsuit', - descriptionTemplate: - 'A class-action lawsuit alleges your model was trained on personal data without consent. The plaintiffs\' lawyers are asking for $500M. Your legal team says the actual exposure is much lower, but the headlines are brutal.', - category: 'regulatory', - eras: ['scaleup', 'bigtech'], - weight: 5, - cooldownTicks: 1500, - maxOccurrences: 1, - prerequisites: [], - conditions: [ - { field: 'economy.money', operator: 'gte', value: 80000 }, - ], - choices: [ - { - label: 'Settle quickly', - description: - 'Pay to make it go away before the headlines get worse.', - consequences: [ - { type: 'money', value: -100000 }, - { type: 'reputation', value: -5 }, - ], - }, - { - label: 'Fight it in court', - description: - 'Hire top litigators and contest every claim. Could take years but sets important precedent.', - consequences: [ - { type: 'money', value: -50000 }, - { type: 'reputation', value: -8 }, - { type: 'regulation', value: 5 }, - ], - }, - { - label: 'Settle and announce new data practices', - description: - 'Settle the lawsuit and make a big show of implementing new privacy standards.', - consequences: [ - { type: 'money', value: -80000 }, - { type: 'reputation', value: 5 }, - { type: 'regulation', value: 8 }, - ], - }, - ], - defaultChoiceIndex: 2, - expiryTicks: 480, - }, - { - id: 'regulatory_lobbying_opportunity', - title: 'Lobbying Opportunity', - descriptionTemplate: - 'A new AI regulation bill is being drafted. A well-connected DC lobbying firm offers to represent your interests. They know the right people.', - category: 'regulatory', - eras: ['scaleup', 'bigtech', 'agi'], - weight: 5, - cooldownTicks: 900, - maxOccurrences: 2, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Hire the lobbyists', - description: - 'Play the game. Everyone else in the industry already has lobbyists.', - consequences: [ - { type: 'money', value: -45000 }, - { type: 'regulation', value: 10 }, - { type: 'reputation', value: -5 }, - ], - }, - { - label: 'Engage through industry associations', - description: - 'Work through existing industry groups for a more collaborative approach.', - consequences: [ - { type: 'money', value: -15000 }, - { type: 'regulation', value: 5 }, - { type: 'reputation', value: 2 }, - ], - }, - { - label: 'Stay out of politics', - description: - 'Focus on building great technology and let the chips fall where they may.', - consequences: [ - { type: 'reputation', value: 3 }, - { type: 'regulation', value: -3 }, - ], - }, - ], - defaultChoiceIndex: 1, - expiryTicks: 360, - }, - { - id: 'regulatory_sandbox_approval', - title: 'Regulatory Sandbox Approval', - descriptionTemplate: - 'Your application to the UK AI Regulatory Sandbox has been approved. You can deploy experimental models with reduced regulatory burden for 12 months.', - category: 'regulatory', - eras: ['startup', 'scaleup'], - weight: 4, - cooldownTicks: 1500, - maxOccurrences: 1, - prerequisites: [], - conditions: [ - { field: 'reputation.score', operator: 'gte', value: 15 }, - ], - choices: [ - { - label: 'Go all-in on experimentation', - description: - 'Use the sandbox to test your most aggressive models. Move fast, learn fast.', - consequences: [ - { type: 'research_speed', value: 0.3 }, - { type: 'reputation', value: 5 }, - { type: 'regulation', value: 3 }, - ], - }, - { - label: 'Use it conservatively', - description: - 'Test incrementally and build a track record of responsible innovation.', - consequences: [ - { type: 'research_speed', value: 0.15 }, - { type: 'reputation', value: 8 }, - { type: 'regulation', value: 6 }, - ], - }, - ], - defaultChoiceIndex: 1, - expiryTicks: 480, - }, - - // ============================================================ - // PR / CULTURAL EVENTS (6) - // ============================================================ - { - id: 'pr_viral_meme', - title: 'Viral AI Meme', - descriptionTemplate: - 'Someone discovers your chatbot gives hilariously unhinged responses when asked about medieval siege warfare. The screenshots go mega-viral. "CatapultGPT" is the top trending topic.', - category: 'pr', - eras: ['startup', 'scaleup', 'bigtech'], - weight: 7, - cooldownTicks: 600, - maxOccurrences: 3, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Lean into it', - description: - 'Post memes from the official account. Embrace the chaos. This is marketing you could never buy.', - consequences: [ - { type: 'reputation', value: 10 }, - { type: 'money', value: 15000 }, - ], - }, - { - label: 'Patch the behavior quickly', - description: - 'Fix the model and issue a professional statement about "ongoing improvements."', - consequences: [ - { type: 'reputation', value: -2 }, - { type: 'research_speed', value: -0.05 }, - ], - }, - ], - defaultChoiceIndex: 0, - expiryTicks: 180, - }, - { - id: 'pr_hallucination_viral', - title: 'Hallucination Incident Goes Viral', - descriptionTemplate: - 'Your model confidently told a user that the Eiffel Tower is located in Berlin and was built by Nikola Tesla. The screenshot has 2 million views. Late night comedians are having a field day.', - category: 'pr', - eras: ['startup', 'scaleup', 'bigtech'], - weight: 6, - cooldownTicks: 900, - maxOccurrences: 2, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Issue a transparent postmortem', - description: - 'Acknowledge the problem, explain the technical root cause, detail your plan to fix it.', - consequences: [ - { type: 'reputation', value: 3 }, - { type: 'money', value: -10000 }, - { type: 'research_speed', value: 0.1 }, - ], - }, - { - label: 'Downplay with humor', - description: - 'Tweet "Even AI needs a geography tutor sometimes" and move on.', - consequences: [ - { type: 'reputation', value: -5 }, - { type: 'money', value: 5000 }, - ], - }, - { - label: 'Rush out a hotfix', - description: - 'Emergency patch the model with guardrails specifically for geography questions.', - consequences: [ - { type: 'money', value: -20000 }, - { type: 'reputation', value: 1 }, - { type: 'research_speed', value: -0.1 }, - ], - }, - ], - defaultChoiceIndex: 0, - expiryTicks: 240, - }, - { - id: 'pr_celebrity_endorsement', - title: 'Celebrity Endorsement', - descriptionTemplate: - 'Pop star Arianna Grandioso posts an Instagram story raving about how she uses your AI to write song lyrics. 80 million followers just learned your name.', - category: 'pr', - eras: ['startup', 'scaleup', 'bigtech'], - weight: 4, - cooldownTicks: 1200, - maxOccurrences: 1, - prerequisites: [], - conditions: [ - { field: 'reputation.score', operator: 'gte', value: 10 }, - ], - choices: [ - { - label: 'Offer a sponsorship deal', - description: - 'Strike while the iron is hot. Pay for an official partnership.', - consequences: [ - { type: 'money', value: -60000 }, - { type: 'reputation', value: 15 }, - ], - }, - { - label: 'Send a thank-you and free API credits', - description: - 'A classy response that keeps the organic feel without breaking the bank.', - consequences: [ - { type: 'money', value: -5000 }, - { type: 'reputation', value: 8 }, - ], - }, - { - label: 'Ignore it', - description: - 'Celebrity endorsements are fleeting. Stay focused on the product.', - consequences: [ - { type: 'reputation', value: 3 }, - ], - }, - ], - defaultChoiceIndex: 1, - expiryTicks: 300, - }, - { - id: 'pr_ai_art_controversy', - title: 'AI Art Controversy', - descriptionTemplate: - 'An AI-generated image made with your model wins a major art competition. Artists are furious. #BanAIArt is trending. The winning piece is admittedly pretty good though.', - category: 'pr', - eras: ['startup', 'scaleup', 'bigtech'], - weight: 5, - cooldownTicks: 1200, - maxOccurrences: 1, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Side with the artists', - description: - 'Announce new content provenance tools and commit to compensating artists whose work was used in training.', - consequences: [ - { type: 'money', value: -40000 }, - { type: 'reputation', value: 8 }, - { type: 'research_speed', value: -0.1 }, - ], - }, - { - label: 'Defend AI creativity', - description: - 'Publish a blog post arguing that AI art is a new medium, not theft. Bold stance.', - consequences: [ - { type: 'reputation', value: -5 }, - { type: 'money', value: 10000 }, - { type: 'research_speed', value: 0.05 }, - ], - }, - { - label: 'Stay neutral', - description: - 'Release a measured statement about the "evolving relationship between AI and creativity."', - consequences: [ - { type: 'reputation', value: -1 }, - ], - }, - ], - defaultChoiceIndex: 0, - expiryTicks: 360, - }, - { - id: 'pr_employee_leak', - title: 'Employee Leaks Model Weights', - descriptionTemplate: - 'A disgruntled former employee uploads your latest model weights to a torrent site. By the time legal gets involved, 50,000 people have downloaded them. Hugging Face already has three fine-tunes.', - category: 'pr', - eras: ['scaleup', 'bigtech', 'agi'], - weight: 4, - cooldownTicks: 1500, - maxOccurrences: 1, - prerequisites: [], - conditions: [ - { field: 'economy.money', operator: 'gte', value: 60000 }, - ], - choices: [ - { - label: 'Embrace it as unplanned open-source', - description: - 'Spin the narrative. "We were going to open-source it anyway!" You were not.', - consequences: [ - { type: 'reputation', value: 5 }, - { type: 'money', value: -20000 }, - { type: 'research_speed', value: 0.1 }, - ], - }, - { - label: 'Pursue legal action', - description: - 'DMCA takedowns, lawsuit against the leaker, the whole nine yards.', - consequences: [ - { type: 'money', value: -50000 }, - { type: 'reputation', value: -8 }, - { type: 'regulation', value: 3 }, - ], - }, - { - label: 'Tighten security and move on', - description: - 'Invest in better internal security. What is leaked is leaked.', - consequences: [ - { type: 'money', value: -30000 }, - { type: 'reputation', value: -3 }, - { type: 'talent', value: -1 }, - ], - }, - ], - defaultChoiceIndex: 2, - expiryTicks: 240, - }, - { - id: 'pr_ai_bestseller', - title: 'AI Writes a Bestselling Novel', - descriptionTemplate: - 'A user publishes a novel written entirely with your model. It hits the NYT bestseller list. The literary world is divided. Oprah wants to do a special episode.', - category: 'pr', - eras: ['scaleup', 'bigtech'], - weight: 3, - cooldownTicks: 1800, - maxOccurrences: 1, - prerequisites: [], - conditions: [ - { field: 'reputation.score', operator: 'gte', value: 25 }, - ], - choices: [ - { - label: 'Celebrate publicly', - description: - 'Tout this as a milestone for AI creativity. Great for brand awareness.', - consequences: [ - { type: 'reputation', value: 12 }, - { type: 'money', value: 25000 }, - ], - }, - { - label: 'Distance yourself', - description: - 'Emphasize that the user is the creative force, not the tool. Authors already hate you enough.', - consequences: [ - { type: 'reputation', value: 3 }, - ], - }, - { - label: 'Launch a creative writing tier', - description: - 'Use the publicity to launch a premium creative writing subscription.', - consequences: [ - { type: 'money', value: 50000 }, - { type: 'reputation', value: 5 }, - { type: 'talent', value: 1 }, - ], - }, - ], - defaultChoiceIndex: 2, - expiryTicks: 360, - }, - - // ============================================================ - // INTERNAL EVENTS (7) - // ============================================================ - { - id: 'internal_researcher_poaching', - title: 'Key Researcher Poaching Attempt', - descriptionTemplate: - 'Goggle DeepThink is trying to poach your lead alignment researcher with a $2M signing bonus and a promise to "let them do whatever they want." They are seriously considering it.', - category: 'internal', - eras: ['startup', 'scaleup', 'bigtech', 'agi'], - weight: 7, - cooldownTicks: 600, - maxOccurrences: 3, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Counter-offer with equity', - description: - 'Match the salary and throw in a generous equity package. Talent retention is worth it.', - consequences: [ - { type: 'money', value: -40000 }, - { type: 'talent', value: 1 }, - { type: 'research_speed', value: 0.15 }, - ], - }, - { - label: 'Let them go gracefully', - description: - 'Wish them well, maintain the relationship. Good people leave sometimes.', - consequences: [ - { type: 'talent', value: -2 }, - { type: 'research_speed', value: -0.15 }, - { type: 'reputation', value: 2 }, - ], - }, - { - label: 'Promise a leadership role', - description: - 'Promote them to Head of Research. Sometimes a title is worth more than money.', - consequences: [ - { type: 'money', value: -15000 }, - { type: 'talent', value: 1 }, - { type: 'research_speed', value: 0.1 }, - { type: 'reputation', value: 3 }, - ], - }, - ], - defaultChoiceIndex: 0, - expiryTicks: 180, - }, - { - id: 'internal_burnout_wave', - title: 'Engineering Burnout Wave', - descriptionTemplate: - 'After months of 80-hour weeks chasing AGI, your engineering team is running on fumes and energy drinks. Three people called in sick this week. One just posted "thinking about touching grass" on LinkedIn.', - category: 'internal', - eras: ['startup', 'scaleup', 'bigtech', 'agi'], - weight: 7, - cooldownTicks: 800, - maxOccurrences: 3, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Mandatory rest week', - description: - 'Shut down non-critical operations for a week. Everyone recharges.', - consequences: [ - { type: 'research_speed', value: -0.1 }, - { type: 'talent', value: 2 }, - { type: 'money', value: -10000 }, - ], - }, - { - label: 'Hire more engineers to spread the load', - description: - 'Throw money at the problem. More people, less individual burden.', - consequences: [ - { type: 'money', value: -60000 }, - { type: 'talent', value: 3 }, - { type: 'research_speed', value: 0.05 }, - ], - }, - { - label: 'Pizza party and motivational speech', - description: - 'Order some pizzas, give a rousing speech about changing the world. That fixes burnout, right?', - consequences: [ - { type: 'money', value: -500 }, - { type: 'talent', value: -1 }, - { type: 'reputation', value: -2 }, - ], - }, - ], - defaultChoiceIndex: 0, - expiryTicks: 240, - }, - { - id: 'internal_brilliant_intern', - title: 'Brilliant Intern', - descriptionTemplate: - 'Your summer intern from MIT just submitted a PR that improves training efficiency by 23%. The senior engineers are equal parts impressed and threatened.', - category: 'internal', - eras: ['startup', 'scaleup', 'bigtech'], - weight: 5, - cooldownTicks: 1200, - maxOccurrences: 2, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Offer a full-time position immediately', - description: - 'Lock this down before anyone else does. Skip the formalities.', - consequences: [ - { type: 'money', value: -25000 }, - { type: 'talent', value: 2 }, - { type: 'research_speed', value: 0.2 }, - ], - }, - { - label: 'Extend the internship', - description: - 'Keep them for another semester. See if the magic continues.', - consequences: [ - { type: 'money', value: -8000 }, - { type: 'talent', value: 1 }, - { type: 'research_speed', value: 0.1 }, - ], - }, - { - label: 'Pat them on the back and move on', - description: - 'Good work, kid. Now get back to your homework.', - consequences: [ - { type: 'research_speed', value: 0.05 }, - { type: 'reputation', value: -2 }, - ], - }, - ], - defaultChoiceIndex: 0, - expiryTicks: 300, - }, - { - id: 'internal_hackathon', - title: 'Internal Hackathon', - descriptionTemplate: - 'Your team proposes a 48-hour hackathon. Past hackathons have produced some of your best features, but they also produced that chatbot that only speaks in haiku.', - category: 'internal', - eras: ['startup', 'scaleup', 'bigtech'], - weight: 6, - cooldownTicks: 800, - maxOccurrences: 3, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Full company hackathon', - description: - 'Everyone participates. Two days of chaos, Red Bull, and potential breakthroughs.', - consequences: [ - { type: 'money', value: -15000 }, - { type: 'research_speed', value: 0.2 }, - { type: 'talent', value: 1 }, - ], - }, - { - label: 'Small volunteer team only', - description: - 'Let enthusiasts participate while everyone else keeps the lights on.', - consequences: [ - { type: 'money', value: -5000 }, - { type: 'research_speed', value: 0.1 }, - ], - }, - { - label: 'Skip it, we have deadlines', - description: - 'Fun is for companies that are not trying to build AGI.', - consequences: [ - { type: 'talent', value: -1 }, - { type: 'reputation', value: -1 }, - ], - }, - ], - defaultChoiceIndex: 0, - expiryTicks: 240, - }, - { - id: 'internal_security_breach', - title: 'Security Breach Discovered', - descriptionTemplate: - 'Your security team discovers unauthorized access to internal systems. Someone has been exfiltrating training data for weeks. The breach appears to originate from a state-sponsored group.', - category: 'internal', - eras: ['scaleup', 'bigtech', 'agi'], - weight: 4, - cooldownTicks: 1500, - maxOccurrences: 1, - prerequisites: [], - conditions: [ - { field: 'economy.money', operator: 'gte', value: 50000 }, - ], - choices: [ - { - label: 'Full incident response', - description: - 'Engage a top-tier cybersecurity firm, notify affected parties, do everything by the book.', - consequences: [ - { type: 'money', value: -80000 }, - { type: 'reputation', value: 5 }, - { type: 'regulation', value: 5 }, - { type: 'research_speed', value: -0.15 }, - ], - }, - { - label: 'Handle it quietly internally', - description: - 'Patch the vulnerability, rotate credentials, and hope nobody notices.', - consequences: [ - { type: 'money', value: -20000 }, - { type: 'reputation', value: -10 }, - { type: 'research_speed', value: -0.05 }, - ], - }, - { - label: 'Disclose and collaborate with authorities', - description: - 'Go public and work with the FBI. Maximum transparency, maximum disruption.', - consequences: [ - { type: 'money', value: -50000 }, - { type: 'reputation', value: 10 }, - { type: 'regulation', value: 8 }, - { type: 'research_speed', value: -0.2 }, - ], - }, - ], - defaultChoiceIndex: 0, - expiryTicks: 180, - }, - { - id: 'internal_office_expansion', - title: 'Office Expansion Needed', - descriptionTemplate: - 'You have more employees than desks. The break room has been converted into a workspace. Someone is coding in the supply closet. Time to think about space.', - category: 'internal', - eras: ['startup', 'scaleup'], - weight: 6, - cooldownTicks: 1200, - maxOccurrences: 2, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Lease a fancy new office', - description: - 'Move to a gleaming office with a roof deck and a meditation room. Attract top talent.', - consequences: [ - { type: 'money', value: -100000 }, - { type: 'talent', value: 2 }, - { type: 'reputation', value: 5 }, - ], - }, - { - label: 'Go fully remote', - description: - 'Cancel the lease entirely. Embrace the future of work. Save a fortune.', - consequences: [ - { type: 'money', value: 30000 }, - { type: 'talent', value: 1 }, - { type: 'research_speed', value: -0.05 }, - ], - }, - { - label: 'Find a modest expansion', - description: - 'Lease the floor above you. Practical, not glamorous.', - consequences: [ - { type: 'money', value: -40000 }, - { type: 'talent', value: 1 }, - ], - }, - ], - defaultChoiceIndex: 2, - expiryTicks: 480, - }, - { - id: 'internal_culture_clash', - title: 'Safety vs. Speed Culture Clash', - descriptionTemplate: - 'A heated Slack thread between the safety team and the product team has devolved into a company-wide debate. The safety team wants to delay the next launch by three months. The product team says competitors will eat your lunch.', - category: 'internal', - eras: ['scaleup', 'bigtech', 'agi'], - weight: 6, - cooldownTicks: 900, - maxOccurrences: 2, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Side with safety', - description: - 'Delay the launch. Responsible AI development is the priority.', - consequences: [ - { type: 'research_speed', value: -0.15 }, - { type: 'reputation', value: 8 }, - { type: 'regulation', value: 5 }, - ], - }, - { - label: 'Side with product', - description: - 'Ship it. You can always patch safety issues after launch.', - consequences: [ - { type: 'research_speed', value: 0.15 }, - { type: 'reputation', value: -5 }, - { type: 'money', value: 30000 }, - ], - }, - { - label: 'Find a compromise', - description: - 'Launch with reduced capabilities and a safety roadmap. Nobody is happy, which means it is probably the right call.', - consequences: [ - { type: 'reputation', value: 3 }, - { type: 'money', value: 10000 }, - { type: 'talent', value: -1 }, - ], - }, - ], - defaultChoiceIndex: 2, - expiryTicks: 300, - }, - - // ============================================================ - // MARKET EVENTS (7) - // ============================================================ - { - id: 'market_enterprise_contract', - title: 'Enterprise Contract Offer', - descriptionTemplate: - 'MegaCorp Industries wants to deploy your AI across their 50,000-person organization. The contract is lucrative but they want custom fine-tuning, 99.99% uptime SLA, and a dedicated support team.', - category: 'market', - eras: ['scaleup', 'bigtech'], - weight: 6, - cooldownTicks: 900, - maxOccurrences: 2, - prerequisites: [], - conditions: [ - { field: 'reputation.score', operator: 'gte', value: 20 }, - ], - choices: [ - { - label: 'Accept the full contract', - description: - 'Commit the resources. Enterprise revenue is the path to sustainability.', - consequences: [ - { type: 'money', value: 150000 }, - { type: 'talent', value: -1 }, - { type: 'research_speed', value: -0.1 }, - { type: 'reputation', value: 5 }, - ], - }, - { - label: 'Negotiate reduced scope', - description: - 'Take the contract but with a more realistic SLA and timeline.', - consequences: [ - { type: 'money', value: 80000 }, - { type: 'reputation', value: 3 }, - ], - }, - { - label: 'Decline - stay focused on the API', - description: - 'Enterprise contracts are a distraction from your core mission.', - consequences: [ - { type: 'reputation', value: -3 }, - { type: 'research_speed', value: 0.05 }, - ], - }, - ], - defaultChoiceIndex: 1, - expiryTicks: 360, - }, - { - id: 'market_consumer_surge', - title: 'Consumer Demand Surge', - descriptionTemplate: - 'Your chatbot goes viral on TikTok after someone uses it to plan an entire wedding. Traffic spikes 500% overnight. Your servers are sweating.', - category: 'market', - eras: ['startup', 'scaleup', 'bigtech'], - weight: 6, - cooldownTicks: 800, - maxOccurrences: 2, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Scale up infrastructure immediately', - description: - 'Throw money at servers to handle the surge. Do not let this moment slip.', - consequences: [ - { type: 'money', value: -45000 }, - { type: 'reputation', value: 8 }, - { type: 'compute', value: -30 }, - ], - }, - { - label: 'Implement a waitlist', - description: - 'Create artificial scarcity. Nothing builds hype like exclusivity.', - consequences: [ - { type: 'money', value: 10000 }, - { type: 'reputation', value: 5 }, - ], - }, - { - label: 'Rate-limit aggressively', - description: - 'Keep the servers stable at the cost of user experience. Some visitors will bounce.', - consequences: [ - { type: 'reputation', value: -3 }, - { type: 'money', value: -5000 }, - ], - }, - ], - defaultChoiceIndex: 0, - expiryTicks: 180, - }, - { - id: 'market_api_price_war', - title: 'API Price War', - descriptionTemplate: - 'Competitor "CheapTokens Inc." slashes their API prices by 70%. Your customers are asking pointed questions about your pricing. The race to the bottom has begun.', - category: 'market', - eras: ['scaleup', 'bigtech'], - weight: 7, - cooldownTicks: 1200, - maxOccurrences: 2, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Match their prices', - description: - 'Cut prices to stay competitive. Margins will hurt but you keep customers.', - consequences: [ - { type: 'money', value: -60000 }, - { type: 'reputation', value: 5 }, - ], - }, - { - label: 'Compete on quality, not price', - description: - 'Emphasize reliability, safety, and performance. Premium product, premium price.', - consequences: [ - { type: 'money', value: -10000 }, - { type: 'reputation', value: 3 }, - { type: 'research_speed', value: 0.1 }, - ], - }, - { - label: 'Introduce a free tier', - description: - 'Launch a limited free tier to hook developers, then upsell them.', - consequences: [ - { type: 'money', value: -30000 }, - { type: 'reputation', value: 8 }, - { type: 'talent', value: 1 }, - ], - }, - ], - defaultChoiceIndex: 1, - expiryTicks: 360, - }, - { - id: 'market_vc_interest', - title: 'Venture Capital Interest', - descriptionTemplate: - 'Andreessen Borrowitz wants to lead your Series B at a $2B valuation. The partner keeps texting your CEO "just vibing with your mission" at 2am.', - category: 'market', - eras: ['startup', 'scaleup'], - weight: 5, - cooldownTicks: 1500, - maxOccurrences: 1, - prerequisites: [], - conditions: [ - { field: 'reputation.score', operator: 'gte', value: 15 }, - ], - choices: [ - { - label: 'Take the funding', - description: - 'Accept the investment. Cash is king in the AI arms race.', - consequences: [ - { type: 'money', value: 200000 }, - { type: 'reputation', value: 8 }, - ], - }, - { - label: 'Negotiate better terms', - description: - 'Push back on valuation and board seats. Show them you have leverage.', - consequences: [ - { type: 'money', value: 150000 }, - { type: 'reputation', value: 10 }, - ], - }, - { - label: 'Bootstrap instead', - description: - 'You do not need their money or their opinions. Revenue will fund growth.', - consequences: [ - { type: 'reputation', value: 5 }, - { type: 'research_speed', value: -0.1 }, - ], - }, - ], - defaultChoiceIndex: 1, - expiryTicks: 480, - }, - { - id: 'market_crypto_api_access', - title: 'Crypto Company Wants API Access', - descriptionTemplate: - 'BlockchainBro Labs wants to integrate your AI into their "decentralized autonomous intelligence" platform. They are offering to pay in a mix of cash and their own cryptocurrency, $MINDCOIN.', - category: 'market', - eras: ['startup', 'scaleup'], - weight: 5, - cooldownTicks: 900, - maxOccurrences: 2, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Accept cash-only deal', - description: - 'Their money spends the same as anyone else is. Just no crypto, please.', - consequences: [ - { type: 'money', value: 40000 }, - { type: 'reputation', value: -3 }, - ], - }, - { - label: 'Accept the mixed payment', - description: - 'Take half cash, half $MINDCOIN. Maybe it moons. Probably it does not.', - consequences: [ - { type: 'money', value: 25000 }, - { type: 'reputation', value: -5 }, - ], - }, - { - label: 'Decline politely', - description: - 'Pass on the reputational risk. Your PR team will thank you.', - consequences: [ - { type: 'reputation', value: 3 }, - ], - }, - ], - defaultChoiceIndex: 2, - expiryTicks: 300, - }, - { - id: 'market_government_contract', - title: 'Government Contract Opportunity', - descriptionTemplate: - 'The Department of Defense puts out an RFP for AI-powered logistics optimization. The contract is worth a fortune, but the ethics debate will be intense.', - category: 'market', - eras: ['scaleup', 'bigtech', 'agi'], - weight: 5, - cooldownTicks: 1500, - maxOccurrences: 1, - prerequisites: [], - conditions: [ - { field: 'reputation.score', operator: 'gte', value: 25 }, - { field: 'economy.money', operator: 'gte', value: 80000 }, - ], - choices: [ - { - label: 'Bid on the contract', - description: - 'The work is strictly logistics. Totally ethical. Your employees might disagree.', - consequences: [ - { type: 'money', value: 180000 }, - { type: 'reputation', value: -10 }, - { type: 'talent', value: -2 }, - { type: 'regulation', value: 5 }, - ], - }, - { - label: 'Bid with ethical constraints', - description: - 'Submit a proposal that explicitly excludes weapons applications. Less money, fewer protests.', - consequences: [ - { type: 'money', value: 100000 }, - { type: 'reputation', value: 3 }, - { type: 'regulation', value: 8 }, - ], - }, - { - label: 'Decline on principle', - description: - 'Issue a public statement about your commitment to civilian AI. Your employees cheer.', - consequences: [ - { type: 'reputation', value: 10 }, - { type: 'talent', value: 1 }, - ], - }, - ], - defaultChoiceIndex: 1, - expiryTicks: 480, - }, - { - id: 'market_startup_acquisition', - title: 'Startup Acquisition Opportunity', - descriptionTemplate: - 'A tiny but brilliant startup called "NeuralNuggets" has built an incredible data curation pipeline. They are running out of money and open to acquisition talks.', - category: 'market', - eras: ['scaleup', 'bigtech'], - weight: 4, - cooldownTicks: 1200, - maxOccurrences: 1, - prerequisites: [], - conditions: [ - { field: 'economy.money', operator: 'gte', value: 120000 }, - ], - choices: [ - { - label: 'Acquire them', - description: - 'Buy the company, absorb the team, integrate the tech. Classic big tech move.', - consequences: [ - { type: 'money', value: -120000 }, - { type: 'talent', value: 3 }, - { type: 'research_speed', value: 0.25 }, - { type: 'reputation', value: 5 }, - ], - }, - { - label: 'Offer a licensing deal instead', - description: - 'Pay for access to their tech without the overhead of an acquisition.', - consequences: [ - { type: 'money', value: -30000 }, - { type: 'research_speed', value: 0.15 }, - ], - }, - { - label: 'Pass', - description: - 'You can build your own data pipeline. It will only take... six months.', - consequences: [ - { type: 'research_speed', value: -0.05 }, - ], - }, - ], - defaultChoiceIndex: 0, - expiryTicks: 360, - }, - - // ============================================================ - // GEOPOLITICAL EVENTS (6) - // ============================================================ - { - id: 'geo_export_controls', - title: 'New AI Export Controls', - descriptionTemplate: - 'The government has announced export controls on advanced AI chips and models. International operations may be affected.', - category: 'regulatory', - eras: ['scaleup', 'bigtech', 'agi'], - weight: 6, - cooldownTicks: 1200, - maxOccurrences: 2, - prerequisites: [], - conditions: [{ field: 'models.trainedModels.length', operator: 'gte', value: 2 }], - choices: [ - { - label: 'Lobby for exemptions', - description: 'Spend money to lobby policymakers for carve-outs.', - consequences: [ - { type: 'money', value: -100000 }, - { type: 'regulation', value: 10 }, - ], - }, - { - label: 'Comply and adapt', - description: 'Accept the restrictions and restructure international operations.', - consequences: [ - { type: 'reputation', value: 5 }, - { type: 'regulation', value: 5 }, - { type: 'money', value: -30000 }, - ], - }, - { - label: 'Challenge in court', - description: 'File a legal challenge. Risky but could set favorable precedent.', - consequences: [ - { type: 'money', value: -200000 }, - { type: 'regulation', value: -10 }, - { type: 'reputation', value: -5 }, - ], - }, - ], - defaultChoiceIndex: 1, - expiryTicks: 300, - }, - { - id: 'geo_energy_crisis', - title: 'Energy Crisis in Data Center Region', - descriptionTemplate: - 'A regional energy crisis is driving electricity costs up 40% in key data center locations. Your infrastructure costs are spiking.', - category: 'market', - eras: ['scaleup', 'bigtech', 'agi'], - weight: 5, - cooldownTicks: 1500, - maxOccurrences: 2, - prerequisites: [], - conditions: [{ field: 'infrastructure.dataCenters.length', operator: 'gte', value: 1 }], - choices: [ - { - label: 'Negotiate long-term energy contracts', - description: 'Lock in rates now before they go higher. Requires upfront capital.', - consequences: [ - { type: 'money', value: -150000 }, - ], - }, - { - label: 'Invest in on-site renewable energy', - description: 'Install solar panels and battery storage. High upfront cost, long-term savings.', - consequences: [ - { type: 'money', value: -300000 }, - { type: 'reputation', value: 10 }, - ], - }, - { - label: 'Absorb the costs', - description: 'Take the hit and hope prices normalize.', - consequences: [ - { type: 'money', value: -50000 }, - ], - }, - ], - defaultChoiceIndex: 2, - expiryTicks: 300, - }, - { - id: 'geo_natural_disaster', - title: 'Natural Disaster Threatens Data Center', - descriptionTemplate: - 'Severe weather has been reported near one of your data center locations. Your ops team is preparing contingency plans.', - category: 'industry', - eras: ['startup', 'scaleup', 'bigtech', 'agi'], - weight: 4, - cooldownTicks: 1800, - maxOccurrences: 3, - prerequisites: [], - conditions: [{ field: 'infrastructure.dataCenters.length', operator: 'gte', value: 1 }], - choices: [ - { - label: 'Activate disaster recovery', - description: 'Failover to backup systems. Costs money but protects uptime.', - consequences: [ - { type: 'money', value: -50000 }, - { type: 'reputation', value: 3 }, - ], - }, - { - label: 'Ride it out', - description: 'Hope the storm misses. Save money but risk downtime.', - consequences: [ - { type: 'reputation', value: -8 }, - ], - }, - ], - defaultChoiceIndex: 0, - expiryTicks: 180, - }, - { - id: 'geo_ai_safety_summit', - title: 'International AI Safety Summit', - descriptionTemplate: - 'World leaders are convening a summit on AI safety. You have been invited to present your company\'s approach to responsible AI.', - category: 'regulatory', - eras: ['scaleup', 'bigtech', 'agi'], - weight: 6, - cooldownTicks: 1500, - maxOccurrences: 2, - prerequisites: [], - conditions: [{ field: 'reputation.score', operator: 'gte', value: 30 }], - choices: [ - { - label: 'Sign voluntary safety commitments', - description: 'Join the safety pledge. Good PR, but constrains future development speed.', - consequences: [ - { type: 'reputation', value: 15 }, - { type: 'regulation', value: 10 }, - { type: 'research_speed', value: -0.05 }, - ], - }, - { - label: 'Attend but make no commitments', - description: 'Show up, listen, and keep your options open.', - consequences: [ - { type: 'reputation', value: 3 }, - ], - }, - { - label: 'Skip the summit', - description: 'Your engineers have models to train. No time for politics.', - consequences: [ - { type: 'reputation', value: -10 }, - { type: 'regulation', value: -5 }, - ], - }, - ], - defaultChoiceIndex: 1, - expiryTicks: 240, - }, - { - id: 'geo_talent_immigration', - title: 'Immigration Policy Changes', - descriptionTemplate: - 'New immigration restrictions are making it harder to recruit international AI talent. Your HR team is concerned about pipeline impact.', - category: 'regulatory', - eras: ['scaleup', 'bigtech'], - weight: 4, - cooldownTicks: 1200, - maxOccurrences: 1, - prerequisites: [], - conditions: [], - choices: [ - { - label: 'Open a remote research lab abroad', - description: 'Set up a satellite research office in a talent-friendly country.', - consequences: [ - { type: 'money', value: -200000 }, - { type: 'talent', value: 5, target: 'research' }, - ], - }, - { - label: 'Increase domestic salaries', - description: 'Compete harder for local talent with premium compensation.', - consequences: [ - { type: 'money', value: -100000 }, - { type: 'talent', value: 2, target: 'research' }, - ], - }, - ], - defaultChoiceIndex: 1, - expiryTicks: 360, - }, - { - id: 'geo_data_sovereignty', - title: 'Data Sovereignty Regulations', - descriptionTemplate: - 'Multiple countries are enacting data localization laws. User data must be stored within national borders, complicating your global infrastructure.', - category: 'regulatory', - eras: ['bigtech', 'agi'], - weight: 5, - cooldownTicks: 1500, - maxOccurrences: 1, - prerequisites: [], - conditions: [{ field: 'market.consumers.totalSubscribers', operator: 'gte', value: 5000 }], - choices: [ - { - label: 'Build regional data centers', - description: 'Comply by deploying infrastructure in affected regions. Expensive but thorough.', - consequences: [ - { type: 'money', value: -500000 }, - { type: 'regulation', value: 15 }, - { type: 'reputation', value: 5 }, - ], - }, - { - label: 'Withdraw from those markets', - description: 'Stop serving users in affected regions to avoid compliance costs.', - consequences: [ - { type: 'reputation', value: -10 }, - ], - }, - { - label: 'Implement data partitioning', - description: 'Use technical solutions to segment data by region without new DCs.', - consequences: [ - { type: 'money', value: -100000 }, - { type: 'regulation', value: 5 }, - ], - }, - ], - defaultChoiceIndex: 2, - expiryTicks: 360, - }, -]; diff --git a/packages/game-engine/src/index.ts b/packages/game-engine/src/index.ts index b8f7fd6..879d722 100644 --- a/packages/game-engine/src/index.ts +++ b/packages/game-engine/src/index.ts @@ -1,10 +1,9 @@ export { GameEngine } from './engine'; -export { processTick, setEventDefinitions, setAchievementDefinitions } from './tick'; +export { processTick, setAchievementDefinitions } from './tick'; export type { TickNotification } from './tick'; export { getAvailableResearch, getResearchNode } from './systems/researchSystem'; export { canRaiseFunding, getNextFundingRound, computeValuation } from './systems/fundingSystem'; export { TECH_TREE } from './data/techTree'; export { INITIAL_RIVALS } from './data/competitors'; export { KEY_HIRE_POOL } from './data/keyHires'; -export { EVENT_DEFINITIONS } from './data/events'; export { ACHIEVEMENT_DEFINITIONS } from './data/achievements'; diff --git a/packages/game-engine/src/systems/eventSystem.ts b/packages/game-engine/src/systems/eventSystem.ts deleted file mode 100644 index 1050442..0000000 --- a/packages/game-engine/src/systems/eventSystem.ts +++ /dev/null @@ -1,118 +0,0 @@ -import type { GameState, EventState, ActiveEvent, EventDefinition, EventCondition } from '@ai-tycoon/shared'; -import { uuid } from '@ai-tycoon/shared'; - -export interface EventTickResult { - events: EventState; - newEvents: ActiveEvent[]; -} - -export function processEvents( - state: GameState, - definitions: EventDefinition[], -): EventTickResult { - const tick = state.meta.tickCount; - const events = { ...state.events }; - const newEvents: ActiveEvent[] = []; - - // Remove expired events (auto-choose default) - const stillActive: ActiveEvent[] = []; - for (const event of events.activeEvents) { - if (tick >= event.expiresAtTick) { - events.eventHistory = [ - ...events.eventHistory, - { - eventId: event.eventId, - instanceId: event.instanceId, - title: event.title, - category: event.category, - tick, - chosenOptionIndex: event.defaultChoiceIndex, - }, - ]; - } else { - stillActive.push(event); - } - } - events.activeEvents = stillActive; - - if (events.eventHistory.length > 50) { - events.eventHistory = events.eventHistory.slice(-50); - } - - // Only try to fire a new event every 30 ticks, and max 1 active at a time - if (tick % 30 !== 0 || events.activeEvents.length > 0) { - return { events, newEvents }; - } - - const eraOrder = ['startup', 'scaleup', 'bigtech', 'agi']; - const currentEraIdx = eraOrder.indexOf(state.meta.currentEra); - - const eligible = definitions.filter(def => { - if (!def.eras.some(e => eraOrder.indexOf(e) <= currentEraIdx)) return false; - const occ = events.eventOccurrences[def.id] ?? 0; - if (occ >= def.maxOccurrences) return false; - const cooldownEnd = events.eventCooldowns[def.id] ?? 0; - if (tick < cooldownEnd) return false; - if (def.prerequisites.some(p => !state.research.completedResearch.includes(p))) return false; - if (!def.conditions.every(c => evaluateCondition(state, c))) return false; - return true; - }); - - if (eligible.length === 0) return { events, newEvents }; - - const totalWeight = eligible.reduce((s, d) => s + d.weight, 0); - let roll = Math.random() * totalWeight; - let chosen: EventDefinition | null = null; - for (const def of eligible) { - roll -= def.weight; - if (roll <= 0) { chosen = def; break; } - } - if (!chosen) return { events, newEvents }; - - // Only fire with 30% probability per check to space events out - if (Math.random() > 0.3) return { events, newEvents }; - - const activeEvent: ActiveEvent = { - eventId: chosen.id, - instanceId: uuid(), - triggeredAtTick: tick, - expiresAtTick: tick + chosen.expiryTicks, - title: chosen.title, - description: chosen.descriptionTemplate, - category: chosen.category, - choices: chosen.choices, - defaultChoiceIndex: chosen.defaultChoiceIndex, - }; - - events.activeEvents = [...events.activeEvents, activeEvent]; - events.eventCooldowns = { ...events.eventCooldowns, [chosen.id]: tick + chosen.cooldownTicks }; - events.eventOccurrences = { - ...events.eventOccurrences, - [chosen.id]: (events.eventOccurrences[chosen.id] ?? 0) + 1, - }; - newEvents.push(activeEvent); - - return { events, newEvents }; -} - -function evaluateCondition(state: GameState, condition: EventCondition): boolean { - const value = getNestedValue(state, condition.field); - if (value === undefined) return false; - switch (condition.operator) { - case 'gt': return value > condition.value; - case 'lt': return value < condition.value; - case 'gte': return value >= condition.value; - case 'lte': return value <= condition.value; - case 'eq': return value === condition.value; - } -} - -function getNestedValue(obj: object, path: string): number | undefined { - const parts = path.split('.'); - let current: unknown = obj; - for (const part of parts) { - if (current == null || typeof current !== 'object') return undefined; - current = (current as Record)[part]; - } - return typeof current === 'number' ? current : undefined; -} diff --git a/packages/game-engine/src/tick.ts b/packages/game-engine/src/tick.ts index cfcdebe..a02d0b1 100644 --- a/packages/game-engine/src/tick.ts +++ b/packages/game-engine/src/tick.ts @@ -1,4 +1,4 @@ -import type { GameState, EventDefinition, AchievementDefinition } from '@ai-tycoon/shared'; +import type { GameState, AchievementDefinition } from '@ai-tycoon/shared'; import { processEconomy } from './systems/economySystem'; import { processInfrastructure } from './systems/infrastructureSystem'; import { processCompute } from './systems/computeSystem'; @@ -7,7 +7,6 @@ import { processModels } from './systems/modelSystem'; import { processMarket } from './systems/marketSystem'; import { processReputation } from './systems/reputationSystem'; import { processTalent } from './systems/talentSystem'; -import { processEvents } from './systems/eventSystem'; import { processCompetitors } from './systems/competitorSystem'; import { processData } from './systems/dataSystem'; import { checkEraTransition } from './systems/eraSystem'; @@ -25,13 +24,8 @@ export interface TickNotification { type: 'info' | 'success' | 'warning' | 'danger'; } -let cachedEventDefs: EventDefinition[] | null = null; let cachedAchievementDefs: AchievementDefinition[] | null = null; -export function setEventDefinitions(defs: EventDefinition[]) { - cachedEventDefs = defs; -} - export function setAchievementDefinitions(defs: AchievementDefinition[]) { cachedAchievementDefs = defs; } @@ -88,18 +82,6 @@ export function processTick(state: GameState): Partial { const data = processData(stateWithTalent); const competitors = processCompetitors(stateWithTalent); - const eventResult = cachedEventDefs - ? processEvents(stateWithTalent, cachedEventDefs) - : { events: state.events, newEvents: [] }; - - for (const evt of eventResult.newEvents) { - notifications.push({ - title: evt.title, - message: evt.description, - type: evt.category === 'regulatory' ? 'warning' : 'info', - }); - } - const tickCount = state.meta.tickCount + 1; let meta = { @@ -137,7 +119,6 @@ export function processTick(state: GameState): Partial { reputation, data, competitors, - events: eventResult.events, achievements: state.achievements, }; @@ -165,7 +146,6 @@ export function processTick(state: GameState): Partial { reputation, data, competitors, - events: eventResult.events, achievements: achievementResult.achievements, }; diff --git a/packages/shared/src/constants/gameBalance.ts b/packages/shared/src/constants/gameBalance.ts index 2fdc6bb..4ed8e84 100644 --- a/packages/shared/src/constants/gameBalance.ts +++ b/packages/shared/src/constants/gameBalance.ts @@ -7,7 +7,6 @@ export const FAST_FORWARD_BATCH_SIZE = 100; export const AUTO_SAVE_INTERVAL_TICKS = 60; export const FINANCIAL_SNAPSHOT_INTERVAL = 60; export const MAX_FINANCIAL_HISTORY = 1000; -export const MAX_EVENT_HISTORY = 50; export const MAX_REPUTATION_HISTORY = 500; export const STARTING_MONEY = 50_000; diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 538daf1..e17ca96 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -9,7 +9,6 @@ export * from './types/competitors'; export * from './types/talent'; export * from './types/data'; export * from './types/reputation'; -export * from './types/events'; export * from './types/achievements'; export * from './utils/formatting'; export * from './constants/gameBalance'; diff --git a/packages/shared/src/types/events.ts b/packages/shared/src/types/events.ts deleted file mode 100644 index 7245267..0000000 --- a/packages/shared/src/types/events.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { Era } from './gameState'; - -export interface EventState { - activeEvents: ActiveEvent[]; - eventHistory: EventHistoryEntry[]; - eventCooldowns: Record; - eventOccurrences: Record; -} - -export interface ActiveEvent { - eventId: string; - instanceId: string; - triggeredAtTick: number; - expiresAtTick: number; - title: string; - description: string; - category: EventCategory; - choices: EventChoice[]; - defaultChoiceIndex: number; -} - -export type EventCategory = 'industry' | 'regulatory' | 'pr' | 'internal' | 'market'; - -export interface EventChoice { - label: string; - description: string; - consequences: EventConsequence[]; -} - -export interface EventConsequence { - type: 'money' | 'reputation' | 'compute' | 'talent' | 'research_speed' - | 'regulation' | 'competitor' | 'unlock' | 'lock' | 'chain_event'; - value: number; - target?: string; - delay?: number; -} - -export interface EventHistoryEntry { - eventId: string; - instanceId: string; - title: string; - category: EventCategory; - tick: number; - chosenOptionIndex: number; -} - -export interface EventDefinition { - id: string; - title: string; - descriptionTemplate: string; - category: EventCategory; - eras: Era[]; - weight: number; - cooldownTicks: number; - maxOccurrences: number; - prerequisites: string[]; - conditions: EventCondition[]; - choices: EventChoice[]; - defaultChoiceIndex: number; - expiryTicks: number; -} - -export interface EventCondition { - field: string; - operator: 'gt' | 'lt' | 'gte' | 'lte' | 'eq'; - value: number; -} - -export const INITIAL_EVENTS: EventState = { - activeEvents: [], - eventHistory: [], - eventCooldowns: {}, - eventOccurrences: {}, -}; diff --git a/packages/shared/src/types/gameState.ts b/packages/shared/src/types/gameState.ts index 484cee9..4fcb1b1 100644 --- a/packages/shared/src/types/gameState.ts +++ b/packages/shared/src/types/gameState.ts @@ -8,7 +8,6 @@ import type { CompetitorState } from './competitors'; import type { TalentState } from './talent'; import type { DataState } from './data'; import type { ReputationState } from './reputation'; -import type { EventState } from './events'; import type { AchievementState } from './achievements'; export interface GameState { @@ -23,7 +22,6 @@ export interface GameState { talent: TalentState; data: DataState; reputation: ReputationState; - events: EventState; achievements: AchievementState; }