ο»Ώ# Wrdler Leaderboard System Specification
Document Version: 1.4.1 Project Version: 0.2.7 Author: GitHub Copilot Last Updated: 2025-12-08 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
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
ββββββββββββββββββββββ
β 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 Sidebar Navigation
Add to _render_sidebar() in ui.py:
st.header("Navigation")
if st.button("π Leaderboards", width="stretch"):
st.session_state["show_leaderboard_page"] = True
st.rerun()
10.2 Game Over Integration
Modify _game_over_content() in ui.py to:
- Call
submit_score_to_all_leaderboards()after generating share link - Display qualification results:
# After score submission
if results["daily"]["qualified"]:
st.success(f"π You ranked #{results['daily']['rank']} on today's leaderboard!")
if results["weekly"]["qualified"]:
st.success(f"π You ranked #{results['weekly']['rank']} on this week's leaderboard!")
10.3 Leaderboard Page Routing
In run_app():
# Check if leaderboard page should be shown
if st.session_state.get("show_leaderboard_page", False):
from wrdler.leaderboard_page import render_leaderboard_page
render_leaderboard_page()
if st.button("β Back to Game"):
st.session_state["show_leaderboard_page"] = False
st.rerun()
return # Don't render game UI
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
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