11 KiB
11 KiB
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.js—window.SimEngine.mount(host, spec):- canvas с мир→экран (равные оси, вписан в viewport, Y вверх) + оверлей-слой
<div>для подписей (KaTeXrenderToString, как в 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 }.
- canvas с мир→экран (равные оси, вписан в viewport, Y вверх) + оверлей-слой
frontend/js/labs/_sim_adapter.js—registerSpecSim(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θ·t−5t²), вектор v0, земля, подпись) — зарегистрирована как
customdemoза флагом?simdemo=1/?sim=customdemo/LAB_SHOW_SPEC_DEMO/ localStoragelab-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.js→window.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.js→window.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.js→window.registerSpecSim(spec)/window.SimAdapter:- Строит манифест LabRegistry из спеки, кладёт
_spec/_instance/instance()для будущих фаз. Каждая спек-симуляция получает ленивый хост#sim-spec-host-<id>внутри#lab-sim.stopпрячет хост (важно при switch — openSim не знает наших хостов),destroyуничтожает инстанс.
- Строит манифест LabRegistry из спеки, кладёт
- Демо
frontend/js/labs/_sim_demo.js—customdemoза флагом, для приёмки. - Подключение в
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 (сейчас спеки только рукописные/локальные, в БД не идут).