# 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`.
