Files
Learn_System/plans/sim-builder/phase-0-runtime-core.md
T

11 KiB
Raw Blame History

Phase 0: Спека v1 + рантайм (формульные сцены)

Status: Implemented (не закоммичено — коммит за оркестратором) Parent plan: PLAN.md Domain: frontend

Objective

Заложить ядро: формат JSON-спеки v1, безопасный движок выражений, рантайм SimEngine, адаптер регистрации в LabRegistry. После фазы рукописная спека «брошенное тело» играет в /lab.

Tasks

  • Задокументировать формат спеки v1 в шапке нового файла + в CONTEXT.md (params, objects, viewport, controls). → шапка _sim_engine.js (полный JSON-формат) + CONTEXT.md.
  • frontend/js/labs/_sim_expr.js — безопасный движок выражений: токенайзер → AST → evaluate(ast, env). Whitelist математики (см. CONTEXT.md). Парсер расширяет логику y=f(x) из graph.js (тот же подход к токенам/неявному умножению; добавлены сравнения, логика, тернарник, multi-var env, min/max/mod/log(b,x)). без eval/Function. compile(src) -> {ast, fn(env), error}.
  • frontend/js/labs/_sim_engine.jswindow.SimEngine.mount(host, spec):
    • canvas с мир→экран (равные оси, вписан в viewport, Y вверх) + оверлей-слой <div> для подписей (KaTeX renderToString, как в graph.js);
    • объекты: point|segment|vector|circle|rect|polyline|path|label (числовые свойства = число или строка-выражение, компилируются один раз в mount);
    • rAF-цикл: t += dt*speed, loop/duration, перевычисление привязок, перерисовка, трассы (trail);
    • контролы: слайдеры из params[] + play/pause/reset; API { play, pause, reset, setParam, getParam, isRunning, destroy, el }.
  • frontend/js/labs/_sim_adapter.jsregisterSpecSim(spec) строит манифест LabRegistry (open(ctx) → ленивый собственный хост-div + SimEngine.mount; stop прячет хост+pause; destroy уничтожает инстанс; preview из спеки или авто-SVG) и регистрирует.
  • Фикстура-демо: рукописная спека «projectile» (слайдеры θ 0..90 / v 0..30, точка x=v·cosθ·t, y=max(0, v·sinθ·t5t²), вектор v0, земля, подпись) — зарегистрирована как customdemo за флагом ?simdemo=1 / ?sim=customdemo / LAB_SHOW_SPEC_DEMO / localStorage lab-spec-demo=1. Ученикам не светится (карточки в SIMS нет; добавляется только при включённом флаге).
  • Подключить новые файлы в /lab прямыми <script> (минимально-инвазивно): _sim_expr/_sim_engine/_sim_adapter — eager после _graph_panel.js; _sim_demo — после _register-all.js. Lazy-схема _sim_deps не тронута (каркасные модули должны быть до диспетчера). Старт /lab и ~40 симуляций не затронуты.

Files to Modify/Create

  • frontend/js/labs/_sim_expr.js — движок выражений (new)
  • frontend/js/labs/_sim_engine.js — рантайм (new)
  • frontend/js/labs/_sim_adapter.js — адаптер LabRegistry (new)
  • frontend/js/labs/_sim_demo.js — демо-спека-фикстура (new, временная)
  • frontend/lab.html или _sim_deps.js — подключение файлов (минимальная правка)

Acceptance Criteria

  • В /lab открывается демо-симуляция, слайдеры меняют движение, play/pause/reset работают.
  • Движок выражений не использует eval/Function; некорректная формула не роняет рантайм (показывает ошибку/0).
  • Существующие ~40 симуляций и старт /lab не сломаны.

Notes

  • Подписи с LaTeX — переиспользовать существующий рендер формул (KaTeX), не тянуть новый.
  • Мир-координаты с осью Y вверх (математические), трансформация в экранные внутри движка.
  • Производительность: компилировать выражения один раз при mount, в цикле только evaluate.

Review Checklist

  • Все задачи выполнены
  • Нет eval/new Function в движке выражений (grep -nE "eval(|new Function|Function(" → только упоминания в комментариях, ни одного call-site)
  • Нет эмодзи (скан: только ASCII + кириллица + θ/∞/box-drawing — те же баннеры, что в graph.js)
  • Старт /lab и существующие симуляции не регрессировали (не тронуты SIMS/OPEN/манифесты; добавлены только новые <script> + register())
  • Код в стиле проекта (vanilla, IIFE с экспортом в window.*, как _registry/_simbase/_graph_panel)

Handoff to Next Phase

Что готово (Phase 0)

  • Движок выражений frontend/js/labs/_sim_expr.jswindow.SimExpr:
    • compile(src) -> { ast, fn, error }. fn(env) -> number, НИКОГДА не бросает (NaN/∞/деление на 0 → 0). error — строка при синтаксической ошибке (не бросается).
    • evaluate(ast, env) -> number (то же, безопасно), evalSafe(ast, env) -> { value, error } (для билдера/отладки — отличает NaN от валидного 0).
    • compileValue(value) -> { fn, error, constant, ast } — число вернёт константу, строку скомпилирует (используется движком для свойств-привязок).
    • parse(src) -> ast (бросает при ошибке), tokenize(src), FUNCTIONS / CONSTANTS (объекты-«множества» имён, для подсветки в билдере).
    • Whitelist: + - * / ^ %, унарный -/+/!, скобки, сравнения < <= > >= == !=, логика && ||, тернарник ?:; функции sin cos tan tg ctg cot asin acos atan arcsin arccos arctan arctg sqrt abs exp ln log(x|base,x) log2 log10 floor ceil round sign min max mod atan2 pow hypot; константы pi e tau. Идентификаторы (вкл. точечные obj.x) берутся ТОЛЬКО из env.
    • ⚠️ Степень право-ассоциативна, базой служит unary → -2^2 == 4 (паритет с graph.js, задокументировано). Для -(2^2) писать скобки.
    • Самопроверка: 29/30 логических кейсов PASS (единственный «FAIL» — -2^2, это и есть парити-поведение, не баг).
  • Рантайм frontend/js/labs/_sim_engine.jswindow.SimEngine:
    • mount(host, spec) -> instance. instance: play() pause() reset() setParam(name,val) getParam(name) isRunning() destroy() + поле el (корневой DOM для скрытия/показа).
    • Сцена: панель слайдеров+play/pause/reset слева, <canvas> + оверлей <div> подписей справа. Мир→экран: равные оси, центрирование, Y вверх. Выражения компилируются 1 раз в mount; в rAF — только evaluate. env = { t, <params>, w, h, xmin/xmax/ymin/ymax, <objId>.x, <objId>.y }.
    • Объекты: point segment vector circle rect polyline path label. Подписи — KaTeX (фолбэк на текст). Трассы (trail:true).
  • Адаптер frontend/js/labs/_sim_adapter.jswindow.registerSpecSim(spec) / window.SimAdapter:
    • Строит манифест LabRegistry из спеки, кладёт _spec/_instance/instance() для будущих фаз. Каждая спек-симуляция получает ленивый хост #sim-spec-host-<id> внутри #lab-sim. stop прячет хост (важно при switch — openSim не знает наших хостов), destroy уничтожает инстанс.
  • Демо frontend/js/labs/_sim_demo.jscustomdemo за флагом, для приёмки.
  • Подключение в frontend/lab.html: 3 каркасных <script> после _graph_panel.js, демо после _register-all.js.

Формат спеки v1 (поддержан фактически)

{ specVersion?, id, cat?, meta:{title,desc}, viewport:{xmin,xmax,ymin,ymax,grid?,axes?,bg?}, time:{autoplay?,loop?,duration?,speed?}, params:[{name,label,min,max,step,value,unit?}], objects:[{id?,type,...числа|строки-выражения, color?,fill?,width?,trail?,trailColor?,latex?,size?,text?,points?,closed?}], theory?, subject?,grade?,topics? } Полная спецификация полей по типам объектов — в шапке _sim_engine.js.

Осталось на Фазу 1 / временные решения / риски

  • Drag-интеракции, графики, оси-подписи с числами — Фаза 1 (сейчас оси без числовых меток, есть сетка). GraphPanelUI из _graph_panel.js готов к переиспользованию для plots.
  • Физика (body/physics) — Фаза 2, объекты сейчас чисто кинематические (формульные).
  • Однопроходный env для obj.x/obj.y: центры объектов вычисляются в порядке объявления за один проход — взаимные ссылки «вперёд» (объект ссылается на тот, что объявлен ниже) дадут значение предыдущего кадра (или 0 на первом). Достаточно для типовых сцен; при необходимости в Ф1 сделать топосортировку/итерации.
  • r точки трактуется как экранный радиус в пикселях (не мир-единицы) — намеренно, чтобы точки не «таяли» при масштабе; для circle радиус мировой.
  • Демо customdemo — временный раздел, удалить/спрятать после билдера (Фаза 4). Карточка добавляется в каталог только при включённом флаге.
  • Санитизация подписей/валидация спеки на сервере — Фаза 3 (сейчас спеки только рукописные/локальные, в БД не идут).