Commit graph

124 commits

Author SHA1 Message Date
26a61b0e7e Слияние feat/viewmodel: посадка вьюмоделей и встроенный weapon_pistol
Some checks are pending
CI / checks (push) Waiting to run
Из ветки feat/viewmodel (32aad74):
- встроенный SWEP weapon_pistol (Slot 1, ClipSize 18, одиночный
  PrimaryAttack, HUD 17/18) — регистрируется только если в ассетах нет
  своего lua/weapons/weapon_pistol.lua (оригинал приоритетнее);
- посадка вьюмодели по модели: таблица right/up/distance/scale/angles
  (w_pistol, v_pistol, c_smg1); ключ — ось ствола: у w_pistol это +Y,
  у v_pistol/c_smg1 — +X; общий view_yaw=-90 ломал флечетган;
- движок: ModelInstance.scale, set_model_instance_scale,
  play_model_animation_oriented / move_model_instance_oriented;
  ViewModelFOV пересчитывается в масштаб, поля SWEP (ViewModelOffset/
  Angles/Scale) перекрывают таблицу;
- слот 1 возвращает пистолет после флечетгана/кулаков, 0 скрывает
  вьюмодель (c_arms без геометрии); тест slot_keys_match_gmod.

Проверено: 28 сьютов зелёные; кадры всех трёх моделей (ближе_near=0,
ствол вперёд), HUD пистолета, переключения.
2026-10-24 21:42:24 +03:00
32aad74eb5 Вьюмодель: посадка по модели и встроенный weapon_pistol
Посадка вьюмодели вынесена в таблицу по модели: у w_pistol ствол вдоль +Y, у v_pistol/c_smg1 — вдоль +X, поэтому общие view_yaw/дистанция не работали. В движке появился масштаб экземпляра и полный доворот модели; для w_pistol/v_pistol/c_smg1 подобраны дистанция, смещение, масштаб и yaw, near-плоскость не режет ни одну модель.

weapon_pistol в HL2 — движковое оружие, его Lua-скрипта в ассетах нет. Добавлен встроенный SWEP (Slot 1/SlotPos 1, 18 патронов, одиночный огонь) с регистрацией в weapons и list.Get("Weapon"); пользовательский lua/weapons/weapon_pistol.lua важнее встроенного. ViewModelFOV и необязательные ViewModelOffset/Angles/Scale из SWEP перекрывают таблицу.

Проверено кадрами на Xvfb: пистолет и флечетган в кадре, ближе_near=0, выстрел 18→17, слот 1 после флечетгана и кулаков возвращает пистолет, c_arms по-прежнему скрывается. Добавлен SOURCE_RS_TEST_SLOT для синтетической клавиши слота (xdotool не доставляет клавиши в SDL-окно на Xvfb).
2026-10-24 21:40:00 +03:00
ed63cb68b8 Физган: дефолты GMod и захват за точку меша
Some checks are pending
CI / checks (push) Waiting to run
- пружина GMod: v = clamp(error / physgun_timeToArrive, physgun_maxSpeed),
  линейная зависимость вместо прежнего разгона;
- дальность 4096 (physgun_maxrange), масса 500, maxAngular 5000;
- трассировка луча до коллайдера, local_offset и удержание за точку на меше;
- поворот колесом вокруг точки захвата с компенсацией omega × r;
- тесты закона скорости, луча и локальной точки; docs/DIARY.
2026-10-24 21:21:37 +03:00
7930f5671f Слияние feat/menu: dirty-снимок Derma, слайдеры, колесо, выравнивание
Some checks are pending
CI / checks (push) Waiting to run
Из ветки feat/menu (1b24f28):
- главный источник лагов — полный снимок Derma раз в 6 кадров (обход дерева,
  DTileLayout по тысячам иконок, команды для обрезанных панелей, сортировка
  детей, InvalidateLayout на hover). Добавлены ui.mark_dirty/ui_dirty/ui_tick,
  отдельный layout_dirty, кэш снимка, culling невидимых поддеревьев, отказ от
  сортировки без ZPos, hover больше не перезапускает раскладку;
  замер: снимок avg 6,4/4,1 мс -> 0,26-0,5 мс, max 84/47 -> 18-27 мс только на
  страховочной пересборке; FPS с меню и без — в пределах разброса;
- слайдеры: dnumberscratch, NoClipping, GetCaretPos/SetCaretPos,
  input.SetCursorPos, локальные координаты OnCursorMoved, MouseCapture;
  проверено на настоящем DNumSlider (5 -> 9,53);
- колесо: всплытие до DScrollPanel/DTree, починен корень DTree (высота
  ChildNodes до VBar), тесты прокрутки;
- выравнивание: surface.GetTextSize по bitmap-метрике, ContentAlignment как в
  GMod, центрирование вкладок и подписей иконок, дерево с отступом;
- иконки: очередь по площади, до 8 за кадр (~8 мс), LRU переиспользуется.

Проверено: 28 сьютов (включая 9 UI-тестов на реальных Derma-классах) зелёные;
кадры menu_fix.png, menu_tree_wheel.png, menu_placeholders.png.
2026-10-24 20:41:03 +03:00
1b24f280d3 Q-меню: dirty-снимок, слайдеры, колесо и выравнивание текста
Снимок Derma пересобирается по dirty-флагу (ввод, таймеры, сеттеры), а не раз в 6 кадров: в покое 0.26-0.5 мс вместо 4-6 мс, без пиков 47-84 мс. Невидимые поддеревья и обрезанные команды не попадают в снимок; сортировка детей без ZPos не аллоцирует; hover не перезапускает раскладку. Меню прогревается на загрузке карты.

Слайдеры: загружается dnumberscratch.lua, добавлены NoClipping/GetCaretPos/SetCaretPos/input.SetCursorPos, OnCursorMoved получает локальные координаты, MouseCapture удерживает перетаскивание; есть fallback DSlider/DNumSlider/DNumberScratch.

Колесо всплывает от иконки/кнопки до DScrollPanel/DTree; починен корень DTree (высота ChildNodes до VBar). Клик/колесо проверяются тестами и отладочными SOURCE_RS_UI_CLICK/WHEEL.

Текст: метрики surface.GetTextSize совпадают с bitmap-рендером, вертикаль по высоте глифа, ContentAlignment как в GMod, вкладки центрируются, дерево слева с отступом, заглушки иконок - две строки по центру с обрезкой.

Иконки: очередь видимых моделей по площади, до 8/~8 мс за кадр, LRU-кэш переиспользуется.
2026-10-24 20:37:18 +03:00
21abe0d4b7 Слияние feat/sandbox: физган, freeze и undo
Some checks are pending
CI / checks (push) Waiting to run
Из ветки feat/sandbox (8c5b852):
- физган как отдельный инструмент (F): захват ЛКМ по лучу (250 юнитов,
  масса до 500, игрок/статика не берутся), удержание через скорость с
  экспоненциальным приближением (не телепорт), гравитация на время захвата
  выключена, колесо — доворот, ПКМ — freeze (fixed↔dynamic), луч из «дула»
  и крест в точке захвата, жёлтый маркер заморозки;
- undo по Z (Shift+Z — redo), стек 32: спавн (тело/сущность убираются, меш
  уводится под карту), перемещение/поворот, заморозка; новое действие
  сбрасывает redo; World::restore_prop возвращает проп;
- бинды/HUD: G спавн, Q меню, Z/Shift+Z undo/redo, F физган, V noclip,
  0-9 оружие; подсказка в две строки, строка режима с дистанцией и undo;
- конфликты с feat/models разрешены: сохранены и подъём пропов до опоры
  (lift_spawned_body), и учёт масс тел (masses) для физгана.

Проверено: 28 сьютов зелёные; кадры физгана (луч/HOLD/заморозка), undo/redo
спавна (alive 10→9→10), клавиши через xdotool.
2026-10-24 20:31:07 +03:00
8c5b852613 Sandbox: физган, freeze и undo
- физган отдельным режимом F: захват лучом, плавное удержание скоростью,
  поворот колесом, freeze по ПКМ, дальность 250 и масса до 500
- луч/крест в отладочном слое и жёлтый маркер замороженного пропа
- undo-стек на 32 действия (Z/Shift+Z): спавн, перемещение, freeze
- World::remove_prop/restore_prop: удаление тела и сущности, скрытие меша
- env-проверки SOURCE_RS_TEST_PHYSGUN/UNDO/REDO, подсказки HUD
- тесты физики/мира, обновлены docs/DIARY и docs/PARITY
2026-10-24 20:27:04 +03:00
47bc9a75ef Модели: локальные индексы вершин VTX и опора пропов
Some checks are pending
CI / checks (push) Waiting to run
- MDL: разбор mstudiomodel_t/mstudiomesh_t (vertex_offset, material,
  vertex_index); VTX origMeshVertID складывается со смещением модели и
  меша, а не читается как глобальный индекс VVD. Это чинит «взрывы»
  второго меша у v_pistol/c_smg1 и многоматериальных пропов.
- Материал меша берётся из MDL; пустой список замен VTX больше не
  выдаётся за индекс материала.
- Тест-страж real_model_meshes_are_coherent: вырожденные треугольники,
  максимальное ребро и диапазон вершин своего меша.
- Физика: динамический проп при спавне поднимается до статической опоры;
  real_map_props_rest_on_ground меряет проникновение лучом без своего тела.
2026-10-24 20:21:08 +03:00
58daab03be Q-меню: 3D-иконки моделей, светлый скин и клиппинг
Some checks are pending
CI / checks (push) Waiting to run
2026-10-24 18:21:41 +03:00
30fe9dfec1 Клиент: настоящее Q-меню Sandbox из Lua
Some checks are pending
CI / checks (push) Waiting to run
В crates/client подключён UI-слой из glua: после Lua-инициализации
вызывается load_ui, и если OnGamemodeLoaded создал g_SpawnMenu, Q
открывает настоящее меню Sandbox, иначе остаётся qmenu.rs.

Рендер: ui_overlay переводит UiCommand в push_rect/push_text, цвета
sRGB→linear, бюджет глифов и отсечение за экраном. push_rect теперь
точный квад, а не растянутый глиф: тонкие линии дерева больше не
превращаются в квадраты.

Ввод: Q/Esc, мышь и колесо уходят в ui_mouse, игрок и камера стоят,
ЛКМ не стреляет; ui_set_screen на старте и resize. На Android добавлена
кнопка MENU и перевод касаний в пиксельные координаты.

Спавн: RunConsoleCommand складывает вызовы в очередь; клиент исполняет
gm_spawn (и gm_spawnsent/gm_spawnswep) обычным спавном пропа на полу
перед игроком.

Попутно: ui_snapshot_into переиспользует буфер, ui_take_errors отдаёт
ошибки панелей в лог; в VGUI-слое добавлены HasChildren/ChildrenSize/
HasFocus/GetTextSize/DrawTextEntryText, без которых сетка иконок имела
высоту 0 и клики не попадали.
2026-10-24 16:02:44 +03:00
ffd9e736bc Derma: настоящее Q-меню Sandbox на Lua поверх VGUI-слоя
Some checks are pending
CI / checks (push) Waiting to run
В crates/lua добавлен минимальный слой VGUI/Derma: панели-таблицы,
vgui.Register/Create, surface/draw с записью команд, ввод и снимок кадра
(до 4096 команд, глубина 32). На нём грузятся настоящие lua/derma,
lua/skins/default.lua, lua/vgui/* и lua/includes/modules/spawnmenu.lua,
затем Sandbox и OnGamemodeLoaded строят g_SpawnMenu.

Публичный контракт: UiKind/UiCommand, LuaRuntime::ui_toggle/ui_visible/
ui_snapshot/ui_mouse/ui_set_screen, load_ui -> UiLoad.

Попутно: file.Find в GMod-семантике (файлы и каталоги, маски *.txt),
DEFINE_BASECLASS -> local BaseClass, //-комментарии, регистронезависимый
include, SpawnableEntities хранит таблицу ENT.

Тесты на реальных ассетах: ~991 команда, вкладки/категории/модели,
клик по вкладке меняет снимок; без ассетов пропуск.
2026-10-24 15:03:32 +03:00
8aff0caf12 Слияние feat/qmenu: Q-меню спавна и SWEP в клиенте
Some checks are pending
CI / checks (push) Waiting to run
- map.rs: объединены ветки ввода. Пока меню открыто, Esc/Q/цифры/ЛКМ
  принадлежат меню (клик выбирает предмет, игрок стоит), иначе работают
  SWEP-атака и физган; отпускание ЛКМ снимает primary_up у SWEP и не
  трогает физган при открытом меню. Спавн из меню и по G идёт общим
  SpawnRequest с досборкой рендерера. HUD показывает активное оружие и
  подсказку с Q; вьюмодель SWEP сохранена.
- lua/lib.rs: сохранены и API SWEP (weapon_number, weapon_bool,
  set_active_weapon, cur_time, weapon_field, weapon_table), и ENT-API
  (entity_string, entity_bool), и лексер/препроцессор continue
  (Token, preprocess_continue, is_statement_continue, read_script),
  и find_model_literal для спавн-меню.
- render: push_rect и сплошной глиф для заливки оверлея.

Проверено: cargo fmt, clippy --workspace --all-targets -D warnings,
cargo test --workspace; glua с ассетами — оружия 3, энтити 6, ошибок 0;
engine world_real — 21 тест; кадры Q-меню (арбуз заспавнен перед камерой)
и SWEP (weapon_fists в HUD). При открытом меню ЛКМ не стреляет, после
закрытия ЛКМ вызывает attack: weapon_fists.
2026-10-24 13:45:44 +03:00
75cfbba057 Q-меню спавна: список из Lua и spawnlist, сетка, спавн на полу 2026-10-24 13:34:24 +03:00
210182a940 Слияние feat/swep: реестр оружия в клиенте, слоты, атака, HUD
Some checks are pending
CI / checks (push) Waiting to run
Из ветки feat/swep (a46e4d8, 1fe36b6, 5a8f971):
- выбор SWEP из list.Get('Weapon') (фильтр Spawnable + существующая
  ViewModel), слоты 1..9, SOURCE_RS_SWEP для старта, Holster/Deploy при смене,
  вьюмодель из SWEP.ViewModel (SOURCE_RS_VIEW_MODEL — override);
- ЛКМ → PrimaryAttack через call_weapon_method, автоматический/одиночный огонь,
  Think каждый кадр; без SWEP — прежние физган и fire-анимация;
- HUD: PrintName и патроны Clip1/ClipSize;
- заглушки GMod-API под реальные скрипты: CurTime, IsValid, NULL, player.GetAll,
  util.TraceLine/TraceHull/TraceEntity/TracePhysics, trace.*, sound.*/EmitSound,
  константы PLAYER_ATTACK*/ACT_VM_*/MASK_*, база SWEP (GetOwner/GetViewModel/
  NetworkVar/SetNextPrimaryFire/SetHoldType/Clip1/EmitSound/SendWeaponAnim/
  SequenceDuration/ShootEffects/Remove), game.SinglePlayer как функция,
  GetConVar для sv_defaultdeployspeed/phys_pushscale;
- тест real_gmod_weapon_methods_run_without_errors прогоняет настоящие
  weapon_fists/flechettegun/medkit (SetupDataTables..Holster) без ошибок Lua.

Конфликт в crates/lua/tests/scripts.rs разрешён: сохранены тесты include и
геймода из feat/lua-pre и добавлен тест SWEP из feat/swep.
Проверено: 27 сьютов, 46+4+4 теста glua зелёные.
2026-10-24 13:33:50 +03:00
5a8f971f78 Дневник: SWEP в клиенте и что осталось
Записан первый срез A23: реестр/слоты, атака, HUD, заглушки Lua,
проверка кадрами; отмечены c_arms без геометрии и общая проекция
вьюмодели как следующие шаги.
2026-10-24 13:29:46 +03:00
0b82cd77f3 Lua: continue, include и загрузка геймода
- Препроцессор GMod: continue внутри for/while/repeat -> goto + уникальная
  метка; учтены строки/комментарии, вложенные циклы и функции, завершающие
  break/return (оборачиваются в do ... end). widget_bones.lua грузится:
  энтити 6 из 6.
- include(): поиск рядом с текущим файлом и в lua/ игры, тот же препроцессор,
  лимиты размера/глубины, защита от циклов и выхода за каталог игры.
- LuaRuntime::load_gamemode(): init.lua и cl_init.lua, сбор ошибок без
  падения. Sandbox init.lua выполняется, cl_init.lua упирается в VGUI
  (AccessorFunc); реестры list/scripted_ents после загрузки целы.
- Безопасные заглушки загрузочного слоя Sandbox: CLIENT/SERVER, is*,
  FindMetaTable, cleanup, drive, duplicator, net, cvars, player_manager.
- Тесты: юнит-тесты препроцессора, include на временных файлах и реальных
  lua/includes, загрузка sandbox на ассетах.
2026-10-24 13:29:30 +03:00
811be825e2 Дневник: декали сделаны
Some checks are pending
CI / checks (push) Waiting to run
2026-10-24 11:50:54 +03:00
b564de5b25 Зеркало func_reflective_glass: отражённая камера и клип по плоскости
Some checks are pending
CI / checks (push) Waiting to run
- сборка мира: модель зеркала помечается материалом-меткой `<mirror>` до
  резолва материала (у reflectiveglass001 нет VTF, иначе грань выпадала),
  из её граней строится плоскость MirrorPlane (origin, normal);
- рендер: проход отражённой камерой в текстуру Rgba16Float, клип по плоскости
  зеркала (vec4 в uniform камеры, discard в шейдере), грань зеркала сэмплирует
  отражение по экранным координатам;
- отражение привязано на слот лайтмапы (группа 3) отдельным bind group:
  у устройства max_bind_groups = 4, пятая группа не создаётся;
- обычный проход пропускает зеркальные батчи (иначе чекер-заглушка
  перекрывала отражение), зеркало рисуется после непрозрачного мира;
- найден и исправлен пропуск слота `sun` в Camera у world.wgsl (из-за него
  fog_color читался со сдвигом).

Проверено: 25 сьютов и 29 картовых тестов зелёные; UV-градиент на грани
подтвердил плоскость и шейдер, отражение показывает стену и потолок.
2026-10-23 19:26:00 +03:00
bb35f6f0c3 Цветокор карты из игры (LUT 32³) + замеры визуальных сущностей
Some checks are pending
CI / checks (push) Waiting to run
- PostProcess: 3D-текстура LUT 32³ Rgba8Unorm (по умолчанию тождественная),
  сэмплер, биндинги 4/5 в композите; set_color_lut принимает raw Source
  (32³ RGB, красный меняется быстрее всего) и включает флаг use_lut.
- post.wgsl: после ACES цвет переводится в sRGB, сэмплируется LUT и
  возвращается в линейный (поверхность sRGB-ная).
- Клиент: читает materials/colorcorrection/<карта>.raw из VFS; для
  gm_construct файл есть. Отключение — SOURCE_RS_NO_COLORCORRECTION=1.
- Замер: gm_construct.raw нейтральный (0/66/132/195/255 по диагонали), то есть
  грейда у карты нет by design — цветокор включён, но визуально не меняет.
- Тест real_map_visual_entities: на карте есть func_reflective_glass (зеркало),
  env_tonemap_controller, 19 infodecal, env_sky/tonemap — записано в дневник.

Все сьюты зелёные.
2026-10-23 18:14:17 +03:00
059d3da8f4 Дневник: самодостаточный APK проверен с чистого листа (распаковка + карта) 2026-10-23 17:10:23 +03:00
075fed97d5 Документация: встроенные ассеты в APK — замер и инструкция 2026-10-23 17:04:55 +03:00
35df3442fd Дневник: проверка Android-сборки после слияния (эмулятор API 35)
Some checks are pending
CI / checks (push) Waiting to run
2026-10-23 14:41:24 +03:00
70fe4eba69 Дневник: итог сессии — все три ветки слиты, состояние зелёное
Some checks are pending
CI / checks (push) Waiting to run
2026-10-23 14:32:08 +03:00
1f42bb9628 Слияние feat/hdr: попиксельная лайтмапа, HDR, мягкие тени
Some checks are pending
CI / checks (push) Waiting to run
Из ветки feat/hdr (dbecb3c, 1a34203, 74db5f5, dec8b67):
- свет по полигонам: LUMP_LIGHTING раскладывается в атлас Rgba16Float
  (полочная упаковка, рамка 1 тексель), у вершины появился lightmap_uv,
  шейдер мира читает атлас на каждый пиксель с билинейной фильтрацией.
  На gm_construct: 6891 грань, 751 718 люкселей, атлас 2048² (32 МиБ).
  Фолбэк на старый вершинный свет, если атлас не собрался;
- HDR: мир/небо/вода в Rgba16Float, композит экспозиция → ACES → bloom
  (порог в ½ разрешения, два прохода, аддитивно), UI отдельно в sRGB;
- тени: PCF 9 отсчётов (центр + два кольца) с поворотом по мировой позиции
  и нормальным смещением, без acne;
- переключатели SOURCE_RS_NO_HDR/EXPOSURE/BLOOM/BLOOM_THRESHOLD/
  DEBUG_SHADOW/NO_SHADOW/NO_VSYNC/LOG_FPS;
- замер A/B: до правок ~24,5 fps, с HDR ~23,7, без HDR ~24,4 — разница в
  пределах разброса; узкое место — ~2661 draw call на проход, не HDR;
- тесты lightmap_atlas_maps_face_block и half_conversion_known_values.

Конфликт был только в docs/DIARY.md (взята секция из ветки).
Проверено: 25 сьютов, 29 картовых тестов, 15 world_real — зелёные; кадр
туннеля показывает плавный градиент вместо ступеней по полигонам.
2026-10-23 14:30:13 +03:00
dec8b67f54 docs: точный A/B замер HDR; диагностика SOURCE_RS_NO_SHADOW
Прогон на коммите до правок с той же инструментацией дал 23,6–26,5 fps;
ветка с HDR — 22,1–26,4, NO_HDR — 23,0–25,7. Разница в пределах разброса,
а не 3% регрессии из прошлого замера (тот шёл параллельно с другим клиентом).
Плюс переключатель SOURCE_RS_NO_SHADOW: нулевая матрица теней для замера их
стоимости (на 320×180 разницы нет — PCF не узкое место).
2026-10-23 14:24:16 +03:00
2edea0125f Слияние feat/android: APK под Android 12+, сенсорное управление
Some checks are pending
CI / checks (push) Waiting to run
Из ветки feat/android:
- клиент разделён на lib.rs (общий запуск) и main.rs; на Android собирается
  cdylib с экспортом SDL_main, паника ловится catch_unwind на FFI-границе;
- steamworks и сетевая команда отключены для Android, Lua — 5.4 vendored
  (LuaJIT не кросс-собирается), на десктопе остался LuaJIT;
- SDL3 собирается из исходников, Java-обвязка SDLActivity + MainActivity
  (ландшафт, путь к ассетам через getExternalFilesDir);
- сенсорное управление: левый стик — ходьба, правая половина — обзор,
  кнопки прыжка/приседа; короткий тап не теряется между кадрами;
- android/build.sh собирает APK без Gradle (cargo + aapt2 + d8 + zipalign +
  apksigner), поддерживает ANDROID_ABI=x86_64;
- docs/ANDROID.md и android/README.md.

APK: target/android/gmod-rs-debug.apk (arm64-v8a, minSdk 31, targetSdk 35).
Проверено на эмуляторе API 35 x86_64: установка, загрузка gm_construct,
стик, свайп-обзор, прыжок. Десктопные 25 сьютов и clippy — зелёные.
2026-10-23 14:23:42 +03:00
8d572b8190 Android: ландшафт, HUD над системными панелями, прыжок по тапу
- MainActivity фиксирует ландшафт (SDL перебивал манифест FULL_USER) и
  передаёт путь к ассетам через getExternalFilesDir;
- короткий тап по JUMP не теряется между кадрами (jump_pending);
- HUD сдвинут внутрь от статус-бара и нижней панели;
- логгер на Android собирается без неиспользуемой ветки.
2026-10-23 14:18:45 +03:00
dbecb3c6c5 feat(render): попиксельная лайтмапа, HDR и мягкие тени
Свет больше не интерполируется по вершинам: LUMP_LIGHTING раскладывается в
атлас Rgba16Float (рамка 1 тексель на блок), в вершине появился lightmap_uv,
шейдер мира читает атлас на каждый пиксель с билинейной фильтрацией. Это
убирает перелом света по рёбрам брашей (A10). Для карт без атласа остаётся
вершинный путь fs_main_vertex.

Мир, небо и вода рисуются в Rgba16Float; затем экспозиция, ACES и bloom
(порог в ½ разрешения, два прохода размытия, аддитивно). UI и диагностика
идут отдельным проходом в sRGB-поверхность. Переключатели: SOURCE_RS_NO_HDR,
SOURCE_RS_EXPOSURE, SOURCE_RS_BLOOM, SOURCE_RS_BLOOM_THRESHOLD.

PCF теней: 9 отсчётов с поворотом по мировой позиции и нормальным смещением,
режим SOURCE_RS_DEBUG_SHADOW. Плюс SOURCE_RS_NO_VSYNC и SOURCE_RS_LOG_FPS для
замеров.

Тесты: lightmap_atlas_maps_face_block, half_conversion_known_values; все
прежние сьюты зелёные. Замер E11: ~22,1 fps до, ~21,0 с HDR (~3% на пост),
NO_HDR ~21,7.
2026-10-23 14:10:58 +03:00
3de8b8945f Слияние feat/anim: RAW-дорожки, анимации по состоянию, вьюмодель
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 — зелёные.
2026-10-23 14:08:03 +03:00
d42d6464f6 A26: постоянные дорожки RAW и выбор анимации по состоянию
- formats: Quaternion48/Quaternion64/Vector48 (RAWROT, RAWROT2, RAWPOS),
  приоритет постоянных значений в frame_pose;
- engine::animation: выбор idle/walk/run/crouch/jump по скорости, приседу,
  земле и восьми направлениям относительно взгляда, сохранение фазы walk↔run;
- клиент: модель игрока играет выбранное имя, вьюмодель качается на ходу,
  отстаёт при повороте и проигрывает fire по ЛКМ; центр анимированной позы
  компенсирует RAWPOS корня;
- тесты: RAW на v_pistol/m_anm, имена и проигрывание секвенций, чистый выбор
  и фаза; docs A26 обновлены.
2026-10-23 14:02:05 +03:00
b52da2ed2e Коллизия пропов: смещение коллайдера от центра модели
Some checks are pending
CI / checks (push) Waiting to run
Коробка-коллайдер ставилась от нуля модели, хотя геометрия часто смещена
(у куклы центр AABB на 13,5 юнита выше нуля, у стола — 9,5 вбок по X). Из-за
этого предметы висели на невидимых выступах: их держала коробка, сдвинутая
относительно меша (жалоба пользователя, кадр с парящими столом и куклой).

- Physics::add_prop_offset — смещение коллайдера в координатах модели,
  поворачивается вместе с телом (Rapier ColliderBuilder::translation);
- оба места создания тел (пропы карты и spawn_prop) передают bounds_center;
- World::prop_collision_boxes — коробки для коллизии игрока с учётом центра и
  поворота (AABB описанной коробки по модулю матрицы поворота); клиент больше
  не собирает их вручную;
- тест physics::prop_collider_honours_model_center_offset — без смещения проп
  висит на 30+ юнитов выше пола, со смещением низ ложится на пол;
- тест real_map_props_rest_on_ground — после 10 с физики низ коробки каждого
  пропа на опоре (кукла лежит краем на столе, центровой луч этого не видит).

Все сьюты зелёные.
2026-10-23 13:46:39 +03:00
c1b35b6263 Дневник: сессия по жалобам — вьюмодель, спавн, HDR/Android/анимации в работе 2026-10-23 13:23:59 +03:00
8a5f43dcd2 A26: кадры анимации играют — модель игрока и вьюмодель
Some checks failed
CI / checks (push) Has been cancelled
Спека декодера взята из настоящего bone_setup.cpp (Mapbase-зеркало SDK 2013).
Прошлые догадки были неверны, и это зафиксировано в docs/PARITY.md: каналы
дорожек — не компоненты кватерниона, а углы Эйлера (raw · rotscale + поза
покоя, затем AngleQuaternion); позиции — raw · posscale + bone.pos; значения
блоков не накапливаются дельтами (ExtractAnimValue идёт по блокам valid+1
записей, внутри блока value[k], дальше повтор последнего); флаг DELTA означает
относительные каналы без позы покоя.

Замеры на живых данных, три теста в tests/world_real.rs:
- @taunt_muscle_base (451 кадр, 53 дорожки): углы ≤ 4,75 рад, сдвиги позиций
  ≤ 28,5 юнитов, кватернионы единичные — при новой спеке нарушений нет;
- кости локальны родителю (цепочка 4,0 / 3,5 / 8,9), корень — таз на 38,6;
- police.mdl: 57 костей, своя анимация @ragdoll лежит в самом MDL
  (anim_block == 0); m_anm.mdl: 1563 анимации в models/m_anm.ani (7,1 МБ);
- модель игрока, idle_all_01: из 3147 вершин 1101 сдвинулась между кадрами 0 и
  30, высота тела 61 юнит и в покое, и в анимации — не схлопывается.

Движок: AnimData (буферы кадров под Arc), frame_pose, play_model_animation
(мировые позы через родителей, A·B⁻¹ по костям, скиннинг весами вершин);
кости меша ищутся в скелете анимации по именам — скелеты совпадают по именам,
но не по числу костей. Кадры читаются и из .ani, и из самого MDL.

Клиент: вьюмодель играет idle, модель игрока — idle_all_01; добавлен вид со
стороны --third-person (иначе свою модель не увидеть: в первом лице камера
внутри неё). Map(Box<MapArgs>) — против clippy::large_enum_variant.

Все 23 сьюта и 29 картовых тестов зелёные.
2026-10-17 12:37:45 +03:00
49ff6da4ac Дневник: точный план проигрывания анимаций
Some checks are pending
CI / checks (push) Waiting to run
Записан следующий шаг A26, чтобы не искать заново: данные анимации (.ani 7 МБ,
блоки, описания) должны жить в меше ссылкой Arc (сейчас читаются при загрузке и
выбрасываются); в движке появляется pose_model_instance с локальными
кватернионами костей, собранными через родителей; в клиенте — выбор секвенции,
кадр по времени (t · fps, цикл) и сборка кватернионов из дорожек; затем выбор по
состоянию ACT_MP_* с блендингом и снятие синуса.
2026-10-17 12:14:44 +03:00
d92b962b36 A26: замер нормировки канала поворота (unit-критерий)
Some checks are pending
CI / checks (push) Waiting to run
Кватернион требует, чтобы модуль вектора поворота не превосходил единицу у всех
костей и кадров — по этому критерию и выбран путь. Сырые значения @idle_all_01:
кость 0 — [43, 1424, 17013] при rot_scale [6,37e−5, 4,66e−5, 9,59e−5].

Умножение на rot_scale даёт 17013 · 9,59e−5 = 1,63 — больше единицы, для
кватерниона невозможно, значит rot_scale не множитель. Деление на 32768 даёт
0,52 — годится.

Записано в docs/PARITY.md A26 как первая задача следующего шага: выяснить точную
нормировку канала (похоже, rot_scale задаёт сжатие, обратное ≈1,5e4…2,1e4 по
осям), собрать кватернион с w = √(1 − x² − y² − z²) и перейти к позам костей по
родителям.

Все 23 сьюта зелёные.
2026-10-17 12:08:48 +03:00
2155bcee54 A26: декодер кадров анимации (mstudioanim_t), проверен на данных GMod
Some checks are pending
CI / checks (push) Waiting to run
Спека взята из studio.h SDK 2013 (112 КБ, скачан как спецификация): mstudioanim_t
— четыре байта (bone, flags, nextoffset), дорожки связаны списком до
nextoffset == 0; mstudioanim_valueptr_t — три смещения каналов; значения
mstudioanimvalue_t идут дельтами с накоплением (valid, total); флаги RAWROT
(Quaternion48), RAWROT2 (Quaternion64), ANIMROT/ANIMPOS (дорожки), DELTA.

Написаны Mdl::animation_tracks и Mdl::decode_track и прогнаны на настоящей
библиотеке GMod: анимация @idle_all_01 — 280 кадров, 51 дорожка, значения вида
17013, 1424, −874 — это шорты, которые при сборке кватерниона делятся на
масштаб кости.

Все 23 сьюта зелёные; docs/PARITY.md A26 обновлён.
2026-10-17 11:51:00 +03:00
2c7ec501e2 A26: чтение анимационного файла .ani (кадры)
Some checks are pending
CI / checks (push) Waiting to run
formats: mdl.animations() — описания анимаций (mstudioanimdesc_t) с определением
размера записи по данным (как у секвенций); anim_block_names() — имена
анимационных файлов; anim_blocks() — границы блоков внутри .ani.

Замеры: у m_anm.mdl 1563 анимации (@reference 101 кадр, @idle_all_01 280,
@idle_all_02 151, все 30 fps), 247 блоков, и ВСЕ имеют anim_block > 0 — то есть
кадры не в MDL, а в отдельном файле. Файл models/m_anm.ani прочитан целиком:
7,1 МБ. В конце таблицы имён блоков есть пустая запись — чтение по ней падает и
пишет предупреждение, это безвредно.

Дальше: декодирование mstudioanim_t (6 каналов на кость, дельты с накоплением,
повороты кватернионом), позы костей с учётом родителей, интерполяция между
кадрами и выбор секвенции по состоянию.

Все 23 сьюта зелёные; docs/PARITY.md A26 обновлён.
2026-10-17 11:40:57 +03:00
3c0bef5169 A26: слияние включённых моделей — у игрока 465 секвенций
Some checks are pending
CI / checks (push) Waiting to run
police.mdl включает models/m_anm.mdl (библиотека анимаций GMod). Включённая
модель грузится лёгким путём: Mdl::parse + секвенции, без геометрии и текстур —
у анимационной библиотеки их нет. Замеры: у m_anm.mdl numlocalseq=464,
numlocalanim=1563, 56 костей; после слияния у модели игрока 465 секвенций
(была одна «ragdoll»).

Находка по пути: у «единичных» (unified) моделей GMod запись секвенции длиннее,
чем у моделей карты, и мой первый набор кандидатов (184..208) не подошёл —
разбор молча возвращал ноль. Перебор расширен до 176..320 с обязательной
проверкой имён ВСЕХ секвенций: иначе шаг не принимается.

Все 23 сьюта зелёные; docs/PARITY.md A26 обновлён.
2026-10-17 11:32:37 +03:00
50ab90debc A26 шаг в: CPU-скиннинг — модель двигается на экране
Some checks are pending
CI / checks (push) Waiting to run
PropMesh теперь несёт скелет и привязку вершин, а World::skin_model_instance
пересчитывает вершины по позе костей: угол кости складывается с родительскими,
вершина смешивается по трём весам, результат пишется в общий буфер и помечается
диапазоном для заливки на GPU — тем же механизмом moved_ranges, что возят
движущиеся пропы.

Проба подключена к вьюмодели: одна кость пистолета качается по синусу.
Проверено кадром — геометрия visibly деформируется, то есть цепочка «скелет →
веса → вершины → GPU» работает целиком. Это пока проба, а не анимация:
настоящие кадры из .ani следующим шагом.

Все 23 сьюта зелёные; docs/PARITY.md A26 обновлён.
2026-10-17 11:25:15 +03:00
f8db4cf73c A26 шаг б: кости и веса вершин в модели
Some checks are pending
CI / checks (push) Waiting to run
Model теперь несёт skins: Vec<VertexSkin> — привязку вершин к костям из VVD
(до трёх костей с весами и их число), по одной записи на вершину. Индексы
глобальные (после fixup VTX), считаться по ним будет матрица скиннинга вместе
со скелетом Model::bones.

Замер на живых моделях (лог «модель загружена»): companion_doll 1471 вершин /
1471 привязок, стол 136/136, диван 556/556 — привязка есть у всех вершин; у
пропов при этом один корневой узел (bones=1). Модель игрока police.mdl — 57
костей.

Все 23 сьюта зелёные; docs/PARITY.md A26 обновлён.
2026-10-17 11:19:21 +03:00
d7796974b1 A26 шаг а: разбор секвенций анимации (mstudioseqdesc_t)
Some checks are pending
CI / checks (push) Waiting to run
Размер записи секвенции версионно-зависим (184…208 байт), поэтому шаг берётся
не на глаз: перебираются кандидаты, и выбирается тот, у которого у ВСЕХ
секвенций имя читается непустой печатной строкой. Модель движка теперь несёт
bones и sequences.

Замеры на живых моделях: пропы карты — bones=1, sequences=1, «idle»; модель
игрока models/player/police.mdl — bones=57, sequences=1 («ragdoll»). Второе не
ошибка: у игроков активности лежат во включённых моделях (mdl.include_models()),
их надо сливать — записано следующим шагом.

Все 23 сьюта зелёные; обновлён docs/PARITY.md A26.
2026-10-17 11:05:09 +03:00
46677edbaa A26: план анимаций моделей (скелет и веса уже читаются)
Some checks are pending
CI / checks (push) Waiting to run
Просьба пользователя: «по возможности делай анимации». Записан план (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
de126d0784 A23: вьюмодель в руках — заработала
Some checks are pending
CI / checks (push) Waiting to run
Первый срез вьюмодели: модель оружия на том же рендере, что пропы и модель
игрока. Не рисовалась по двум причинам, обе найдены кадром:

1. разворот на 180° (нужный модели игрока) уводил ствол ЗА камеру: у моделей
   оружия Source геометрия вынесена вперёд по оси модели (у пистолета ~47
   юнитов, у монтировки −52 по Y);
2. привязка шла к origin модели, а надо к центру её габарита: у пистолета центр
   ≈(0, 0, 47), поэтому ствол уезжал выше кадра. Теперь смещение считается как
   `глаз + 20 вперёд + 6 вправо − 10 вниз − повёрнутый центр габарита`.

Проверено кадром: `SOURCE_RS_VIEW_MODEL=models/weapons/v_pistol.mdl` — пистолет
стоит в кадре. По умолчанию берётся пистолет: `v_physgun.mdl` в ассетах нет
(это модель GMod, а не HL2), есть откат на монтировку.

Все 23 сьюта и 29 картовых тестов зелёные; разбор и посадка записаны в
docs/DIARY.md.
2026-10-17 10:45:10 +03:00
d700d15416 Дневник: вьюмодель не доходит до отрисовки — записаны факты и порядок проверки
Some checks are pending
CI / checks (push) Waiting to run
Замер: материал вьюмодели в мире — индекс 54, имя crowbar_cyl (виден в
--debug-materials). Рендер одним этим материалом (--only-material 54) даёт
пустой кадр, то есть геометрия и поза ни при чём — не доходит сам батч.

Записано, что проверять дальше: попал ли батч в renderer.batches после
refresh_batches (диапазоны индексов забираются в блоке физики — возможно,
раньше, чем обновление списка), совпадает ли batch.material с позицией
материала в world.materials, и не мешает ли фильтр --only-material.
2026-10-17 07:55:32 +03:00
2043ebf39a Спавн и вьюмодель: рендерер перечитывает батчи, буферы принимают заливку
Some checks are pending
CI / checks (push) Waiting to run
Ещё две поломки на пути «новой геометрии»:

1. Буфер вершин мира создавался без COPY_DST, поэтому точечная заливка
диапазонов падала с ошибкой wgpu (у пользователя это уронило бы клиент —
поймано прогоном, а не тестом). Теперь VERTEX | COPY_DST, для индексов
INDEX | COPY_DST, добавлен upload_index_ranges.

2. Рендерер копировал список батчей при сборке, поэтому добавленная геометрия
(спавн) в кадр не попадала до полной пересборки. Добавлен
WorldRenderer::refresh_batches: когда появились новые индексы, список батчей и
признаки воды перечитываются, а полная пересборка нужна только при новой
текстуре.

Вьюмодель (A23, первый срез): создаётся тем же рендером моделей, следует за
взглядом, включается SOURCE_RS_VIEW_MODEL (по умолчанию физган, откат на
монтировку — v_physgun.mdl в ассетах не нашёлся). В кадре она пока не
появляется: создание работает, а звено «перенос позы каждый кадр + заливка»
нет — записано открытым вопросом в docs/DIARY.md.

Все 23 сьюта и 28 картовых тестов зелёные.
2026-10-17 07:53:56 +03:00
c58ad8817e Физика и картинка: вершины движущихся пропов уходят на GPU
Some checks are pending
CI / checks (push) Waiting to run
Жалоба пользователя: «физика вроде работает, но позиция рендера обновляется
только при спавне нового». Причина: мир двигал вершины в памяти, а буфер
рендера заливался один раз при сборке мира — потому пересборка рендерера при
спавне и «лечила» картинку.

Сделано: World::moved_ranges и take_moved_ranges (диапазоны заполняет
rewrite_instance, то есть любое движение пропа, модели игрока или физгана),
WorldRenderer::upload_vertex_ranges — точечная заливка queue.write_buffer по
этим диапазонам, и вызов в кадре клиента сразу после шага физики.

Тест probe_moving_prop_reports_vertex_ranges (диагностика, ignored) пока не
сходится и это записано честно: коробка в тесте задана в мировых координатах,
а меш обязан быть в локальных (видно по двойному смещению вершины), и список
диапазонов после step_physics пуст — проверить is_sleeping у тела после
телепорта. Проверка в интерактиве: заспавнить арбуз и посмотреть, падает ли он
на экране.

Все 23 сьюта и 28 картовых тестов зелёные; разбор в docs/DIARY.md.
2026-10-17 07:33:11 +03:00
03edb27a33 A21/A23: физган — взять проп под прицелом и таскать по взгляду
Some checks are pending
CI / checks (push) Waiting to run
Первый срез взаимодействия с пропами (то, что в GMod делает weapon_physgun):
ЛКМ берёт ближайшее тело под прицелом — луч из глаз, расстояние 8..260 юнитов,
допуск 40 в стороны, побеждает ближайшее; пока кнопка зажата, тело каждый кадр
телепортируется на луч взгляда на взятом расстоянии (углы сохраняются);
отпустил — тело свободно и падает само.

Добавлен переключатель SOURCE_RS_PHYSGUN=1: держит ближайшее тело без мыши —
нужен для проверки скриптом, а не руками (SDL-событий мыши в скрипте нет).

Проверка: код собирается, clippy чистый, все 23 сьюта зелёные. Интерактивно
проверить не смог — в скриптовых кадрах камера упирается в стены (наводить
нечем). Проверять у кучи пропов в районе -2930 -1370 -64: посмотреть на проп,
зажать ЛКМ. Записано в docs/DIARY.md.
2026-10-16 23:53:51 +03:00
35e04409c4 A24: спавн пропа из игры — ядро спавн-меню
Some checks are pending
CI / checks (push) Waiting to run
Движок: геометрия модели вынесена в append_model_geometry (меш, материалы,
вершины с трансформацией, батчи со смещением индексов на вершины экземпляра).
Поверх неё World::spawn_prop — то же, что делает Q-меню GMod: геометрия, сущность
класса prop_physics (её видят Lua и физика), тело в мире физики по габаритам
меша, экземпляр пропа для move_prop.

Клиент: клавиша G — спавн пропа по взгляду (модель из SOURCE_RS_SPAWN_MODEL, по
умолчанию models/props_junk/watermelon01.mdl), и аргумент --spawn <модель> для
отладки и скриптов. Проп сразу попадает в габариты коллизии игрока — их клиент
пересобирает каждый кадр.

Проверено кадром: арбуз заспавнен в 96 юнитах по взгляду и отрисован
(--player-pos="-2438 -966 48", камера у "-2500 -1050 60|5 0"). Грабли записаны
в дневник: буферы рендера собраны на сборке мира, поэтому после спавна рендерер
пересобирается целиком — позже заменим до-аллокацией вершин.
2026-10-16 23:49:43 +03:00
8a2eb4ef60 A20/A23 первый срез: модель игрока в мире
Some checks are pending
CI / checks (push) Waiting to run
Первый срез модели игрока, чтобы не блокировать Sandbox: рендер тот же, что у
пропов (статический меш PropMesh), скелет и анимации следующим срезом.

Движок: ModelInstance (экземпляр модели без сущности) плюс
World::spawn_model_instance (модель с диска, материалы с дедупликацией, вершины
с трансформацией, батчи) и World::move_model_instance; перезапись геометрии
вынесена в общий rewrite_instance, им пользуются и пропы.

Клиент: модель создаётся после разбора --player-pos и едет за игроком (позиция —
ступни, курс — взгляд, развёрнутый на 180°, потому что у моделей Source ось Y
смотрит влево). Включается переменной SOURCE_RS_PLAYER_MODEL: пустое значение —
models/player/police.mdl, иначе путь к модели.

Проверено кадром: модель стоит на площадке в -2438 -966 48, текстуры на месте,
вершин в мире стало 46359 против 43212 без модели. Грабли записаны в дневник:
индексы модели локальные, при вставке в общий буфер к ним прибавляется
first_vertex (без этого кадр превращался в мусор).

Заведён docs/DIARY.md — живой дневник состояния, замеров, инструментов и
грабель, чтобы уплотнение контекста ничего не теряло.
2026-10-16 23:44:39 +03:00
4ad0405ed3 Очередь: система оружия и ускоренный порядок под Sandbox-UX
Some checks are pending
CI / checks (push) Waiting to run
Просьба пользователя: быстрее дойти до спавна из Q-меню, нормальной модели
игрока, физгана и «Weapon всё сделать».

Добавлено:

A23 — система оружия: реестр SWEP из Lua (weapons/*.lua: PrimaryAttack,
SecondaryAttack, Deploy, Holster, Think, Draw), вьюмодель в первом лице на том
же рендере моделей (скелет, скиннинг, аттачменты), переключение по слотам,
заготовка стрельбы через util.TraceLine, gmod_tool и weapon_physgun (выбор
пропа лучом, таскание, вращение, заморозка), gmod_camera и DrawHUD по мере
надобности. Приёмка: взять физган, подцепить и протащить проп; спавнить из
gmod_tool; переключать оружие; видеть вьюмодель.

A24 — Q-меню спавна: сначала консольные эквиваленты (gm_spawn, gm_give, режимы
gmod_tool) на данных из Lua (list.Get('SpawnableEntities')), затем быстрый UI на
Rust, как в P10.

A25 — ускоренный порядок: A23 оружие и вьюмодель, сюда же A20 (модель игрока —
тот же рендер моделей), затем A21 физика взаимодействия (физган без полной
гибернации), затем A24 спавн, затем A22 Sandbox целиком (undo/cleanup/
duplicator); визуальная точность (A10 свет, A11–A12 вода, A16–A18 наполнение)
идёт параллельно и не блокирует.
2026-10-16 23:24:23 +03:00