Add persistence documentation

Explains the store module architecture, password security considerations,
and the three-layer save strategy (auto-save, logout save, disconnect save).
This commit is contained in:
Jared Miller 2026-02-07 21:42:19 -05:00
parent 54998c29c5
commit f36880f82c
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C

77
docs/how/persistence.txt Normal file
View file

@ -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