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>
@
This commit is contained in:
Maxim Dolgolyov
2026-06-13 17:07:33 +03:00
parent 02ab886bee
commit 978448d99b
9 changed files with 569 additions and 19 deletions
+12 -1
View File
@@ -240,7 +240,7 @@
<svg class="ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18 9 12 15 6"/></svg>
К карте
</button>
<span class="qg-pill">Физика</span>
<span class="qg-pill" id="qg-pill">Физика</span>
</div>
<!-- Вид карты -->
@@ -287,6 +287,14 @@
var backBtn = document.getElementById('qg-back');
var titleEl = document.getElementById('qg-title');
var subEl = document.getElementById('qg-sub');
var pillEl = document.getElementById('qg-pill');
// Бейдж темы по предмету уровня (аддитивно; граф-уровни — «Алгебра»).
var SUBJECT_LABEL = { physics: 'Физика', algebra: 'Алгебра', math: 'Математика' };
function setPill(level) {
if (!pillEl) return;
pillEl.textContent = SUBJECT_LABEL[level && level.subject] || 'Физика';
}
if (!window.SimEngine || !window.SimExpr || !window.QuantikLevels ||
!window.QuantikGame || !window.QuantikMap || !window.QuantikProgress) {
@@ -321,6 +329,7 @@
backBtn.style.display = 'none';
titleEl.textContent = 'Квантик — Законы Мира';
subEl.textContent = 'Карта мира — выбери уровень и почини закон';
if (pillEl) pillEl.textContent = 'Физика';
history.replaceState(null, '', '/quantik');
// перезагрузить прогресс (мог обновиться после победы) и перерисовать
loadProgress().then(function () { map.render(progressMap); });
@@ -334,6 +343,7 @@
backBtn.style.display = '';
titleEl.textContent = level.title || 'Квантик';
subEl.textContent = (level.spec && level.spec.goal && level.spec.goal.title) || level.hint || '';
setPill(level);
history.replaceState(null, '', '/quantik?level=' + encodeURIComponent(level.id));
// Pre-win значение (фолбэк, если пересчёт после победы недоступен).
@@ -373,6 +383,7 @@
backBtn.style.display = '';
titleEl.textContent = level.title || 'Квантик';
subEl.textContent = (level.spec && level.spec.goal && level.spec.goal.title) || '';
setPill(level);
var intro = window.QuantikGame.buildIntro(level, window.QuantikGame.getSkin());
intro.btnGo.addEventListener('click', function () {