99 lines
17 KiB
Markdown
99 lines
17 KiB
Markdown
# Feature Context: Конструктор симуляций (SimForge)
|
||
|
||
## Current State
|
||
- **Фаза 4 РЕАЛИЗОВАНА** (в рабочем дереве, не закоммичено — коммит за оркестратором). Только
|
||
новые файлы `frontend/sim-builder.html` + `frontend/js/sim-builder.js` + аддитивная правка
|
||
`js/sidebar.js` (lab.html/lab-glue.js НЕ тронуты — зона параллельной сессии).
|
||
- **Учительский редактор `/sim-builder`** (гейт teacher/admin через `LS.initPage()`): панели-
|
||
аккордеоны (Мета+сцена / Параметры / Объекты / Графики / Физика) слева + живое превью
|
||
(`SimEngine.mount`, перемонтаж с debounce 280мс) справа + тулбар (Тест/Сброс/Сохранить/
|
||
Опубликовать). `window.SimBuilder.create({host,previewHost,panelHost,toolbarHost})`.
|
||
- **Генерация спеки** `buildSpec()` → JSON v1 (specVersion:1, meta, viewport, time, params[],
|
||
objects[]+merged plots, physics?). `_uid` — UI-метка, вырезается; plot материализуется
|
||
(range_a/range_b → range[a,b]); числовые поля — число ИЛИ строка-выражение (движок ест оба).
|
||
- **Выражения**: каждое поле проверяется `SimExpr.compile` → inline-ошибка у поля; палитра
|
||
функций/констант/параметров/`id.x` через модалку. **Запрет имени param `e`** (и pi/t/w/h/...).
|
||
- **Drag-on-preview**: кнопка-«прицел» у объекта → клик/перетаскивание по `inst.canvas` (px→мир
|
||
через `inst._toWorld()`) пишет x/y (или конец segment/vector) в свойства. Только на паузе.
|
||
- **Save/Load**: `customSimCreate`/`customSimUpdate` (?id= → update + replaceState), публикация
|
||
`status:'published'`; `?id=<id>` → `customSimGet` → `loadFromSim` раскладывает по панелям.
|
||
- **Клиентская валидация** зеркалит серверную (params≤50/objects≤200/walls≤20/springs≤50/
|
||
expr≤500/restitution 0..1/JSON≤200КБ) с дружелюбной модалкой-списком ошибок ДО запроса.
|
||
- **Сайдбар**: пункт `/sim-builder` «Конструктор симуляций» (teacher-only, icon pencil-ruler)
|
||
в группе «Практика и игры» после «Лаборатория» — минимальная правка `js/sidebar.js`.
|
||
- Верификация: `node --check` обоих новых .js + извлечённого инлайна html OK; эмодзи нет (скан
|
||
кодпойнтов, включая no-entry sign — заменён на текст); eval/Function нет (вычисления — SimExpr);
|
||
headless-смоук (vm + DOM/Blob-стаб) 23/23: buildSpec форма, merge plot+range, strip _uid,
|
||
physics-блок, валидация valid/reserved-`e`/syntax-error, loadFromSim round-trip стабилен.
|
||
lab.html/lab-glue.js/_sim_engine.js/_sim_expr.js НЕ тронуты (git status).
|
||
- **Фаза 3 РЕАЛИЗОВАНА** (в рабочем дереве, не закоммичено — коммит за оркестратором). Только backend + клиент `js/api.js` (lab.html/lab-glue.js НЕ тронуты — зона параллельной сессии).
|
||
- **Миграция 071** `backend/src/db/migrations/071_custom_sims.sql` — таблица `custom_sims` (применена к живой БД через `npm run migrate`, без ошибок).
|
||
- **API `/api/custom-sims`** (роутер `backend/src/routes/customSims.js`, контроллер `backend/src/controllers/customSimController.js`, смонтировано в `server.js`): GET `/` (свои+published), GET `/:id` (own ИЛИ published), POST `/` (teacher/admin), PUT `/:id` (owner/admin), DELETE `/:id` (owner/admin). Read — router-level authMiddleware; мутации — inline requireRole + per-row ownership.
|
||
- **`validateSpec(spec)`** в контроллере — серверная валидация БЕЗ исполнения: ≤200KB, specVersion=1, лимиты (params≤50/objects≤200/walls≤20/springs≤50/expr≤500/глубина≤8/points≤1000), whitelist типов объектов, physics (restitution 0..1, dt 1/2000..1/30, mass>0), санитизация текст-полей (escape &<>). Возврат `{ ok, error?, clean? }`.
|
||
- **Клиент** `js/api.js`: `customSimsList/Get/Create/Update/Delete` → `req(...)`, добавлены в `window.LS`.
|
||
- Верификация: `node --check` всех новых/изменённых .js OK; `npm run migrate` OK; `npm run lint:routes` чисто (0 unprotected, baseline 0); `backend/tests/custom-sims.test.js` 24/24 pass; общий suite 201/209 (8 fail = 3 baseline auth.test.js + 5 page-тестов без devDep `jsdom` — окружение, не моя фаза). Эмодзи нет; БД через node:sqlite.
|
||
- **Фаза 2 РЕАЛИЗОВАНА** (в рабочем дереве, не закоммичено — коммит за оркестратором). Только `_sim_engine.js` + `_sim_demo.js` (lab.html/lab-glue.js НЕ тронуты — зона параллельной сессии).
|
||
- **Физический режим**: блок `physics:{ enabled, gravity:{x,y}, friction?, restitution?, dt?, walls?:[...], springs?:[...] }` + `body:{ mass, vx, vy, fixed }` на point/circle. Фикс-шаговый полу-неявный Эйлер (накопитель dt, кламп шага/скорости), опора на математику `_fx_motion.spring`. Упругие столкновения круг-круг и круг-стена (restitution), пружины (Гук+демпф) между телами/якорями. Drag тел (тащишь — позиция, отпускаешь — бросок со скоростью). Тела сосуществуют с формульными объектами Ф0/Ф1.
|
||
- **env-поля тел**: `<id>.x/.y/.vx/.vy` берутся из СОСТОЯНИЯ интегратора и кладутся в env первыми — снимает forward-ref проблему однопроходного env для тел.
|
||
- **Интегратор экспортирован** как `window.SimPhysics` (для билдера/доски/headless). Отдельного файла `_sim_physics.js` НЕТ (нельзя подключить без правки lab.html — зона параллельной сессии); код внутри `_sim_engine.js`.
|
||
- Демо за флагом: +`customphys` (пружинный маятник), +`customballs` (упругие шары). Гочи: имя param `e` зарезервировано (число Эйлера) — в демо «шары» упругость названа `el`.
|
||
- Верификация: `node --check` обоих файлов OK; eval/Function — только в комментарии; эмодзи нет (скан кодпойнтов); headless (vm+DOM/canvas-стаб) 28/28: падение под гравитацией (парабола, без NaN), упругие шары (скорости меняются, тела в коробке, ограничены), пружинный маятник (колебания, без взрыва), drag тела (позиция+бросок), смешанная сцена (формульный point + segment на ball.x/y + readout ball.y живут вместе), `SimPhysics.step` raw.
|
||
- **Фаза 1 РЕАЛИЗОВАНА** (в рабочем дереве, не закоммичено — коммит за оркестратором). Только `_sim_engine.js` + `_sim_demo.js` (lab.html/lab-glue.js НЕ тронуты — зона параллельной сессии).
|
||
- Новые типы объектов спеки: **plot** (график `f(var)` на canvas движка, `trace` — след по `t`), **readout** (живой бейдж, мягкая ошибка через `evalSafe`), **vector** с формой `origin+dx/dy`. **drag** на point/circle (`drag:{param,axis,min,max,paramY}`) — pointer events (мышь+тач), хит-тест в px (16px), двойной clamp (drag.min/max + диапазон параметра). Точные поля — в шапке `_sim_engine.js` и handoff phase-1.
|
||
- Демо `customdemo` расширено: +слайдеры x0/y0, draggable-старт (axis xy), plot траектории, 2 readout (R, H). По-прежнему за флагом.
|
||
- Верификация: `node --check` обоих файлов OK; eval/Function — только в комментарии, ни одного call-site; эмодзи нет (скан кодпойнтов); headless-тесты (vm + DOM-стаб): подготовка типов, vector end=origin+(dx,dy), plot evaluate, readout evalSafe, drag clamp+slider-sync, рендер всех 8 типов демо ×6 кадров без ошибок, trail/readout-слоты накапливаются корректно.
|
||
- **Фаза 0 РЕАЛИЗОВАНА** (в рабочем дереве, не закоммичено — коммит за оркестратором). Ветка `feature/sim-builder` от `master`.
|
||
- `frontend/js/labs/_sim_expr.js` → `window.SimExpr` (безопасный движок выражений, без eval/Function; `compile/evaluate/evalSafe/compileValue/parse/tokenize`, whitelist + сравнения/логика/тернарник/multi-var env).
|
||
- `frontend/js/labs/_sim_engine.js` → `window.SimEngine.mount(host, spec) -> { play, pause, reset, setParam, getParam, isRunning, destroy, el }`. Canvas (мир→экран, Y вверх) + KaTeX-оверлей подписей + слайдеры/play/pause/reset. Формат спеки v1 задокументирован в шапке файла.
|
||
- `frontend/js/labs/_sim_adapter.js` → `window.registerSpecSim(spec)` / `window.SimAdapter` — строит манифест LabRegistry (ленивый хост `#sim-spec-host-<id>` в `#lab-sim`).
|
||
- `frontend/js/labs/_sim_demo.js` — демо `customdemo` (бросок тела) за флагом `?simdemo=1` / `?sim=customdemo` / `LAB_SHOW_SPEC_DEMO` / localStorage `lab-spec-demo=1`. Ученикам не светится.
|
||
- Подключение в `frontend/lab.html`: 3 каркасных модуля eager после `_graph_panel.js`, демо после `_register-all.js`. `_sim_deps.js` НЕ тронут.
|
||
- Верификация: `node --check` все 4 файла OK; eval/Function отсутствуют (только в комментариях); эмодзи нет; SimExpr self-test 29/30 (единственный «FAIL» `-2^2=4` — это парити с graph.js).
|
||
- Лаборатория уже декларативна на уровне регистрации: `frontend/js/labs/_registry.js`
|
||
(`LabRegistry.register/get/all/setActive/stop/destroy/resolvePreview`), манифест с
|
||
`open(ctx)/mount(host)/stop/destroy`. ~40 симуляций — рукописные JS-модули в `frontend/js/labs/`.
|
||
- Каталог в БД: миграция `042_lab_sims.sql` (`lab_sims`), роуты `backend/src/routes/lab.js`
|
||
(`GET /api/lab/sims`, PATCH/:id, POST /reorder, links). Привязка к программе: `043_lab_sim_links.sql`.
|
||
|
||
## Архитектурные решения (зафиксированы при планировании)
|
||
- **Спека = JSON-данные.** Версия `specVersion`. Корень: `{ specVersion, meta, viewport, params[], objects[], physics?, plots[], controls }`.
|
||
- **Движок выражений безопасный** — собственный парсер (расширение `y=f(x)` из graph.js):
|
||
токенайзер → AST → eval по окружению `{ params, t, объекты, whitelisted Math fns }`.
|
||
⛔ Без `eval`/`Function`. Whitelist: + - * / ^ %, sin cos tan asin acos atan sqrt abs exp ln log min max floor ceil round sign pi e, сравнения, ?:.
|
||
- **Рантайм** `window.SimEngine.mount(host, spec) -> instance{ play, pause, reset, setParam, destroy }`.
|
||
Рендер: canvas для геометрии/трасс + SVG/absolute-div оверлей для подписей (KaTeX).
|
||
Регистрируется в LabRegistry адаптером (одна функция строит манифест из спеки).
|
||
- **Объект**: `{ id, type, ...props-with-bindings }`. type ∈ point|segment|vector|circle|rect|polyline|path|label|image. Любое числовое свойство может быть числом ИЛИ строкой-выражением.
|
||
- **Физический режим (Фаза 2)**: объект с `body:{ mass, vx, vy, fixed }` интегрируется `_fx_motion`; силы `physics:{ gravity, springs[], collisions, friction, walls }`. Формульный и физический режимы сосуществуют (формульные объекты — кинематические).
|
||
- **Безопасность шаринга**: published-спека валидируется на сервере (размер, схема, глубина AST, число объектов/параметров); подписи-строки санитизируются как svg/текст.
|
||
|
||
## Temporary Workarounds
|
||
- (нет)
|
||
|
||
## Cross-Phase Dependencies
|
||
- Ф1 (графики/drag) зависит от рантайма Ф0.
|
||
- Ф2 (физика) зависит от Ф0 (модель объектов/цикл).
|
||
- Ф4 (билдер) зависит от Ф0–Ф2 (что строить) + Ф3 (куда сохранять).
|
||
- Ф5 (каталог) зависит от Ф3 (БД) + Ф0 (адаптер LabRegistry).
|
||
- Ф6 (раздача) зависит от Ф3+Ф5.
|
||
- Ф7 (доска) зависит от Ф0 (рантайм) + Ф5 (источник sim) + существующего `simOpen/simState`.
|
||
|
||
## Implementation Notes
|
||
- Каждая фаза должна оставлять /lab рабочим (Incremental).
|
||
- Тестировать рантайм Ф0–Ф2 рукописными спеками-фикстурами (без билдера).
|
||
- Reuse > переписывание: сначала смотреть `_fx_motion`, `_graph_panel`, `graph.js`.
|
||
|
||
## RESUME STATE
|
||
- Последний коммит фичи: — (Ф0 + Ф1 + Ф2 + Ф3 + Ф4 реализованы, ещё не закоммичены — ждут оркестратора)
|
||
- Текущая фаза: Phase 4 — Builder UI (✅ Implemented, pending commit) → дальше Phase 5 — Каталог (custom-sims в /lab)
|
||
- Режим: Automated / Orchestrator / Incremental
|
||
- Файлы Ф4 (несведённые с параллельной сессией): `frontend/sim-builder.html` (new),
|
||
`frontend/js/sim-builder.js` (new), `js/sidebar.js` (modify, аддитивный пункт `/sim-builder`).
|
||
lab.html/lab-glue.js НЕ тронуты. Публичное API билдера: `window.SimBuilder.create(...)`.
|
||
- **Номер миграции Ф3: 071** (`071_custom_sims.sql`); следующая свободная — 072.
|
||
- Новые публичные API для следующих фаз: `window.SimExpr`, `window.SimEngine.mount`, `window.SimPhysics` (step/integrate/resolveCollisions), `window.registerSpecSim` / `window.SimAdapter`. Формат спеки v1 + типы plot/readout/drag/vector + блок `physics`/`body`/`springs`/`walls` — в шапке `_sim_engine.js` и в handoff phase-0/1/2.
|
||
- **API персистентности (Ф3)**: `/api/custom-sims` (GET `/`, GET/PUT/DELETE `/:id`, POST `/`) + клиент `LS.customSimsList/Get/Create/Update/Delete`. Контракт спеки на вход/санитизация — в handoff phase-3.
|
||
- Файлы Ф2 (несведённые с параллельной сессией): `frontend/js/labs/_sim_engine.js`, `frontend/js/labs/_sim_demo.js`.
|
||
- Файлы Ф3: `backend/src/db/migrations/071_custom_sims.sql`, `backend/src/controllers/customSimController.js`, `backend/src/routes/customSims.js`, `backend/tests/custom-sims.test.js` (new); `backend/src/server.js`, `js/api.js` (точечные добавления). lab.html/lab-glue.js НЕ тронуты.
|
||
- Для Ф4 (билдер): слать/получать спеку через `LS.customSimCreate/Update/Get`; сервер вернёт спеку санитизированной (escaped-текст). Лимиты/коды 400 — см. handoff phase-3.
|