Files
Learn_System/plans/quantik-game/phase-3-graph-levels.md
T
Maxim Dolgolyov 978448d99b @
feat(quantik-game): фаза 3 — граф-уровни (движение по f(x)) + зоны

Новый тип уровня: Квантик едет по кривой y=f(x), которую игрок собирает
слайдерами коэффициентов, проходя сквозь зоны-препятствия. Движок
(аддитивно): plot.runner → env-поля curve.runX/runY/runDone (f компилится
1 раз, питает И кривую, И бегунок-героя, без само-ссылки); type zone
(forbidden/target/collect) → булево env-поле zone.hit. Грамматика
выражений ЗАКРЫТА — никаких inzone()-предикатов, только именованные
env-поля (модель t/tries из Ф0), без eval. Глава-созвездие functions из
5 уровней (луч/синус/парабола/модуль/экспонента), разблокировка 9/11/13/
15/17 (цепочка проходима). validateSpec принимает zone+runner. Все 5
уровней независимо проверены на движке (2★ достижимы). npm test 253/8
baseline; custom-sims 26/26; lint:routes 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
2026-06-13 17:07:33 +03:00

10 KiB
Raw Blame History

Phase 3: Граф-уровни (движение по f(x)) + зоны-препятствия

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

Objective

Новый тип уровня: Квантик движется по кривой y=f(x), которую собирает игрок (настраивает параметры/выбирает выражение). Препятствия — «запретные зоны»; цель/звёзды/проигрыш — выражения. Реюз plot + SimExpr. Сид граф-главы.

Tasks

  • Task 1: «Бегунок по кривой»: герой-точка с x = функция t (напр. линейный проход xmin→xmax), y = f(x) через ту же скомпилированную функцию, что у plot. Кривая рисуется (P3 plot), герой едет по ней с glow/trail. Без физики (кинематический проход), либо мягкая физика — на выбор уровня. → plot.runner:{duration,hold} кладёт в env <plotId>.runX/.runY/.runDone; герой = обычный point с x:'curve.runX', y:'curve.runY', glow+trail. f компилируется 1 раз и питает И кривую, И бегунок.
  • Task 2: Тип объекта/поле «зона» (forbidden/target): прямоугольник/круг в мире + удобные env-предикаты (или документированный паттерн: fail:'inzone(...)'). Реализовать helper-предикаты БЕЗ расширения небезопасного синтаксиса — предпочесть готовить булевы поля зон в env (напр. zone1.hit) на основе позиции героя, чтобы goal/fail ссылались на них. → type:'zone' (shape rect/circle, kind forbidden/target/collect, track). Движок кладёт <zoneId>.hit (1/0) в env. Никаких inzone()-предикатов в грамматике — только именованные булевы env-поля.
  • Task 3: Цель = добраться до конца/в целевую зону, не задев запретные (fail). Звёзды: пройти под нормативом, собрать бонус-точки (зоны-сборы). → goal.when='gate.hit', fail='pit.hit', stars=[collect-zone hit, доп. условие формы кривой].
  • Task 4: Управление: слайдеры коэффициентов f(x) (a·sin(b·x+c)+d и т.п.) ИЛИ выбор/набор выражения с inline-проверкой SimExpr.compile(...).error (как в sim-builder). Безопасно. → коэффициенты = обычные params-слайдеры движка; крутишь → кривая+путь героя перестраиваются. Свободный ввод выражения не понадобился (слайдеры коэффициентов достаточны для MVP-главы).
  • Task 5: Контент: сид граф-главы (~4–5 уровней): синус под мостом, парабола над ямой, кусочная подгонка, экспонента/логарифм — растущая сложность, привязка к темам алгебры. → 5 уровней в functions: луч (a·x+b), синус (A·sin(k·x)), парабола (a·(x−5)²+k), модуль (a·|x−m|+1), экспонента (c·e^(r·x)). Все solvable (см. Concerns).
  • Task 6: Интеграция в карту (Ф2): новая глава-созвездие; общий конвейер результата/XP. → глава functions в CHAPTERS; map.js НЕ тронут (рисует по метаданным). Бейдж темы в quantik.html стал per-level (subject → Физика/Алгебра) — аддитивно.
  • Task 7: Тесты: проход по кривой достигает цели; задевание зоны → fail; смоук рендера кривой+героя. → headless vm-смоук (логика+per-level solvability, 29/29, удалён); серверный тест приёма zone+runner спеки (custom-sims.test.js, +2 теста, остаётся).

Files to Modify/Create

  • frontend/js/labs/_sim_engine.js — поддержка «бегунка по кривой» (если не выразимо текущими полями) и подготовка булевых полей зон в env. Аддитивно, документировать в шапке.
  • frontend/js/game/levels.js — граф-глава.
  • frontend/js/game/quantik-game.js / map.js — новая глава, управление коэффициентами.
  • тест(ы).

Acceptance Criteria

  • Квантик едет по собранной игроком кривой; правильная f(x) проводит между препятствиями к цели.
  • Задевание запретной зоны → проигрыш; норматив/сборы дают звёзды.
  • Кривая безопасна (SimExpr, без eval); existing симуляции/уровни не затронуты; тесты зелёные.

Notes

  • НЕ вводить произвольные функции-предикаты в синтаксис выражений (безопасность). Зоны → булевы env-поля.
  • Переиспользовать P3 plot (несколько кривых, заливка, маркеры) для визуала «земли»/препятствий.

Review Checklist

  • Все задачи; аддитивность движка; без эмодзи/eval; тесты зелёные; lint baseline 0

Handoff to Next Phase

Контракт «бегунка по кривой» (движок, _sim_engine.js)

  • На объекте plot: runner:{ duration?:8, hold?:true }. Делает из ПЕРВОЙ кривой plot дорожку.
  • Движок кладёт в env (в _buildEnv, ДО формульных центров): <plotId>.runX (= a + (ba)·clamp(t/duration,0,1)), <plotId>.runY (= f(runX) ТОЙ ЖЕ скомпил. функции, что рисует кривую), <plotId>.runDone (1 при t≥duration).
  • Герой = обычный point с x:'curve.runX', y:'curve.runY' + glow + trail. НЕ тело → нет само-ссылки (f компилируется один раз, питает И кривую, И бегунок). hold:true — остаётся на конце; иначе зацикливание по time.loop.
  • Никакого eval: f — обычное SimExpr-выражение кривой.

Контракт зон (движок)

  • type:'zone', id, shape:'rect'|'circle', kind:'forbidden'|'target'|'collect' (цвет/семантика), геометрия (rect: x,y центр + w,h; circle: x,y + r — числа ИЛИ выражения), track?:'ball' (чью позицию тестить), label?, color?.
  • Движок кладёт <zoneId>.hit (1/0) в env (последним — нужна актуальная позиция героя). goal.when/fail/stars[].when ссылаются на него.
  • Предикаты в синтаксис выражений НЕ добавлялись — только именованные булевы env-поля (модель безопасности t/tries из Ф0).
  • Рисуется в _drawObject/_drawZone: forbidden=красный пунктир, target=зелёный, collect=золотой пунктир. Цвета — только canvas-стоки.
  • Зона НЕ кладёт <zoneId>.x/.y как центр объекта (hasCenter пропущен для type==='zone').

Как определяется граф-уровень (данные, levels.js)

  • Хелперы: road(exprStr,a,b,dur) (plot+runner, id 'curve'), graphHero() (point ball на curve.runX/runY), rectZone/circZone(id,kind,...), startMarker. Уровень = спека с этими объектами + goal{when:'gate.hit',fail:'<forb>.hit',stars}.
  • ⚠️ ГОЧА: имена param t/w/h/pi/e/E/PI/tau зарезервированы движком (h=высота вьюпорта!). abs-уровень использует m (вершина), НЕ h. При добавлении уровней проверять имена коэффициентов.
  • time:{duration,loop:false} синхронизирован с runner.duration — герой доезжает до конца за один проход.

Карта / запуск

  • Глава functions добавлена в CHAPTERS (key/title/subtitle/accent). map.js НЕ тронут — узлы рисуются по метаданным, тип спеки карте безразличен. Разблокировка: unlockStars 9/11/13/15/17 (≤ 18 макс. звёзд физ-глав → нет дедлока).
  • Запуск тот же (QuantikGame.startSimEngine.mount); граф-уровни используют те же слайдеры params, спец-вайринг НЕ нужен. Бейдж темы в quantik.html — per-level по level.subject (аддитивно).

Для Ф4 (квантовые способности)

  • runDone/runX/.hit — готовые env-поля для условий способностей (напр. «туннель» = временно игнорить forbidden.hit в fail). Способность может менять params (коэффициенты) или подменять выражение кривой — всё через тот же SimExpr-конвейер.
  • Зоны kind:'collect' уже «залипают» через механизм stars (Ф0). Новая способность = новый env-флаг + условие, БЕЗ eval.
  • Сервер уже принимает zone+runner (validateSpec, OBJECT_TYPES) — авторённые граф-уровни (Ф5) пройдут гейт.