Files
Learn_System/plans/quantik-game/phase-5-authoring-sharing.md
T
Maxim Dolgolyov c780b6fd96 @
feat(quantik-game): фаза 5 — авторинг игровых уровней в sim-builder + раздача

Учитель собирает игровой уровень без кода: новая (аддитивная, сворачиваемая)
панель в sim-builder задаёт блок goal (when/title/hint/hold/fail) + до 3
звёзд + game-мету (chapter/order/par_ms); выражения проверяются inline через
SimExpr.compile (без eval). buildSpec/loadFromSim — round-trip без потерь
(goal/game пишутся только при включённом слое; обычная sim не меняется).
Кнопка «Играть» монтирует черновик в SimEngine-модалке (HUD цели из Ф0).
QuantikLevels стал async: подмешивает custom_sims cat=game (свои+
published) в реестр (custom:<dbid>), offline-safe, строки без goal
отбрасываются; deep-link /quantik?level=custom:<id> с серверной проверкой
доступа (own|published → иначе 403/404), мимо геймплейного гейта unlockStars.
Раздача классу — реюз share Ф6 (game-aware ссылка + durable pushNotif).
Правки sim-builder строго аддитивны (параллельная сессия). npm test 259/8
baseline; quantik-authoring 6/6; lint:routes 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-06-14 16:09:10 +03:00

7.9 KiB
Raw Blame History

Phase 5: Авторинг уровней в sim-builder + раздача классу

Status: Done (reviewed — PASS, committed) Parent plan: PLAN.md Domain: fullstack

Objective

Дать учителю собирать игровые уровни без кода в существующем sim-builder: задать цель/звёзды/ подсказку/главу/норматив, сохранить как custom_sims с cat='game', опубликовать и раздать классу. Игра начинает грузить уровни из БД (а не только встроенные).

⚠️ ПЕРЕД СТАРТОМ: свериться с base-веткой feature/sim-builder — чужой P4-WIP билдера должен быть смержен. При необходимости влить base в feature/quantik-game и разрешить конфликты.

Tasks

  • Task 1: Режим «Игровой уровень» в sim-builder.js/.html: панель цели (goal.when, title, hint, hold, fail), список звёзд (add/del: when+label), глава/порядок/par_ms. Inline-проверка выражений через SimExpr.compile().error (как остальные поля билдера).
  • Task 2: buildSpec() материализует блок goal/game; loadFromSim() раскладывает обратно (round-trip), как сделано с plot-range в Ф4 билдера.
  • Task 3: Кнопка «Играть» в билдере — открыть текущую спеку в игровом режиме (тест уровня автором).
  • Task 4: Каталог уровней: игра грузит custom_sims c cat='game' (свои+published) — реюз LS.customSimsList/Get. Категория game в списке CATS (customSimController) + фильтр.
  • Task 5: Раздача классу: реюз паттерна Ф6 sim-builder (авто-публикация + pushNotif ученикам, ссылка /quantik?level=custom:<id>); привязка к программе через lab_sim_links (sim_id='custom:<id>').
  • Task 6: Deep-link /quantik?level=custom:<id> (паттерн Ф5/Ф7 sim-builder, доступ own|published|admin).
  • Task 7: Тесты: round-trip goal в билдере (headless как Ф4 sim-builder); доступ к чужому draft запрещён; published-уровень виден; раздача шлёт уведомление.

Files to Modify/Create

  • frontend/sim-builder.html, frontend/js/sim-builder.js — режим игрового уровня (аддитивно).
  • backend/src/controllers/customSimController.jsCATS += 'game'; (goal уже в validateSpec из Ф0).
  • frontend/js/game/quantik-game.js — загрузка уровней из custom_sims + deep-link.
  • тест(ы).

Acceptance Criteria

  • Учитель собирает уровень с целью/звёздами, тестирует «Играть», сохраняет/публикует.
  • Игра грузит уровни из БД; deep-link открывает конкретный уровень с проверкой доступа.
  • Раздача классу публикует + уведомляет; round-trip спеки без потерь; тесты зелёные; lint baseline 0.

Notes

  • Билдер — зона, где мог идти параллельный P4-WIP; правки строго аддитивны, свериться с base.
  • Санитизация goal-полей — уже на сервере (Ф0). Клиентская валидация зеркалит её (как в Ф4 билдера).

Review Checklist

  • Все задачи; аддитивность билдера; ownership/доступ корректны; без эмодзи/eval; тесты зелёные

Handoff to Next Phase

Как авторённый уровень попадает в реестр игры

  • Хранилище: custom_sims с cat='game'. Спека = обычная SimForge-спека + блок goal{when,title,hint,hold,fail,stars[]} + блок game{chapter,order,par_ms,unlockStars?}.
  • window.QuantikLevels стал «асинхронным»: встроенные LEVELS доступны сразу (offline), а опубликованные/свои игровые спеки подмешиваются через QuantikLevels.ensureCustom() (Promise, кэш): LS.customSimsList() → фильтр cat==='game'LS.customSimGet(id) каждой → customToLevel(row) → запись реестра. list() = LEVELS.concat(CUSTOM); get(id) ищет в обоих.
  • Форма записи авторённого уровня (customToLevel): { id:'custom:<dbid>', dbid, title, chapter:(game.chapter||'custom'), order:(game.order||1000+dbid), unlockStars:(game.unlockStars||0), par_ms, subject, hint:(goal.hint), spec, _custom:true }. Запись БЕЗ goal отбрасывается (не уровень).
  • Новая глава-созвездие custom в CHAPTERS (levels.js) — авторённые уровни без явной главы группируются в неё; map.js рисует автоматически (по метаданным, не тронут). Если автор задал game.chapter='kinematics' и т.п. — уровень встанет в соответствующее созвездие.
  • /quantik?level=custom:<dbid>QuantikLevels.getAsync('custom:<dbid>'): если уже в кэше — синхронно; иначе LS.customSimGet(dbid) (сервер: доступ own|published|admin → иначе 404/403 → карта). Авторённый уровень по deep-link открывается БЕЗ гейта unlockStars (получатель ссылки заходит прямо). Встроенный ?level=<id> — как раньше (через isUnlocked).
  • Прогресс игрока по авторённым уровням пишется так же: LS.gameProgressSubmit('custom:<dbid>', ...) (game_progress.level_id — TEXT ≤120, двоеточие проходит; бэкенд НЕ менялся).

Share-flow

  • Реюз контроллера customSimController.share (Ф6). Для cat==='game' ссылка/тип уведомления переключены: link /quantik?level=custom:<id>, тип game_level_shared (обычная sim — /lab?sim=…, sim_shared). Авто-публикация + durable pushNotif ученикам класса. Ответ теперь содержит link.
  • Раздача игрового уровня из билдера — той же кнопкой «Раздать» (openShareModalLS.customSimShare), отдельный UI не нужен. Курикулумная привязка — lab_sim_links sim_id='custom:<id>' (Ф6, не трогалось).

Для Phase 6 (лидерборд / живая гонка)

  • Лидерборд может агрегировать game_progress по level_id (включая custom:<dbid>). Уровень-метаданные (title/chapter) для custom доступны через QuantikLevels.getAsync или прямой LS.customSimGet.
  • Живая гонка (мост sim_state) — он на base-ветке sim-builder Ф7; авторённый игровой уровень уже монтируется тем же SimEngine, что и встроенные, поэтому мост применим без изменений в этой фазе.
  • Авторинг-панель пишет goal/game только при st.game.enabled — обычные симуляции не затронуты.