Из ветки 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 — зелёные.
23 KiB
Дневник агента: где я и что дальше
Живой документ. Обновляю по ходу работы, чтобы после автоматического уплотнения контекста ничего не терялось. Коротко: состояние, замеры, следующий шаг, грабли.
Обновлён: 2026-10-23, день.
1. Что просит пользователь (приоритеты)
Ускоренный путь до Sandbox-UX (его слова): «быстрее дойти до того, как смогу спавнить через Q-меню, иметь норм модель игрока, таскать физганом, Weapon всё сделать». Поэтому очередь переставлена (docs/PARITY.md A25):
- A23 оружие (SWEP из Lua + вьюмодель + аттачменты) — сюда же A20 (модель игрока: один и тот же рендер моделей).
- A21 физика взаимодействия: физган (луч → проп → таскание, вращение, заморозка), без полной гибернации.
- A24 спавн: сначала консольные команды на данных из Lua (
gm_spawn,gm_give, режимыgmod_tool), потом окно Q. - A22 Sandbox целиком: undo/cleanup/duplicator.
- Визуальная точность (A10 свет на пиксель, A11–A12 вода, A16 трава/декали, A18 скайбокс) — параллельно, Sandbox не блокирует.
2. Где я сейчас (в работе)
A20/A23, первый срез сделан: модель игрока в мире. Рендер тот же, что у
пропов (статический меш PropMesh), скелет и анимации — следующий срез.
Как включается и как проверять:
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 и ранний выход по совпадению позы —
вероятно, тело «спит» после телепорта). Проверять в интерактиве: заспавнить
арбуз, посмотреть, что он падает на экране.
Найдены и закрыты две поломки по жалобе «физика висит на экране»:
move_propостался со своей копией перезаписи вершин и не запоминал диапазоны — теперь он идёт черезrewrite_instance(замер: было «диапазонов 0» каждый шаг, стало «диапазонов 1 [(39585, 8)]»).- Буфер вершин мира создавался без
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-скиннинг (виден кадром).
Следующий шаг — проигрывание. План, чтобы не искать заново:
PropMesh(илиModelInstance) должен держать данные анимации:.ani(7 МБ) + список блоков + описания анимаций, ссылкойArc, — сейчас они читаются при загрузке модели и выбрасываются;- в движке
pose_model_instance(index, origin, angles, bone_quats: &[[f32;4]]): локальные кватернионы костей в мировые через родителей (world[i] = world[parent] · (T(pos_i) · R(q_i) · T(−pos_i))), затем скиннинг теми же весами (функция скиннинга уже есть — сейчас её ведёт синус); - в клиенте: выбрать секвенцию (
@idle_all_01через последовательность activity), взять кадр по времени (t · fps, циклом), собрать кватернионы костей из дорожек и позватьpose_model_instance; - затем выбор по состоянию (
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,d35efefA13 туман сделан целиком (чтение + шейдер);72bad60A19: подшаг не гасит скорость о склон (35°: 65 → 147);5bdbdbf,e638607A19: замер по углам + найден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_normal0,7 (45,6°),DIST_EPSILON1/32,sv_stepsize18, воздушный потолок желаемой скорости 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+ картовые тесты.