feat(sim-builder): фаза 2 — физический интегратор (SimPhysics: гравитация/пружины/столкновения, drag тел)
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
# Feature Context: Конструктор симуляций (SimForge)
|
||||
|
||||
## Current State
|
||||
- **Фаза 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). По-прежнему за флагом.
|
||||
@@ -47,8 +53,9 @@
|
||||
- Reuse > переписывание: сначала смотреть `_fx_motion`, `_graph_panel`, `graph.js`.
|
||||
|
||||
## RESUME STATE
|
||||
- Последний коммит фичи: — (Ф0 + Ф1 реализованы, ещё не закоммичены — ждут оркестратора)
|
||||
- Текущая фаза: Phase 1 — Plots & interactions (✅ Implemented, pending commit) → дальше Phase 2 — Physics
|
||||
- Последний коммит фичи: — (Ф0 + Ф1 + Ф2 реализованы, ещё не закоммичены — ждут оркестратора)
|
||||
- Текущая фаза: Phase 2 — Physics (✅ Implemented, pending commit) → дальше Phase 3 — Persistence + API
|
||||
- Режим: Automated / Orchestrator / Incremental
|
||||
- Новые публичные 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`.
|
||||
- Новые публичные 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.
|
||||
- Файлы Ф2 (несведённые с параллельной сессией): `frontend/js/labs/_sim_engine.js`, `frontend/js/labs/_sim_demo.js`.
|
||||
- Для Ф3 сериализовать/валидировать: блок `physics` (gravity x/y, friction, restitution, dt, walls[], springs[{a,b,k,length,damping}]) и `body{mass,vx,vy,fixed}` на объектах; строки-выражения санитизировать как x/y/expr.
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
- [x] Phase 0: Спека v1 + рантайм (формульные сцены) [domain: frontend] → [subplan](./phase-0-runtime-core.md)
|
||||
- [x] Phase 1: Графики + интеракции [domain: frontend] → [subplan](./phase-1-plots-interactions.md)
|
||||
- [ ] Phase 2: Физический интегратор [domain: frontend] → [subplan](./phase-2-physics.md)
|
||||
- [x] 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)
|
||||
- [ ] Phase 5: Каталог (custom-sims в /lab) [domain: fullstack] → [subplan](./phase-5-catalog.md)
|
||||
@@ -54,7 +54,7 @@
|
||||
|-------|--------|--------|--------|-------|-----------|
|
||||
| Phase 0: Runtime core | frontend | ✅ Done | ✅ | ✅ | ✅ |
|
||||
| Phase 1: Plots & interactions | frontend | ✅ Done | ✅ | ✅ | ✅ |
|
||||
| Phase 2: Physics | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
| Phase 2: Physics | frontend | ✅ Done | ✅ | ✅ | ✅ |
|
||||
| Phase 3: Persistence + API | backend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
| Phase 4: Builder UI | frontend | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
| Phase 5: Catalog | fullstack | ⬜ Not Started | ⬜ | ⬜ | ⬜ |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Phase 2: Физический интегратор
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Status:** ✅ Implemented (не закоммичено — коммит за оркестратором)
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** frontend
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
После фазы маятник/столкновения/брошенное тело идут динамически из спеки.
|
||||
|
||||
## Tasks
|
||||
- [ ] Блок `physics` в спеке: `{ enabled, gravity:{x,y}, friction, walls:[...], restitution }`.
|
||||
- [ ] Тело-объект: `body:{ mass, vx, vy, fixed }` — интегрируется (опора на `_fx_motion.js`, посмотреть API; не дублировать интегратор).
|
||||
- [ ] Пружины: `springs:[{ a, b, k, length }]` (между телами или телом и точкой-якорем).
|
||||
- [ ] Столкновения: упругие шары/стены (restitution), базовый бродфейз достаточно (N небольшое).
|
||||
- [ ] Drag тела: перетаскивание задаёт позицию/скорость (отпустил — летит). Кинематические (формульные) объекты Ф0 сосуществуют с физическими.
|
||||
- [ ] Траектория: накопление следа центра тела (toggle в спеке).
|
||||
- [ ] Демо-спеки: «маятник» (груз+нить как пружина/констрейнт), «упругие шары».
|
||||
- [x] Блок `physics` в спеке: `{ enabled, gravity:{x,y}, friction, walls:[...], restitution, dt?, springs? }`. gravity/friction/restitution — числа ИЛИ выражения от params (вычисляются на reset).
|
||||
- [x] Тело-объект: `body:{ mass, vx, vy, fixed }` на point/circle — интегрируется фикс-шагом (накопитель dt). Нач. позиция/vx/vy/масса — числа или выражения от params (вычисляются при reset/init, далее интегрируются). Опора на математику `_fx_motion` (полу-неявный Эйлер); см. ниже.
|
||||
- [x] Пружины: `springs:[{ a, b, k, length, damping? }]` — концы: id тела ИЛИ якорь-точка `[x,y]`. Сила Гука + демпфирование вдоль оси. Рисуются зигзагом.
|
||||
- [x] Столкновения: упругие круг-круг (по нормали, импульс + позиционная коррекция по обратным массам) и круг-стена (restitution). Broadphase O(n^2) (N мало).
|
||||
- [x] Drag тела: тащишь — задаёт позицию (тело «приколото», не интегрируется); отпустил — сообщает скорость (бросок, кламп 40 м/с). Формульные объекты Ф0/Ф1 сосуществуют (drag-ручки и физ-тела в одной сцене).
|
||||
- [x] Траектория: тело с `trail:true` пишет след центра (переиспользован существующий механизм trail; позиция берётся из env-полей тела).
|
||||
- [x] Демо-спеки за флагом: «пружинный маятник» (`customphys`: тело+пружина+гравитация+drag) и «упругие шары» (`customballs`: 3 тела + 4 стены, g/упругость от слайдеров).
|
||||
|
||||
## Files to Modify/Create
|
||||
- `frontend/js/labs/_sim_engine.js` — физический режим, интеграция с _fx_motion (modify)
|
||||
@@ -33,10 +33,72 @@
|
||||
- Не переусложнять коллизии — школьный уровень (круги/стены).
|
||||
|
||||
## Review Checklist
|
||||
- [ ] Все задачи выполнены
|
||||
- [ ] Использует _fx_motion, без своего дубля интегратора без причины
|
||||
- [ ] Стабильность (нет взрыва энергии на разумных параметрах)
|
||||
- [ ] Нет регрессий Ф0/Ф1
|
||||
- [x] Все задачи выполнены
|
||||
- [x] Опирается на математику `_fx_motion.spring` (полу-неявный/симплектический Эйлер `v+=a·dt; x+=v·dt`) — обобщена на связанные тела в `SimPhysics`; API tween/spring-фабрики `_fx_motion` (rAF-замыкания на одно значение) не подходит для N связанных тел, поэтому тонкий модуль поверх той же математики, без дубля иного интегратора.
|
||||
- [x] Стабильность (нет взрыва энергии): фикс-шаг dt (кламп 1/2000..1/30), накопитель + кап подшагов (8), кламп скорости (1e4), вязкое трение через `exp(-friction·dt)`. Headless-прогон: падение/маятник/шары — конечные, без NaN/∞.
|
||||
- [x] Нет регрессий Ф0/Ф1: формульные point/segment/circle/rect/polyline/path/vector/label/plot/readout/drag работают; тела и формульные объекты в одной сцене (тест mixed).
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Заполняет реализатор -->
|
||||
|
||||
### Что реализовано (Phase 2) — только `_sim_engine.js` + `_sim_demo.js`
|
||||
Файл `_sim_physics.js` НЕ создавался: его нельзя подключить без правки `frontend/lab.html`
|
||||
(зона параллельной сессии). Интегратор живёт внутри `_sim_engine.js` и экспортируется
|
||||
как `window.SimPhysics` (переиспользуемо headless/билдером/доской). Если Ф4+ захочет
|
||||
вынести в отдельный файл — добавить `<script>` в lab.html и `module.exports`-ветку.
|
||||
|
||||
- **`window.SimPhysics`** — `{ step(state, dtFrame), integrate(...), resolveCollisions(...) }`.
|
||||
Полу-неявный Эйлер (математика `_fx_motion.spring`), фикс-шаг, пружины (Гук+демпф),
|
||||
упругие столкновения круг-круг и круг-стена. Без DOM/eval — чистая функция над state.
|
||||
- **Engine**: `_preparePhysics()` (сборка тел/пружин/стен из спеки на reset),
|
||||
`_stepPhysics(dt)` (вызывается в rAF до `_renderFrame`; удерживаемое тело временно
|
||||
fixed), drag тел (`_dragBody`), `_drawSprings()`, рендер point/circle-тел из состояния
|
||||
интегратора, body-поля в `_buildEnv`.
|
||||
|
||||
### Формат (точные поля)
|
||||
```jsonc
|
||||
// глобальный блок физики
|
||||
physics: {
|
||||
enabled: true, // false/отсутствие -> чистая кинематика (Ф0/Ф1)
|
||||
gravity: { x:0, y:-9.8 }, // число ИЛИ выражение от params (вычисляется на reset)
|
||||
friction?: 0, // вязкое затухание (1/с), exp(-friction·dt); число/выражение
|
||||
restitution?: 1, // упругость 0..1 (кламп); число/выражение
|
||||
dt?: 1/240, // фикс-шаг (кламп 1/2000..1/30)
|
||||
walls?: [
|
||||
{ side:'bottom'|'top'|'left'|'right' }, // из границ viewport
|
||||
{ x1,y1, x2,y2 } // произвольный отрезок (нормаль к центру)
|
||||
],
|
||||
springs?: [
|
||||
{ a:'bodyId'|[x,y], b:'bodyId'|[x,y], // конец: id тела ИЛИ якорь-точка
|
||||
k:40, length:5, damping?:0.4 } // k/length/damping — число/выражение
|
||||
]
|
||||
}
|
||||
// тело на point/circle (интегрируется, НЕ формула)
|
||||
{ id:'bob', type:'circle', r:0.6, x:'3', y:'-4', // нач. позиция — число/выражение
|
||||
trail?:true, trailColor?:'#...', // след тела (механизм Ф0)
|
||||
body:{ mass?:1, vx?:0, vy?:0, fixed?:false } } // масса/vx/vy — число/выражение
|
||||
```
|
||||
|
||||
### env-поля тел (доступны readout/plot/label/привязкам)
|
||||
Для каждого тела в env кладутся `<id>.x`, `<id>.y`, `<id>.vx`, `<id>.vy` ИЗ СОСТОЯНИЯ
|
||||
ИНТЕГРАТОРА (не из выражения). Кладутся ПЕРВЫМИ в `_buildEnv` — снимает forward-ref
|
||||
проблему однопроходного env: формульные объекты, ссылающиеся на тело, видят его
|
||||
актуальную позицию/скорость в этом же кадре.
|
||||
|
||||
### Гочи / решения / риски
|
||||
- **Имя param `e` зарезервировано** (число Эйлера в SimExpr). Не использовать `e` как
|
||||
имя параметра в выражениях физики — взять `el`/`elast` и т.п. (демо «шары» учтено).
|
||||
- **Радиус тела для коллизий**: circle — мировой `r`; point — экранные px → переводятся
|
||||
в мир через текущий масштаб (фолбэк 0.3) при `_preparePhysics`. Зависит от `_scale`,
|
||||
поэтому физика собирается ПОСЛЕ `_fit()` (в reset, который зовётся после первого fit).
|
||||
- **Изменение params на лету**: gravity/k/length/restitution/масса/нач.условия пересчит. на
|
||||
**reset** (и на паузе в `t==0` — для предпросмотра старта). Во время проигрывания слайдеры
|
||||
меняют только сами params в env (для readout/формул), но не телепортируют тела/не меняют силы
|
||||
до следующего reset. Это намеренно (стабильность). Если Ф4 захочет live-силы — пересобирать
|
||||
`opts`/springs каждый кадр (тела не трогать).
|
||||
- **Drag во время play**: удерживаемое тело временно `fixed`; на отпускании — скорость из
|
||||
сглаженной оценки движения курсора (кламп 40 м/с).
|
||||
- **Сериализация для Ф3 (БД/API)**: в спеке надо хранить/валидировать блок `physics`
|
||||
(gravity x/y, friction, restitution, dt, walls[], springs[]) и `body{}` на объектах
|
||||
(mass, vx, vy, fixed — числа или строки-выражения). Глубину/число springs/walls и
|
||||
диапазоны (k, mass>0, restitution 0..1, dt) — проверять на сервере как и остальные
|
||||
выражения. Строки-выражения санитизировать так же, как x/y/expr Ф0/Ф1.
|
||||
|
||||
Reference in New Issue
Block a user