A newer version of the Streamlit SDK is available:
1.52.2
ο»Ώ# Wrdler Leaderboard System Specification
Document Version: 1.4.3 Project Version: 0.2.10 Author: GitHub Copilot Last Updated: 2025-12-18 Status: β Implemented and Documented
Table of Contents
- Executive Summary
- Goals and Objectives
- System Architecture
- Data Models
- New Python Modules
- Implementation Steps
- Version Changes
- File Changes Summary
- API Reference
- UI Components
- Testing Requirements
- Migration Notes
- Operational Considerations
1. Executive Summary
This specification documents the implemented Daily and Weekly Leaderboard System for Wrdler. The system:
- β Tracks top 25 scores for daily leaderboards (resets at UTC midnight)
- β Tracks top 25 scores for weekly leaderboards (resets at UTC Monday 00:00)
- β Creates separate leaderboards for each unique combination of game-affecting settings
- β Automatically adds qualifying scores from any game completion (including challenge mode)
- β Provides a dedicated leaderboard page with historical lookup capabilities
- β Stores leaderboard data in HuggingFace repository using existing storage infrastructure
- β Uses folder-based discovery (no index.json) with descriptive folder names
- β Uses a unified JSON format consistent with existing challenge settings.json files
- All leaderboard and challenge submissions use the latest st.session_state, including challenge overrides.
Implementation Status: All features complete and deployed as of version 0.2.0
2. Goals and Objectives
Primary Goals
Settings-Based Leaderboards: Each unique combination of game-affecting settings creates a separate leaderboard. Players using different settings compete on different leaderboards.
Daily Leaderboards: Create and maintain daily leaderboards with top 25 entries displayed (can store more), organized by date folders (e.g.,
games/leaderboards/daily/2025-01-27/)Weekly Leaderboards: Create and maintain weekly leaderboards with top 25 entries displayed (can store more), organized by ISO week folders (e.g.,
games/leaderboards/weekly/2025-W04/)Automatic Qualification: Every game completion (challenge or solo) checks if score qualifies for the matching daily/weekly leaderboard based on current settings
Leaderboard Page: New Streamlit page displaying:
- Last 7 days of daily leaderboards (filtered by current settings)
- Last 5 weeks of weekly leaderboards with per-week expanders (current week open unless
week=YYYY-Wwwquery overrides) - Historical lookup via dropdown
Folder-Based Discovery: No index.json file. Leaderboards are discovered by scanning folder names. Folder names include settings info for fast filtering.
Unified File Format: Leaderboard files use the same structure as challenge settings.json with an
entry_typefield to distinguish between "daily", "weekly", and "challenge" entries
Secondary Goals
- Maintain backward compatibility with existing challenge system
- Minimize HuggingFace API calls through caching
- Support sorting by score (descending), then time (ascending), then difficulty (descending)
- Use
challenge_idas the primary identifier across all entry types
Game-Affecting Settings
The following settings define a unique leaderboard:
| Setting | Type | Description |
|---|---|---|
game_mode |
string | "classic", "easy", "too easy" |
wordlist_source |
string | Wordlist file (e.g., "classic.txt", "easy.txt") |
show_incorrect_guesses |
bool | Whether incorrect guesses are shown |
enable_free_letters |
bool | Whether free letters feature is enabled |
puzzle_options |
object | Puzzle configuration (spacer, may_overlap) |
Example: A player using game_mode: "easy" with wordlist_source: "easy.txt" competes on a different leaderboard than a player using game_mode: "classic" with wordlist_source: "classic.txt".
3. System Architecture
3.1 Storage Structure
Each date/week has settings-based subfolders. The folder name (file_id) encodes the settings for fast discovery. All leaderboards use settings.json as the filename (consistent with challenges):
HF_REPO_ID/
βββ games/ # All game-related storage
β βββ {challenge_id}/ # Existing challenge storage
β β βββ settings.json # entry_type: "challenge"
β βββ leaderboards/
β βββ daily/
β β βββ 2025-01-27/
β β β βββ classic-classic-0/
β β β β βββ settings.json
β β β βββ easy-easy-0/
β β β βββ settings.json
β β βββ 2025-01-26/
β β βββ classic-classic-0/
β β βββ settings.json
β βββ weekly/
β βββ 2025-W04/
β β βββ classic-classic-0/
β β β βββ settings.json
β β βββ easy-too_easy-0/
β β βββ settings.json
β βββ 2025-W03/
β βββ classic-classic-0/
β βββ settings.json
βββ shortener.json # Existing URL shortener
3.2 File ID Format
The file_id (folder name) encodes settings for discovery without an index:
{wordlist_source}-{game_mode}-{sequence}
Examples:
classic-classic-0- Classic wordlist, classic mode, first instanceeasy-easy-0- Easy wordlist, easy mode, first instanceclassic-too_easy-1- Classic wordlist, "too easy" mode, second instance
Sanitization Rules:
.txtextension is removed from wordlist_source- Spaces are replaced with underscores
- All lowercase
3.3 Folder-Based Discovery
Instead of maintaining an index.json file, leaderboards are discovered by:
- List period folders: Scan
games/leaderboards/daily/orgames/leaderboards/weekly/for date/week folders - List file_id folders: For each period, scan for settings folders
- Filter by prefix: Match file_ids that start with
{wordlist_source}-{game_mode}- - Load and verify: Load
settings.jsonto verify full settings match
Benefits:
- No index synchronization issues
- Self-documenting folder structure
- Can browse folders directly
- Reduced write operations (no index updates)
3.4 Data Flow
- All leaderboard and challenge submissions use the latest st.session_state, including challenge overrides.
ββββββββββββββββββββββ
β Game Completion β
ββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββ
β Get current game β
β settings β
ββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββ
β Build file_id β
β prefix from β
β settings β
ββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββ
β Scan folders for β
β matching file_id β
β or create new β
ββββββββββββββββββββββ
β
βββββββ΄ββββββ
β β
βΌ βΌ
βββββββββ βββββββββββββ
β Daily β β Weekly β
β LB β β LB |
βββββββββ βββββββββββββ
β β
ββββββ¬ββββββ
β
βΌ
βββββββββββββββββββββββ
β Check if score β
β qualifies (top β
β 25 displayed) β
βββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββ
β Update & Upload β
β to HF repo β
βββββββββββββββββββββββ
4. Data Models
4.1 Entry Type Definition
The entry_type field distinguishes between different types of game entries:
| entry_type | Description | Storage Location |
|---|---|---|
"challenge" |
Player-created challenge for others to compete | games/{challenge_id}/settings.json |
"daily" |
Daily leaderboard entry | games/leaderboards/daily/{date}/{file_id}/settings.json |
"weekly" |
Weekly leaderboard entry | games/leaderboards/weekly/{week}/{file_id}/settings.json |
4.2 Unified File Schema (Consistent with Challenge settings.json)
Both leaderboard files and challenge files use the same base structure. The settings in the file define what makes this leaderboard unique:
{
"challenge_id": "2025-01-27/classic-classic-0",
"entry_type": "daily",
"game_mode": "classic",
"grid_size": 8,
"puzzle_options": {
"spacer": 0,
"may_overlap": false
},
"users": [
{
"uid": "20251130T190249Z-0XLG5O",
"username": "Charles",
"word_list": ["CHEER", "PLENTY", "REAL", "ARRIVE", "DEAF", "DAILY"],
"word_list_difficulty": 117.48,
"score": 39,
"time": 132,
"timestamp": "2025-11-30T19:02:49.544933+00:00",
"source_challenge_id": null
}
],
"created_at": "2025-11-30T19:02:49.544933+00:00",
"version": "0.2.0",
"show_incorrect_guesses": true,
"enable_free_letters": true,
"wordlist_source": "classic.txt",
"game_title": "Wrdler Gradio AI",
"max_display_entries": 25
}
4.3 Field Descriptions
| Field | Type | Description |
|---|---|---|
challenge_id |
string | Unique identifier. For daily: "2025-01-27/classic-classic-0", weekly: "2025-W04/easy-easy-0", challenge: "20251130T190249Z-ABCDEF" |
entry_type |
string | One of: "daily", "weekly", "challenge" |
game_mode |
string | Game difficulty: "classic", "easy", "too easy" |
grid_size |
int | Grid width (8 for Wrdler) |
puzzle_options |
object | Puzzle configuration (defines leaderboard uniqueness) |
users |
array | Array of user entries (sorted by score desc, time asc, difficulty desc) |
created_at |
string | ISO 8601 timestamp when entry was created |
version |
string | Schema version |
show_incorrect_guesses |
bool | Display setting (defines leaderboard uniqueness) |
enable_free_letters |
bool | Free letters feature toggle (defines leaderboard uniqueness) |
wordlist_source |
string | Source wordlist file (defines leaderboard uniqueness) |
game_title |
string | Game title for display |
max_display_entries |
int | Maximum entries to display (default 25, configurable via MAX_DISPLAY_ENTRIES env var) |
4.4 User Entry Schema
Each user entry in the users array:
{
"uid": "20251130T190249Z-0XLG5O",
"username": "Charles",
"word_list": ["CHEER", "PLENTY", "REAL", "ARRIVE", "DEAF", "DAILY"],
"word_list_difficulty": 117.48,
"score": 39,
"time": 132,
"timestamp": "2025-11-30T19:02:49.544933+00:00",
"source_challenge_id": null
}
| Field | Type | Description |
|---|---|---|
uid |
string | Unique user entry ID |
username |
string | Player display name |
word_list |
array | 6 words played |
word_list_difficulty |
float | Calculated difficulty score |
score |
int | Final score |
time |
int | Time in seconds |
timestamp |
string | ISO 8601 when entry was recorded |
source_challenge_id |
string|null | If from a challenge, the original challenge_id |
4.5 Settings Matching
Two leaderboards are considered the same if ALL of the following match:
game_modewordlist_source(after sanitization - .txt removed, lowercase)show_incorrect_guessesenable_free_letterspuzzle_options.spacerpuzzle_options.may_overlap
4.6 Weekly Leaderboard Naming
Uses ISO 8601 week numbering:
- Format:
YYYY-Www(e.g.,2025-W04) - Week starts on Monday
- Week 1 is the week containing the first Thursday of the year
5. New Python Modules
5.1 wrdler/leaderboard.py (NEW FILE)
Purpose: Core leaderboard logic for managing daily and weekly leaderboards with settings-based separation and folder-based discovery.
Key classes:
GameSettings- Settings that define a unique leaderboardUserEntry- Single user entry in a leaderboardLeaderboardSettings- Unified leaderboard/challenge settings format
Key functions:
_sanitize_wordlist_source()- Remove .txt extension and normalize_build_file_id()- Create file_id from settings_parse_file_id()- Parse file_id into componentsfind_matching_leaderboard()- Find leaderboard by scanning folderscreate_or_get_leaderboard()- Get or create a leaderboardsubmit_score_to_all_leaderboards()- Main entry point for submissions
5.2 wrdler/modules/storage.py (UPDATED)
Added functions:
_list_repo_folders()- List folder names under a path in HuggingFace repo_list_repo_files_in_folder()- List files in a folder
6. Implementation Steps
Phase 1: Core Leaderboard Module (v0.2.0-alpha) β COMPLETE
| Step | Task | Files | Status |
|---|---|---|---|
| 1.1 | Create wrdler/leaderboard.py with GameSettings and data models |
NEW | β Complete |
| 1.2 | Implement folder listing in storage.py | storage.py | β Complete (_list_repo_folders) |
| 1.3 | Implement find_matching_leaderboard() with folder scanning |
leaderboard.py | β Complete |
| 1.4 | Implement check_qualification() and sorting |
leaderboard.py | β Complete (_sort_users) |
| 1.5 | Implement submit_to_leaderboard() and submit_score_to_all_leaderboards() |
leaderboard.py | β Complete |
| 1.6 | Write unit tests for leaderboard logic including settings matching | tests/test_leaderboard.py | β³ Recommended |
Phase 2: UI Integration (v0.2.0-beta) β COMPLETE
| Step | Task | Files | Status |
|---|---|---|---|
| 2.1 | Create wrdler/leaderboard_page.py with settings filtering |
NEW | β Complete |
| 2.2 | Add leaderboard navigation to ui.py sidebar |
ui.py | β Complete (footer menu) |
| 2.3 | Integrate score submission in _game_over_content() with current settings |
ui.py | β Complete |
| 2.4 | Add leaderboard results display in game over dialog | ui.py | β Complete |
| 2.5 | Style leaderboard tables to match ocean theme | leaderboard_page.py | β Complete (pandas dataframe styling) |
Phase 3: Challenge Format Migration (v0.2.0-beta) β COMPLETE
| Step | Task | Files | Status |
|---|---|---|---|
| 3.1 | Add entry_type field to existing challenge saves |
game_storage.py | β Complete |
| 3.2 | Update challenge loading to handle entry_type |
game_storage.py | β Complete (defaults to "challenge") |
| 3.3 | Test backward compatibility with old challenges | Manual | β Verified |
Phase 4: Testing & Polish (v0.2.0-rc) β COMPLETE
| Step | Task | Files | Status |
|---|---|---|---|
| 4.1 | Integration testing with HuggingFace | Manual | β Verified |
| 4.2 | Add caching for leaderboard data | leaderboard.py | β³ Future optimization |
| 4.3 | Add error handling and retry logic | leaderboard.py | β Complete (logging) |
| 4.4 | Update documentation | README.md, specs/, CLAUDE.md | β Complete |
| 4.5 | Version bump and release notes | pyproject.toml, init.py | β Complete (v0.2.0) |
7. Version Changes
pyproject.toml
[project]
name = "wrdler"
version = "0.2.0" # Updated from 0.1.0
description = "Wrdler vocabulary puzzle game with daily/weekly leaderboards"
wrdler/init.py
__version__ = "0.2.0" # Updated from existing version
wrdler/game_storage.py
__version__ = "0.2.0" # Updated from 0.1.5
wrdler/leaderboard.py (NEW)
__version__ = "0.2.0"
wrdler/leaderboard_page.py (NEW)
__version__ = "0.2.0"
wrdler/modules/storage.py
__version__ = "0.1.6" # Updated to add folder listing functions
8. File Changes Summary
New Files
| File | Purpose |
|---|---|
wrdler/leaderboard.py |
Core leaderboard logic with folder-based discovery |
wrdler/leaderboard_page.py |
Streamlit leaderboard page |
tests/test_leaderboard.py |
Unit tests for leaderboard |
specs/leaderboard_spec.md |
This specification |
Modified Files
| File | Changes |
|---|---|
pyproject.toml |
Version bump to 0.2.0 |
wrdler/__init__.py |
Version bump, add leaderboard exports |
wrdler/modules/storage.py |
Add _list_repo_folders() and _list_repo_files_in_folder() |
wrdler/game_storage.py |
Version bump, add entry_type field, integrate leaderboard submission |
wrdler/ui.py |
Add leaderboard nav, integrate submission in game over |
wrdler/modules/__init__.py |
Export new functions if needed |
9. API Reference
Public Functions in wrdler/leaderboard.py
def submit_score_to_all_leaderboards(
username: str,
score: int,
time_seconds: int,
word_list: List[str],
settings: GameSettings,
word_list_difficulty: Optional[float] = None,
source_challenge_id: Optional[str] = None,
repo_id: Optional[str] = None
) -> Dict[str, Any]:
"""Main entry point for submitting scores after game completion."""
def load_leaderboard(
entry_type: EntryType,
period_id: str,
file_id: str,
repo_id: Optional[str] = None
) -> Optional[LeaderboardSettings]:
"""Load a specific leaderboard by file ID."""
def find_matching_leaderboard(
entry_type: EntryType,
period_id: str,
settings: GameSettings,
repo_id: Optional[str] = None
) -> Tuple[Optional[str], Optional[LeaderboardSettings]]:
"""Find a leaderboard matching given settings."""
def get_last_n_daily_leaderboards(
n: int = 7,
settings: Optional[GameSettings] = None,
repo_id: Optional[str] = None
) -> List[Tuple[str, Optional[LeaderboardSettings]]]:
"""Get recent daily leaderboards for display."""
def list_available_periods(
entry_type: EntryType,
limit: int = 30,
repo_id: Optional[str] = None
) -> List[str]:
"""List available period IDs from folder structure."""
def list_settings_for_period(
entry_type: EntryType,
period_id: str,
repo_id: Optional[str] = None
) -> List[Dict[str, Any]]:
"""List all settings combinations for a period."""
def get_current_daily_id() -> str:
"""Get today's period ID."""
def get_current_weekly_id() -> str:
"""Get this week's period ID."""
10. UI Components
10.1 Footer Navigation (Updated)
Leaderboard navigation is now accessed via the footer menu at the bottom of the page, not the sidebar. The footer contains links to Leaderboard, Play, and Settings pages. This replaces the previous sidebar navigation.
10.2 Game Over Integration (Updated)
The game over dialog now integrates leaderboard submission and displays qualification results (rankings). After submitting your score, the dialog will show if you qualified for the daily or weekly leaderboard and your rank.
10.3 Leaderboard Page Routing (Updated)
Leaderboard page routing uses query parameters and custom navigation links for tab selection (e.g., ?page=today, ?page=weekly). Tabs are not implemented with Streamlit's native tabs but with custom links for better URL support.
11. Testing Requirements
Unit Tests (tests/test_leaderboard.py)
class TestUserEntry:
def test_create_entry(self): ...
def test_to_dict_roundtrip(self): ...
def test_from_legacy_time_seconds_field(self): ...
class TestLeaderboardSettings:
def test_create_leaderboard(self): ...
def test_entry_type_values(self): ...
def test_get_display_users_limit(self): ...
def test_format_matches_challenge(self): ...
class TestGameSettings:
def test_settings_matching_same(self): ...
def test_settings_matching_different_mode(self): ...
def test_settings_matching_txt_extension_ignored(self): ...
def test_get_file_id_prefix(self): ...
class TestFileIdFunctions:
def test_sanitize_wordlist_source_removes_txt(self): ...
def test_build_file_id(self): ...
def test_parse_file_id(self): ...
class TestQualification:
def test_qualify_empty_leaderboard(self): ...
def test_qualify_not_full(self): ...
def test_qualify_by_score(self): ...
def test_qualify_by_time_tiebreaker(self): ...
def test_qualify_by_difficulty_tiebreaker(self): ...
def test_not_qualify_lower_score(self): ...
class TestDateIds:
def test_daily_id_format(self): ...
def test_weekly_id_format(self): ...
def test_daily_path(self): ... # Tests new folder structure
def test_weekly_path(self): ... # Tests new folder structure
Integration Tests
- Test full flow: game completion β leaderboard submission β retrieval
- Test with mock HuggingFace repository
- Test folder-based discovery logic
- Test concurrent submissions (edge case)
- Test backward compatibility with legacy challenge files (no entry_type)
12. Migration Notes
Backward Compatibility
- Existing challenges continue to work unchanged (entry_type defaults to "challenge")
- No changes to
shortener.jsonformat - Challenge
settings.jsonformat is extended (new fields are optional) - No index.json migration needed - folder-based discovery is self-contained
Schema Evolution
| Version | Changes |
|---|---|
| 0.1.x | Original challenge format |
| 0.2.0 | Added entry_type, max_display_entries, source_challenge_id fields |
| 0.2.0 | Changed to folder-based discovery (no index.json) |
| 0.2.0 | New folder structure: games/leaderboards/{type}/{period}/{file_id}/settings.json |
Data Migration
- No migration required for existing challenges
- New leaderboard files use folder-based storage from start
- Legacy challenges without
entry_typedefault to"challenge"
Rollback Plan
- Remove leaderboard imports from
ui.py - Remove sidebar navigation button
- Remove game over submission calls
- Optionally: delete
games/leaderboards/directory from HF repo
13. Implementation Notes
13.1 Actual Implementation Details
The following represents the actual implementation as of v0.2.0:
Core Modules Implemented
wrdler/leaderboard.py (v0.2.0):
- β
GameSettingsdataclass with settings matching logic - β
UserEntrydataclass for individual scores - β
LeaderboardSettingsdataclass (unified format) - β File ID sanitization and parsing functions
- β
Folder-based discovery with
_list_period_folders()and_list_file_ids_for_period() - β
find_matching_leaderboard()with prefix filtering and full verification - β
create_or_get_leaderboard()with automatic sequence management - β
submit_score_to_all_leaderboards()as main entry point - β
check_qualification()for top 25 filtering - β
Period ID generators:
get_current_daily_id(),get_current_weekly_id() - β
Historical lookup functions:
list_available_periods(),list_settings_for_period() - β
URL generation with
get_leaderboard_url()
wrdler/leaderboard_page.py (v0.2.0):
- β Four-tab navigation system (Today, Daily, Weekly, History)
- β
Query parameter routing (
?page=,?gidd=,?gidw=) - β Settings badge display with full configuration info
- β Styled pandas dataframes with rank emojis (π₯π₯π₯)
- β Challenge indicator badges (π―)
- β Expandable leaderboard groups per settings combination
- β Last updated timestamps and entry counts
wrdler/modules/storage.py (Updated):
- β
_list_repo_folders()for folder discovery - β Seamless integration with existing HF dataset functions
wrdler/ui.py (Updated):
- β Footer menu integration for leaderboard page navigation
- β Automatic submission call in game over flow
- β
Current settings extraction via
GameSettings - β Display qualification results with rank notifications
Storage Implementation
Folder Structure (as built):
HF_REPO_ID/games/
βββ leaderboards/
β βββ daily/
β β βββ {YYYY-MM-DD}/
β β βββ {wordlist_source}-{game_mode}-{sequence}/
β β βββ settings.json
β βββ weekly/
β βββ {YYYY-Www}/
β βββ {wordlist_source}-{game_mode}-{sequence}/
β βββ settings.json
βββ {challenge_id}/
βββ settings.json
File ID Sanitization:
.txtextension removed from wordlist_source- Spaces replaced with underscores
- All lowercase
- Regex:
r'[^\w\-]'replaced with_
UI/UX Features Implemented
Today Tab:
- Displays current daily and weekly leaderboards side-by-side in two columns
- Query parameter filtering:
?gidd={file_id}and?gidw={file_id}show specific leaderboards - Expandable settings groups with full configuration captions
Daily Tab:
- Shows last 7 days of daily leaderboards
- One expander per date with all settings combinations nested
- Today's date auto-expanded by default
Weekly Tab:
- Shows current ISO week leaderboard
- All settings combinations displayed in expandable groups
History Tab:
- Two-column layout: Daily (left) and Weekly (right)
- Dropdown selectors for period and settings combination
- "Load Daily" and "Load Weekly" buttons for explicit loading
Table Styling:
- Pandas DataFrame with custom CSS styling
- Rank column: large bold text with emojis
- Score column: green color (#20d46c) with bold font
- Challenge indicator: π― badge appended to username
- Last updated timestamp and entry count displayed below table
Key Differences from Spec
- Navigation: Implemented as 'Leaderboard' link in the footer menu instead of sidebar button
- Caching: Not implemented in v0.2.0 (deferred to v0.3.0 for optimization)
- Tab Implementation: Used query parameters with custom nav links instead of Streamlit native tabs for better URL support
- Table Rendering: Used pandas DataFrames with styling instead of custom HTML tables
Known Limitations (as of v0.2.0)
- No caching: Each page load fetches from HF repository (can be slow)
- No pagination: Displays top 25 only (additional entries stored but not shown)
- Limited error handling: Basic logging, could benefit from retry logic
- No rate limiting: Submission frequency not constrained
- No archival: Old leaderboards remain indefinitely (no cleanup script)
Future Enhancements (Planned for v0.3.0+)
- β³ In-memory caching with TTL (60s for periods, 15s for leaderboards)
- β³ Pagination for >25 entries
- β³ Retry logic with exponential backoff
- β³ Rate limiting per IP/session
- β³ Archival script for old periods (>365 days daily, >156 weeks weekly)
- β³ Manual refresh button in UI
14. Operational Considerations
14.1 Concurrency and Consistency
- Write model:
- Use optimistic concurrency: read
settings.json, merge newusersentry, write back with a unique commit message includingchallenge_idanduid. - Implement retry with exponential backoff on HTTP 409/5xx or checksum mismatch.
- Ensure atomicity per file write: do not split updates across multiple files per leaderboard.
- Use optimistic concurrency: read
- Simultaneous file creation:
- When no matching
file_idfolder exists, first attempt creation; if a concurrent process creates it, fallback to loading and merging. - Always re-verify settings match by reading
settings.jsonafter folder discovery.
- When no matching
- Sequence collisions:
- If
{wordlist}-{mode}-{sequence}collides but settings differ, incrementsequenceuntil a unique folder is found; verify match via file content, not only prefix.
- If
14.2 Caching and Discovery Performance
- Cache tiers:
- In-memory (per app session):
- Period listings for
games/leaderboards/{type}/(TTL 60s). file_idlistings inside a period (TTL 30s).- Loaded
settings.jsonfor leaderboards (TTL 15s or invalidated on write).
- Period listings for
- In-memory (per app session):
- Invalidation:
- On successful submission, invalidate the specific leaderboard cache (file content and directory listing for that period).
- Provide a manual refresh option in UI (leaderboard page).
- Discovery limits:
- Cap directory scans to the most recent N periods (configurable; default 30). UI uses explicit period selection for older data.
- Prefer prefix filtering client-side before loading file content.
14.3 Error Handling and Observability
- Error taxonomy:
- Storage errors:
HF_STORAGE_UNAVAILABLE,HF_WRITE_CONFLICT,HF_NOT_FOUND. - Validation errors:
LB_INVALID_INPUT,LB_SETTINGS_MISMATCH. - Operational errors:
LB_TIMEOUT,LB_RETRY_EXCEEDED.
- Storage errors:
- User feedback:
- On non-critical failure (e.g., leaderboard write conflict), show non-blocking warning and retry silently up to 3 times.
- Logging:
- Log submission events with fields:
entry_type,period_id,file_id,uid,score,time,rank_result,repo_path,latency_ms. - Log error events with
code,message,attempt,backoff_ms.
- Log submission events with fields:
- Telemetry (optional):
- Count successful submissions per period and per settings combination for basic monitoring.
14.4 Security and Abuse Controls
- Input validation:
username: max 40 chars, strip control chars, allow alphanumerics, spaces, basic punctuation; reject offensive content if possible.word_list: array of 6 uppercase AβZ strings, length 3β10; drop invalid entries.score: 0β999;time: 1β36000 (10 hours);word_list_difficulty: float if provided, clamp to 0β10000.
- Spam and duplicates:
- Rate limit per IP/session (e.g., max 10 submissions per hour).
- Detect duplicate entries by same
uid+timestampwithin 10 seconds window; deduplicate silently.
- Repository permissions:
- Submissions require HF write permissions for the space; ensure credentials are scoped to the specific repo.
- Do not expose write tokens in client logs; keep server-side commit operations.
14.5 Data Lifecycle and Retention
- Retention:
- Keep daily leaderboards for 365 days; weekly leaderboards for 156 weeks (3 years).
- Optional archival: move older periods to
games/leaderboards_archive/{type}/or leave as-is with documented retention.
- Cleanup:
- Provide a maintenance script to prune old periods and reindex cache.
- Privacy:
- Store only display names and gameplay metrics; avoid PII.
- Users must enter a name (Anonymous not allowed); do not display IP or identifiers publicly.
14.6 Time and Period Boundaries
- Timezone:
- All operations use UTC. The periods roll over at 00:00:00 UTC for daily, Monday 00:00:00 UTC for weekly.
- ISO week:
- Use Pythonβs
isocalendar()to deriveYYYY-Wwwand handle year transitions (weeks spanning year boundaries).
- Use Pythonβs
- Clock source:
- Use server-side timestamp for submissions; do not trust client clock. If unavailable, fall back to Python
datetime.utcnow().
- Use server-side timestamp for submissions; do not trust client clock. If unavailable, fall back to Python
14.7 UI Reliability and UX
- Loading states:
- Show skeleton/loading indicators while scanning folders or reading leaderboard JSON.
- Empty states:
- Display βNo entries yetβ when a leaderboard exists without users or has not been created.
- Accessibility:
- Ensure sufficient color contrast, keyboard navigation for tabs/period selectors, and alt text for icons.
- Internationalization (future):
- Keep date/time ISO formatting and English labels; design UI to allow future localization.
14.8 Ranking and Tie-Breaks (Operational Clarification)
- Sort order:
- Primary:
scoredesc; secondary:timeasc; tertiary:word_list_difficultydesc; quaternary: stable bytimestampasc.
- Primary:
- Display limit:
- Always store full
userslist; applymax_display_entriesat render time only.
- Always store full
- Rank reporting:
- Return rank based on full sorted list even if not displayed; if outside display limit, mark
qualified=False.
- Return rank based on full sorted list even if not displayed; if outside display limit, mark
- Duplicate removal:
- If multiple entries exist with identical
username,word_list,score,time,timestamp, andword_list_difficulty, only keep one entry. - Prefer the entry with a non-null
source_challenge_idif duplicates are found.
- If multiple entries exist with identical
14.9 Commit and Retry Strategy (HF)
- Commit messages:
- Format:
leaderboard:{entry_type}:{period_id}:{file_id} add uid={uid} score={score} time={time}.
- Format:
- Retries:
- Backoff sequence: 0.25s, 0.5s, 1s; max 3 attempts; abort on
LB_SETTINGS_MISMATCH.
- Backoff sequence: 0.25s, 0.5s, 1s; max 3 attempts; abort on
- Partial failures:
- If daily succeeds and weekly fails (or vice versa), return both statuses independently; UI reports partial success.
14.10 Timezone Handling
- Daily leaderboard files use UTC for period boundaries.
- When displaying, show the UTC period as a PST date range:
For daily leaderboards, display the period as:
"YYYY-MM-DD 00:00:00 UTC to YYYY-MM-DD 23:59:59 UTC"
and
"YYYY-MM-DD HH:MM:SS PST to YYYY-MM-DD HH:MM:SS PST"
(PST is UTC-8; adjust for daylight saving as needed)
For example, a UTC file date of
2025-12-08covers2025-12-08 00:00:00 UTCto2025-12-08 23:59:59 UTC, which is displayed as2025-12-07 16:00:00 PSTto2025-12-08 15:59:59 PST. The leaderboard expander label should show:Mon, Dec 08, 2025 4:00 PM PST β Tue, Dec 09, 2025 3:59:59 PM PST [settings badge]
Leaderboard Page UI:
- Today Tab: Current daily and weekly leaderboards
- Daily Tab: Last 7 days of daily leaderboards
- Weekly Tab: Last 5 weeks displayed as individual expanders (current week or
week=YYYY-Wwwquery opens by default) - History Tab: Historical leaderboard browser