From 978448d99b912f867605ad8b9647c8b78262135b Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 13 Jun 2026 17:07:33 +0300 Subject: [PATCH] =?UTF-8?q?@=20feat(quantik-game):=20=D1=84=D0=B0=D0=B7?= =?UTF-8?q?=D0=B0=203=20=E2=80=94=20=D0=B3=D1=80=D0=B0=D1=84-=D1=83=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=BD=D0=B8=20(=D0=B4=D0=B2=D0=B8=D0=B6=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE=20f(x))=20+=20=D0=B7=D0=BE?= =?UTF-8?q?=D0=BD=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Новый тип уровня: Квантик едет по кривой 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) @ --- CLAUDE.md | 10 + .../src/controllers/customSimController.js | 9 + backend/tests/custom-sims.test.js | 28 ++ frontend/js/game/levels.js | 291 +++++++++++++++++- frontend/js/labs/_sim_engine.js | 154 ++++++++- frontend/quantik.html | 13 +- plans/quantik-game/CONTEXT.md | 12 + plans/quantik-game/PLAN.md | 4 +- plans/quantik-game/phase-3-graph-levels.md | 67 +++- 9 files changed, 569 insertions(+), 19 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 11dbcf7..211976c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -238,3 +238,13 @@ git push origin master - **Навигация (inline-bootstrap quantik.html)**: 2 вида `#qg-map-view`/`#qg-level-view` (класс `.show`). `showMap` перезагружает прогресс (`LS.gameProgressList`) → `map.render`. `openLevel→интро→launchLevel→onGoal→успех→onNext(nextPlayable)|onMap`. **Смена уровня ВСЕГДА через `destroyLevel()` (=`inst.destroy()`)** до нового mount (гоча Ф1). Deep-link `?level=` открывает только разблокированный. - **Per-level winnability обязательна** (как Ф1): harness грузит РЕАЛЬНЫЕ `_sim_expr.js`+`_sim_engine.js` в `vm`, свипует слайдеры через движок, проверяет `getResult().won`. Гоча OOM: **переиспользовать ОДИН `inst` через `reset()` по сотням комбо ТЕЧЁТ** (накопление через goal-state/bodyById-замыкания) → mount+`destroy()` СВЕЖИЙ inst на каждое комбо (leak-proof). Headless `_renderFrame` рано выходит при `_cw/_ch==0` (рендер не нужен, физика/`_evalGoal` идут в `play`-кадре независимо); для point-радиуса в физике выставить `inst._scale`. Виртуальные часы синхронны с `performance.now()`/rAF-timestamp. Результат: ВСЕ 6 winnable, у всех достижимы обе звезды (combos: artillery 28/196, arc 5/196, bounce 92/343, pendulum 189/196, orbit 94/196, gravimanёvr 170/343). - **Верификация P2**: `node --check` всех новых/изменённых JS + inline-`