# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

BaoLife is a real-time life simulation game **monorepo** containing:
- **Backend (Active)**: Node.js/TypeScript WebSocket server in `server/` - **ACTIVE DEVELOPMENT**
- **Backend (Legacy)**: Python WebSocket server in `ws/` - being replaced
- **iOS App**: SwiftUI app in `ios/`
- **Android App**: Coming soon (will be added to `android/`)
- **Database**: MySQL (database: `lifesim`)
- **Architecture**: Real-time event-driven communication via WebSockets

The game simulates a character's life from birth through various life stages, managing relationships, activities, education, and daily schedules in a time-based loop system.

## Quick Navigation

| Component | Location | Documentation |
|-----------|----------|---------------|
| Backend (TypeScript) | `server/` | This file |
| Backend (Python/Legacy) | `ws/` | This file |
| iOS App | `ios/` | [ios/CLAUDE.md](./ios/CLAUDE.md) |
| Android App | `android/` | [docs/CODEBASE_MAP.md](./docs/CODEBASE_MAP.md#android-app-android) |
| API Protocol | `ios/BACKEND.md` | [ios/BACKEND.md](./ios/BACKEND.md) |

**Additional Documentation:**
- [docs/CODEBASE_MAP.md](./docs/CODEBASE_MAP.md) - Complete architecture map with module guides, data flows, and navigation
- [docs/FRONTEND.md](./docs/FRONTEND.md) - Frontend documentation
- [docs/PROJECT_STATUS.md](./docs/PROJECT_STATUS.md) - Current project status, missing features, and completion roadmap
- [docs/TESTING_PLAN.md](./docs/TESTING_PLAN.md) - Testing strategy and test coverage
- [ios/CLAUDE.md](./ios/CLAUDE.md) - iOS app architecture and development guide
- [ios/BACKEND.md](./ios/BACKEND.md) - Detailed backend architecture reference

**DEPRECATED**: The HTML/JS web frontend (`index.html`, `main.js`) and Python backend (`ws/`) are legacy. All new backend development happens in `server/` (TypeScript).

## Development Commands

### Node/TypeScript Backend (Active)
```bash
# Install dependencies
cd server && npm install

# Start dev server with hot reload (port 8001)
npm run dev

# Build for production
npm run build

# Run tests
npm test
```

### Legacy Python Backend
```bash
# Start the legacy Python server (runs on port 8001)
cd ws && ./startServer.sh
```
The Python startup script uses `watchmedo` for auto-restart.

### Production Server

**Host:** `lichun-master` (GCE instance, Debian 11, us-central1-c)
**Project path:** `/var/www/lichun.app/lichun`
**Service:** `baolife-websocket.service` (runs as `www-data`)
**Backend:** TypeScript (`server/`), run directly via `tsx src/index.ts` — no build step required.

**Verified 2026-04-13:** systemd unit ExecStart is `/var/www/lichun.app/lichun/server/node_modules/.bin/tsx src/index.ts`. The Python backend (`ws/`) is legacy reference only and is NOT running in production.

**SSH Access (always use Tailscale):**
```bash
# Connect to production server — ALWAYS use this command
ssh lichun-master

# Tailscale IP: 100.94.57.1 (alternative)
ssh 100.94.57.1
```

**Do NOT use `gcloud compute ssh`** — Tailscale SSH is configured and requires no keys. The server is on the same tailnet as the development Mac.

**Deploying changes (git only — do not scp individual files):**
```bash
# 1. Local: commit, push to origin/main
git push origin main

# 2. Deploy script (checks push, refuses dirty production tree)
chmod +x scripts/deploy-lichun-master.sh
./scripts/deploy-lichun-master.sh

# Or manually on the server:
ssh lichun-master
cd /var/www/lichun.app/lichun
sudo -u www-data git pull origin main
cd server && sudo -u www-data npm install --omit=dev
sudo systemctl restart baolife-websocket.service
```

Pushes to `main` can also auto-deploy via GitHub webhook (`git_hook/index.php` → `git pull` + `systemctl restart baolife-websocket`).

Verify deployed revision: `ssh lichun-master 'sudo -u www-data git -C /var/www/lichun.app/lichun log -1 --oneline'`

Note: `npm run build` is still valid for local type checking and producing `dist/` for local `npm start`, but production does not use `dist/`.

**Service management (run on lichun-master):**
```bash
# Check status
systemctl status baolife-websocket.service

# Restart server
sudo systemctl restart baolife-websocket.service

# View logs
tail -f /var/log/baolife-websocket.log
```

**Important**: The service runs as `www-data` user. Ensure all files in `server/src/` and `.env` are readable by www-data:
```bash
chown -R www-data:www-data server/src server/.env
chmod 600 server/.env  # Keep credentials secure
```

**Monitor Node processes** to prevent runaway processes (especially from stuck tests):
```bash
ps aux | grep -E 'node|npm' | grep -v grep
```
Expected: 2 processes (tsx wrapper + node) when server is running normally. If you see many vitest/test processes, kill them with `pkill -f vitest`.

### Server URLs
- Local: `ws://localhost:8001/`
- Production: `wss://lichun.app/wss/`

### Database
MySQL/MariaDB database `lifesim` on localhost.

**Connection (from server):**
- Credentials in `server/.env` (DB_HOST, DB_USER, DB_PASSWORD, DB_NAME)
- Uses mysql2 package for Node.js connections

**Quick database queries:**
```bash
# Query player data via Node.js (from server/ directory)
node -e "
const mysql = require('mysql2/promise');
const dotenv = require('dotenv');
dotenv.config();
async function query() {
  const conn = await mysql.createConnection({
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME
  });
  // List all players
  const [rows] = await conn.execute('SELECT id, JSON_EXTRACT(data, \"\$.c.firstname\") as name FROM players');
  console.log(rows);
  await conn.end();
}
query();
"
```

**Schema:**
- `players` table: id (varchar), data (longtext JSON), updated_at, connection_status
- Player data stored as JSON blob in `data` column
- Key JSON paths: `$.c` (character), `$.r` (relationships), `$.conversations`, `$.lifeEvents`

### Frontend Development
The iOS app is located at `ios/`. See [ios/CLAUDE.md](./ios/CLAUDE.md) for iOS development setup and architecture.

## Architecture

### Core Game Loop
The game runs on a time-based simulation loop in `ws/app.py`:
1. **Main loop** (`initLifeSim`): Iterates every game tick based on `player.gameSpeed`
2. **Time progression**: `minuteOfHour` → `hourOfDay` → `dayOfWeek` → `date`
3. **Event parsing**: Each tick checks for events, questions, conversations, and day-specific events
4. **State updates**: Player stats (energy, hunger, mood) update based on activities
5. **Client sync**: Changes pushed to client via WebSocket messages

### Key Classes

**playerClass** (`ws/functions.py`)
- Core game state container
- Properties:
  - `c`: The main character (personClass)
  - `r`: Array of relationship characters (personClass instances)
  - `l`: Array of locations (locationClass instances)
  - `gameSpeed`: Tick rate (lower = faster)
  - `ticks`, `hourOfDay`, `minuteOfHour`, `date`: Time tracking
  - `events`, `askedQuestions`, `conversations`: Event tracking
  - `controller`: 'active' | 'inactive' (game running state)

**personClass** (`ws/functions.py`)
- Represents any character (player or NPC)
- Properties include: `firstname`, `lastname`, `sex`, `age*`, `energy`, `hunger`, `mood`, `money`, `relationships`, `schedules`, `dailyPlan`
- Tracks stats, relationships, activities, and education records

### Event System

Event types (`ws/events.py`):
- **messageEvent**: One-way notifications to player (with optional costs/rewards)
- **questionEvent**: Binary/multiple choice questions that pause game
- **conversationEvent**: Multi-turn conversations with NPCs (`ws/conversationEvents.py`)
- **dilemmaClass**: Complex decision points with lasting consequences

Event flow:
1. Events checked during game loop via `parseEvents()`, `parseDayEvents()`, `parseConversations()`
2. Events have `fname` (function name) to prevent duplicates in `player.events`
3. Questions pause game by setting high `gameSpeed` until answered
4. Responses handled in `consumer()` function in `app.py`

### Daily Activities

**Daily Plan System** (`ws/intradayActivity.py`):
- Each character has a `dailyPlan` array of scheduled events
- Generated by `get_dailyPlan()` based on age, education, schedules
- Events trigger at specific `hourOfDay` values
- Activities affect location, stats, and trigger interactions

### WebSocket Communication

**Client → Server messages**:
```json
{ "type": "init", "userID": "string" }
{ "type": "command", "value": "start"|"stop"|"restart" }
{ "type": "speed", "value": "number" }
{ "type": "questionEvent", "id": "string", "response": "string" }
{ "type": "conversation", "message": {"id": "string", "message": "string"} }
{ "type": "characterSetup", "message": {"name": "string", "age": "number", "sex": "string"} }
{ "type": "applyForJob", "message": {"jobId": "string"} }
{ "type": "purchaseItem", "message": {"itemId": "string"} }
{ "type": "claimEvent", "message": {"eventId": "string", "timestamp": "string"} }
```

**Server → Client messages**:
- `playerObject`: Full player state update
- `u`: Lightweight updates (energy, money, time, etc.)
- `messageEvent`: Display notification with optional costs/rewards
- `questionEvent`: Show question modal with answer options
- `conversationEvent`: Show conversation UI with character
- `personObject`: Show character details modal

For complete message protocol documentation, see [ios/BACKEND.md](./ios/BACKEND.md)

### File Organization

```
server/                       # Node/TypeScript Backend (ACTIVE)
├── src/
│   ├── index.ts              # Server entry point
│   ├── config.ts             # Configuration
│   ├── handlers/             # WebSocket message handlers
│   ├── game/                 # Game loop and core logic
│   ├── events/               # Event system
│   ├── models/               # Data models (Player, Person, etc.)
│   ├── services/             # Business logic services
│   ├── database/             # MySQL connection and queries
│   ├── auth/                 # Authentication
│   ├── monetization/         # In-app purchases
│   └── monitoring/           # Logging and metrics
├── tests/                    # Vitest tests
├── package.json
└── tsconfig.json

ws/                           # Python Backend (LEGACY)
├── app.py                    # WebSocket server, main game loop
├── functions.py              # Core classes, database, utility functions
├── events.py                 # Event definitions
├── dayEvents.py              # Holiday events
├── conversationEvents.py     # NPC conversations
├── intradayActivity.py       # Daily schedule
└── startServer.sh            # Server launcher

docs/                         # Documentation
├── PROJECT_STATUS.md         # Current project status
├── TESTING_PLAN.md           # Testing strategy
├── FRONTEND.md               # Frontend reference
├── DEPLOYMENT.md             # Deployment guide
├── archived/                 # Historical implementation docs
└── plans/                    # Planning documents

ios/                          # iOS App (SwiftUI)
├── lichunWebsocket/
│   ├── Core/                 # Models, ViewModels, Services
│   ├── Features/             # Feature modules
│   └── Shared/               # Shared components
├── lichunWebsocket.xcodeproj
├── CLAUDE.md                 # iOS-specific development guide
└── BACKEND.md                # API protocol documentation

android/                      # Android App (Coming Soon)
└── (to be added)
```

## Important Development Notes

### Game State Persistence
- Games saved to MySQL via `saveGame(player)` in `ws/functions.py`
- Loaded via `loadGame(userID)` on reconnection
- Auto-saves occur on major events and periodically

### Adding New Events
1. Create event function in appropriate file (`events.py`, `dayEvents.py`, etc.)
2. Follow naming pattern: `def eventName(player, type='message'):`
3. Use `messageFunction()` or `questionFunction()` helpers
4. Check `fname not in player.events` to prevent duplicates
5. Add event parsing call to `parseEvents()` or `parseDayEvents()` in `app.py`

### Modifying Player Stats
Common stat modifications:
- Energy: Depletes with activities, restored by sleep
- Hunger/Thirst: Increase over time, satisfied by meals
- Money: Earned through jobs, spent on purchases
- Affinity: Relationship strength with NPCs

Always call `updateStats(player)` after manual modifications.

### Time System
- 1 game tick = player-controlled (via gameSpeed)
- 60 minutes per hour, 24 hours per day
- Real calendar dates used for `player.date`
- Weekend detection: `player.weekend` set based on `dayOfWeek`

### Testing Locally

**TypeScript Backend Testing:**
```bash
cd server
npm test                    # Run all tests
npm run test:coverage       # Run with coverage report
```

**Manual WebSocket Testing:**
```bash
# Start dev server
cd server && npm run dev

# Test with wscat
wscat -c ws://localhost:8001/
```

**Frontend Testing:**
1. Open iOS app in Xcode: `open ios/lichunWebsocket.xcodeproj`
2. Build and run in simulator (⌘R)
3. Use Xcode console to monitor WebSocket messages and app state

**Database Testing:**
```bash
mysql -u root -p lifesim
SELECT * FROM players;
```
