> For the complete documentation index, see [llms.txt](https://docs.kouch.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kouch.dev/docs/achievements_plan.md).

# Steam achievements

Owner, 2026-09-30: Kouch gets Steam achievements, including **"Beat a Game"** when someone beats a game, with RetroAchievements as the way to know it happened. The owner asked for the whole list below. This is an addendum; it doesn't replace `PLAN_V2.md` or any other plan.

## Today (checked in the code)

* Kouch sets no stats and no achievements. Nothing in the repo calls ISteamUserStats.
* The vendored wrapper already has what's needed: `user_stats().set_stat_i32` / `get_stat_i32`, `achievement(name).set()` / `.get()`, and `store_stats()`. It has no `RequestCurrentStats`; the SDK loads the current user's stats by itself.
* The `sessions` table (`kouch-library/src/sessions.rs`, written by `app/src-tauri/src/playtime.rs`) already records, per session:

  * the game, its system and its profile;
  * start and end;
  * the kind (local, netplay, Remote Play);
  * the lobby and its peers;
  * the local seats.

  Most counters below can be computed from it, including everything played before the app id arrives.
* `seats` is counted when the session *ends*. A couch achievement needs the **peak** during the session, so that has to be tracked (AC3).
* Kouch runs as a borrowed id or as app 480 until Steam Direct clears. Setting achievements there would write to another game's achievements.

## Steam's rules and limits

* **100 achievements at first.** More unlock once Kouch passes Steam's "Profile Features" threshold; Valve's page gives no upper ceiling after that (partner.steamgames.com/doc/features/achievements).
* **Stats drive ladders.** An achievement can name a *progress stat* and an unlock value. Steam then draws a progress bar and unlocks it by itself when the stat reaches the value. The stats below drive the ladders.
* Names and icons must be all-ages appropriate. Each achievement needs a locked and an unlocked icon.
* Achievements and stats are defined in the Steamworks partner site (App Admin › Stats & Achievements) and published there; the client only sets them.

## Rules

* **Only under Kouch's own app id** (`input::has_own_app_id()`, like Cloud, Workshop and Steam screenshots). Under 480 or the borrowed id, nothing reaches Steam.
* **Count locally from day one, report later.** Kouch keeps its own counters (a small ledger in `library.db`) whatever the app id. When the own id arrives, the first run sets every stat from the ledger and the sessions table, so nothing played before then is lost.
* **Counters only go up.** Stats are "increment only" in Steamworks. A game counts once toward a "different games" or "beaten" counter, however often it's played, marked or unmarked.
* **Store in batches.** Kouch calls `store_stats()` when a session ends and when an achievement unlocks, never on a timer or per tick. Steam rate-limits it.
* **Threading (ground rule 4):**
  * ISteamUserStats isn't ISteamInput, so it goes through the cloned `steamworks::Client` off the input thread, like `cloud.rs`.
  * `achievements.rs` joins the sanctioned list in `CLAUDE.md` when it lands.
  * No Steam handle is held across an `await`.
* **Nothing rewards collecting games or system files:**
  * no achievement counts library size, ROMs, BIOS, firmware, keys or downloads of anything but emulators;
  * "different games" counts games *played through Kouch*, never games found by a scan (ground rule 2).
* **Brand- and title-free:**
  * achievement names and texts name no console, pad brand or game;
  * no per-title achievements, ever (ground rule 2); the only game-specific signal is RetroAchievements' own "beaten" award.
* **Honest:** only what Kouch can see unlocks anything. Nothing is guessed from time alone.

## The stats

| API name              | What it counts                                                                                                                  | Drives                                     |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
| `hours_played`        | whole hours in sessions launched from Kouch (the ledger keeps exact seconds; Steam gets whole hours, so the bars read in hours) | the time ladder                            |
| `games_played`        | different games with at least one counted session                                                                               | the sampler ladder                         |
| `systems_played`      | different systems with at least one counted session                                                                             | the explorer ladder                        |
| `games_beaten`        | different games beaten (see "Beat a game")                                                                                      | the beat ladder                            |
| `most_seats`          | the most local seats held at once in one session                                                                                | the couch ladder                           |
| `pad_kinds`           | different pad classes ever seated                                                                                               | `PAD_KINDS_3`                              |
| `friends_played_with` | different Steam friends in netplay or Remote Play sessions                                                                      | Long Distance (and later tiers)            |
| `couch_sessions`      | counted sessions with 2 or more local seats at once                                                                             | Same time next week?                       |
| `longest_streak`      | the most days in a row Kouch was opened                                                                                         | the streak ladder                          |
| `streak_current`      | today's streak (under the own id only; it drops back to 1 after a missed day)                                                   | nothing: it carries the streak between PCs |
| `streak_day`          | the last local day counted, as days since 1970-01-01 (own id only)                                                              | nothing: it carries the streak between PCs |

A session counts once `record_session` counts it (the same "too short to count" rule as playtime).

## The achievements (55)

Every row is an owner-approved idea from 2026-09-30. The names here mirror the Steamworks templates below, which the owner edits directly. The owner dropped thirteen ideas the same day; don't bring them back:

* a seat taken while a game runs ("Hot seat");
* hosting a lobby someone joins ("Host");
* sending a chat message from Kouch ("Say hi");
* playing on a handheld ("On the go");
* playing on a second device after a Cloud sync ("Pick up where you left off");
* switching themes ("New look");
* making a collection ("Curator");
* importing someone else's profile ("Community");
* publishing to the Workshop ("Publisher");
* playing with motion controls on ("Steady Hands");
* shaking the controller to trigger a shake in a game ("Shake It");
* a controller dying mid-game ("Battery low");
* still playing together past midnight ("Nobody's going home tonight.").

### Couch play

| API name            | Name                                | Unlocks when                                                 | Source                                     |
| ------------------- | ----------------------------------- | ------------------------------------------------------------ | ------------------------------------------ |
| `COUCH_2`           | Duo Action                          | 2 players seated at once in one game                         | `most_seats` ≥ 2                           |
| `COUCH_4`           | Full Couch                          | 4 players at once                                            | `most_seats` ≥ 4                           |
| `COUCH_8`           | Who invited all these people?       | 8 players at once                                            | `most_seats` ≥ 8                           |
| `COUCH_16`          | Where did you even find the chairs? | all 16 seats at once                                         | `most_seats` ≥ 16                          |
| `MIXED_CROWD`       | A bit from this... and that...      | a pad, a keyboard seat and a LAN guest seated in one session | the seats' source kinds during the session |
| `COUCH_SESSIONS_50` | Same time next week?                | 50 sessions with 2 or more players                           | `couch_sessions` ≥ 50                      |

### Playing

| API name     | Name                                | Unlocks when                       | Source                        |
| ------------ | ----------------------------------- | ---------------------------------- | ----------------------------- |
| `FIRST_BOOT` | And so it begins.                   | the first game launched from Kouch | a session reaches running     |
| `HOURS_1`    | Getting comfy                       | 1 hour played                      | `hours_played` ≥ 1            |
| `HOURS_10`   | You've left a dent.                 | 10 hours                           | ≥ 10                          |
| `HOURS_100`  | Is the sun blue?                    | 100 hours                          | ≥ 100                         |
| `HOURS_500`  | Sticky Situation                    | 500 hours                          | ≥ 500                         |
| `HOURS_1000` | you = 🛋️                           | 1 000 hours                        | ≥ 1 000                       |
| `SYSTEMS_3`  | Just looking around.                | games on 3 different systems       | `systems_played` ≥ 3          |
| `SYSTEMS_5`  | I can also do that?!                | 5 systems                          | ≥ 5                           |
| `SYSTEMS_10` | Been everywhere, played everything. | 10 systems                         | ≥ 10                          |
| `SYSTEMS_20` | It is still not enough              | 20 systems                         | ≥ 20                          |
| `GAMES_10`   | A little bit of everything.         | 10 different games                 | `games_played` ≥ 10           |
| `GAMES_25`   | Refined taste.                      | 25                                 | ≥ 25                          |
| `GAMES_50`   | You'll play anything, won't you?    | 50                                 | ≥ 50                          |
| `GAMES_100`  | Short attention span                | 100                                | ≥ 100                         |
| `GAMES_250`  | Have you even beaten half of these? | 250                                | ≥ 250                         |
| `LONG_HAUL`  | Long Haul                           | one session of 6 hours or more     | the session record            |
| `NIGHT_OWL`  | Night Owl                           | still playing at 3 a.m. local time | the session spans 03:00 local |
| `BEAT_1`     | Beat a Game                         | the first game beaten              | `games_beaten` ≥ 1            |
| `BEAT_5`     | On a Roll                           | 5 games beaten                     | ≥ 5                           |
| `BEAT_10`    | You finish what you start.          | 10                                 | ≥ 10                          |
| `BEAT_25`    | They'll write songs about this.     | 25                                 | ≥ 25                          |
| `BEAT_50`    | Credits enjoyer                     | 50                                 | ≥ 50                          |
| `BEAT_100`   | Do you ever stop?                   | 100                                | ≥ 100                         |

### Motion and controllers

| API name      | Name                           | Unlocks when                                      | Source                               |
| ------------- | ------------------------------ | ------------------------------------------------- | ------------------------------------ |
| `POINTER`     | Who needs a mouse?             | the motion pointer used in Kouch's menus          | `InputEvent::PointerSeat` turning on |
| `PAD_KINDS_3` | The controller drawer pays off | three different pad classes seated, over any time | `pad_kinds` ≥ 3                      |
| `PAD_KINDS_6` | Suspicious lot                 | six different pad classes seated, over any time   | `pad_kinds` ≥ 6                      |

`pad_kinds` counts classes, not models: standard pad, touchpad pad, pro-style pad, remote, split-pair, Steam Controller, a handheld's built-in controls, the keyboard seat. `Unknown` and LAN seats don't count; the guest PC's own pad counts on the guest PC.

### Friends and online

| API name    | Name               | Unlocks when                                                    | Source                                    |
| ----------- | ------------------ | --------------------------------------------------------------- | ----------------------------------------- |
| `LAN`       | Neighbour          | a LAN session with another PC (hosting or joining)              | a LAN seat or a LAN host during a session |
| `ONLINE`    | Long Distance      | a game with a Steam friend over netplay or Remote Play Together | `friends_played_with` ≥ 1                 |
| `ONLINE_5`  | Friend magnet      | 5 different Steam friends                                       | `friends_played_with` ≥ 5                 |
| `ONLINE_10` | You know everyone. | 10                                                              | ≥ 10                                      |
| `DISCORD`   | Friends everywhere | link Discord                                                    | `discord_me.rs` signed in                 |

### Making Kouch yours

| API name         | Name            | Unlocks when             | Source                       |
| ---------------- | --------------- | ------------------------ | ---------------------------- |
| `WORKSHOP_THEME` | Window shopping | install a Workshop theme | `workshop.rs` theme install  |
| `ART`            | Picky           | set custom art on a game | `art.rs` user override saved |

### Sharing and setup

| API name         | Name               | Unlocks when                      | Source                           |
| ---------------- | ------------------ | --------------------------------- | -------------------------------- |
| `FIRST_EMULATOR` | The first of many. | install an emulator through Kouch | `downloads.rs` item reaches done |
| `EXPORT`         | Sharing Is Caring  | export a `.kod`                   | `kod_export` succeeded           |

### Streaks

| API name     | Name                   | Unlocks when                    | Source               |
| ------------ | ---------------------- | ------------------------------- | -------------------- |
| `STREAK_3`   | Oh, you're back.       | Kouch opened on 3 days in a row | `longest_streak` ≥ 3 |
| `STREAK_7`   | Creature of habit      | 7 days in a row                 | ≥ 7                  |
| `STREAK_14`  | This is a routine now. | 14 days in a row                | ≥ 14                 |
| `STREAK_30`  | Did you move in?       | 30 days in a row                | ≥ 30                 |
| `STREAK_100` | Do you pay rent here?  | 100 days in a row               | ≥ 100                |
| `STREAK_365` | Every. Single. Day.    | 365 days in a row               | ≥ 365                |

How a day counts (owner, 2026-09-30: "the app should track each log in, every day at least, so if i launch it every day it is a streak"):

* **A day counts when Kouch is open at any moment of that local calendar day.** Kouch marks the day at start, and again at local midnight and on waking from sleep while it's still running, so leaving it open overnight counts both days. Playing a game isn't needed; opening Kouch is enough.
* **Days in a row build the current streak.** A missed day starts it over at 1. `longest_streak` keeps the best one, and only that drives the achievements, so a broken streak never takes an achievement or a bar back.
* **Every PC on the account adds to one streak.** Under the own id, the current streak and its last day also live in two Steam stats (`streak_current`, `streak_day`), which Steam keeps per account. Opening Kouch on the desktop one day and on a handheld the next continues the streak. Before the id, the ledger counts per PC, and the catch-up reports the best streak so far.
* **No nagging.** Kouch shows no reminders, notifications or "don't lose your streak" prompts. A streak is only counted.
* **The local date is taken as it is.** Changing the clock or time zone can add or skip a day; Kouch doesn't police it.

### Funny moments

| API name         | Name                        | Unlocks when                                                           | Source                                                                                          |
| ---------------- | --------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `SESSION_12H`    | Uhhh... yeah.               | one session of 12 hours or more                                        | the session record                                                                              |
| `SESSION_24H`    | You left it on, didn't you? | one session of 24 hours or more                                        | the session record                                                                              |
| `LAST_PLAYER`    | Everyone else went to bed.  | a game that had 4 or more players at once ends with one                | peak seats ≥ 4 and one seat at the end                                                          |
| `ONE_MORE_ROUND` | Just one more round         | the same game launched 5 times in one day                              | counted sessions of one game on one local date                                                  |
| `QUICK_BEAT`     | Was that the whole game?    | a RetroAchievements beat within 30 minutes of first starting that game | the award time against the game's first session (a mark never counts: it needs an hour of play) |
| `DOUBLE_FEATURE` | Double feature              | 2 different games beaten on the same local day                         | the beat times                                                                                  |
| `WEEKEND`        | What weekend?               | 10 hours played across one Saturday and Sunday, local time             | sessions in that weekend                                                                        |
| `SESSION_100`    | Again?                      | the 100th counted session of one game                                  | sessions of that game                                                                           |

## Steamworks templates

This is what gets typed into the partner site (App Admin › Stats & Achievements) once Kouch's own app id exists (AC0). The tables above say how Kouch detects each achievement; these say what players see.

* **Set by:** Client, for every stat and achievement.
* **Hidden:** in Steamworks, a hidden achievement "does not show up on a user's Community page (at all) until they have achieved it".
  * **Hidden (10):** Long Haul, Night Owl and the eight under "Funny moments". They're surprises you stumble into.
  * **Visible (45):** everything else. Goals a player aims for and achievements that teach a feature stay visible, and a ladder rung is never hidden because its progress bar is the point.
* **The format** (the owner's own edits, 2026-09-30):
  * the joke is usually the **display name** ("Who invited all these people?", "And so it begins.");
  * the **description says plainly what to do** ("Play a game with 8 players at once."), sometimes with an aside ("(Is there even a game that supports this many players?)");
  * a **hidden** achievement is a name plus a joke ("Night Owl | what are you even doing. go to sleep."), because it's only read after it unlocks;
  * all ages, and no console, brand or game references, not even as a joke.
* **Four working names were replaced because they're also published games' titles** (ground rule 2). Check every new name against game titles the same way (a store search for the exact title is enough).
* **Display name and description** can be localised in Steamworks; English first.

### Stats

| API name              | Display name         | Type | Increment only | Min | Max | Default | Max change | Aggregated |
| --------------------- | -------------------- | ---- | -------------- | --- | --- | ------- | ---------- | ---------- |
| `hours_played`        | Hours played         | INT  | yes            | 0   | —   | 0       | —          | no         |
| `games_played`        | Games played         | INT  | yes            | 0   | —   | 0       | —          | no         |
| `systems_played`      | Systems played       | INT  | yes            | 0   | —   | 0       | —          | no         |
| `games_beaten`        | Games beaten         | INT  | yes            | 0   | —   | 0       | —          | no         |
| `most_seats`          | Most players at once | INT  | yes            | 0   | 16  | 0       | —          | no         |
| `pad_kinds`           | Kinds of controller  | INT  | yes            | 0   | 8   | 0       | —          | no         |
| `friends_played_with` | Friends played with  | INT  | yes            | 0   | —   | 0       | —          | no         |
| `couch_sessions`      | Couch sessions       | INT  | yes            | 0   | —   | 0       | —          | no         |
| `longest_streak`      | Longest streak       | INT  | yes            | 0   | —   | 0       | —          | no         |
| `streak_current`      | Current streak       | INT  | **no**         | 0   | —   | 0       | —          | no         |
| `streak_day`          | Last streak day      | INT  | **no**         | 0   | —   | 0       | —          | no         |

* **Max change stays empty.** The first run under the own id sets each stat to its whole catch-up value in one step, and a max change would reject that.
* `most_seats` is a maximum, not a sum. "Increment only" still fits, because Kouch only ever raises it.
* `pad_kinds` tops out at 8, the eight classes listed under "Motion and controllers".

### Achievements

**Progress** "`stat` 0 → N" means: Progress Stat = that stat, min 0, max N (the unlock value); Steam draws the bar and unlocks it by itself. "—" means Kouch sets the achievement directly.

**Couch play**

| API name            | Display name                        | Description                                                                            | Hidden | Progress                |
| ------------------- | ----------------------------------- | -------------------------------------------------------------------------------------- | ------ | ----------------------- |
| `COUCH_2`           | Duo Action                          | Play a game with someone.                                                              | no     | `most_seats` 0 → 2      |
| `COUCH_4`           | Full Couch                          | Play a game with 4 players at once.                                                    | no     | `most_seats` 0 → 4      |
| `COUCH_8`           | Who invited all these people?       | Play a game with 8 players at once.                                                    | no     | `most_seats` 0 → 8      |
| `COUCH_16`          | Where did you even find the chairs? | Fill all 16 seats in one game. (Is there even a game that supports this many players?) | no     | `most_seats` 0 → 16     |
| `MIXED_CROWD`       | A bit from this... and that...      | A controller, a keyboard and a friend on another PC walk into a game.                  | no     | —                       |
| `COUCH_SESSIONS_50` | Same time next week?                | Play 50 sessions with 2 or more players.                                               | no     | `couch_sessions` 0 → 50 |

**Playing**

| API name     | Display name                        | Description                                                      | Hidden  | Progress                |
| ------------ | ----------------------------------- | ---------------------------------------------------------------- | ------- | ----------------------- |
| `FIRST_BOOT` | And so it begins.                   | Launch your first game from Kouch.                               | no      | —                       |
| `HOURS_1`    | Getting comfy                       | Play games from Kouch for 1 hour.                                | no      | `hours_played` 0 → 1    |
| `HOURS_10`   | You've left a dent.                 | Play games from Kouch for 10 hours.                              | no      | `hours_played` 0 → 10   |
| `HOURS_100`  | Is the sun blue?                    | Play games from Kouch for 100 hours.                             | no      | `hours_played` 0 → 100  |
| `HOURS_500`  | Sticky Situation                    | You are literally glued in. Play games from Kouch for 500 hours. | no      | `hours_played` 0 → 500  |
| `HOURS_1000` | you = 🛋️                           | Play games from Kouch for 1,000 hours.                           | no      | `hours_played` 0 → 1000 |
| `SYSTEMS_3`  | Just looking around.                | Play games on 3 different systems.                               | no      | `systems_played` 0 → 3  |
| `SYSTEMS_5`  | I can also do that?!                | Play games on 5 different systems.                               | no      | `systems_played` 0 → 5  |
| `SYSTEMS_10` | Been everywhere, played everything. | Play games on 10 different systems.                              | no      | `systems_played` 0 → 10 |
| `SYSTEMS_20` | It is still not enough              | Play games on 20 different systems.                              | no      | `systems_played` 0 → 20 |
| `GAMES_10`   | A little bit of everything.         | Play 10 different games.                                         | no      | `games_played` 0 → 10   |
| `GAMES_25`   | Refined taste.                      | Play 25 different games.                                         | no      | `games_played` 0 → 25   |
| `GAMES_50`   | You'll play anything, won't you?    | Play 50 different games.                                         | no      | `games_played` 0 → 50   |
| `GAMES_100`  | Short attention span                | Play 100 different games.                                        | no      | `games_played` 0 → 100  |
| `GAMES_250`  | Have you even beaten half of these? | Play 250 different games.                                        | no      | `games_played` 0 → 250  |
| `LONG_HAUL`  | Long Haul                           | Six hours without getting up. Your legs called. They miss you.   | **yes** | —                       |
| `NIGHT_OWL`  | Night Owl                           | what are you even doing. go to sleep.                            | **yes** | —                       |
| `BEAT_1`     | Beat a Game                         | Beat a game you launched from Kouch.                             | no      | `games_beaten` 0 → 1    |
| `BEAT_5`     | On a Roll                           | Beat 5 games.                                                    | no      | `games_beaten` 0 → 5    |
| `BEAT_10`    | You finish what you start.          | Beat 10 games.                                                   | no      | `games_beaten` 0 → 10   |
| `BEAT_25`    | They'll write songs about this.     | Beat 25 games.                                                   | no      | `games_beaten` 0 → 25   |
| `BEAT_50`    | Credits enjoyer                     | Beat 50 games.                                                   | no      | `games_beaten` 0 → 50   |
| `BEAT_100`   | Do you ever stop?                   | Beat 100 games.                                                  | no      | `games_beaten` 0 → 100  |

**Motion and controllers**

| API name      | Display name                   | Description                                       | Hidden | Progress          |
| ------------- | ------------------------------ | ------------------------------------------------- | ------ | ----------------- |
| `POINTER`     | Who needs a mouse?             | Point at Kouch's menus by moving your controller. | no     | —                 |
| `PAD_KINDS_3` | The controller drawer pays off | Play with 3 different kinds of controller.        | no     | `pad_kinds` 0 → 3 |
| `PAD_KINDS_6` | Suspicious lot                 | Play with 6 different kinds of controller.        | no     | `pad_kinds` 0 → 6 |

**Friends and online**

| API name    | Display name       | Description                                  | Hidden | Progress                     |
| ----------- | ------------------ | -------------------------------------------- | ------ | ---------------------------- |
| `LAN`       | Neighbour          | Play with another PC on your home network.   | no     | —                            |
| `ONLINE`    | Long Distance      | Play a game online with a Steam friend.      | no     | `friends_played_with` 0 → 1  |
| `ONLINE_5`  | Friend magnet      | Play online with 5 different Steam friends.  | no     | `friends_played_with` 0 → 5  |
| `ONLINE_10` | You know everyone. | Play online with 10 different Steam friends. | no     | `friends_played_with` 0 → 10 |
| `DISCORD`   | Friends everywhere | Link your Discord account to Kouch.          | no     | —                            |

**Making Kouch yours**

| API name         | Display name    | Description                              | Hidden | Progress |
| ---------------- | --------------- | ---------------------------------------- | ------ | -------- |
| `WORKSHOP_THEME` | Window shopping | Install a theme from the Steam Workshop. | no     | —        |
| `ART`            | Picky           | Choose your own art for a game.          | no     | —        |

**Sharing and setup**

| API name         | Display name       | Description                           | Hidden | Progress |
| ---------------- | ------------------ | ------------------------------------- | ------ | -------- |
| `FIRST_EMULATOR` | The first of many. | Install an emulator through Kouch.    | no     | —        |
| `EXPORT`         | Sharing Is Caring  | Export a .kod to share with a friend. | no     | —        |

**Streaks**

| API name     | Display name           | Description                      | Hidden | Progress                 |
| ------------ | ---------------------- | -------------------------------- | ------ | ------------------------ |
| `STREAK_3`   | Oh, you're back.       | Open Kouch 3 days in a row.      | no     | `longest_streak` 0 → 3   |
| `STREAK_7`   | Creature of habit      | Open Kouch 7 days in a row.      | no     | `longest_streak` 0 → 7   |
| `STREAK_14`  | This is a routine now. | Open Kouch 14 days in a row.     | no     | `longest_streak` 0 → 14  |
| `STREAK_30`  | Did you move in?       | Open Kouch 30 days in a row.     | no     | `longest_streak` 0 → 30  |
| `STREAK_100` | Do you pay rent here?  | Open Kouch 100 days in a row.    | no     | `longest_streak` 0 → 100 |
| `STREAK_365` | Every. Single. Day.    | Open Kouch every day for a year. | no     | `longest_streak` 0 → 365 |

**Funny moments**

| API name         | Display name                | Description                                                     | Hidden  | Progress |
| ---------------- | --------------------------- | --------------------------------------------------------------- | ------- | -------- |
| `SESSION_12H`    | Uhhh... yeah.               | Twelve hours in one go.                                         | **yes** | —        |
| `SESSION_24H`    | You left it on, didn't you? | A game ran for 24 hours straight.                               | **yes** | —        |
| `LAST_PLAYER`    | Everyone else went to bed.  | A game that started with 4 or more players ended with just you. | **yes** | —        |
| `ONE_MORE_ROUND` | Just one more round         | You said that four times already.                               | **yes** | —        |
| `QUICK_BEAT`     | Was that the whole game?    | You beat it in under 30 minutes.                                | **yes** | —        |
| `DOUBLE_FEATURE` | Double feature              | Two credits rolls in one day.                                   | **yes** | —        |
| `WEEKEND`        | What weekend?               | Saturday and Sunday, gone.                                      | **yes** | —        |
| `SESSION_100`    | Again?                      | Your 100th session of the same game.                            | **yes** | —        |

### Icons

* Two per achievement: **Achieved** (colour) and **Unachieved** (the same drawing in grey), 110 files named `<API name>_achieved` and `<API name>_unachieved`.
* Square. The SDK page doesn't state the size or format; take both from the upload form when the app id arrives.
* No console, brand or game imagery (ground rule 1).
* A hidden achievement's icon and text only appear after it unlocks, so they may give the surprise away.

## Beat a game

Kouch launches the emulator and sees the session start and end; it can't see the credits roll. Two sources feed `games_beaten`, and a game counts once whichever says so first.

### 1. RetroAchievements (the owner's pick)

Many emulators already sign in to RetroAchievements, and many of its game sets have a **beaten** award. RetroAchievements only records a beat for a player who was signed in while playing, so a player who doesn't use it can't be detected this way. Kouch's goal is **no second sign-in**.

* **The username comes from the emulator.**
  * A profile may declare where its emulator keeps the RetroAchievements username: a new optional `retroachievements` section with the settings file and the key, read with the same parsers as `config_edit`. That's a schema change (B, `PROFILE_AUTHORING.md`, `schemas/`).
  * The first time Kouch finds one, it asks once: "Found RetroAchievements account *name* in *emulator*. Use it for achievements?" After that the player types nothing.
  * Kouch reads **only the username, never the login token** stored next to it. Kouch never lifts another app's credentials.
  * A player can also type a username in Settings › Accounts.
* **Asking RetroAchievements.** Its web API needs an API key on every call, and one key can read anyone's public progress by username. Its docs say to keep the key as secret as a password, so a key never ships inside Kouch.
  * **Now:** the player pastes their own API key once, in Settings › Accounts. It lives in the OS secret store and syncs encrypted through Steam Cloud, the same way as the SteamGridDB key. This reuses ADR 0021's mechanism for a second key; amend ADR 0021 before code.
  * **Later:** the owner's metadata server (`CONTROLLER_TYPES_PLAN.md` "Later") holds one key. Kouch sends only the public username; the server answers "beaten or not" and caches it. Then the player never pastes anything. Before this: RetroAchievements' OK for a server-side key serving Kouch's players, and their rate limits.
* **What counts.**
  * After each session, Kouch asks for the player's awards and looks for a beaten award dated inside that session (or within a few minutes after it ends).
  * That beat is credited to the session's game. No ROM hashing is needed, and a beat Kouch didn't launch never counts.
  * Every request sends a proper User-Agent (`Kouch/<version>`), and Kouch polls only at session end, never on a timer.
* **Offline:** the check queues and runs at the next start with a network.

### 2. Mark as beaten (the fallback)

* A **Mark as beaten** action in the game menu and on the game page stores a completion status per game in the library (B: a column plus a command; A: the UI).
* A mark counts toward `games_beaten` only after the game has at least **1 hour** of counted playtime, so clicking through a list does nothing.
* Unmarking removes the mark but not the count. The ledger remembers which games already counted, so marking and unmarking the same game never adds twice.

## Open decisions (the owner's)

1. **Softcore beats:** does a beat in softcore mode (save states allowed) count, or only hardcore? *Proposal: both count; Kouch isn't a competitive ladder.*
2. **Keep "Mark as beaten"?** *Proposal: yes, as the fallback for players without RetroAchievements, with the 1-hour floor.*
3. **Hidden achievements:** the templates hide ten (Long Haul, Night Owl and the funny moments) and show the other 45. *The owner can flip any in the Hidden column.*
4. **Sign off the names and descriptions in the templates,** and who draws the 110 icons. The trophy icon from ADR 0025's amendment can serve as the Kouch-side mark.

## Who and status

| Part                                                                                                                                                                                                                                                            | Owner                                                  | Status                              |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ----------------------------------- |
| AC0 Steamworks: the owner signs off names and texts, the icons are drawn, and the stats and achievements are entered and published in the partner site                                                                                                          | owner (their account), central prepares the exact text | waiting on the app id and the owner |
| AC1 `kouch-steam` user-stats wrapper, the app glue (`achievements.rs`: ledger, first-run catch-up from the ledger and `sessions`, batched `store_stats`, the own-id guard)                                                                                      | C (Steam integration), B (the ledger table)            | not started                         |
| AC2 Triggers from sessions: time, games, systems, long haul, night owl, LAN, online, couch sessions, and the funny moments that come from session records (12 and 24 hours, the last player, one more round, the weekend, the 100th session)                    | B                                                      | not started                         |
| AC3 Triggers from input: peak seats, mixed crowd, pointer, pad kinds                                                                                                                                                                                            | C                                                      | not started                         |
| AC4 Triggers from actions: Workshop theme, art, Discord, export, first emulator                                                                                                                                                                                 | B, with A for art, C for Discord                       | not started                         |
| AC5 Mark as beaten: the library column and command; the game menu and game page action                                                                                                                                                                          | B, A                                                   | not started                         |
| AC6 RetroAchievements: the profile's `retroachievements` section and schema, the one-time confirm, the pasted key in the secret store (after the ADR 0021 amendment), the post-session award check, and the two beat moments (under 30 minutes, two in one day) | B (profile, lookup), C (Settings › Accounts)           | not started; decisions 1–2 first    |
| AC7 RetroAchievements through the owner's metadata server                                                                                                                                                                                                       | central, with the owner's server                       | later                               |
| AC8 Verify: unit tests for the ledger, the catch-up and each trigger; under 480, check nothing reaches Steam; with the own id, a real unlock of each kind                                                                                                       | central                                                | not started                         |
| AC9 Guide: what the achievements are and how "Beat a game" works                                                                                                                                                                                                | docs session                                           | not started                         |
| AC10 Streaks: the day ledger (marked at start, at local midnight and on waking), `longest_streak`, and the two account-wide streak stats under the own id                                                                                                       | B                                                      | not started                         |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.kouch.dev/docs/achievements_plan.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
