Verify that:
1. Avatar without sect knows current location's region
2. Avatar with sect knows their sect headquarters
3. Avatar without sect does not automatically know sect regions
4. Avatar only knows their own sect's headquarters, not others
* fix: CSV column name mismatches in data loading
- sect.py: Fix headquarter_name/headquarter_desc -> name/desc when reading sect_region.csv
- sect.py: Move sid initialization before technique lookup to fix unbound variable bug
- technique.py: Change sect (name) to sect_id (int) to match technique.csv column
- elixir.py: Remove redundant get_int(row, "id") that reads non-existent column
These fixes ensure:
1. Sect headquarters display correct location names (e.g., "大千光极城" instead of "不夜城")
2. Sect techniques are correctly associated and displayed
3. Technique sect restrictions work properly
* fix: update main.py to use sect_id, add CSV loading tests
- main.py: Change technique.sect to technique.sect_id in API response
- Add tests/test_csv_loading.py to verify CSV column names match code
* test: add API test for /api/meta/game_data endpoint
Verify that techniques in API response use sect_id field (not sect)
* fix: add None check for hq_region in AvatarFactory
Remove redundant code that adds sect headquarters to known_regions.
This logic is already handled by Avatar._init_known_regions() which
uses sect_id matching (more reliable than name-based lookup).
The removed code was causing a flaky test (~1% failure rate) because
resolve_query returns None in test environments with simplified maps.
Add splash layer, support game start, settings, exit
Modify settings layer, add "go back to splash" and "exit"
Add character threshold for history input
Closes#28
- Add async initialization with 6 phases: scanning_assets, loading_map,
initializing_sects, generating_avatars, checking_llm, generating_initial_events
- Add /api/init-status endpoint for frontend polling
- Add /api/control/reinit endpoint for error recovery
- Add LoadingOverlay.vue component with:
- Progress ring with gradient
- Phase text in xianxia style (rotating messages for LLM phase)
- Tips that rotate every 5 seconds
- Time-based background transparency (fades to 80% over 20s)
- Backdrop blur effect
- Error state with retry button
- Preload map and avatars during LLM initialization for smoother UX
- Add comprehensive tests for init status API
Problem:
When loading a save (e.g., from year 106), events from year 100 would appear.
This happened because the game auto-started on server startup and client
connection, generating initialization events before the user could load a save.
Solution:
1. Backend: Keep game paused on startup even if LLM check passes
2. Backend: Remove auto-resume on first WebSocket connection
3. Frontend: Start with game paused (isManualPaused = true)
Now the user must explicitly click 'resume' to start a new game, or load a
save first. This prevents the race condition where game_loop generates events
with stale world state.
- Fix load_game to use World.create_with_db() for SQLite event storage
- Add get_events_db_path() to compute event database path from save path
- Add JSON to SQLite migration for backward compatibility with old saves
- Close old EventManager before loading new save to prevent connection leaks
- Add events_db metadata to save file
- Add comprehensive tests for database switching bug and save/load cycle
Implement SQLite-based event persistence as specified in sqlite-event-manager.md.
## Changes
### Backend
- **EventStorage** (`src/classes/event_storage.py`): New SQLite storage layer
- Cursor-based pagination with compound cursor `{month_stamp}_{rowid}`
- Avatar filtering (single and pair queries)
- Major/minor event separation
- Cleanup API with `keep_major` and `before_month_stamp` filters
- **EventManager** (`src/classes/event_manager.py`): Refactored to use SQLite
- Delegates to EventStorage for persistence
- Memory fallback mode for testing
- New `get_events_paginated()` method
- **API** (`src/server/main.py`):
- `GET /api/events` - Paginated event retrieval with filtering
- `DELETE /api/events/cleanup` - User-triggered cleanup
### Frontend
- **EventPanel.vue**: Scroll-to-load pagination, dual-person filter UI
- **world.ts**: Event state management with pagination
- **game.ts**: New API client methods
### Testing
- 81 new tests for EventStorage, EventManager, and API
- Added `pytest-asyncio` and `httpx` to requirements.txt
## Known Issues: Save/Load is Currently Broken
After loading a saved game, the following issues occur:
1. **Wrong database used**: API returns events from the startup database instead
of the loaded save's `_events.db` file
2. **Events from wrong time period**: Shows events from year 115 when loaded
save is at year 114
3. **Pagination broken after load**: `has_more` returns `False` despite hundreds
of events in the saved database
4. **Filter functionality broken**: Character selection filter stops working
after loading a game
Root cause: `load_game.py` does not properly switch the EventManager's database
connection to the loaded save's events database.
- Add validate_target_avatar() to TargetingMixin for unified validation.
- Update Attack and Assassinate to use the new validation method.
- Add comment to MutualAction.can_start() explaining why it uses inline check.
- Add tests for dead target validation.