diff --git a/docs/how/persistence.txt b/docs/how/persistence.txt new file mode 100644 index 0000000..d5ec445 --- /dev/null +++ b/docs/how/persistence.txt @@ -0,0 +1,77 @@ +PERSISTENCE + +This document explains how player accounts and state are persisted to disk. + +WHAT'S PERSISTED + +Player accounts: +- Username (case-insensitive) +- Password (hashed with PBKDF2-HMAC-SHA256 and random salt) +- Position (x, y coordinates) +- Combat stats (pl, stamina, max_stamina) +- Flying status +- Created timestamp +- Last login timestamp + +What's NOT persisted (runtime state only): +- Telnet I/O (writer, reader) +- Mode stack (normal/combat/editor modes) +- Active effects +- Combat encounters +- Commands being executed + +DATABASE LOCATION + +Default: data/mud.db (SQLite) + +The database is initialized on server startup via init_db(). The store module +uses a simple module-level _db_path variable to track the connection. + +LOGIN FLOW + +New player: +1. Enter name +2. If account doesn't exist, prompt "Create new account? (y/n)" +3. If yes, prompt for password twice (must match) +4. Create account with hashed password +5. Start at default position (world center, or nearest passable tile) + +Existing player: +1. Enter name +2. Prompt for password +3. Verify password (max 3 attempts) +4. Load saved position and stats +5. If saved position is no longer passable (world changed), find nearest + passable tile + +WHEN SAVES HAPPEN + +1. On quit: When player types "quit" command +2. On disconnect: When connection is lost (network error, timeout, etc) +3. Periodic auto-save: Every 60 seconds for all connected players + +The periodic auto-save runs in the game loop alongside combat and effects +processing. It only triggers if at least one player is connected. + +IMPLEMENTATION NOTES + +Password hashing: +- Uses hashlib.pbkdf2_hmac (stdlib, no external dependencies) +- 100,000 iterations of SHA-256 +- Random 32-byte salt per account (from os.urandom) +- Both hash and salt stored in database + +Case-insensitive names: +- SQLite COLLATE NOCASE on name column +- "Jared", "jared", and "JARED" are the same account + +Synchronous operations: +- Store module uses sync sqlite3 (not async) +- Save operations are fast enough for our scale +- Game loop calls save_player() directly (doesn't block tick processing) + +TESTING + +See tests/test_store.py for store module unit tests +See tests/test_login_flow.py for login/registration integration tests +See tests/test_persistence.py for save-on-quit and auto-save tests