# Phase 1: Графики + интеракции **Status:** ✅ Implemented (не закоммичено — коммит за оркестратором) **Parent plan:** [PLAN.md](./PLAN.md) **Domain:** frontend ## Objective Добавить в рантайм графики (plot-объекты), перетаскиваемые ручки (drag → параметр), векторы и числовые readout. После фазы спека со слайдером, draggable-точкой и live-графиком работает. ## Tasks - [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) - `frontend/js/labs/_sim_demo.js` — расширить демо (modify) ## Acceptance Criteria - Перетаскивание ручки меняет параметр; зависимые объекты/график обновляются. - График строится по выражению; trace накапливает след во времени. - Readout показывает живое значение. Тач работает. ## Notes - Drag не должен конфликтовать с pan/zoom рантайма (если есть). Приоритет хит-теста — ручки. - Сэмплинг графика разумный (без фриза на больших range). ## Review Checklist - [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`.