10 KiB
Phase 2: Физический интегратор
Status: ✅ Implemented (не закоммичено — коммит за оркестратором) Parent plan: PLAN.md Domain: frontend
Objective
Добавить настоящую физику: тела с массой, гравитация/пружины/столкновения/трение, перетаскивание тел силой, траектории. Динамика считается движком, а не формулой. После фазы маятник/столкновения/брошенное тело идут динамически из спеки.
Tasks
- Блок
physicsв спеке:{ enabled, gravity:{x,y}, friction, walls:[...], restitution, dt?, springs? }. gravity/friction/restitution — числа ИЛИ выражения от params (вычисляются на reset). - Тело-объект:
body:{ mass, vx, vy, fixed }на point/circle — интегрируется фикс-шагом (накопитель dt). Нач. позиция/vx/vy/масса — числа или выражения от params (вычисляются при reset/init, далее интегрируются). Опора на математику_fx_motion(полу-неявный Эйлер); см. ниже. - Пружины:
springs:[{ a, b, k, length, damping? }]— концы: id тела ИЛИ якорь-точка[x,y]. Сила Гука + демпфирование вдоль оси. Рисуются зигзагом. - Столкновения: упругие круг-круг (по нормали, импульс + позиционная коррекция по обратным массам) и круг-стена (restitution). Broadphase O(n^2) (N мало).
- Drag тела: тащишь — задаёт позицию (тело «приколото», не интегрируется); отпустил — сообщает скорость (бросок, кламп 40 м/с). Формульные объекты Ф0/Ф1 сосуществуют (drag-ручки и физ-тела в одной сцене).
- Траектория: тело с
trail:trueпишет след центра (переиспользован существующий механизм trail; позиция берётся из env-полей тела). - Демо-спеки за флагом: «пружинный маятник» (
customphys: тело+пружина+гравитация+drag) и «упругие шары» (customballs: 3 тела + 4 стены, g/упругость от слайдеров).
Files to Modify/Create
frontend/js/labs/_sim_engine.js— физический режим, интеграция с _fx_motion (modify)frontend/js/labs/_sim_physics.js— обёртка интегратора/коллизий, если чище отдельно (new, опц.)frontend/js/labs/_sim_demo.js— физ-демо (modify)
Acceptance Criteria
- Тело под гравитацией падает/летит по параболе через интегратор (не по формуле).
- Пружина колеблет груз; шары упруго сталкиваются; стены отражают.
- Drag тела работает; формульные объекты Ф0 продолжают работать в той же сцене.
Notes
- Шаг интегратора фиксированный (накопитель dt) для стабильности.
- Не переусложнять коллизии — школьный уровень (круги/стены).
Review Checklist
- Все задачи выполнены
- Опирается на математику
_fx_motion.spring(полу-неявный/симплектический Эйлерv+=a·dt; x+=v·dt) — обобщена на связанные тела вSimPhysics; API tween/spring-фабрики_fx_motion(rAF-замыкания на одно значение) не подходит для N связанных тел, поэтому тонкий модуль поверх той же математики, без дубля иного интегратора. - Стабильность (нет взрыва энергии): фикс-шаг dt (кламп 1/2000..1/30), накопитель + кап подшагов (8), кламп скорости (1e4), вязкое трение через
exp(-friction·dt). Headless-прогон: падение/маятник/шары — конечные, без NaN/∞. - Нет регрессий Ф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.
Формат (точные поля)
// глобальный блок физики
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.