# Session Management

This document describes the game session system for Code of Conquest, covering both solo and multiplayer sessions.

**Last Updated:** November 22, 2025

---

## Overview

Game sessions track the state of gameplay including:
- Current location and discovered locations
- Conversation history between player and AI DM
- Active quests (max 2)
- World events
- Combat encounters

Sessions support both **solo play** (single character) and **multiplayer** (party-based).

---

## Data Models

### SessionType Enum

```python
class SessionType(Enum):
    SOLO = "solo"               # Single-player session
    MULTIPLAYER = "multiplayer" # Multi-player party session
```

### LocationType Enum

```python
class LocationType(Enum):
    TOWN = "town"               # Town or city
    TAVERN = "tavern"           # Tavern or inn
    WILDERNESS = "wilderness"   # Outdoor wilderness areas
    DUNGEON = "dungeon"         # Underground dungeons/caves
    RUINS = "ruins"             # Ancient ruins
    LIBRARY = "library"         # Library or archive
    SAFE_AREA = "safe_area"     # Safe rest areas
```

### GameState

Tracks current world state for a session:

```python
@dataclass
class GameState:
    current_location: str = "Crossroads Village"
    location_type: LocationType = LocationType.TOWN
    discovered_locations: List[str] = []
    active_quests: List[str] = []      # Max 2
    world_events: List[Dict] = []
```

### ConversationEntry

Single turn in the conversation history:

```python
@dataclass
class ConversationEntry:
    turn: int                          # Turn number (1-indexed)
    character_id: str                  # Acting character
    character_name: str                # Character display name
    action: str                        # Player's action text
    dm_response: str                   # AI DM's response
    timestamp: str                     # ISO timestamp (auto-generated)
    combat_log: List[Dict] = []        # Combat actions if any
    quest_offered: Optional[Dict] = None  # Quest offering info
```

### GameSession

Main session object:

```python
@dataclass
class GameSession:
    session_id: str
    session_type: SessionType = SessionType.SOLO
    solo_character_id: Optional[str] = None  # For solo sessions
    user_id: str = ""
    party_member_ids: List[str] = []         # For multiplayer
    config: SessionConfig
    combat_encounter: Optional[CombatEncounter] = None
    conversation_history: List[ConversationEntry] = []
    game_state: GameState
    turn_order: List[str] = []
    current_turn: int = 0
    turn_number: int = 0
    created_at: str                    # ISO timestamp
    last_activity: str                 # ISO timestamp
    status: SessionStatus = SessionStatus.ACTIVE
```

---

## SessionService

The `SessionService` class (`app/services/session_service.py`) provides all session operations.

### Initialization

```python
from app.services.session_service import get_session_service

service = get_session_service()
```

### Creating Sessions

#### Solo Session

```python
session = service.create_solo_session(
    user_id="user_123",
    character_id="char_456",
    starting_location="Crossroads Village",      # Optional
    starting_location_type=LocationType.TOWN     # Optional
)
```

**Validations:**
- User must own the character
- User cannot exceed their tier's session limit

**Session Limits by Tier:**
| Tier | Max Sessions |
|------|--------------|
| FREE | 1 |
| BASIC | 2 |
| PREMIUM | 3 |
| ELITE | 5 |

**Returns:** `GameSession` instance

**Raises:**
- `CharacterNotFound` - Character doesn't exist or user doesn't own it
- `SessionLimitExceeded` - User has reached their tier's session limit

### Retrieving Sessions

#### Get Single Session

```python
session = service.get_session(
    session_id="sess_789",
    user_id="user_123"      # Optional, validates ownership
)
```

**Raises:** `SessionNotFound`

#### Get User's Sessions

```python
sessions = service.get_user_sessions(
    user_id="user_123",
    active_only=True,       # Default: True
    limit=25                # Default: 25
)
```

#### Count Sessions

```python
count = service.count_user_sessions(
    user_id="user_123",
    active_only=True
)
```

### Updating Sessions

#### Direct Update

```python
session.turn_number += 1
session = service.update_session(session)
```

#### End Session

```python
session = service.end_session(
    session_id="sess_789",
    user_id="user_123"
)
# Sets status to COMPLETED
```

---

## Conversation History

### Adding Entries

```python
session = service.add_conversation_entry(
    session_id="sess_789",
    character_id="char_456",
    character_name="Brave Hero",
    action="I explore the tavern",
    dm_response="You enter a smoky tavern filled with patrons...",
    combat_log=[],                              # Optional
    quest_offered={"quest_id": "q1", "name": "..."}  # Optional
)
```

**Automatic behaviors:**
- Increments `turn_number`
- Adds timestamp
- Updates `last_activity`

### Retrieving History

```python
# Get all history (with pagination)
history = service.get_conversation_history(
    session_id="sess_789",
    limit=20,              # Optional
    offset=0               # Optional, from end
)

# Get recent entries for AI context
recent = service.get_recent_history(
    session_id="sess_789",
    num_turns=3            # Default: 3
)
```

---

## Game State Tracking

### Location Management

```python
# Update current location
session = service.update_location(
    session_id="sess_789",
    new_location="Dark Forest",
    location_type=LocationType.WILDERNESS
)
# Also adds to discovered_locations if new

# Add discovered location without traveling
session = service.add_discovered_location(
    session_id="sess_789",
    location="Ancient Ruins"
)
```

### Quest Management

```python
# Add active quest (max 2)
session = service.add_active_quest(
    session_id="sess_789",
    quest_id="quest_goblin_cave"
)

# Remove quest (on completion or abandonment)
session = service.remove_active_quest(
    session_id="sess_789",
    quest_id="quest_goblin_cave"
)
```

**Raises:** `SessionValidationError` if adding 3rd quest

### World Events

```python
session = service.add_world_event(
    session_id="sess_789",
    event={
        "type": "festival",
        "description": "A harvest festival begins in town"
    }
)
# Timestamp auto-added
```

---

## Session Limits

| Limit | Value | Notes |
|-------|-------|-------|
| Active sessions per user | Tier-based (1-5) | See tier limits below |
| Active quests per session | 2 | Complete or abandon to accept new |
| Conversation history | Unlimited | Consider archiving for very long sessions |

**Session Limits by Tier:**
| Tier | Max Sessions |
|------|--------------|
| FREE | 1 |
| BASIC | 2 |
| PREMIUM | 3 |
| ELITE | 5 |

---

## Database Schema

Sessions are stored in Appwrite `game_sessions` collection:

| Field | Type | Description |
|-------|------|-------------|
| `$id` | string | Session ID (document ID) |
| `userId` | string | Owner's user ID |
| `sessionData` | string | JSON serialized GameSession |
| `status` | string | "active", "completed", "timeout" |
| `sessionType` | string | "solo" or "multiplayer" |

### Indexes

- `userId` - For user session queries
- `status` - For active session filtering
- `userId + status` - Composite for active user sessions

---

## Usage Examples

### Complete Solo Gameplay Flow

```python
from app.services.session_service import get_session_service
from app.models.enums import LocationType

service = get_session_service()

# 1. Create session
session = service.create_solo_session(
    user_id="user_123",
    character_id="char_456"
)

# 2. Player takes action, AI responds
session = service.add_conversation_entry(
    session_id=session.session_id,
    character_id="char_456",
    character_name="Hero",
    action="I look around the village square",
    dm_response="The village square bustles with activity..."
)

# 3. Player travels
session = service.update_location(
    session_id=session.session_id,
    new_location="The Rusty Anchor Tavern",
    location_type=LocationType.TAVERN
)

# 4. Quest offered and accepted
session = service.add_active_quest(
    session_id=session.session_id,
    quest_id="quest_goblin_cave"
)

# 5. End session
session = service.end_session(
    session_id=session.session_id,
    user_id="user_123"
)
```

### Checking Session State

```python
session = service.get_session("sess_789")

# Check session type
if session.is_solo():
    char_id = session.solo_character_id
else:
    char_id = session.get_current_character_id()

# Check current location
location = session.game_state.current_location
location_type = session.game_state.location_type

# Check active quests
quests = session.game_state.active_quests
can_accept_quest = len(quests) < 2

# Get recent context for AI
recent = service.get_recent_history(session.session_id, num_turns=3)
```

---

## Error Handling

### Exception Classes

```python
from app.services.session_service import (
    SessionNotFound,
    SessionLimitExceeded,
    SessionValidationError,
)

try:
    session = service.get_session("invalid_id", "user_123")
except SessionNotFound:
    # Session doesn't exist or user doesn't own it
    pass

try:
    service.create_solo_session(user_id, char_id)
except SessionLimitExceeded:
    # User has reached their tier's session limit
    pass

try:
    service.add_active_quest(session_id, "quest_3")
except SessionValidationError:
    # Already have 2 active quests
    pass
```

---

## Testing

Run session tests:

```bash
# Unit tests
pytest tests/test_session_model.py -v
pytest tests/test_session_service.py -v

# Verification script (requires TEST_USER_ID and TEST_CHARACTER_ID in .env)
python scripts/verify_session_persistence.py
```

---

## Related Documentation

- [DATA_MODELS.md](DATA_MODELS.md) - Character and item models
- [STORY_PROGRESSION.md](STORY_PROGRESSION.md) - Story turn system
- [QUEST_SYSTEM.md](QUEST_SYSTEM.md) - Quest mechanics
- [API_REFERENCE.md](API_REFERENCE.md) - Session API endpoints
