feat(sim-builder): фаза 1 — графики (plot), drag-ручки, readout, векторы origin+dx/dy

This commit is contained in:
Maxim Dolgolyov
2026-06-13 11:30:37 +03:00
parent 4dd92f83a0
commit e51b57d9c7
6 changed files with 423 additions and 34 deletions
+8 -3
View File
@@ -1,6 +1,10 @@
# Feature Context: Конструктор симуляций (SimForge)
## Current State
- **Фаза 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 задокументирован в шапке файла.
@@ -43,7 +47,8 @@
- Reuse > переписывание: сначала смотреть `_fx_motion`, `_graph_panel`, `graph.js`.
## RESUME STATE
- Последний коммит фичи: — (Ф0 реализована, но ещё не закоммичена — ждёт оркестратора)
- Текущая фаза: Phase 0Runtime core (✅ Implemented, pending commit) → дальше Phase 1 — Plots & interactions
- Последний коммит фичи: — (Ф0 + Ф1 реализованы, ещё не закоммичены — ждут оркестратора)
- Текущая фаза: Phase 1Plots & interactions (✅ Implemented, pending commit) → дальше Phase 2 — Physics
- Режим: Automated / Orchestrator / Incremental
- Новые публичные API для следующих фаз: `window.SimExpr`, `window.SimEngine.mount`, `window.registerSpecSim` / `window.SimAdapter`. Формат спеки v1 — в шапке `_sim_engine.js` и в handoff phase-0.
- Новые публичные API для следующих фаз: `window.SimExpr`, `window.SimEngine.mount`, `window.registerSpecSim` / `window.SimAdapter`. Формат спеки v1 + типы plot/readout/drag/vector — в шапке `_sim_engine.js` и в handoff phase-0/phase-1.
- Файлы Ф1 (несведённые с параллельной сессией): `frontend/js/labs/_sim_engine.js`, `frontend/js/labs/_sim_demo.js`.
+2 -2
View File
@@ -40,7 +40,7 @@
## Phases
- [x] Phase 0: Спека v1 + рантайм (формульные сцены) [domain: frontend] → [subplan](./phase-0-runtime-core.md)
- [ ] Phase 1: Графики + интеракции [domain: frontend] → [subplan](./phase-1-plots-interactions.md)
- [x] Phase 1: Графики + интеракции [domain: frontend] → [subplan](./phase-1-plots-interactions.md)
- [ ] Phase 2: Физический интегратор [domain: frontend] → [subplan](./phase-2-physics.md)
- [ ] Phase 3: БД + API (custom_sims) [domain: backend] → [subplan](./phase-3-persistence-api.md)
- [ ] Phase 4: Билдер (редактор) [domain: frontend] → [subplan](./phase-4-builder-ui.md)
@@ -53,7 +53,7 @@
| Phase | Domain | Status | Review | Build | Committed |
|-------|--------|--------|--------|-------|-----------|
| Phase 0: Runtime core | frontend | ✅ Done | ✅ | ✅ | ✅ |
| Phase 1: Plots & interactions | frontend | ⬜ Not Started | | | |
| Phase 1: Plots & interactions | frontend | ✅ Done | | | |
| Phase 2: Physics | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 3: Persistence + API | backend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
| Phase 4: Builder UI | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
+56 -13
View File
@@ -1,6 +1,6 @@
# Phase 1: Графики + интеракции
**Status:** ⬜ Not Started
**Status:** ✅ Implemented (не закоммичено — коммит за оркестратором)
**Parent plan:** [PLAN.md](./PLAN.md)
**Domain:** frontend
@@ -9,13 +9,13 @@
векторы и числовые readout. После фазы спека со слайдером, draggable-точкой и live-графиком работает.
## Tasks
- [ ] Plot-объект в спеке: `{ type:'plot', expr:'...', var:'x', range:[a,b], samples, trace? }`
рисует график выражения; `trace:true` — накапливает след по `t`. Переиспользовать `_graph_panel.js` (посмотреть API).
- [ ] Draggable-ручка: объект/маркер с `drag:{ param:'<name>', axis:'x|y|xy', min,max }` — перетаскивание мышью/тачем меняет параметр (и наоборот, позиция следует за параметром). Хит-тест в мир-координатах.
- [ ] Readout: `{ type:'readout', label, expr, unit, precision }` — живое значение выражения как текст/бейдж.
- [ ] Vector-объект с привязкой к (origin, dx, dy)-выражениям + стрелка.
- [ ] Тач-поддержка drag (pointer events), не ломая существующую логику доски/лабы.
- [ ] Обновить демо-спеку: добавить слайдер, draggable-точку, plot + readout.
- [x] Plot-объект в спеке: `{ type:'plot', expr:'...', var:'x', range:[a,b], samples, trace? }`
рисует график выражения; `trace:true` — накапливает след по `t`. Решение: рисуем на canvas движка в мир-координатах (НЕ тянем `GraphPanelUI` — он stacked time-series в фикс. оверлее, не `y=f(x)` инлайн). `samples` деф. 200, клампится 2..2000.
- [x] Draggable-ручка: `point`/`circle` с `drag:{ param, axis:'x|y|xy', min,max, paramY? }` — перетаскивание мышью/тачем (pointer events) меняет параметр(ы); позиция ручки следует за параметром (x/y объекта = тот же параметр). Хит-тест в экранных px (допуск 16px), приоритет ручек. Clamp по `drag.min/max` И по диапазону самого параметра.
- [x] Readout: `{ type:'readout', label, expr, unit, precision, x?, y? }` — живой бейдж; мягкая ошибка через `evalSafe` (NaN/синтаксис → «—», не роняет цикл). Без позиции — авто-столбик в верх-правом углу.
- [x] Vector-объект с привязкой к `origin:[ox,oy]` + `dx`/`dy`-выражениям + стрелка (x1/y1/x2/y2 тоже поддержаны). Стрелка уже была в Ф0.
- [x] Тач-поддержка drag (pointer events + `touchAction:none`), не ломая логику лабы (слушатели только на canvas движка, снимаются в destroy).
- [x] Обновить демо-спеку: +слайдеры x0/y0, draggable-старт (axis xy), plot траектории y(x), 2 readout (дальность R, высота H).
## Files to Modify/Create
- `frontend/js/labs/_sim_engine.js` — типы plot/readout/vector, drag-интеракции (modify)
@@ -31,10 +31,53 @@
- Сэмплинг графика разумный (без фриза на больших range).
## Review Checklist
- [ ] Все задачи выполнены
- [ ] Drag работает мышью и тачем
- [ ] Нет регрессий рантайма Ф0
- [ ] Нет эмодзи, стиль проекта
- [x] Все задачи выполнены
- [x] Drag работает мышью и тачем (pointer events; headless-тест clamp/sync OK)
- [x] Нет регрессий рантайма Ф0 (рендер всех 8 типов демо × 6 кадров без ошибок; point/segment/circle/rect/polyline/path/vector/label не тронуты в логике)
- [x] Нет эмодзи, стиль проекта (скан кодпойнтов — чисто; IIFE-стиль)
## Handoff to Next Phase
<!-- Заполняет реализатор -->
### Что готово (Phase 1) — только `_sim_engine.js` + `_sim_demo.js`
- **plot** — график выражения `f(var)` на отрезке, рисуется на canvas движка в мир-координатах.
- **drag** — `point`/`circle` становятся ручками; pointer events (мышь+тач); clamp двойной (drag.min/max + диапазон параметра).
- **readout** — живой бейдж на оверлее (тот же `_labelLayer`), мягкая ошибка через `SimExpr.evalSafe`.
- **vector** — добавлена форма `origin:[ox,oy]+dx/dy` (конец = origin + (dx,dy)); старая `x1/y1/x2/y2` сохранена; стрелка из Ф0.
### Формат новых типов спеки (точные поля)
```jsonc
// график выражения (мир-координаты)
{ type:'plot', expr:'sin(x)', var:'x', // var деф. 'x'
range:[a,b], // числа/выражения; деф. xmin..xmax
samples?:200, // клампится 2..2000
trace?:false, // true: точка (var=t) пишется в trail по времени;
// при trace без range статич. кривая НЕ рисуется
color?, width? }
// перетаскиваемая ручка (на point/circle)
{ type:'point', x:'x0', y:'y0',
drag:{ param:'x0', // axis x|y -> этот параметр; xy -> X
axis:'x'|'y'|'xy', // деф. 'x'
paramY:'y0', // ТОЛЬКО axis:'xy' -> Y (обязателен для 2D)
min?, max? } } // деф. ±Infinity; доп. clamp по диапазону параметра
// живой числовой бейдж
{ type:'readout', expr:'...', label?:'R', unit?:'м', precision?:2, // precision 0..8, деф.2
x?, y?, // мир-коорд.; без них — авто-столбик верх-право
color? }
// вектор (новая форма)
{ type:'vector', origin:[ox,oy], dx:'...', dy:'...', color?, width? }
```
### API инстанса (без изменений сигнатуры)
`mount(host,spec) -> { play, pause, reset, setParam, getParam, isRunning, destroy, el }`.
Добавлены внутр.: `_toWorld(px,py)`, `_setupDrag`, `_applyDrag`, `_setParamClamped`, `_drawPlot`, `_drawReadout`, `_accumPlotTrace`, `_paramRange`.
### Осталось / риски / на Фазу 2 (физика)
- **Однопроходный env** (`obj.x/obj.y`): из Ф0 — взаимные «вперёд»-ссылки дают значение прошлого кадра. Не трогал; при физике может потребоваться топосорт.
- **Drag только point/circle.** Тащить за конец вектора/вершину polyline — не реализовано (не требовалось).
- **readout позиционирование** на canvas — через DOM-оверлей (`_labelLayer`), как label. На сервере (Ф3) `label`/`unit` readout надо санитизировать как текст.
- **plot и trace на больших range**: ограничены `samples<=2000` и trail `<=2000` точек — без фриза. Очень большие range с тонкой кривой при экстремальном zoom могут ступенчатить — норм для учебных сцен.
- **Физика (`body`/`physics`)** — Фаза 2. Plot/drag/readout/vector полностью совместимы с физ-объектами (drag может задавать начальные условия, readout — читать body-величины, если их положить в env в Ф2).
-`lab.html` и `lab-glue.js` НЕ трогались (зона параллельной сессии). Новых файлов не создавал — всё в `_sim_engine.js`/`_sim_demo.js`.