Some checks are pending
CI / checks (push) Waiting to run
Из ветки feat/anim (d42d646):
- распаковка постоянных дорожек: Quaternion48 (RAWROT), Quaternion64
(RAWROT2), Vector48 (RAWPOS) — раньше такие кости застревали в позе покоя
(в @taunt_muscle_base 24 дорожки из 53);
- engine::animation — выбор секвенции по состоянию: стоя/шаг/бег/присед/
воздух, 8 направлений относительно взгляда, сохранение фазы walk↔run;
- клиент: покачивание вьюмодели при ходьбе, инерция взгляда, одиночный
выстрел fire по ЛКМ, компенсация RAWPOS-сдвига корня (у v_pistol 68 юнитов).
Разрешение конфликтов: вьюмодель осталась в камерном базисе (мои правки —
смещения через up камеры, доворот model_yaw), анимации и покачивание взяты
из ветки; play_model_animation с шестым аргументом model_yaw во всех вызовах.
Проверено: 23 сьюта, 29 картовых тестов, 15 тестов world_real — зелёные.
280 lines
23 KiB
Markdown
280 lines
23 KiB
Markdown
# Дневник агента: где я и что дальше
|
||
|
||
Живой документ. Обновляю по ходу работы, чтобы после автоматического
|
||
уплотнения контекста ничего не терялось. Коротко: состояние, замеры, следующий
|
||
шаг, грабли.
|
||
|
||
Обновлён: 2026-10-23, день.
|
||
|
||
---
|
||
|
||
## 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` фильтрует батчи, но не мешает ли ему что-то ещё.
|
||
|
||
**2026-10-23, сессия по жалобам пользователя.** Закрыто коммитом `f58eafe`:
|
||
|
||
- падение wgpu на втором спавне («Copy at offset … overrunning the bounds»):
|
||
буферы GPU не росли — добавлены `ensure_capacity` (рост вдвое с перезаливкой)
|
||
и `refresh_materials` (bind group для новых материалов, иначе новые батчи
|
||
молча не рисовались);
|
||
- вьюмодель: обновляется после `player.update` и `camera.pos` (раньше
|
||
отставала на кадр — «плывёт при повороте»), по умолчанию `w_pistol.mdl`,
|
||
курс −90° (у модели ствол вдоль +Y, а не +X), дистанция 26, анимация `idle01`;
|
||
- `bone_world_transforms` обходит кости рекурсивно (порядок в файле не
|
||
гарантирован);
|
||
- HUD: отладочные строки (verts/tris, лог Lua) скрыты по умолчанию, клавиша `.
|
||
|
||
**Коллизия пропов (2026-10-23).** Коллайдер-коробка ставилась от **нуля
|
||
модели**, хотя геометрия часто смещена (у куклы центр AABB на 13,5 юнита выше
|
||
нуля) — пропы висели на невидимых выступах. Добавлен
|
||
`Physics::add_prop_offset` (смещение коллайдера в координатах модели,
|
||
поворачивается вместе с телом), оба места создания тел (пропы карты и спавн)
|
||
передают `bounds_center`. Коробки пропов для коллизии игрока теперь тоже
|
||
учитывают центр и поворот (`World::prop_collision_boxes`, AABB описанной
|
||
коробки по модулю матрицы поворота).
|
||
|
||
Замер: тест `real_map_props_rest_on_ground` — после 10 с физики низ коробки
|
||
каждого пропа лежит на опоре (допуск 4 юнита, луч от центра вниз); кукла
|
||
остаётся с зазором 10 юнитов, потому что лежит краем на столе — центровой луч
|
||
опору не видит.
|
||
|
||
**Известная проблема:** `v_pistol.mdl` рисуется плоским «листом» (2157 вершин,
|
||
2 меша, 1 материал, скиннинг при этом цел — тест `real_view_model_skin_stays_coherent`
|
||
даёт максимальный сдвиг 4 юнита). Похоже на проблему загрузки геометрии именно
|
||
этой модели; до выяснения по умолчанию стоит `w_pistol.mdl`.
|
||
|
||
**Запущены параллельно (воркдеревья от `f58eafe`):** `feat/android` — APK под
|
||
Android 12+; `feat/hdr` — HDR, тонмаппинг, bloom и мягкие тени/свет; `feat/anim` —
|
||
RAWROT/RAWPOS/RAWROT2, выбор анимации по состоянию (идёт/бежит/приседает/
|
||
прыжок), покачивание вьюмодели.
|
||
|
||
**A26, состояние на 2026-10-17:** сделаны секвенции (с определением шага записи
|
||
по данным), кости и веса вершин, слияние включённых моделей (у игрока 465
|
||
секвенций), чтение `.ani` (7,1 МБ, 1563 анимации, 247 блоков), декодер кадров
|
||
(`mstudioanim_t`, проверен: 280 кадров, 51 дорожка), сборка кватерниона с
|
||
нормировкой 1/32768 (выбрана замером: умножение на rot_scale давало 1,63 —
|
||
для кватерниона невозможно) и CPU-скиннинг (виден кадром).
|
||
|
||
**Следующий шаг — проигрывание.** План, чтобы не искать заново:
|
||
1. `PropMesh` (или `ModelInstance`) должен держать **данные анимации**:
|
||
`.ani` (7 МБ) + список блоков + описания анимаций, ссылкой `Arc`, — сейчас
|
||
они читаются при загрузке модели и выбрасываются;
|
||
2. в движке `pose_model_instance(index, origin, angles, bone_quats: &[[f32;4]])`:
|
||
локальные кватернионы костей в мировые через родителей
|
||
(`world[i] = world[parent] · (T(pos_i) · R(q_i) · T(−pos_i))`), затем скиннинг
|
||
теми же весами (функция скиннинга уже есть — сейчас её ведёт синус);
|
||
3. в клиенте: выбрать секвенцию (`@idle_all_01` через последовательность
|
||
activity), взять кадр по времени (`t · fps`, циклом), собрать кватернионы
|
||
костей из дорожек и позвать `pose_model_instance`;
|
||
4. затем выбор по состоянию (`ACT_MP_*`) и блендинг, и снять синус.
|
||
|
||
**A26 «анимации» — план записан** (docs/PARITY.md A26). Ключевое открытие: в
|
||
`formats` скелет и веса **уже читаются** (`mdl.bones()`, `vvd.vertices()`,
|
||
`vtx.body_parts()`), не хватает разбора секвенций/`.ani`, костей в
|
||
`ModelVertex` и CPU-скиннинга. Заливка вершин на GPU для этого уже готова —
|
||
тот же путь `moved_ranges`, что у движущихся пропов.
|
||
|
||
**A26, сделано 2026-10-23 (этот заход):** постоянные дорожки
|
||
`RAWROT`/`RAWROT2`/`RAWPOS` разобраны по `compressed_vector.h` (Quaternion48 6
|
||
байт, Quaternion64 8, Vector48 — три half-float). Замеры: `v_pistol @idle01` —
|
||
10 дорожек, 7 RAWROT + 2 RAWPOS; `m_anm @taunt_muscle_base` — 53 дорожки, 24
|
||
RAWROT2, кватернионы единичные, неизвестных флагов нет. `@taunt` теперь
|
||
двигает 3145 из 3147 вершин между кадрами 0 и 30.
|
||
|
||
Выбор анимации по состоянию — `engine::animation`: `idle_all_01`, `a_WalkN`,
|
||
`a_RunN`, `@cidle_all`, `a_CwalkN`, `@jump_fist` (первая существующая), восемь
|
||
направлений относительно взгляда. Фаза walk↔run сохраняется, смена типа —
|
||
сброс. Клиент играет выбранное имя; вьюмодель качается при ходьбе и отстаёт
|
||
при повороте, ЛКМ без захвата проигрывает `fire`. Проверки:
|
||
`real_animation_raw_flags_decode`, `real_player_taunt_moves_vertices_with_raw_tracks`,
|
||
юнит-тесты `engine::animation`.
|
||
|
||
**Следующее:** (а) до-аллокация вершин под спавн вместо пересборки рендерера;
|
||
(б) блендинг анимаций игрока и вьюмодели; (в) вращение пропа колесом и
|
||
заморозка у физгана; (г) оружие в руках связывает `@idle_*`/`@jump_*` и
|
||
вьюмодель; (д) консольные команды спавна на данных из 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` + картовые тесты.
|