- Updated `HiddenDomainInfo` interface to include `cd_years` and `open_prob` properties for better domain management.
- Modified `serialize_active_domains` function to serialize new properties.
- Enhanced `StatusWidget` to display additional information about hidden domains, including cooldown years and open probability.
- Updated localization files for English and Chinese to reflect new status labels for hidden domains.
- Updated `serialize_active_domains` to include both active and inactive hidden domains, adding an `is_open` property for each domain.
- Modified `StatusBar` and `StatusWidget` components to reflect the new domain state, displaying appropriate labels and colors based on the activation status.
- Adjusted localization files for English and Chinese to align with the updated hidden domain features.
- Implemented `serialize_active_domains` function to gather and format active hidden domains from the game world.
- Updated `game_loop` to include active domains in the broadcast state.
- Enhanced `StatusBar` component to display active domains with a new `StatusWidget`, including dynamic labels and colors based on the number of active domains.
- Added localization support for hidden domain messages in both English and Chinese.
- Updated the world store to manage active domains state and synchronize with the backend.
* feat: add avatar metrics tracking feature (#110)
Add AvatarMetrics dataclass for tracking avatar state snapshots
- Add AvatarMetrics dataclass for recording monthly snapshots
- Add metrics_history field to Avatar with opt-in tracking
- Implement automatic monthly snapshot recording in Simulator
- Add backward compatibility support for existing save files
- Set default tracking limit to 1200 months (100 years)
- Add comprehensive tests with 100% coverage
- Move documentation to specs directory with simplified chinese
* fix: convert Traditional Chinese comments to Simplified Chinese
修正程式碼中的繁體中文註解為簡體中文,以符合專案規範。
Fix Traditional Chinese comments to Simplified Chinese in codebase.
* fix: use str() instead of .value for realm i18n display
Fix bug where user-facing messages displayed raw enum values like
"FOUNDATION_ESTABLISHMENT" instead of translated names like "筑基".
The Realm and Stage classes already have __str__ methods that return
i18n translated text, but several places were incorrectly using
.value which returns the raw enum string.
Changed files:
- src/classes/single_choice.py: item exchange messages
- src/classes/kill_and_grab.py: loot messages
- src/classes/fortune.py: fortune discovery messages
- src/classes/avatar/inventory_mixin.py: purchase error messages
Also added unit tests and integration tests to prevent regression.
* test: add integration tests for all modified files
Add tests covering:
- kill_and_grab.py: context string realm display
- fortune.py: weapon/auxiliary intro realm display
- inventory_mixin.py: can_buy_item error message realm display
* fix: make cooldown_action decorator properly await async finish()
The decorator was incorrectly wrapping the async finish() method with a sync
wrapper, causing cooldown to be recorded BEFORE the action actually executed.
This fix:
- Changes wrapper to async def finish()
- Awaits original_finish() before recording cooldown
- Ensures cooldown is only recorded on successful execution
Fixes#74
* test: add test to verify cooldown not recorded on failure
This test actually reveals the async/await bug:
- Creates a FailingAction that raises in finish()
- Verifies cooldown is NOT recorded when action fails
- Fails with buggy sync wrapper, passes with async fix
PR #32 changed Technique.sect (str) to Technique.sect_id (int), but
fortune.py was not updated. This caused AttributeError when fortune
event triggered and tried to access t.sect.
Error: 'Technique' object has no attribute 'sect'
The region_names dictionary was a redundant index that needed manual
sync when HistoryManager modified region names. This caused bugs where
resolve_query couldn't find regions by their new (history-modified) names.
Instead of maintaining two data structures (regions[id] and region_names[name]),
we now only use regions[id] as the single source of truth. The _resolve_region
function iterates over regions.values() to find matches by name.
This approach:
- Eliminates the sync bug entirely
- Simplifies the codebase
- Has negligible performance impact (region count is small)
Co-authored-by: Zihao Xu <xzhseh@gmail.com>
* 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.
The game loop could generate events with the old world's timestamp while
loading a save, causing 100年1月 events to appear after loading a 106年
save. Now we pause the game before loading and keep it paused after,
giving the frontend time to refresh state.