Achievements
Definitions, unlocks, and Steam mirroring
This backend is the canonical achievement store; Steam is a mirror. That ordering matters: it means achievements work in the browser build (no Steam), survive Steam outages, and can be re-synced.
Definitions
One source of truth: packages/shared/src/achievements.ts.
export const ACHIEVEMENTS = [
{ key: "FIRST_STEPS", name: "First Steps",
description: "Survive your first day.", hidden: false },
// ...
] as const;The key is the stable API name used by this backend, the game client, and Steamworks — when creating achievements on the partner site, enter the same key as the achievement's API Name so mirroring is 1:1. Adding an achievement = adding an entry here; the Zod enum, the OpenAPI docs, and the endpoints all follow. The current entries are placeholders keyed to real game beats — rename freely before launch.
Endpoints
# Definitions (public)
curl http://localhost:3001/api/achievements
# Your progress (signed in) — every definition + unlockedAt or null
curl http://localhost:3001/api/achievements/me -b cookies.txt
# Unlock (signed in, idempotent)
curl -X POST http://localhost:3001/api/achievements/unlock \
-H 'Content-Type: application/json' -b cookies.txt \
-d '{"key": "FIRST_STEPS"}'
# → {"ok": true, "key": "FIRST_STEPS", "alreadyUnlocked": false}Unknown keys are rejected at validation (400) — the enum comes from the definitions.
What a first-time unlock triggers
- Row in
user_achievements(the canonical fact). - Discord:
🏆 **Player** unlocked *First Steps*(if webhook configured). - Steam mirror — if the user has a linked Steam account (
accountrow withproviderId: "steam", whoseaccountIdis their steamId64) andSTEAM_WEB_API_KEY+STEAM_APP_IDare set, the unlock is pushed viaISteamUserStats/SetUserStatsForGame. Failures are logged, never surfaced — see Steam.
Repeat unlocks are no-ops (alreadyUnlocked: true) and trigger nothing.
How the game will report unlocks
The Rust client (or eventually the multiplayer server) calls POST /api/achievements/unlock with the player's session. Until Steam sign-in lands, unlocks simply accumulate server-side; once an account links Steam, future unlocks mirror automatically. A backfill sync for pre-link unlocks is a small follow-up (iterate user_achievements, call the Steam client per row).