# Phase 1: Оболочка игры + 1 физ-уровень + прогресс (MVP) **Status:** ⬜ Not Started **Parent plan:** [PLAN.md](./PLAN.md) **Domain:** fullstack ## Objective Сквозной играбельный срез: страница `/quantik` грузит уровень-спеку, монтирует движок в «игровом режиме» (управление = слайдеры закона + кнопка «Запуск»), на победу шлёт результат на сервер, показывает экран успеха со звёздами/временем. Прогресс сохраняется в БД. Первый уровень — «Артиллерия Квантика»: угол+скорость, попасть в портал, собрать звезду. ## Tasks - [ ] Task 1: Миграция (следующий свободный номер) `game_progress`: `id, user_id, level_id TEXT, best_time_ms INTEGER, best_stars INTEGER, attempts INTEGER, completed_at`. Индекс по (user_id, level_id) UNIQUE. - [ ] Task 2: Контроллер `gameController.js` + роутер `game.js`, смонтировать в `server.js` (после `/api/custom-sims`). Эндпоинты: `GET /api/game/progress` (свой прогресс по всем уровням), `POST /api/game/progress` `{level_id, time_ms, stars}` (upsert: пишем лучший результат — min time / max stars; attempts++). auth-only; валидация входа. - [ ] Task 3: Клиент `LS.gameProgressList()` / `LS.gameProgressSubmit(levelId, {time_ms, stars})` в js/api.js. - [ ] Task 4: Уровень как ДАННЫЕ: модуль `frontend/js/game/levels.js` (или сид в `custom_sims`). Для MVP — встроенная спека уровня `phys-artillery-1` (physics + goal + 1 star + portal/star объекты). Решение источника уровней зафиксировать в CONTEXT.md (встроенные данные сейчас; custom_sims в Ф5). - [ ] Task 5: Страница `frontend/quantik.html` + `frontend/js/game/quantik-game.js`: доступ всем авторизованным (LS.initPage()); подключает `_sim_expr.js`+`_sim_engine.js` тем же путём, что lab.html/sim-builder.html. Монтирует уровень, ставит `onGoal` → submit + экран успеха. - [ ] Task 6: «Игровой режим» движка/обёртки: цель видна (HUD из Ф0), управление = существующие слайдеры params; кнопки «Запуск»(play)/«Сброс»(reset). Без редакторских панелей. - [ ] Task 7: Экран успеха (DOM-оверлей страницы): звёзды, время, попытки, кнопки «Ещё раз»/«Дальше» (для MVP «Дальше» неактивна/возврат). Inline SVG, без эмодзи. - [ ] Task 8: Пункт в сайдбаре `js/sidebar.js` — `/quantik` в группе practice (по примеру `/sim-builder`), видимость по роли (доступно ученикам — это игра). `isActive('/quantik')` подсветка. - [ ] Task 9: Тест бэкенда `backend/tests/game.test.js` (паттерн lab-links.test.js: свой app.use нового роутера, getToken/inject): submit пишет лучший результат, не ухудшает, attempts++, требует auth, валидирует вход. Headless-смоук страницы по возможности (vm + стаб), иначе ручная проверка логики. ## Files to Modify/Create - `backend/src/db/migrations/0NN_game_progress.sql` — таблица прогресса. - `backend/src/controllers/gameController.js`, `backend/src/routes/game.js` — API. - `backend/src/server.js` — монтаж роутера. - `frontend/quantik.html`, `frontend/js/game/quantik-game.js`, `frontend/js/game/levels.js` — клиент+уровень. - `frontend/js/api.js` — `LS.gameProgress*`. - `frontend/js/sidebar.js` — пункт меню. - `backend/tests/game.test.js` — тест. ## Acceptance Criteria - `/quantik` грузится, монтирует уровень, цель видна; «Запуск» проигрывает физику. - Попадание в портал (+звезда) → экран успеха с временем/звёздами; результат записан в `game_progress`. - Повторный худший результат не перезаписывает лучший; attempts растёт. - `npm run migrate` применяет миграцию; `npm test` зелёный (+ новый тест); `lint:routes` baseline 0. ## Notes - Маршрутизация `/js/game/*`: помнить гочу sim-builder — `/js` мапится на корневой `js/`, а файлы лежат во `frontend/js/game/` → отдаются через `express.static(frontendDir)`. Не трогать server.js static. - Роуты `:id` прикрыть `authMiddleware` на уровне роутера (lint:routes baseline 0). - Время — из `getResult().timeMs` (Ф0). ## Review Checklist - [ ] Все задачи; конвенции (ownership/auth как studentMaterials/customSim); без эмодзи/eval - [ ] Миграция применяется; API безопасен; тест зелёный; lint baseline 0; existing тесты не сломаны ## Handoff to Next Phase