From 0f3e12426a0af7e2ea47fbcb0d51d9d6ebf48dcc Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 13 Jun 2026 16:24:31 +0300 Subject: [PATCH] =?UTF-8?q?@=20feat(quantik-game):=20=D1=84=D0=B0=D0=B7?= =?UTF-8?q?=D0=B0=202=20=E2=80=94=20=D0=BA=D0=B0=D1=80=D1=82=D0=B0-=D1=81?= =?UTF-8?q?=D0=BE=D0=B7=D0=B2=D0=B5=D0=B7=D0=B4=D0=B8=D0=B5=20+=20=D0=BC?= =?UTF-8?q?=D0=B8=D1=80=20+=20XP/=D1=81=D0=BA=D0=B8=D0=BD=D1=8B=20(MVP-?= =?UTF-8?q?=D0=BC=D0=B8=D1=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Одиночный уровень → играбельный мир: карта-созвездие из 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) @ --- CLAUDE.md | 13 + frontend/js/game/levels.js | 420 +++++++++++++++++---- frontend/js/game/map.js | 385 +++++++++++++++++++ frontend/js/game/progress-logic.js | 195 ++++++++++ frontend/js/game/quantik-game.js | 180 +++++++-- frontend/quantik.html | 394 +++++++++++++++++-- plans/quantik-game/CONTEXT.md | 14 + plans/quantik-game/PLAN.md | 4 +- plans/quantik-game/phase-2-map-world-xp.md | 77 +++- 9 files changed, 1539 insertions(+), 143 deletions(-) create mode 100644 frontend/js/game/map.js create mode 100644 frontend/js/game/progress-logic.js diff --git a/CLAUDE.md b/CLAUDE.md index 2813140..11dbcf7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -225,3 +225,16 @@ git push origin master - **Доступ страницы**: `LS.initPage()` (без `{requireLogin:false}`) сам редиректит на `/login` если не авторизован и возвращает null → бутстрап выходит. Любой авторизованный играет. - **Верификация P1**: `node --check` всех новых/изменённых JS — OK; `npm run migrate` 076 применяется чисто; `npm test` 251 pass / 8 baseline fail (3 auth + 5 jsdom page-тестов — пре-существующие; **game.test.js 13/13 PASS**); `lint:routes` 247 :id-роутов, 0 unprotected (baseline 0). Эмодзи в коде нет (флагуются только `→`/`⛔` в комментариях — конвенция проекта); eval/new Function — 0. Спека без goal по-прежнему работает (Ф0 не задет). - **На Phase 2 (карта/мир/XP)**: реестр уровней расширяемый (добавить запись в `LEVELS`); `game_progress`-API готов; экран успеха `buildSuccessOverlay` переиспользуем (расширить «следующим уровнем», активировать «Дальше»); при смене уровня без перезагрузки — `inst.destroy()` перед новым mount. + +### Phase 2 — Learnings (Карта-созвездие + мир физ-уровней + XP/скины) + +- **Phase 2 = FRONTEND-ONLY** (осознанное решение): XP/уровень игрока агрегируются на КЛИЕНТЕ из `game_progress` (Ф1), скин — localStorage. Без новых таблиц/роутов/миграций → `lint:routes` baseline 0 не тронут, `npm test` ровно как в Ф1 (259 tests / 251 pass / 8 baseline fail). Перенос XP на сервер позже тривиален — те же чистые функции `progress-logic.js`. +- **Чистая логика в отдельном модуле `frontend/js/game/progress-logic.js`** (`window.QuantikProgress`, без DOM/сети/eval — тестируемо в изоляции): `isUnlocked(level,map,levels)` (Σ звёзд во всех уровнях с меньшим `order` ≥ `level.unlockStars`; порог в ДАННЫХ уровня), `computeXp`(звёзды·100+40/пройден), `playerLevel(xp)` (квадратичная шкала `xpForLevel(L)=240·(L-1)L/2`), `groupByChapter`, `nextPlayable`, `fromProgressList`, `starsFor/starsToUnlock/nodeStatus`. Гоча тестов: `assert.deepEqual` через `vm`-границу сравнивает массивы РАЗНЫХ реалмов (прототипы ≠) → ложный fail; сравнивать через `JSON.stringify`. +- **Карта `frontend/js/game/map.js`** (`window.QuantikMap.create({host,headerHost,onPlay,getSkin,onSkin})->{render(progressMap),destroy()}`): созвездия по главам (`groupByChapter`), узлы — ` Физика -
+ + +
+
+
+
+
+
+ + +
+
+
@@ -71,48 +263,168 @@ + + - + + + diff --git a/plans/quantik-game/CONTEXT.md b/plans/quantik-game/CONTEXT.md index 51b0e60..5669e5c 100644 --- a/plans/quantik-game/CONTEXT.md +++ b/plans/quantik-game/CONTEXT.md @@ -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`. diff --git a/plans/quantik-game/PLAN.md b/plans/quantik-game/PLAN.md index 636ce24..b5940f9 100644 --- a/plans/quantik-game/PLAN.md +++ b/plans/quantik-game/PLAN.md @@ -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 | ⬜ | ⬜ | ⬜ | diff --git a/plans/quantik-game/phase-2-map-world-xp.md b/plans/quantik-game/phase-2-map-world-xp.md index 9bf9e62..ad283f3 100644 --- a/plans/quantik-game/phase-2-map-world-xp.md +++ b/plans/quantik-game/phase-2-map-world-xp.md @@ -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`) — `