# app.py Refactoring - Quick Start Guide

## 📋 Overview

Reduce `/home/user/lichun/ws/app.py` from **1,291 lines to ~400 lines** in 3 phases over 2-3 weeks.

**Main win:** Convert 476-line if/elif chain into table-driven command dispatcher.

## 📚 Documentation

Read in this order:

1. **APP_REFACTORING_QUICKSTART.md** ← You are here (5 min read)
2. **APP_REFACTORING_SUMMARY.md** - Executive summary (15 min)
3. **APP_REFACTORING_EXAMPLES.md** - Before/after code (20 min)
4. **APP_REFACTORING_STRUCTURE.md** - Visual diagrams (10 min)
5. **APP_REFACTORING_PLAN.md** - Detailed strategy (30 min)
6. **APP_REFACTORING_EXTRACTION_MAP.md** - Line-by-line guide (45 min)

**Total reading time:** ~2 hours for full understanding

## 🎯 Quick Stats

| Metric | Before | After | Change |
|--------|--------|-------|--------|
| app.py size | 1,291 lines | ~400 lines | **-69%** |
| Longest function | 476 lines | 60 lines | **-87%** |
| New modules | 0 | 7 | +7 files |
| Command dispatch | O(n) if/elif | O(1) dict | **Faster** |

## 🚀 Getting Started (5 minutes)

### Step 1: Backup Current Code
```bash
cd /home/user/lichun/ws
cp app.py app.py.backup
git status  # Ensure clean working directory
```

### Step 2: Create Feature Branch
```bash
git checkout -b refactor/app-phase-1
# Or if you want all phases in one branch:
# git checkout -b refactor/app-modularization
```

### Step 3: Create Directory Structure
```bash
cd /home/user/lichun/ws
mkdir -p server game_loop
touch server/__init__.py
touch game_loop/__init__.py
ls -la server/ game_loop/  # Verify creation
```

### Step 4: Run Baseline Tests
```bash
# Run existing tests to establish baseline
python -m pytest tests/ -v

# Note performance metrics
# (You'll compare these after refactoring)
```

## 📦 Phase 1: Low-Risk Extractions (Days 1-3)

**Goal:** Extract startup code and utilities. **Risk: LOW**

### Files to Create:
1. `server/event_registration.py` (230 lines)
2. `server/websocket_registry.py` (120 lines)
3. `server/websocket_messaging.py` (100 lines)

### Task 1.1: Event Registration (~2 hours)

```bash
# Create file
touch server/event_registration.py
```

**Copy lines 34-263 from app.py to server/event_registration.py:**
- Lines 34-135: `register_all_event_handlers()`
- Lines 138-263: `register_all_events()`

**Add wrapper function:**
```python
def initialize_all_events():
    """Single function to initialize all event systems"""
    register_all_event_handlers()
    register_all_events()
```

**Update app.py:**
```python
# Replace lines 34-263 with:
from server.event_registration import initialize_all_events

initialize_all_events()
print('startup')
```

**Test:**
```bash
python ws/app.py  # Should start without errors
# Check: "Registered X event handlers" message appears
```

**Commit:**
```bash
git add server/event_registration.py ws/app.py
git commit -m "refactor: extract event registration to separate module (230 lines)"
```

### Task 1.2: WebSocket Registry (~1 hour)

```bash
touch server/websocket_registry.py
```

**Copy lines 267-344 from app.py to server/websocket_registry.py:**
- Lines 267-299: `UserRegistry` class
- Lines 301-335: `BatchedUpdate` class
- Lines 341-344: `ServerState` class

**Add module-level instances and getters:**
```python
_USERS = UserRegistry()
_server = ServerState()

def get_user_registry() -> UserRegistry:
    return _USERS

def get_server_state() -> ServerState:
    return _server
```

**Update app.py:**
```python
# Replace lines 267-344 with:
from server.websocket_registry import get_user_registry, get_server_state, BatchedUpdate

USERS = get_user_registry()
server = get_server_state()
playerRecords = PlayerCache(max_size=config.MAX_CONNECTIONS)
lastIteration = False
```

**Test and commit:**
```bash
python ws/app.py  # Should start without errors
git add server/websocket_registry.py ws/app.py
git commit -m "refactor: extract websocket registry classes (78 lines)"
```

### Task 1.3: WebSocket Messaging (~1 hour)

```bash
touch server/websocket_messaging.py
```

**Copy lines 346-385 from app.py to server/websocket_messaging.py:**
- All messaging functions
- `iterateGames()` function

**Update app.py:**
```python
# Replace lines 346-385 with:
from server.websocket_messaging import (
    sendToUser, sendEventMessage, sendUserInfo, sendDict,
    ComplexHandler, get_websocket_for_player, iterateGames
)
```

**Test and commit:**
```bash
python ws/app.py  # Should start without errors
git add server/websocket_messaging.py ws/app.py
git commit -m "refactor: extract websocket messaging utilities (40 lines)"
```

### Phase 1 Checkpoint ✅

```bash
# Verify app.py size
wc -l ws/app.py
# Should be ~940 lines (down from 1,291)

# Run tests
python -m pytest tests/ -v

# Check git status
git log --oneline -3  # Should see 3 commits
```

**Expected reduction:** 1,291 → ~940 lines

---

## 📦 Phase 2: Medium-Risk Extractions (Days 4-7)

**Goal:** Extract game loop infrastructure. **Risk: MEDIUM**

### Files to Create:
1. `game_loop/producer_consumer.py` (80 lines)
2. `server/websocket_handlers.py` (200 lines)
3. `game_loop/loop_manager.py` (250 lines - helpers only)

### Task 2.1: Producer/Consumer (~3 hours)

```bash
touch game_loop/producer_consumer.py
```

**Copy lines 578-644 from app.py:**
- Lines 578-580: `producer()`
- Lines 582-583: `TARGET_FPS`, `FRAME_DURATION`
- Lines 585-628: `producer_handler()`
- Lines 630-644: `consumer_handler()`

**Handle circular dependencies:**
```python
# In producer_consumer.py, import initLifeSim inside function
async def producer(websocket):
    from app import initLifeSim  # Import here to avoid circular dependency
    await initLifeSim(websocket)
    return False
```

**Update app.py:**
```python
from game_loop.producer_consumer import producer_handler, consumer_handler

# Remove lines 578-644
# Keep initLifeSim() and consumer() functions in app.py
```

**Test and commit:**
```bash
python ws/app.py
git add game_loop/producer_consumer.py ws/app.py
git commit -m "refactor: extract producer/consumer to game_loop module (68 lines)"
```

### Task 2.2: WebSocket Handlers (~4 hours)

```bash
touch server/websocket_handlers.py
```

**Copy lines 1123-1244 from app.py:**
- Lines 1123-1168: `start()`
- Lines 1170-1181: `shutdown()`
- Lines 1183-1188: `error()`
- Lines 1189-1201: `every_minute()`
- Lines 1203-1230: `handler()`
- Lines 1232-1244: `initialize_dummy_user()`

**Add playerRecords setter:**
```python
# At top of websocket_handlers.py
playerRecords = None

def set_player_records(records):
    global playerRecords
    playerRecords = records
```

**Update app.py:**
```python
from server.websocket_handlers import (
    handler, every_minute, initialize_dummy_user, set_player_records
)

# After creating playerRecords
set_player_records(playerRecords)

# Keep main() in app.py, remove handler/start/shutdown/etc
```

**Test and commit:**
```bash
python ws/app.py
git add server/websocket_handlers.py ws/app.py
git commit -m "refactor: extract websocket connection handlers (170 lines)"
```

### Task 2.3: Loop Helpers (~2 hours)

```bash
touch game_loop/loop_manager.py
```

**Extract helpers (don't move main loop!):**
- Create `create_hourly_batch_update()` from lines 414-432
- Create `process_daily_tick()` from lines 442-489 (optional)
- Create `process_weekly_tick()` from lines 471-489 (optional)

**Update app.py:**
```python
from game_loop.loop_manager import create_hourly_batch_update

# In initLifeSim():
if player.minuteOfHour == 0:
    updateObject = create_hourly_batch_update(player)  # Helper function
    # Rest stays in app.py
```

**Test and commit:**
```bash
python ws/app.py
git add game_loop/loop_manager.py ws/app.py
git commit -m "refactor: extract game loop helper functions"
```

### Phase 2 Checkpoint ✅

```bash
wc -l ws/app.py
# Should be ~665 lines (down from ~940)

python -m pytest tests/ -v

git log --oneline -6  # Should see 6 total commits
```

**Expected reduction:** ~940 → ~665 lines

---

## 📦 Phase 3: Command Dispatcher (Days 8-14)

**Goal:** Convert 476-line if/elif chain to table-driven dispatch. **Risk: MEDIUM**

### Task 3.1: Create Dispatcher Module (~8 hours)

```bash
touch server/command_dispatcher.py
```

This is the biggest task. You'll create:
- `CommandHandler` base class
- 40+ specific handler classes
- `CommandDispatcher` class
- `dispatch_command()` function

**Start with simple commands:**

```python
class CommandHandler:
    """Base class for all command handlers"""
    async def handle(self, player, event, websocket):
        raise NotImplementedError

class StopCommandHandler(CommandHandler):
    """Handle stop command"""
    async def handle(self, player, event, websocket):
        from server.websocket_messaging import sendUserInfo
        await sendUserInfo(player, websocket)
        print('stopped!')
        player.controller = 'inactive'

class StartCommandHandler(CommandHandler):
    """Handle start command"""
    async def handle(self, player, event, websocket):
        print('started!')
        player.controller = "active"

# ... 38 more handler classes ...

class CommandDispatcher:
    def __init__(self):
        self._handlers = {}
        self._register_handlers()

    def _register_handlers(self):
        self._handlers['stop'] = StopCommandHandler()
        self._handlers['start'] = StartCommandHandler()
        # ... register all handlers ...

    async def dispatch(self, event, player, websocket):
        # Check message field
        if event.get('message') in self._handlers:
            handler = self._handlers[event.get('message')]
            await handler.handle(player, event, websocket)
            return True

        # Check type field
        if event.get('type') in self._handlers:
            handler = self._handlers[event.get('type')]
            await handler.handle(player, event, websocket)
            return True

        # Fallback to event system
        return await self._handle_event_dispatch(event, player, websocket)

_dispatcher = CommandDispatcher()

async def dispatch_command(event, player, websocket):
    return await _dispatcher.dispatch(event, player, websocket)
```

**Map of commands to extract:**

See `APP_REFACTORING_EXTRACTION_MAP.md` for complete list of 40+ commands.

**Recommended order:**
1. Simple commands (stop, start, restart) - 2 hours
2. Medium commands (speed, focus, habits) - 3 hours
3. Complex commands (conversation, jobs, dating) - 3 hours
4. Monetization (purchases, rewards) - 2 hours
5. Data management (export, delete) - 1 hour
6. Event fallback handler - 1 hour

### Task 3.2: Simplify Consumer (~1 hour)

**Update app.py consumer():**

```python
async def consumer(message, websocket):
    """
    Handle incoming WebSocket messages.
    Simplified - routing delegated to command_dispatcher.
    """
    player = playerRecords.get(websocket.userID)

    # Create update object (keep this)
    updateObject = { ... }

    if not message:
        return False

    if isinstance(message, bytes):
        message = message.decode('utf-8')

    print('received' + message)
    event = json.loads(message)

    # SINGLE LINE - dispatch to command handler!
    await dispatch_command(event, player, websocket)

    # Handle message queue (keep this)
    if player.messageQueue and len(player.messageQueue) > 0:
        player.message = player.messageQueue.pop(0)
        player.messageLog.append(player.message)
        await sendEventMessage(websocket, messageFunction('answerQueue', player.message, player, True))
        player.message = ""

    # Send updates (keep this)
    updateObject = handleUpdates(updateObject, player, websocket)
    if updateObject and updateObject != {}:
        await sendDict(websocket, updateObject)

    return True
```

**Replace lines 691-1117 with above simplified version.**

### Task 3.3: Test Thoroughly (~4 hours)

**Create unit tests:**

```python
# tests/unit/server/test_command_dispatcher.py

import pytest
from server.command_dispatcher import StopCommandHandler, StartCommandHandler, SpeedCommandHandler

class TestStopCommandHandler:
    async def test_stop_command(self):
        handler = StopCommandHandler()
        player = create_mock_player()
        player.controller = 'active'

        await handler.handle(player, {}, mock_websocket)

        assert player.controller == 'inactive'

class TestStartCommandHandler:
    async def test_start_command(self):
        handler = StartCommandHandler()
        player = create_mock_player()
        player.controller = 'inactive'

        await handler.handle(player, {}, mock_websocket)

        assert player.controller == 'active'

# ... 40+ more test classes ...
```

**Test manually:**
```bash
# Start server
python ws/app.py

# Connect client and test commands:
# - start/stop game
# - change speed
# - character setup
# - conversations
# - purchases
# - jobs
# - relationships
# etc.
```

### Task 3.4: Commit

```bash
git add server/command_dispatcher.py ws/app.py tests/
git commit -m "refactor: convert if/elif chain to table-driven command dispatcher (416 lines)

- Extract 40+ command handlers to separate classes
- Implement O(1) command dispatch system
- Add comprehensive unit tests
- Simplify consumer() from 476 lines to 60 lines"
```

### Phase 3 Checkpoint ✅

```bash
wc -l ws/app.py
# Should be ~350-450 lines (down from ~665)

wc -l server/command_dispatcher.py
# Should be ~500 lines

# Run ALL tests
python -m pytest tests/ -v --cov=server --cov=game_loop

git log --oneline -7  # Should see 7 total commits
```

**Expected reduction:** ~665 → ~400 lines

---

## ✅ Final Verification

### Check File Sizes
```bash
cd /home/user/lichun/ws

# Before
# app.py: 1,291 lines

# After
wc -l app.py                              # ~400 lines ✅
wc -l server/*.py                         # ~1,170 lines (5 files)
wc -l game_loop/*.py                      # ~330 lines (2 files)
```

### Run Full Test Suite
```bash
# Unit tests
python -m pytest tests/unit/ -v

# Integration tests
python -m pytest tests/integration/ -v

# All tests with coverage
python -m pytest tests/ -v --cov=server --cov=game_loop --cov=app

# Target: 80%+ coverage
```

### Performance Benchmarks
```bash
# Load test with 100 concurrent players
# Should maintain same FPS as before refactoring

python tests/load/websocket_load.py --users 100 --duration 60

# Profile game loop
python -m cProfile -o profile.stats ws/app.py

# Compare with baseline
# Memory usage increase should be < 5%
# FPS should be maintained
# Message latency should be < 50ms p95
```

### Code Quality Checks
```bash
# Check for Python issues
pylint ws/app.py ws/server/ ws/game_loop/

# Check for type issues
mypy ws/app.py ws/server/ ws/game_loop/

# Format code
black ws/app.py ws/server/ ws/game_loop/
```

---

## 🎉 Success Checklist

- [ ] app.py reduced to < 500 lines ✅
- [ ] 7 new focused modules created ✅
- [ ] All existing functionality preserved ✅
- [ ] Test coverage > 80% ✅
- [ ] No performance regression ✅
- [ ] All tests passing ✅
- [ ] Code reviewed ✅
- [ ] Documentation updated ✅

---

## 📝 Update Documentation

After refactoring is complete:

### 1. Update CLAUDE.md
```markdown
## Server Architecture

The WebSocket server is organized into focused modules:

### server/
- `event_registration.py` - Event system initialization
- `websocket_registry.py` - Connection management
- `websocket_messaging.py` - Client communication
- `command_dispatcher.py` - Command routing (table-driven)
- `websocket_handlers.py` - Connection lifecycle

### game_loop/
- `loop_manager.py` - Game loop helper functions
- `producer_consumer.py` - FPS control and rate limiting
```

### 2. Create Architecture Diagram
```
docs/architecture/websocket-server.md
```

### 3. Update Developer Onboarding
```
docs/DEVELOPER_GUIDE.md
```

---

## 🐛 Troubleshooting

### Issue: Circular Imports
**Solution:** Import inside functions or pass dependencies as parameters

```python
# Instead of:
from app import initLifeSim

# Do:
async def producer(websocket):
    from app import initLifeSim  # Import inside function
    await initLifeSim(websocket)
```

### Issue: Test Failures
**Solution:** Check mock setup, verify all imports are correct

```python
# Make sure mocks are complete
player = MagicMock()
player.gameSpeed = 10
player.c.energy = 100
# ... set all required attributes
```

### Issue: Performance Regression
**Solution:** Profile to find bottleneck, check if hot path was moved

```python
# Profile before and after
python -m cProfile -o before.stats ws/app.py
# ... refactor ...
python -m cProfile -o after.stats ws/app.py
# Compare
python -m pstats before.stats -c "sort cumtime" -c "stats 20"
```

---

## 📞 Questions?

Refer to detailed documentation:
- **APP_REFACTORING_SUMMARY.md** - High-level overview
- **APP_REFACTORING_EXAMPLES.md** - Concrete before/after examples
- **APP_REFACTORING_EXTRACTION_MAP.md** - Precise line-by-line guide
- **APP_REFACTORING_PLAN.md** - Complete strategy and rationale

---

## 🚀 Ready to Start?

```bash
# 1. Backup
cp ws/app.py ws/app.py.backup

# 2. Create branch
git checkout -b refactor/app-phase-1

# 3. Create directories
mkdir -p ws/server ws/game_loop
touch ws/server/__init__.py ws/game_loop/__init__.py

# 4. Start with Phase 1, Task 1.1 (Event Registration)
# See detailed instructions above

# Good luck! 🎉
```
