# Architecture Guide ## Layer Stack ``` React UI Layer (components, pages, dashboards) ↕ reads/writes via Zustand selectors and actions Zustand State Layer (14 slices, persisted to localStorage) ↕ engine writes state, UI reads it Game Engine (pure TypeScript, zero DOM dependencies) ↕ uses formulas, runs systems Simulation Core (math, scaling laws, cost curves) ``` The game engine and simulation core have no React dependency. They can run identically for real-time play, offline catch-up, and automated testing. ## Monorepo Layout ``` ai-tycoon/ ├── turbo.json # Turborepo task config ├── pnpm-workspace.yaml # Workspace definition │ ├── apps/ │ ├── web/ # React frontend (Vite) │ │ └── src/ │ │ ├── components/ # layout/, common/, charts/, game/ │ │ ├── pages/ # One page per game system │ │ ├── store/ # Zustand store with slice pattern │ │ ├── hooks/ # useGameLoop, useOfflineCatchUp, etc. │ │ └── lib/ # API client, formatters │ │ │ └── server/ # Hono backend │ └── src/ │ ├── db/ # Drizzle schema + migrations │ ├── routes/ # auth, saves, leaderboards │ ├── middleware/ # auth, rate limiting │ └── services/ # Business logic │ └── packages/ ├── shared/ # Shared types & constants │ └── src/ │ ├── types/ # All game state type definitions │ ├── constants/ # gameBalance.ts (single source of truth) │ └── utils/ # Number/money/time formatting │ └── game-engine/ # Pure TS game simulation └── src/ ├── engine.ts # GameEngine class (tick scheduling) ├── tick.ts # Tick processor (orchestrates systems) ├── systems/ # One file per simulation system └── data/ # Tech tree, achievements, datasets ``` ## Tick System The game uses a fixed-tick model: 1 tick = 1 second of game time at 1x speed. ### Game Loop The `useGameLoop` hook runs in the React app: 1. `requestAnimationFrame` fires continuously 2. An accumulator tracks elapsed time since the last tick 3. When accumulated time exceeds `TICK_INTERVAL_MS / gameSpeed`, a tick fires 4. `processTick(state)` runs all systems and returns a partial state update 5. A single `setState` call applies the update to Zustand Speed controls (1x, 2x, 5x) divide the tick interval, so 5x processes 5 ticks per real second. ### Per-Tick Processing Order Each tick runs these systems sequentially, since later systems depend on earlier results: ``` 1. Infrastructure — GPU health, failures, maintenance 2. Models — Training progress, model completion 3. Market — Demand, subscribers, API calls, revenue calc 4. Compute — Capacity, utilization, allocation 5. Talent — Department effectiveness, morale 6. Research — R&D progress, project completion 7. Reputation — Safety incidents, regulatory standing, score 8. Economy — Revenue, expenses, net cash flow 9. Data — Data acquisition, user data flywheel 10. Competitors — AI rival decisions and actions 11. Era check — Threshold-based era transitions 12. Valuation — Dynamic company valuation 13. Achievements — Milestone checks (every 10 ticks) ``` ### Offline Catch-Up When the player returns after being away: - Elapsed ticks = `min((now - lastTick) / interval, MAX_OFFLINE_TICKS)` - Max offline cap: 24 hours (86,400 ticks) - Ticks process with reduced fidelity (`OFFLINE_EFFICIENCY = 0.8`) - A progress bar shows catch-up progress - A summary screen reports what happened while away ## State Management ### Zustand Store The store uses a slice pattern with 14 slices, each owning a portion of the game state: | Slice | Responsibility | |-------|---------------| | `gameMetaSlice` | Tick count, era, pause state, speed, timestamps | | `economySlice` | Money, revenue, expenses, funding, financial history | | `infrastructureSlice` | Datacenters, GPUs, locations, total FLOPS | | `computeSlice` | Training/inference allocation, utilization | | `researchSlice` | Tech tree, active/completed projects | | `modelsSlice` | Trained models, active training, deployment | | `marketSlice` | Subscribers, API demand, pricing, overload policy | | `competitorSlice` | Rival AI labs, their models, actions | | `talentSlice` | Departments, headcount, morale, hiring | | `dataSlice` | Datasets, quality, user data generation | | `reputationSlice` | Safety record, public perception, regulatory standing | | `achievementSlice` | Unlocked achievements | | `uiSlice` | Active page, notifications, modals (not persisted) | ### Persistence - **localStorage**: Auto-save every 60 ticks under key `ai-tycoon-save`. The Zustand `persist` middleware handles serialization. - **Cloud saves**: Optional. POST to `/api/saves` every 5 minutes when authenticated. Requires the Hono backend + PostgreSQL. - **Save format versioning**: A `version` field in meta enables migration functions for breaking state changes. ### State Flow ``` User Action (click "Buy GPU") → Zustand action (buyGPU) → Mutates store directly (immer-style) → React re-renders via selectors Tick fires → processTick(currentState) → Returns Partial → Zustand merges into store → React re-renders ``` Actions that happen instantly (buying, selling, hiring) mutate the store directly. The tick system handles everything time-based (training progress, revenue accrual, demand changes). ## Game Engine Systems ### Economy System (`economySystem.ts`) Calculates per-tick financials: - **Revenue**: Market revenue (API tokens + subscriptions) - **Expenses**: GPU energy costs + maintenance + talent salaries + compliance costs - **Compliance**: Scales with model capability and era index: `capability * REGULATION_COMPLIANCE_PER_CAPABILITY * (1 + eraIdx * 0.5) / 100` - **Financial snapshots**: Recorded every 60 ticks, circular buffer of 1000 ### Infrastructure System (`infrastructureSystem.ts`) - Each GPU has a per-tick failure probability (`GPU_FAILURE_RATE_BASE`) - Redundancy level reduces failure rate by `REDUNDANCY_FAILURE_REDUCTION` - Failed GPUs are removed from capacity calculations - Total FLOPS = sum of healthy GPU FLOPS across all datacenters - Locations have different energy costs, latency tiers, and regulatory environments ### Model System (`modelSystem.ts`) Training a model: 1. Player commits compute + data tokens + researchers 2. Progress increments per tick, boosted by researcher and engineer effectiveness 3. On completion, `createTrainedModel` generates capability scores: - Base capability from `sqrt(compute) * 5 + log10(1 + dataTokens/1e8) * 10 + researchBonus` - Each capability dimension (reasoning, coding, creative, multimodal, agents) gets a random multiplier - Safety score: `30 + safetyResearch * 15 + random * 10` - Benchmark penalized by high safety: `(safetyScore - 60) * 0.1` when safety > 60 ### Market System (`marketSystem.ts`) - **Subscribers**: Grow based on model quality, churn based on satisfaction - **API demand**: Function of subscriber count and pricing - **Open-source effect**: Boosts subscriber growth, reduces revenue per model - **Overload handling**: When demand > capacity, player-configured policy determines behavior (queue, degrade quality, prioritize enterprise) - **Subscriber history**: Sampled every 60 ticks, max 500 entries ### Reputation System (`reputationSystem.ts`) Composite score (0-100) from four factors: - **Safety record** (30%): Damaged by safety 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)`. ### Competitor System (`competitorSystem.ts`) - 3+ AI rival labs with archetypes (safety-first, move-fast, big tech, open-source) - Scripted core milestones for pacing - Personality-driven decision-making - Light reactive behavior to player actions - Can be acquired in bigtech/agi eras ### Achievement System (`achievementSystem.ts`) - 15 achievement definitions with dot-path field access conditions - Virtual fields for computed values (`meta._eraIndex`, `meta._deployedModelCount`, `infrastructure._totalGpuCount`) - Checked every 10 ticks for performance - Notifications on unlock ### Funding System (`fundingSystem.ts`) - 6 rounds: Seed → Series A → B → C → D → IPO - Each round has amount, dilution percentage, and requirements (min revenue, users, reputation) - Dynamic valuation computed from revenue, subscribers, and model capability - Dilution reduces founder equity permanently ## Frontend Architecture ### Routing No router library — the active page is stored in Zustand (`uiSlice.activePage`). The sidebar sets it, `MainLayout` renders the corresponding page component. This keeps things simple for a single-page game. ### Pages | Page | System | |------|--------| | Dashboard | Overview KPIs, tutorial hints, trend charts | | Infrastructure | Datacenter management, GPU purchasing | | Research | Tech tree, active projects | | Models | Training, deployment, tuning | | Market | Pricing, subscribers, overload policy | | Talent | Department management, hiring | | Data | Dataset marketplace, data quality | | Competitors | Rival labs, acquisition | | Finance | Cash flow, funding rounds, financial history | | Achievements | Achievement grid with unlock status | | Leaderboard | Global rankings by category | | Settings | Sound, save management, export/import | ### Progressive Disclosure - Pages unlock as the player progresses through eras - Newly available pages get a "NEW" badge in the sidebar - Tutorial hints appear contextually and persist dismissal to localStorage ### Charts All charts use Recharts with consistent dark-theme styling. Chart data comes from circular buffer history arrays in the game state (financial snapshots, subscriber history, reputation history). ## Backend Architecture ### API Routes ``` POST /api/auth/anonymous # Create anonymous session (UUID token) POST /api/auth/link # Link email/password to anonymous session POST /api/auth/login # Email/password login GET /api/saves # List saves for authenticated user POST /api/saves # Create/update save GET /api/saves/:id # Load specific save GET /api/leaderboard/:cat # Get leaderboard by category POST /api/leaderboard # Submit score ``` ### Database PostgreSQL with Drizzle ORM. Tables: `users`, `saves`, `leaderboard_entries`. ### Auth Anonymous-first: players get a UUID token on first visit. Optional email/password linking for cross-device play. JWT-based session tokens. ## Performance Considerations - Game engine runs outside React's render cycle; single `setState` per tick - 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) - Speed modes simply increase tick frequency, not computation complexity ## Key Design Decisions 1. **Pure engine**: The game-engine package has zero DOM/React dependencies, enabling offline catch-up, testing, and potential server-side simulation. 2. **Single constants file**: All balance numbers live in `gameBalance.ts`. One place to tune everything. 3. **Slice pattern**: Each game system owns its state slice. Slices compose into the full store without coupling. 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**: Achievement definitions are set once at game start via `setAchievementDefinitions`, avoiding repeated imports in the hot tick path.