# The collection: Memory (achievements + recollections), save/load, codes The **Memory** overlay is the game's persistence and replayability layer. It turns the things a player does across runs (escaping arcs, finding alternate endings, uncovering flashback fragments, *how* they won) into a record they can see fill in, carry between devices, and extend with codes. It is reached from a quiet **corner button** (`#ach-open`, in the top-right cluster with the sound and settings toggles, shown on the selection screen only) so it lives off the landing's main path; the overlay's header is **Memory** (`mem_title`). It is engine-wide: arcs supply flashback/ending content, the engine supplies the board. Everything here is **client-side**. Progress lives in `localStorage` under `h8_mem`; the server never sees or scores it. This is the deliberate opposite of the game's core answer state (which is server-only), a collection is the player's to keep, so it stays on the player's machine. ## Layout Top to bottom (device controls first, then the record): - **Save / Load** and a redeem **Code** row (see below). - **Achievements**: a grid of tiles, each a **drawn line glyph** in the game's thin, single-weight noir hand. **Only earned achievements appear**, populated as they unlock; there is no wall of padlocks and no `{n} of {total}` count (it would spoil how many exist). Order follows the `ACHS` array (grouped). - **Recollections** (`col_section`): the flashback **fragment ledger**, a quieter inset panel: one row per arc, a dot per fragment, filled in the arc's skin colour as it is seen. A seen dot is a **button**: tap it to *recall* that fragment's own words in a fleeting italic line under the ledger (see *Fragment recall*). All of it is driven from `renderAchievements()` in `static/game.js`. ## The badges The board includes the play achievements, the **records** (how a run was won: `steady`, `cold`, `nerve`, `streak`, see below), the hard-mode escapes, and one hidden studio badge. Only earned badges are shown, so a new player sees an empty board (an `ach_empty` line) that fills in as they play; there is no count and no locked tiles. The studio badge, like all the rest, simply appears once redeemed. | Badge (`id`) | Unlocked by | |---|---| | `out_hallway-eight` / `out_stairway8` / `out_coach8` | Escape that arc once | | `out_all` | Escape all three arcs | | `flawless` | Escape an arc with no reset (clean first run) | | `eight_times` | Escape one arc eight times | | `end_hallway-eight` / `end_stairway8` / `end_coach8` | Find that arc's alternate (false-exit) ending | | `npc_knows` | Be acknowledged by the loop-aware NPC | | `shared` | Pass the place on (any share target) | | `opening_night` | Play on release day (unlocks the day after) | | `flash_hallway-eight` / `flash_stairway8` / `flash_coach8` | See every flashback fragment in that arc | | `total_recall` | See every fragment, in every arc | | `melon` *(hidden)* | **Melon Supporter**: redeemed with a studio code | Badge criteria live as `{id, test()}` in the `ACHS` array; the test reads `mem`. Names and one-line descriptions are localized UI strings (`ach_n_` / `ach_d_`), with an English fallback in `ACH_TEXT`. Descriptions are written to avoid spoiling the specific detail a player must watch, they name the *achievement*, never the anomaly. ### The Melon Supporter badge is the one deliberate style break Every other icon is monochrome line-work. The studio badge is a warm, full-colour **watermelon slice**: the single intentional break from the noir palette, a small thank-you that pops. Its name is *Melon Supporter* and its caption is a thank-you note, not a task. It exists only once redeemed, so it reads as a bonus, never as something withheld. ## Fragment recall When a player uncovers a flashback during Act 2, the client already receives the fragment's `idx` and its localized `line`. `recordFlashback(arc, idx, line)` stores the index in `mem.flash[arc]` **and** the text in `mem.flashText[arc]`, so the collection can quote the fragment back later. In the ledger, tapping a filled dot surfaces its stored words (`col_recall` is the button label; `col_recall_empty` is the fallback for a fragment collected before text was kept). The recalled line fades in, and a ring marks the chosen dot. The text is captured in whatever locale it was collected in, a memory as it was seen. Only collected fragments are ever quoted; uncollected ones stay dots. ## Records (how a run was won): now achievements How a run was won is graded and **surfaced as achievements** on the board (not a separate strip), so, like every other badge, they stay hidden until earned. Each escape's winning climb (the unbroken run from level 0 to the exit) is graded on marks tracked client-side in a live `run` object and stored in `mem.records`; matching `ACHS` entries turn each into a badge: - **Steady** (`steady`): won without ever admitting a guess (no low-confidence re-decide on the winning climb). - **Cold read** (`cold`): won without a single touch or second look (pure memory, no Act 2 assistance). - **Nerve** (`nerve`): the final call was a correct *turn-back on a real change* (the hardest, clutch read), not a walk-on. - **On a roll** (`streak`): a best clean streak of **three** consecutive escapes made without a reset. The `run` object resets whenever a climb starts fresh (entering a corridor, a wrong call, or a fold), so the marks always describe the actual winning climb. None of this changes the win logic; it only observes it. On the win screen, the strongest mark of the just-finished climb also surfaces as a fleeting **read record**, one in-world line (`read_steady` / `read_cold` / `read_nerve`) tucked under the existing "look back" run-count line and fading with it. It is the in-the-moment skill signal, in the game's own quiet voice, no numbers. ## Save and load (obfuscated, not encrypted) The **Save** button exports `eight-save.json`; **Load** reads one back. The file holds three blobs, the player's memory (NPC habits + collection), the computed achievement state, and the **settings** (difficulty, reading register, text size, brightness, reduce-motion, language), each passed through `encBlob`, a keystream XOR (FNV-1a seed → xorshift stream) seeded with the phrase `melon8`. This is **light obfuscation, not cryptography**: it stops a save from being human-readable and stops the number of achievements being eyeballed from the JSON, nothing more. Loading a file decodes the memory back into `mem` and restores the settings to `localStorage` (re-applying them live), so a device swap carries the whole setup, not just progress. A wrong or corrupt file is ignored silently. ## Promo codes An 8-character code redeems a reward (`redeemPromo` in `static/game.js`). The engine is extensible (arcs or features could be gated later); today two families exist: - **`MELONADE`** → unlocks the **Melon Supporter** badge. - **`I18N-XX`** (XX = a short ISO code, e.g. `I18N-TH`) → unlocks one of the 21 supported languages, adds it to the picker, and switches to it. The map of short code → locale is `i18n.PROMO_LOCALE`, exposed to the client via `/api/langs` (`unlock_map()`), restricted to locales that actually have a compiled catalog. Redeemed codes are recorded in `mem.promos` and **persist through save/load**, so an unlocked language or the studio badge survives export/import and reappears in the picker on load (`rebuildLangPicker()`). ## Memory shape (`blankMem`) ```js { v, flash:{arc:[idx…]}, flashText:{arc:{idx:line}}, wins:{arc:n}, clean:{arc:bool}, ends:{arc:bool}, npc:{arc:{prop:true}}, shared:bool, releaseDay:bool, plays:n, promos:[code…], melon:bool, langs:[locale…], records:{ steady:bool, cold:bool, nerve:bool, streak:n, best:n } } ``` Recording happens at the natural moments: `recordWin`/`noteAnomaly` on a win, `recordEnding` on a false exit, `recordFlashback` on a touch, `recordShare` on a share, `recordPlay` on entering a loop. `saveMem()` writes through and re-renders the board if it is open. ## Localization All collection strings go through the same pipeline as the rest of the game (see [`LOCALIZATION.md`](LOCALIZATION.md)): the two section headers (`ach_section`, `col_section`), the 34 badge strings (`ach_n_*` / `ach_d_*`), and the two recall strings (`col_recall`, `col_recall_empty`). Arc names and "Melon Labs" are kept verbatim across locales; metaphoric lines are translated by meaning. All are translated and compiled for the eight exposed base locales, with English fallback per key for the hidden ones. ## Conventions to keep - **The collection stays client-side.** Never move answer state here, and never send collection state to the server for scoring. - **Descriptions never spoil.** A badge names the feat, not the detail to watch. - **Only earned badges show, no count.** The board fills in as achievements unlock; do not reintroduce locked tiles or a `{n} of {total}` denominator (it spoils how many exist). - **One colour break, only the melon.** Every other icon stays monochrome line-work in the chrome/accent tier.