Files
Learn_System/CLAUDE.md
T

87 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# BQ-System — правила для Claude
## Поиск по коду
**ast-index — дефолт.** ВСЕГДА первым для «найти символ по имени / usages / callers / outline».
Grep/Read — только если ast-index вернул пустой результат.
**vex** — для поиска **по смыслу**, AST-паттернов, дубликатов, компактного тела символа:
`vex search "..." --semantic`, `vex similar`, `vex pattern`, `vex duplicates`, `vex show`.
Что и когда — подробно в `.claude/rules/search-tools.md`. (usages/callers по JS — только ast-index.)
```bash
# Найти класс/функцию/символ
ast-index class "ClassName"
ast-index symbol "functionName"
# Найти использования
ast-index usages "symbolName"
# Поиск по содержимому файла
ast-index search "keyword" --in-file "filename"
# Структура файла
ast-index outline "path/to/file.js"
# Универсальный поиск
ast-index search "query"
```
Grep использовать только для:
- Поиска строковых литералов (`"some text"`)
- Regex-паттернов
- Если ast-index вернул пустой результат
## Git — обновление репо
**После любых изменений** — коммит и push:
```bash
git add <изменённые файлы>
git commit -m "тип: описание"
git push origin master
```
- Коммитить только изменённые файлы (не `git add -A`)
- Сообщение коммита: `feat:` / `fix:` / `refactor:` / `style:` + краткое описание на русском или английском
- Push выполнять сразу после коммита
## Стек
- Node.js/Express backend, SQLite (better-sqlite3, sync)
- Frontend: vanilla JS, без бандлера
- ast-index проиндексирован: `ast-index rebuild` при добавлении новых файлов
## Feature: Конструктор симуляций (SimForge)
Движок авторинга интерактивных 2D-симуляций из JSON-спеки (данные, НЕ код). План: `plans/sim-builder/`.
### Phase 0 — Learnings
- **Спека = данные.** Любое числовое свойство объекта = число ИЛИ строка-выражение. Выражения шарятся между людьми → движок безопасный, ⛔ без `eval`/`new Function`.
- **`window.SimExpr`** (`frontend/js/labs/_sim_expr.js`): токенайзер → AST → evaluate. `compile(src)->{ast,fn,error}`; `fn(env)` НИКОГДА не бросает (NaN/∞/деление на 0 → 0). Whitelist: `+ - * / ^ %`, унарный `- + !`, сравнения `< <= > >= == !=`, логика `&& ||`, тернарник `?:`, функции `sin cos tan tg ctg cot asin..arctg sqrt abs exp ln log log2 log10 floor ceil round sign min max mod atan2 pow hypot`, константы `pi e tau`. Идентификаторы (вкл. точечные `obj.x`) — только из `env`. Парсер — расширение `y=f(x)` из `graph.js`; `-2^2 == 4` (парити). Также `evalSafe`, `compileValue`, `parse`, `tokenize`, `FUNCTIONS`, `CONSTANTS`.
- **`window.SimEngine.mount(host, spec)`** (`_sim_engine.js`) → `{ play, pause, reset, setParam, getParam, isRunning, destroy, el }`. Canvas (мир→экран, равные оси, Y вверх) + KaTeX-оверлей подписей (`katex.renderToString`, как graph.js) + слайдеры из `params[]`. Выражения компилируются 1 раз в mount; в rAF — только evaluate. `env = { t, <params>, w, h, xmin..ymax, <objId>.x, <objId>.y }`. Объекты: `point segment vector circle rect polyline path label`. **Формат спеки v1 — в шапке `_sim_engine.js`.**
- **`window.registerSpecSim(spec)`** (`_sim_adapter.js`): спека → манифест LabRegistry (ленивый хост `#sim-spec-host-<id>` в `#lab-sim`; `stop` прячет, `destroy` уничтожает). Так спек-сим открывается тем же путём, что рукописные ~40 (через `openSim` → реестр).
- Демо `customdemo``_sim_demo.js`, за флагом `?simdemo=1` / `?sim=customdemo` / `LAB_SHOW_SPEC_DEMO` / localStorage `lab-spec-demo=1` (ученикам не светится).
- Подключение: 3 каркасных `<script>` eager после `_graph_panel.js` в `lab.html`, демо — после `_register-all.js`. `_sim_deps.js` не трогать (каркас грузится до диспетчера).
### Phase 1 — Learnings
- **Новые типы объектов** (в `_sim_engine.js`, формат — в шапке файла):
- `plot` — график `f(var)` на canvas движка в мир-координатах (НЕ через `GraphPanelUI` — тот stacked time-series в фикс. оверлее, не `y=f(x)`). Поля: `expr`, `var` (деф.`x`), `range:[a,b]` (числа/выражения, деф. xmin..xmax), `samples` (клампится 2..2000, деф.200), `trace` (точка var=t пишется в trail; при trace без range статич. кривая не рисуется), `color/width`. Свободная переменная подставляется во временную копию env-ключа (восстанавливается после).
- `readout` — живой бейдж на DOM-оверлее (`_labelLayer`, как label). Поля: `expr`, `label`, `unit`, `precision` (0..8, деф.2), `x/y` (мир-коорд.; без них — авто-столбик верх-право, счётчик `_readoutSlot` сбрасывается на кадр). Ошибка — мягко через `SimExpr.evalSafe` (AST компилируется 1 раз в prepare), показывает «—».
- `vector` — новая форма `origin:[ox,oy]+dx/dy` (конец = origin + (dx,dy)); старая `x1/y1/x2/y2` сохранена; стрелка из Ф0.
- **Drag** (`point`/`circle` с `drag:{param,axis,min,max,paramY}`): pointer events на canvas (мышь+тач, `touchAction:none`); хит-тест в экранных px (допуск 16px, ближайшая ручка), приоритет ручек. `axis:'xy'` требует `paramY`. Курсор → мир через `_toWorld` (инверсия `_toPx`) → `_setParamClamped` (clamp по `drag.min/max` И по диапазону параметра из `_paramRange` — не полагаться на DOM-clamp слайдера). Слушатели снимаются в `destroy`. Drag только point/circle (вершины polyline/конец вектора — не реализовано).
- Тестировать движок headless: `vm.createContext` + ручной DOM/canvas-стаб (canvas-ctx через `Proxy` с noop). `_renderFrame` рано выходит при `_cw/_ch==0` — выставить вручную. `setParam`/drag используют `new Event('input')` (браузерно безопасно, в стабе нужен `Event`).
-`lab.html`/`lab-glue.js` — зона параллельной сессии (Ф2 измерит. инструменты); Ф1 их НЕ трогала, работала только в `_sim_engine.js`/`_sim_demo.js`.
### Phase 2 — Learnings
- **Физический режим** (всё в `_sim_engine.js`, формат — в шапке файла): блок `physics:{ enabled, gravity:{x,y}, friction?, restitution?, dt?, walls?:[...], springs?:[...] }` + `body:{ mass, vx, vy, fixed }` на point/circle. gravity/friction/restitution/k/length/damping/mass/нач.позиция/vx/vy — число ИЛИ выражение от params (вычисляются на **reset**, не каждый кадр — для стабильности).
- **`window.SimPhysics`** — экспортированный интегратор (`step(state,dtFrame)`, `integrate`, `resolveCollisions`). Полу-неявный (симплектический) Эйлер `v+=a·dt; x+=v·dt` — та же математика, что `_fx_motion.spring`, обобщённая на N связанных тел. Фикс-шаг с накопителем (кламп dt 1/2000..1/30, кап подшагов 8, кламп скорости 1e4, вязкое трение `exp(-friction·dt)`) → энергия не «взрывается». Упругие столкновения круг-круг (импульс по нормали + позиционная коррекция по обратным массам) и круг-стена. Чистая функция над state, без DOM/eval — переиспользуемо headless. Отдельного файла `_sim_physics.js` НЕТ (нельзя подключить без правки lab.html — зона параллельной сессии); код внутри `_sim_engine.js`.
- **`_fx_motion` API не подходит** для спек-движка напрямую: `tween`/`springFactory` — rAF-замыкания, тянущие ОДНО значение к цели, не связанные тела с силами. Переиспользована только их интеграционная математика (формула спринга), а не сами функции.
- **env-поля тел**: `<id>.x/.y/.vx/.vy` берутся из СОСТОЯНИЯ интегратора и кладутся в `_buildEnv` ПЕРВЫМИ (до формульных центров) — это снимает forward-ref проблему однопроходного env для тел: формульный объект, ссылающийся на тело (`segment x2:'ball.x'`), видит актуальную позицию в том же кадре. point/circle с `body` рисуются из env-полей тела, а не из выражения x/y.
- **Drag тела**: тело (point/circle с `body`, не fixed) перетаскиваемо по умолчанию (без `drag`-конфига). Тащишь — `body.x/y = курсор`, тело временно `fixed` в `_stepPhysics`; отпускаешь — `body.vx/vy` из сглаженной оценки скорости курсора (кламп 40 м/с). Хит-тест тела — max(16px, экранный радиус). drag-ручки Ф1 и физ-тела сосуществуют.
- **Гочи**: (1) имя param **`e`** зарезервировано — это число Эйлера в SimExpr (parser проверяет CONSTANTS до env), выражение `e` даст 2.718, не значение param. Брать `el`/`elast`. (2) Радиус тела для коллизий: circle — мировой `r`; point — экранные px → мир через `_scale`, поэтому физика собирается в `reset()` ПОСЛЕ первого `_fit()`. (3) Слайдеры во время play меняют только env (readout/формулы), но НЕ силы/тела до reset (намеренно). На паузе при `t==0` — пересборка для предпросмотра старта.
- Headless-тест физики: виртуальные часы (`vclock`) синхронны с `performance.now()` и таймстампом rAF (иначе первый кадр получит огромный/отрицательный dt и ничего не сдвинется).