Phase 20: achievement system
CI / Build, lint & test (push) Failing after 1m19s

18 data-driven achievements (achievements.json) across 8 trigger categories:
matches_played, matches_won, goals_scored, packs_opened, sbcs_completed,
cards_owned, level, objectives_completed, drafts_completed. Rarities span
common → epic. Coin rewards range from 500 (first_match) to 6000 (win_50).

check_and_unlock() queries the relevant metric from existing tables, skips
already-earned achievements via INSERT OR IGNORE, grants coin rewards, and
fires a persistent notification per unlock. Trigger values are cached per
call to avoid redundant DB round-trips for same-trigger achievements.

Checks run automatically after every match result (all triggers), every
pack open (packs_opened), and every successful SBC submission (sbcs_completed).

GET /achievements returns all definitions annotated with unlocked/unlocked_at,
plus earned and total counts. POST /matches/result response gains an
achievements_unlocked array (empty when nothing new unlocked).

AppState gains achievement_defs (Arc<Vec<AchievementDefinition>>) loaded
from data/achievements/**/*.json at startup — same pattern as obj_defs.

5 new tests: list endpoint, first_match unlock, first_win unlock, no-dup
guard, coin reward verification. Core now at 82 tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-06-25 18:03:54 -07:00
parent f0dbabc409
commit 679f147c6a
15 changed files with 565 additions and 6 deletions
+7
View File
@@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS player_achievements (
id TEXT PRIMARY KEY,
achievement_id TEXT NOT NULL UNIQUE,
unlocked_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_player_achievements_id ON player_achievements (achievement_id);