Achievements engine — unlockable badges (Racing · Speed · Collection)#23
Merged
Conversation
Data-driven catalog of 13 goals across Racing / Speed / Collection, evaluated against a flat AchievementStats snapshot. Supports both reach-at-least (gte) and get-under (lte, for lap times) goals, reports 0..1 progress, and detects newly-crossed thresholds. Pure + Node-tested (19 vitest cases); no framework/storage/time deps. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- achievementsStore: unlocked-set + latest stats, stamps new unlocks, write-through persistence hook (mirrors race/garage stores). - achievementsRepository (interface + InMemory) + SQLite adapter (INSERT OR IGNORE so first-unlock time wins); migration v5 adds the achievements table to the shared redlineid.db. - RaceRepository gains aggregate() (lifetime totals, not the capped leaderboard) — one roll-up query on SQLite, pure helper for InMemory. - initPersistence hydrates unlocked, recomputes stats from race.aggregate() + the live garage, and refreshes on every banked race (after the durable write) and garage change. Retroactive unlocks work. - 140 vitest pass (+35); tsc clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- app/achievements.tsx: grouped badges (Racing / Speed / Collection) read live from useAchievementsStore via the pure evaluate() engine — unlocked show their date, locked show a progress bar toward the goal. X/Y count chip. - achievements/format.ts: pure label/percent/date helpers (+6 vitest). - Route registered in _layout.tsx; 🏆 Achievements button on Home. - 146 vitest pass; tsc clean; production iOS export clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This was referenced Jun 20, 2026
burkben
added a commit
that referenced
this pull request
Jun 20, 2026
Adds Phase 5 "share results": a Share button on the finished-race view and on a History session detail, each handing a clean text recap to the OS share sheet (Messages, Notes, AirDrop, etc.). - New pure apps/mobile/src/share/summary.ts (+13 tests): raceShareText (total time, best/avg/worst splits, lap-by-lap with the fastest lap flagged) and sessionShareText (date, pass count, duration, best, fastest passes). Speeds run through the same speed/format converter as every readout, so shared numbers honor the user's unit + calibration; lap times mirror race.tsx. - race.tsx Results: "Share result" button (RN Share API), resolving the car's garage nickname; honors the haptics setting. - history/[id].tsx: header "Share" action (shown once a session has passes), passing the current speed display + car nickname map. - Uses React Native's built-in Share — no new native module. Also refreshed stale ROADMAP Phase 5 checkboxes (Achievements #23, speed units #25, this as #26). 178 vitest pass; tsc --noEmit clean; iOS export clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this adds
A new Achievements feature (Phase 5 polish): a data-driven catalog of 13 unlockable badges across Racing, Speed, and Collection, with durable unlocks and live progress. Builds entirely on the shipped Phase 3 persistence seam.
🏆 New Achievements button on Home → a grouped screen showing unlocked badges (with the date earned) and locked ones (with a progress bar toward the goal) plus an
X/Ycount chip.The 13 achievements
Speed thresholds are sized to the gauge (max 300, redline 240) and the mock portal's pass magnitudes, so they're meaningful in demo mode too. No Mattel/Hot Wheels imagery or naming.
How it works (mirrors the Race/Garage/History pattern)
src/achievements/):catalog.ts(data-driven defs),stats.ts(flatAchievementStatssnapshot +combineStats),engine.ts(evaluate→ progress + unlock state; supports both reach ≥ X and get under ≤ X goals for lap times),format.ts(label/percent/date helpers). Framework-free, fully Node-tested.store/achievementsStore.ts): holds the unlocked set + latest stats, stamps newly-crossed goals with an unlock time, and forwards each to storage via a write-through hook — same persistence-seam pattern asraceStore/garageStore.achievementsRepository.ts(interface + InMemory) +sqliteAchievementsRepository.ts(INSERT OR IGNORE, so the first-unlock time wins).RaceRepositorygainsaggregate()for lifetime totals (the leaderboard is capped at 20, so achievements read a one-row roll-up of the durablerace_resultstable instead).initPersistence.ts): hydrates unlocked badges, recomputes stats fromrace.aggregate()+ the live garage, and refreshes on every banked race (after the durable write, so it's counted) and every garage change. Retroactive unlocks work — earlier races/cars count the first time you open the build.Schema
Migration v5 appends an
achievements (id TEXT PK, unlocked_at INTEGER)table to the sharedredlineid.db. Append-only ladder,CREATE TABLE IF NOT EXISTS— no native rebuild required; it activates on a JS reload, just like Garage/History/Settings. (SQLite-less builds stay graceful: everything reads locked at 0%.)Scope notes / deferred
applyStatsreturns them), so a celebratory toast is a tidy follow-up PR. Unlocks are recorded immediately and shown on the screen.Tests / gates
tsc --noEmitclean.npx expo export, exit 0) — the new route + screen bundle in Metro.Validation (USER)
Device-validate on the SQLite dev client: open Achievements, confirm retroactive unlocks from prior races/cars, run a race / detect a new car and watch progress advance + new badges unlock, then quit→relaunch to confirm they persist.
🤖 Generated with GitHub Copilot CLI