Files
AIHostingTycoon/docs/architecture.md
T
josh 00e790591e
CI / build-and-push (push) Successful in 32s
Game balance audit: wire research effects, rework capability formula, fix dead systems
- Create researchBonuses utility to aggregate tech tree effects into all game systems
  (infrastructure energy costs, compute efficiency, training speed, model capability, reputation)
- Rework model capability from sqrt(compute) to 4-pillar formula (params + compute + data + research)
- Make context window affect benchmarks and inference speed
- Add MoE tradeoffs: 1.5x VRAM, 0.8x training speed
- Enforce research point costs as a gate for unlocking research
- Add real consequences to data contamination events (reputation hit, legal costs)
- Scale talent costs from $0.03 to $5/tick per headcount
- Scale compliance costs 100x to be meaningful
- Rework competitor acquisition: cheaper but grants headcount, RP, and reputation
- Remove dead code: sfxVolume, autoSaveInterval, notificationsEnabled,
  FAST_FORWARD_BATCH_SIZE, CHINCHILLA_OPTIMAL_RATIO

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-25 09:36:31 -04:00

293 lines
12 KiB
Markdown

# 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<GameState>
→ 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.