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 (zone, x, y — see object-model.rst for location system)
- 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.

WHAT'S NOT YET PERSISTED

The object model (object-model.rst) introduces containment, inventory, and
zones. Persistence for these is an open question:

- Player inventory (Things with location=player)
- Item instances on the ground (Things with location=zone)
- Container state (open/closed/locked, contents)
- Zone edits from paint mode (tile changes)
- Mob respawn state

These will be addressed as each system gets implemented. Player inventory
will likely go in SQLite alongside account data. Zone edits will persist to
TOML files. Mob instances probably don't persist (respawn from templates).

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
