# 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