@
feat(quantik-game): фаза 2 — карта-созвездие + мир + XP/скины (MVP-мир) Одиночный уровень → играбельный мир: карта-созвездие из 6 физ-уровней (2 главы, нарастающая сложность), разблокировка по звёздам, клиентский XP/уровень игрока, пикер из 8 скинов (тинт героя+нарратора), нарратор PetSprite на интро/победе (mood по звёздам). Навигация карта→интро→игра→ успех→карта/дальше; кнопка «Дальше» пересчитывает nextPlayable после дозагрузки прогресса (фикс stale-hasNext). Логика прогресса — чистый модуль progress-logic.js (unlock/XP/группировка). Только фронт, без бэкенда: XP агрегируется из game_progress (Ф1). Каждый уровень проверен на реальном движке (выигрываем + обе звезды достижимы); цепочка разблокировки доказуемо проходима. npm test 251/8 baseline; lint:routes 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> @
This commit is contained in:
@@ -24,6 +24,20 @@
|
||||
`js/game/levels.js`, `js/game/quantik-game.js`, `tests/game.test.js`. Изменены: `server.js`,
|
||||
`js/api.js`, `js/sidebar.js`. `npm test` 251 pass / 8 baseline fail (game.test.js 13/13);
|
||||
lint:routes 0; миграция применяется чисто.
|
||||
- **Phase 2 реализован** (pending review): одиночный уровень превращён в **играбельный мир**.
|
||||
Карта-созвездие (`frontend/js/game/map.js`, `window.QuantikMap`) на звёздном фоне: 6 физ-уровней
|
||||
в 2 главах (Кинематика 1–3, Динамика 4–6), узлы-«звёзды» со статусом (locked/available/completed+
|
||||
звёзды), линии-связи, поэтапное появление. Шапка: нарратор-Квантик (`PetSprite`), XP-бар + «уровень
|
||||
Квантика», всего звёзд, скин-пикер (8 скинов, часть за XP/звёзды). Контент уровней расширен в
|
||||
`levels.js` (метаданные `chapter/order/par_ms/unlockStars`, по 2 звезды: кристалл + норматив времени).
|
||||
Разблокировка/XP/группировка — ЧИСТЫЕ функции в новом `frontend/js/game/progress-logic.js`
|
||||
(`window.QuantikProgress`), покрыты тестом. Навигация: карта→интро(нарратор)→уровень→успех
|
||||
(нарратор по звёздам)→карта; «Дальше» активирована (`nextPlayable`); скин тинтует героя+нарратора
|
||||
(localStorage `quantik-skin`). **Backend НЕ тронут** — XP клиентская агрегация из `game_progress`.
|
||||
Новые: `js/game/map.js`, `js/game/progress-logic.js`. Изменены: `quantik.html`, `js/game/levels.js`,
|
||||
`js/game/quantik-game.js`. `node --check` все OK; смоуки (логика 16/16, рендер 7/7, winnability 6/6
|
||||
на реальном движке) зелёные и удалены; `npm test` 259/251 pass / 8 baseline fail (без изменений);
|
||||
lint:routes 0.
|
||||
|
||||
## Key Architecture Decisions
|
||||
- **«Атом» = блок `goal` в спеке** (булево SimExpr). Любой уровень = спека SimForge + `goal`.
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
|
||||
- [x] Phase 0: Слой целей в движке (goal/HUD/result) [domain: frontend] → [subplan](./phase-0-objective-layer.md)
|
||||
- [x] Phase 1: Оболочка игры + 1 физ-уровень + прогресс [domain: fullstack] → [subplan](./phase-1-shell-first-level.md)
|
||||
- [ ] Phase 2: Карта-созвездие + мир физ-уровней + XP/скины [domain: fullstack] → [subplan](./phase-2-map-world-xp.md)
|
||||
- [x] Phase 2: Карта-созвездие + мир физ-уровней + XP/скины [domain: fullstack] → [subplan](./phase-2-map-world-xp.md)
|
||||
- [ ] Phase 3: Граф-уровни (движение по f(x)) + зоны-препятствия [domain: fullstack] → [subplan](./phase-3-graph-levels.md)
|
||||
- [ ] Phase 4: Квантовые способности + SR-комнаты [domain: fullstack] → [subplan](./phase-4-quantum-abilities-sr.md)
|
||||
- [ ] Phase 5: Авторинг уровней в sim-builder + раздача классу [domain: fullstack] → [subplan](./phase-5-authoring-sharing.md)
|
||||
@@ -72,7 +72,7 @@
|
||||
|-------|--------|--------|--------|-------|-----------|
|
||||
| Phase 0: Слой целей в движке | frontend | ✅ Done | ✅ | ✅ | ✅ |
|
||||
| Phase 1: Оболочка + 1 уровень + прогресс | fullstack | ✅ Done | ✅ | ✅ | ✅ |
|
||||
| Phase 2: Карта + мир + XP/скины | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
| Phase 2: Карта + мир + XP/скины | fullstack | ✅ Done | ✅ (1 🟡 fixed) | ✅ | ✅ |
|
||||
| Phase 3: Граф-уровни + зоны | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
| Phase 4: Квантовые способности + SR | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
| Phase 5: Авторинг + раздача | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Phase 2: Карта-созвездие + мир физ-уровней + XP/скины (MVP-мир)
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Status:** ✅ Done (reviewed — PASS w/ notes; «Дальше» stale-hasNext 🟡 fixed; committed)
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** fullstack
|
||||
|
||||
@@ -10,24 +10,24 @@
|
||||
победе. После этой фазы игра полноценно отгружаема.
|
||||
|
||||
## Tasks
|
||||
- [ ] Task 1: Контент — ~5–6 физ-уровней-спек (данные в `levels.js`), нарастающая сложность:
|
||||
- [x] Task 1: Контент — ~5–6 физ-уровней-спек (данные в `levels.js`), нарастающая сложность:
|
||||
артиллерия → перелёт через стену → отскок (restitution) → пружина/маятник → орбита/гравитация.
|
||||
Каждый: `goal` + 1–3 звезды + норматив времени (`par_ms`) для 3-й звезды.
|
||||
- [ ] Task 2: Структура «мир/глава»: метаданные уровня (id, title, chapter, order, par_ms, hint).
|
||||
- [x] Task 2: Структура «мир/глава»: метаданные уровня (id, title, chapter, order, par_ms, hint).
|
||||
Карта группирует по главам (созвездиям).
|
||||
- [ ] Task 3: Карта-созвездие `frontend/js/game/map.js` (+ разметка в quantik.html): узлы-уровни
|
||||
- [x] Task 3: Карта-созвездие `frontend/js/game/map.js` (+ разметка в quantik.html): узлы-уровни
|
||||
на SVG/canvas-фоне, линии-связи, статус (заблокирован/доступен/пройден + число звёзд).
|
||||
Разблокировка: уровень открыт, если набрано ≥ threshold звёзд в предыдущих (правило в данных).
|
||||
- [ ] Task 4: XP/уровень игрока: XP = сумма звёзд × коэффициент (+ бонус за par). Хранить в
|
||||
- [x] Task 4: XP/уровень игрока: XP = сумма звёзд × коэффициент (+ бонус за par). Хранить в
|
||||
прогрессе (расширить `game_progress` агрегацией на клиенте ИЛИ доб. поле/таблицу `game_player`).
|
||||
Полоса XP + «уровень Квантика» в шапке карты.
|
||||
- [ ] Task 5: Скины Квантика: выбор `colorKey` из палитр `PetSprite` (+ позже паттерны). Скин
|
||||
- [x] Task 5: Скины Квантика: выбор `colorKey` из палитр `PetSprite` (+ позже паттерны). Скин
|
||||
влияет на цвет glow-точки героя в уровне (param/проп движка) и на `PetSprite` на карте.
|
||||
Хранить выбор (localStorage сейчас; серверно — опц.). Разблокировка скинов по XP/звёздам.
|
||||
- [ ] Task 6: Нарратор: `PetSprite.render(...)` в интро уровня (краткая формулировка «почини закон…»)
|
||||
- [x] Task 6: Нарратор: `PetSprite.render(...)` в интро уровня (краткая формулировка «почини закон…»)
|
||||
и на экране победы (реакция по числу звёзд: happy/ecstatic). Реюз mood из pet-sprite.js.
|
||||
- [ ] Task 7: Навигация: карта → уровень → результат → возврат на карту с обновлённым статусом/XP.
|
||||
- [ ] Task 8: Тесты: разблокировка (логика чистой функцией — юнит-тест), агрегация XP; смоук карты.
|
||||
- [x] Task 7: Навигация: карта → уровень → результат → возврат на карту с обновлённым статусом/XP.
|
||||
- [x] Task 8: Тесты: разблокировка (логика чистой функцией — юнит-тест), агрегация XP; смоук карты.
|
||||
|
||||
## Files to Modify/Create
|
||||
- `frontend/js/game/levels.js` — контент мира (расширить).
|
||||
@@ -53,4 +53,61 @@
|
||||
- [ ] Карта/навигация работают; 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`) — `<button class="qm-node qm-{locked|available|completed}">` с ядром
|
||||
(`.qm-node-core`), подписью, звёздами/порогом. Позиция в % через `layoutNodes(levels)` (зигзаг-дуга).
|
||||
- Статус узла = `QuantikProgress.nodeStatus`. Клик по available/completed → `onPlay(level)`.
|
||||
- Звёздное небо — SVG `<circle class="qm-tw">` (мерцание CSS), линии-связи `<line class="qm-link[.on]">`.
|
||||
- Поэтапное появление: `staggerReveal` снимает `.qm-pre`/ставит `.qm-in` через setTimeout (70 мс шаг).
|
||||
|
||||
### Как добавить главу (созвездие)
|
||||
- В `levels.js`: дать новым уровням `chapter:'<key>'` + добавить запись в `CHAPTERS`
|
||||
(`{ key, title, subtitle, accent }`). Карта группирует автоматически (`groupByChapter` сохраняет
|
||||
порядок появления глав). Узлы внутри главы сортируются по `order`. Никаких правок map.js не нужно.
|
||||
- **Фаза 3 (граф-уровни) = НОВАЯ глава** (напр. `chapter:'functions'`): добавить уровни-спеки с
|
||||
`objects:[{type:'plot',...}]` + `goal.when` по форме функции; `unlockStars` гейтит её за Динамику.
|
||||
Узел рисуется тем же `buildNode` (тип спеки карте безразличен — она читает только метаданные).
|
||||
|
||||
### Модуль логики прогресса (`frontend/js/game/progress-logic.js`, `window.QuantikProgress`)
|
||||
Чистые функции (без DOM/сети/eval) — переносимы на сервер, покрыты тестом:
|
||||
- `fromProgressList(list)` → карта `{level_id: row}` из ответа `/api/game/progress`.
|
||||
- `starsFor(id, map)` / `isCompleted(id, map)` / `totalStars(levels, map)`.
|
||||
- `isUnlocked(level, map, levels)` — уровень открыт, если Σ звёзд во ВСЕХ уровнях с меньшим `order`
|
||||
≥ `level.unlockStars` (порог в данных уровня). `unlockStars:0` (или нет) → всегда открыт.
|
||||
- `nodeStatus` / `starsToUnlock` — для карты.
|
||||
- `computeXp(levels, map)` = Σ(звёзды·`STAR_XP`=100 + `COMPLETE_XP`=40 за пройденный).
|
||||
- `playerLevel(xp)` → `{ level, xp, xpInto, xpForNext, progress01 }`. Шкала: `xpForLevel(L)=240·(L-1)L/2`.
|
||||
- `groupByChapter(levels)` → `[{ chapter, levels:[…sorted by order] }]`.
|
||||
- `nextPlayable(curId, levels, map)` → след. разблокированный уровень (для кнопки «Дальше») или null.
|
||||
|
||||
### Скины
|
||||
- localStorage ключ **`quantik-skin`** (экспортирован `QuantikGame.SKIN_KEY`). Значение = `colorKey`
|
||||
из `PetSprite.PALETTES` (валидируется при чтении, иначе fallback `'cyan'`).
|
||||
- `QuantikGame.getSkin()/setSkin(key)/skinColor(key)`. Тинт героя — `tintHeroSpec(spec, key)`:
|
||||
глубокая копия спеки (JSON), переписывает `color/glowColor/trailColor` объекта с `id:'ball'`.
|
||||
Гейты скинов — массив `SKIN_GATES` в map.js (needStars/needXp). 8 скинов.
|
||||
|
||||
### Нарратор
|
||||
- `PetSprite.render(level, mood, [], colorKey, 0, 'none')` (DOM SVG-строка). Вызовы:
|
||||
- Карта-шапка: `QuantikMap.renderHeader` (mood по уровню игрока: ecstatic≥5 / happy≥2 / neutral).
|
||||
- Интро уровня: `QuantikGame.buildIntro(level, skin)` (mood `happy`).
|
||||
- Экран успеха: `QuantikGame.buildSuccessOverlay(state, {skin, hasNext})` — mood `ecstatic`, если все
|
||||
звёзды (got≥total и total≥2), иначе `happy`.
|
||||
|
||||
### Навигация (inline-bootstrap в quantik.html)
|
||||
- Два вида: `#qg-map-view` (карта) и `#qg-level-view` (`#qg-stage` под движок). Переключение
|
||||
классом `.show`. `showMap()` перезагружает прогресс (`LS.gameProgressList`) → `map.render`.
|
||||
`openLevel→интро→launchLevel→onGoal→успех→onNext(nextPlayable)|onMap`. При смене уровня
|
||||
ВСЕГДА `destroyLevel()` (= `inst.destroy()` + очистка `#qg-stage`) до нового mount (гоча Ф1).
|
||||
- Deep-link `?level=<id>` открывает уровень, если он разблокирован; иначе карта.
|
||||
|
||||
### Решения/гочи (для ревью и Ф3+)
|
||||
- **XP/прогресс игрока — чисто клиентская агрегация** из `game_progress` (Ф1). Новых таблиц/роутов НЕТ
|
||||
→ lint:routes baseline 0 не тронут, бэкенд-тесты не изменились (259, 251 pass / 8 baseline fail).
|
||||
- Уровни 3/5/6 имеют «лёгкий» выигрышный путь, попутно дающий обе звезды; «честная» механика
|
||||
(отскок/орбита/колодец) присутствует, но не единственно-возможна — НЕ блокер MVP (см. winnability).
|
||||
- На сервер агрегацию XP перенести легко: те же чистые функции в `progress-logic.js` (без DOM).
|
||||
|
||||
Reference in New Issue
Block a user