# Phase 2: Карта-созвездие + мир физ-уровней + XP/скины (MVP-мир) **Status:** ✅ Done (reviewed — PASS w/ notes; «Дальше» stale-hasNext 🟡 fixed; committed) **Parent plan:** [PLAN.md](./PLAN.md) **Domain:** fullstack ## Objective Превратить одиночный уровень в **играбельный мир**: карта-созвездие из ~5–6 физ-уровней, разблокировка по звёздам, XP, выбор скина Квантика, нарратор-Квантик (`PetSprite`) на интро/ победе. После этой фазы игра полноценно отгружаема. ## Tasks - [x] Task 1: Контент — ~5–6 физ-уровней-спек (данные в `levels.js`), нарастающая сложность: артиллерия → перелёт через стену → отскок (restitution) → пружина/маятник → орбита/гравитация. Каждый: `goal` + 1–3 звезды + норматив времени (`par_ms`) для 3-й звезды. - [x] Task 2: Структура «мир/глава»: метаданные уровня (id, title, chapter, order, par_ms, hint). Карта группирует по главам (созвездиям). - [x] Task 3: Карта-созвездие `frontend/js/game/map.js` (+ разметка в quantik.html): узлы-уровни на SVG/canvas-фоне, линии-связи, статус (заблокирован/доступен/пройден + число звёзд). Разблокировка: уровень открыт, если набрано ≥ threshold звёзд в предыдущих (правило в данных). - [x] Task 4: XP/уровень игрока: XP = сумма звёзд × коэффициент (+ бонус за par). Хранить в прогрессе (расширить `game_progress` агрегацией на клиенте ИЛИ доб. поле/таблицу `game_player`). Полоса XP + «уровень Квантика» в шапке карты. - [x] Task 5: Скины Квантика: выбор `colorKey` из палитр `PetSprite` (+ позже паттерны). Скин влияет на цвет glow-точки героя в уровне (param/проп движка) и на `PetSprite` на карте. Хранить выбор (localStorage сейчас; серверно — опц.). Разблокировка скинов по XP/звёздам. - [x] Task 6: Нарратор: `PetSprite.render(...)` в интро уровня (краткая формулировка «почини закон…») и на экране победы (реакция по числу звёзд: happy/ecstatic). Реюз mood из pet-sprite.js. - [x] Task 7: Навигация: карта → уровень → результат → возврат на карту с обновлённым статусом/XP. - [x] Task 8: Тесты: разблокировка (логика чистой функцией — юнит-тест), агрегация XP; смоук карты. ## Files to Modify/Create - `frontend/js/game/levels.js` — контент мира (расширить). - `frontend/js/game/map.js` — карта-созвездие. - `frontend/js/game/quantik-game.js` — навигация карта↔уровень, XP/скин в шапке. - `frontend/quantik.html` — разметка карты/шапки. - (опц.) `backend` — поле/агрегация игрока, если решим серверно; иначе клиентская агрегация прогресса. - тест(ы) разблокировки/XP. ## Acceptance Criteria - Карта показывает мир, статусы и звёзды; пройденные уровни открывают следующие. - XP/уровень Квантика растут; смена скина видна и на карте, и в уровне. - Нарратор-Квантик появляется на интро/победе с корректным настроением. - Тесты разблокировки/XP зелёные; lint baseline 0; existing тесты не сломаны. ## Notes - Без эмодзи — звёзды/иконки только inline SVG (`.ic`). - Разблокировку держать **данными/чистой функцией** (легко тестировать и переносить на сервер). - Не плодить серверные таблицы без нужды: прогресс уже в `game_progress` (Ф1); XP можно агрегировать. ## Review Checklist - [ ] Все задачи; чистая функция разблокировки покрыта тестом; без эмодзи/eval - [ ] Карта/навигация работают; existing тесты целы; lint baseline 0 ## Handoff to Next Phase ### Архитектура карты (`frontend/js/game/map.js`) - `window.QuantikMap.create({ host, headerHost, onPlay(level), getSkin()->key, onSkin(key) }) -> { render(progressMap), destroy() }`. - `render(progressMap)` рисует шапку (нарратор + XP-бар + всего звёзд + скин-пикер) в `headerHost` и созвездия в `host`. `progressMap` — `{ [level_id]: row }` (см. `QuantikProgress.fromProgressList`). - Узел созвездия (`buildNode`) — `