gmod-rs/docs/DIARY.md
Niko Marmeladkov 46677edbaa
Some checks are pending
CI / checks (push) Waiting to run
A26: план анимаций моделей (скелет и веса уже читаются)
Просьба пользователя: «по возможности делай анимации». Записан план (A26) с
тем, что уже есть и чего нет.

Уже есть в formats: mdl.bones() (скелет: родители, позиции, кватернионы),
mdl.attachments(), vvd.vertices() (веса и кости по вершинам), vtx.body_parts()
(топология). То есть данные скелета и весов читаются — не хватает переноса их в
рендер.

Чего нет: разбора секвенций (mstudioseqdesc_t) и кадров из отдельного .ani
(mstudioanimdesc_t, сжатие кривой/дельтами); костей и весов в ModelVertex;
CPU-скиннинга; выбора секвенции по состоянию (ACT_MP_*) с блендингом.

Порядок: (а) секвенции и .ani в mdl.rs, (б) кости/веса в ModelVertex,
(в) CPU-скиннинг и заливка на GPU (механизм moved_ranges уже готов — тот же
путь, что у движущихся пропов), (г) проигрывание одной секвенции с циклом,
(д) выбор по состоянию и блендинг. Приёмка: модель двигается, ходьба переходит
в бег и присед, прыжок отдельной секвенцией.
2026-10-17 10:48:29 +03:00

208 lines
17 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.

# Дневник агента: где я и что дальше
Живой документ. Обновляю по ходу работы, чтобы после автоматического
уплотнения контекста ничего не терялось. Коротко: состояние, замеры, следующий
шаг, грабли.
Обновлён: 2026-10-16, вечер.
---
## 1. Что просит пользователь (приоритеты)
**Ускоренный путь до Sandbox-UX** (его слова): «быстрее дойти до того, как смогу
спавнить через Q-меню, иметь норм модель игрока, таскать физганом, Weapon всё
сделать». Поэтому очередь переставлена (docs/PARITY.md A25):
1. A23 оружие (SWEP из Lua + вьюмодель + аттачменты) — сюда же A20 (модель
игрока: один и тот же рендер моделей).
2. A21 физика взаимодействия: физган (луч → проп → таскание, вращение,
заморозка), без полной гибернации.
3. A24 спавн: сначала консольные команды на данных из Lua (`gm_spawn`,
`gm_give`, режимы `gmod_tool`), потом окно Q.
4. A22 Sandbox целиком: undo/cleanup/duplicator.
5. Визуальная точность (A10 свет на пиксель, A11–A12 вода, A16 трава/декали,
A18 скайбокс) — параллельно, Sandbox не блокирует.
## 2. Где я сейчас (в работе)
**A20/A23, первый срез сделан: модель игрока в мире.** Рендер тот же, что у
пропов (статический меш `PropMesh`), скелет и анимации — следующий срез.
Как включается и как проверять:
```bash
SOURCE_RS_PLAYER_MODEL= DISPLAY=:99 SDL_VIDEODRIVER=x11 cargo run -q -p client -- \
map --assets ~/gmod_server/server gm_construct --hidden --frames 4 --noclip \
--player-pos="-2438 -966 48" --flythrough /tmp/opencode/pl4 --flythrough-frames 4 \
--route="-2438 -880 75|6 270; -2438 -1180 75|6 90"
```
Пустое значение — модель по умолчанию (`models/player/police.mdl`), иначе —
путь к модели. Курс разворачивается на 180° (у моделей Source ось Y влево).
Движок: `World::spawn_model_instance` и `World::move_model_instance`,
`ModelInstance` (экземпляр без сущности). Грабли: индексы модели **локальные** —
при вставке в общий буфер к ним обязательно прибавляется `first_vertex`
(без этого был мусор в кадре).
**Спавн пропа из игры сделан (ядро A24).** `World::spawn_prop` (геометрия из
`append_model_geometry`, сущность `prop_physics`, тело в физике, экземпляр
пропа) + в клиенте клавиша `G` (спавн по взгляду) и аргумент
`--spawn <модель>` (отладка и скрипты). Проверено кадром: арбуз заспавнен и
отрисован (`--spawn models/props_junk/watermelon01.mdl`, камера у
`-2500 -1050 60|5 0`).
Грабли: буферы рендера созданы на сборке мира — после спавна рендерер
пересобирается целиком (медленно, но работает); позже нужна до-аллокация
вершин под спавн.
**Физган, первый срез (A21/A23).** ЛКМ — взять ближайшее тело под прицелом
(луч из глаз, расстояние 8..260 юнитов, допуск 40 в стороны), пока держим —
тело каждый кадр телепортируется на луч взгляда на взятом расстоянии; отпустил —
свободно. `SOURCE_RS_PHYSGUN=1` держит ближайшее тело без мыши (для скриптов).
**Нужна интерактивная проверка физгана:** мои скриптовые кадры упираются в
стены — камеру без глаз не навести. Проверять так: встать у кучи пропов
(она в районе `-2930 -1370 -64`), посмотреть на проп, зажать ЛКМ.
**Синхронизация физики и картинки (жалоба «позиция рендера обновляется
только при спавне нового»).** Причина найдена: мир двигал вершины в памяти, а
буфер рендера заливался один раз при сборке — потому спавн (пересборка
рендерера) и «лечил». Сделано: `World::moved_ranges` + `take_moved_ranges()`
(диапазоны заполняет `rewrite_instance`), `WorldRenderer::upload_vertex_ranges`
(точечная заливка `queue.write_buffer`) и вызов в кадре клиента.
**Не сходится (диагностика `probe_moving_prop_reports_vertex_ranges`):**
коробка в тесте задана в мировых координатах, а меш обязан быть в локальных —
видно по двойному смещению вершины; плюс список диапазонов почему-то пуст после
`step_physics` (проверить `is_sleeping` и ранний выход по совпадению позы —
вероятно, тело «спит» после телепорта). Проверять в интерактиве: заспавнить
арбуз, посмотреть, что он падает на экране.
**Найдены и закрыты две поломки по жалобе «физика висит на экране»:**
1. `move_prop` остался со своей копией перезаписи вершин и не запоминал
диапазоны — теперь он идёт через `rewrite_instance` (замер: было «диапазонов 0»
каждый шаг, стало «диапазонов 1 [(39585, 8)]»).
2. Буфер вершин мира создавался **без `COPY_DST`** — заливка падала с ошибкой
wgpu (у пользователя это роняло клиент). Теперь `VERTEX | COPY_DST`, для
индексов `INDEX | COPY_DST` плюс `upload_index_ranges`.
Плюс `WorldRenderer::refresh_batches`: рендерер копировал список батчей при
сборке, поэтому **новая** геометрия (спавн) в кадр не попадала без полной
пересборки. Теперь список обновляется, когда появились новые индексы, а полная
пересборка нужна только при новой текстуре.
**Открытый вопрос: вьюмодель.** Создание работает (модель в буфере, вершины
+1000, батч есть, габарит ±15), но в кадре её нет — при этом модель игрока
рисуется. Разница в том, что модель игрока в том тесте не двигалась (кадры
пролёта), а вьюмодель обязана двигаться каждый кадр. Значит сломано именно
звено «перенос позы каждый кадр + заливка на GPU». Включается
`SOURCE_RS_VIEW_MODEL` (по умолчанию физган, есть откат на монтировку —
`v_physgun.mdl` в ассетах не нашёлся, грузится `v_crowbar.mdl`).
**Вьюмодель: СДЕЛАНО (первый срез).** Механизм проверен кадром: пистолет
(`SOURCE_RS_VIEW_MODEL=models/weapons/v_pistol.mdl`) стоит в кадре. Две причины,
почему раньше не рисовалась: (1) разворот на 180° уводил ствол за камеру —
у моделей оружия Source геометрия вынесена вперёд по оси модели; (2) у моделей
оружия геометрия смещена от начала координат (у пистолета центр габарита
`≈(0,0,47)`, у монтировки `−52` по Y), поэтому привязываться надо к **центру
габарита** модели (`engine::model::rotate_model_vector(centre, ...)`), а не к её
origin. Посадка: глаз + 20 вперёд + 6 вправо − 10 вниз − центр.
Что осталось по вьюмодели: `v_physgun.mdl` в ассетах нет (GMod-модель), поэтому
по умолчанию пистолет; отдельный FOV вьюмодели и анимации — позже.
**Старые факты (сохранены):** Материал вьюмодели в мире —
индекс 54, имя `crowbar_cyl` (виден в `--debug-materials`). Рендер одним этим
материалом (`--only-material 54`) даёт **пустой кадр** — то есть дело не в позе:
батч вьюмодели не доходит до отрисовки. Проверять по порядку: (1) попал ли батч
в `renderer.batches` после `refresh_batches` (он копирует список только когда
`moved_index_ranges` непуст, а они забираются в блоке физики — возможно,
диапазоны берутся ДО того, как `refresh_batches` увидит их); (2) совпадает ли
`batch.material` с позицией материала в `world.materials` (дедупликация по
имени); (3) `--only-material` фильтрует батчи, но не мешает ли ему что-то ещё.
**A26 «анимации» — план записан** (docs/PARITY.md A26). Ключевое открытие: в
`formats` скелет и веса **уже читаются** (`mdl.bones()`, `vvd.vertices()`,
`vtx.body_parts()`), не хватает разбора секвенций/`.ani`, костей в
`ModelVertex` и CPU-скиннинга. Заливка вершин на GPU для этого уже готова —
тот же путь `moved_ranges`, что у движущихся пропов.
**Следующее:** (а) до-аллокация вершин под спавн вместо пересборки рендерера;
(б) реестр SWEP из Lua и вьюмодель; (в) вращение пропа колесом и заморозка у
физгана; (г) скрытие своей модели, когда камера внутри неё; (д) скелет+анимации
моделей; (е) консольные команды спавна на данных из Lua (`gm_spawn`).
## 3. Что уже сделано и лежит в `main`
Последние коммиты (свежие сверху):
- `4ad0405` очередь: A23 оружие, A24 Q-меню, A25 ускоренный порядок;
- `7a8a750` очередь: A20 модель игрока, A21 полная физика, A22 Sandbox через GLua;
- `257ac07`, `d35efef` **A13 туман сделан** целиком (чтение + шейдер);
- `72bad60` A19: подшаг не гасит скорость о склон (35°: 65 → 147);
- `5bdbdbf`, `e638607` A19: замер по углам + найден `flRedirectCoeff` в движке;
- `3bfd078`, `d88988f` рельеф: треугольники отсекают шаг, нормаль наружу (стены
по рёбрам полигонов ушли);
- `b74824b` склон: догон земли трассировкой (рампы 242–250 вместо 197,8);
- `23c5034` присед: 250/150/520/216,7 по спеке, камера 46 → 28 (была выше
хитбокса);
- `aa11713`, `d220b5a` рампы и стыки: отсечение по последней входной грани,
касание ребром не преграда;
- `86a6cb6`, `5380184` инструменты: слой коллизий, дамп кирпича, обход карты.
Все 23 сьюта и 28 картовых тестов зелёные.
## 4. Замеры, которые нельзя потерять
- **A19 (скорость на склоне)**: замер `probe_slope_angles` (синтетический клин).
Было: 5° 239, 10° 232, 15° 196, 20° 152, 25° 116, 30° 88, 35° **65**, 40° 49,
44° 39. Стало: 239 / 233 / 226 / 214 / 195 / 175 / **147** / 132 / 107.
Цель — 250 до 44°. Остаток теряется на прямом (скользящем) пути: иногда он
выигрывает выбор у подшага. Инструмент: `SOURCE_RS_TRACE_STEP=1` печатает,
какой путь победил (`step_move`).
- **Механизм движка для скорости на склоне**: `gamemovement.cpp`, `ClipVelocity`
(строка ~3144) — блок `flRedirectCoeff`: `out *= (len + |v·n|·coeff)/len`.
Наивный перенос копит бонус за кадр (15° → 442 юнита/с) — откачен.
- **Туман карты gm_construct**: цвет (0,557 0,643 0,698), start 1024, end
250000, max_density 1,0, farz −1 (дальний, на ближних дистанциях нейтрален).
- **Габариты/линейки Source**: тело 72/36 (стоя/присед), глаза 64/28
(`VEC_VIEW`/`VEC_DUCK_VIEW`), `sv_walkable_normal` 0,7 (45,6°), `DIST_EPSILON`
1/32, `sv_stepsize` 18, воздушный потолок желаемой скорости 30.
- **Коллизия gm_construct**: 2118 кирпичей; 140 за габаритами карты (3D-скайбокс,
часть с несогласованными плоскостями — см. E19.2); 11776 треугольника рельефа,
у всех намотка «гео внутрь» (нормаль разворачиваем в `clip_triangle`).
- **Присед**: обрезка `HandleDuckingSpeedCrop` = 0.33333333 только в приседе и
на земле, желаемая скорость 450 (бег 650), максимум 250 / 520.
## 5. Инструменты диагностики (не переизобретать)
- `SOURCE_RS_DEBUG_COLLISION=<радиус>` — слой коллизий: красные рёбра кирпичей,
жёлтые габариты, голубой рельеф, зелёное тело.
- `probe_brush_dump` (`SOURCE_RS_TEST_BSP_AT="x,y,z,радиус"`) — полный дамп
кирпича: содержимое с расшифровкой, владелец-сущность, каждая грань с
текстурой, габарит, попадание в коллизию.
- `probe_ramp_movement` — движение из точки: 8 направлений, рывки, залипания,
кто мешает (кирпич + текстуры).
- `probe_slanted_surface` — лучи по наклонным кирпичам против настоящей формы.
- `probe_slope_angles` — таблица скорости по углам (приёмка A19).
- `probe_phantom_walls_scan`, `probe_collision_contents` — обход карты и состав
коллизии.
- `real_map_*` — 28 картовых тестов, требуют `SOURCE_RS_TEST_BSP`.
- Запуск клиента: `DISPLAY=:99 SDL_VIDEODRIVER=x11 cargo run -q -p client --
map --assets ~/gmod_server/server gm_construct ...`, для кадров нужен
`--flythrough <dir>`.
## 6. Грабли (уже наступал)
- `Vec3` без `Index`: писать покомпонентно.
- `%[fx:mean*255]` в ImageMagick без имени канала врёт — считать
`mean.r/g/b`.
- Тесты с картой имеют свой `VirtualFs::new()` (189 материалов) — индексы
материалов не как в рантайме (54).
- Правки кода делать `edit`, не sed/python, кроме больших перестановок блоков —
и после каждой проверять `cargo clippy` и тесты.
- Перед коммитом всегда: `cargo fmt --all && cargo clippy --workspace
--all-targets -- -D warnings && cargo test --workspace` + картовые тесты.