Commit graph

183 commits

Author SHA1 Message Date
ae36d3aea6 Физган: трение отключается на удержании; найден баг шага физики (скорость /3)
Some checks are pending
CI / checks (push) Waiting to run
Откачен костыль с телепортом: это было лечение симптома.

Что найдено замером (тест body_with_set_velocity_actually_moves, ignored):
тело с заданной скоростью 1200 за шаг едет только 400 — и это НЕ контакт:
воспроизводится без пола, с friction 0 (combine rule Min), gravity_scale 0,
damping 0.000, dt=1/60 до и после шага. Скорость 400 читается уже после
world.step(), то есть шаг физики гасит её ровно втрое. Это и есть причина
отставания пропа при любой пружине.

Плюс сделано по делу: на время удержания у тела выключается трение
(set_body_friction + combine rule Min), на отпускании возвращается 0.6 —
это убирает потерю скорости о пол.

Дальше: разобраться, почему шаг Rapier делит скорость на 3 (подозрение —
настройки integration_parameters/units в нашей обёртке), затем включить
assert в тесте.
2026-10-24 21:58:30 +03:00
41d49cad80 Revert "Физган: жёсткая привязка при большой ошибке (телепорт к лучу)"
This reverts commit 99d50ab63d.
2026-10-24 21:50:16 +03:00
99d50ab63d Физган: жёсткая привязка при большой ошибке (телепорт к лучу)
Some checks are pending
CI / checks (push) Waiting to run
Диагностика показала: команда скорости упирается в 5000, target_v ~1200
(игрок), а error растёт до 1600 — тело физически не успевает (коллизия/
солвер), поэтому пружина бесполезна.

Добавлен порог 256 юнитов: при большей ошибке точка захвата переставляется
на цель (teleport тела), скорость обнуляется, last_target обновляется. Меньше
порога — прежняя линейная пружина. Так проп всегда остаётся у луча, как
жёсткая привязка в GMod.
2026-10-24 21:48:37 +03:00
49bd9f127f Физган: диагностика SOURCE_RS_PHYSGUN_DEBUG (ошибка и скорости раз в 30 кадров)
Some checks are pending
CI / checks (push) Waiting to run
2026-10-24 21:46:01 +03:00
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
bde743aa10 Физган: feed-forward скорости цели — проп летит вместе с игроком
Some checks are pending
CI / checks (push) Waiting to run
Пользователь: «могу улететь далеко от пропа, он просто линейно летит».
Причина: пружина считала скорость только от ошибки (`error / 0.05`), а цель
(точка на луче) едет вместе с игроком. Ошибка за кадр = путь игрока за кадр,
поэтому проп двигался со скоростью ~игрок·(dt/0.05) — на 60 fps втрое
медленнее игрока и безнадёжно отставал.

Добавлен feed-forward: скорость целевой точки (разница с прошлым кадром / dt)
складывается с пружиной и клампится `physgun_maxSpeed`. Теперь проп летит со
скоростью игрока, а пружина выбирает только остаточную ошибку. Поле
`Grab::last_target` хранит прошлую цель.

28 сьютов зелёные.
2026-10-24 21:35:23 +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
3b715dcd65 AGENTS.md: приоритет оригинальным ресурсам GMod (Lua, ассеты, данные карты)
Some checks are pending
CI / checks (push) Waiting to run
2026-10-24 20:43:41 +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
dc10ee1500 Q-меню: кэш снимка Derma (не каждый кадр) и Esc не закрывает игру
Some checks are pending
CI / checks (push) Waiting to run
Пользователь: «от Q-меню ужасно лагает, во время лагов нажал Escape и игра
закрылась».

- снимок панелей (Lua + строки) пересобирается раз в 6 кадров и сразу после
  любого ввода в меню (движение/кнопки/колесо), а не каждый кадр — это и было
  основным источником лагов;
- Esc теперь только закрывает меню; выхода из клиента по Esc больше нет
  (в GMod Esc открывает/закрывает меню, а не завершает игру). Закрыть клиент
  можно кнопкой окна.
2026-10-24 17:32:31 +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
f41bfde690 Lua: поля ENT-скриптов и модель-представитель для спавн-меню 2026-10-24 13:33:34 +03:00
70ee8d9ece Render: сплошной глиф и заливка прямоугольников для оверлеев 2026-10-24 13:33:28 +03:00
5eaa877b37 Merge branch 'feat/lua-pre' 2026-10-24 13:30:32 +03:00
5a8f971f78 Дневник: SWEP в клиенте и что осталось
Записан первый срез A23: реестр/слоты, атака, HUD, заглушки Lua,
проверка кадрами; отмечены c_arms без геометрии и общая проекция
вьюмодели как следующие шаги.
2026-10-24 13:29:46 +03:00
1fe36b6869 Клиент: выбор SWEP, слоты, атака и HUD
- weapon.rs: реестр list.Get("Weapon") с фильтром Spawnable/ViewModel,
  слоты Slot/SlotPos, SOURCE_RS_SWEP, Holster/Deploy при смене;
- map.rs: клавиши 0..9 выбирают слот, вьюмодель пересоздаётся под
  SWEP.ViewModel (SOURCE_RS_VIEW_MODEL остаётся override), ЛКМ вызывает
  PrimaryAttack, автоматический огонь по Primary.Automatic и
  GetNextPrimaryFire, Think каждый кадр;
- HUD: PrintName активного оружия и патроны Clip1/ClipSize;
- SOURCE_RS_TEST_ATTACK и SOURCE_RS_TEST_SWITCH для проверки без мыши;
- ошибка Lua в методах оружия логируется, кадр не падает.
2026-10-24 13:29:41 +03:00
a46e4d827d Lua: база SWEP и GMod-заглушки для оружия
- swep_base.lua: GetOwner/GetViewModel, NetworkVar, SetNextPrimaryFire,
  Clip1, SetHoldType, EmitSound и безопасные заглушки;
- глобальные CurTime, IsValid, player.GetAll (таблица player),
  util.TraceLine/TraceHull, trace.*, sound.*, константы CLIENT/SERVER;
- game.SinglePlayer стал функцией (как в GMod), GetConVar знает
  sv_defaultdeployspeed/phys_pushscale — ноль давал inf в Deploy;
- загрузчик SWEP даёт базовые методы через метатаблицу, инициализирует
  SetupDataTables/Initialize и стартовый магазин;
- модель без геометрии (c_arms) теперь логирует причину, а не молчит;
- тест прогоняет реальные Deploy/PrimaryAttack/Think/Holster трёх SWEP.
2026-10-24 13:29:35 +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
209412ea3d Нуклип: Ctrl замедляет (как в GMod), а не опускает
Some checks are pending
CI / checks (push) Waiting to run
Пользователь: в оригинале Ctrl в ноуклипе замедляет движение. Убрано
`move_up -= 1` по Ctrl; скорость: Shift ×3, Ctrl ×0.35, иначе обычная.
Плюс тесты-сканы карты: поверхности с «зеркальными» материалами (0 в зале) и
сущности рядом с залом — подтверждают, что func_reflective_glass один
(model *6, точка -2064 -2076 -286).
2026-10-24 13:23:42 +03:00
300a6c6d98 Lua: Rust-API к реестрам list/weapons (list_names, call_weapon_method, weapon_string)
Some checks are pending
CI / checks (push) Waiting to run
2026-10-24 13:01:27 +03:00
087d57b582 Lua GMod: библиотеки list/weapons/scripted_ents и загрузка скриптов
Some checks are pending
CI / checks (push) Waiting to run
- библиотека `list` (Set/Add/Get/GetEntry/HasEntry) — реестры GMod, на них
  держатся списки оружия, энтити и спавна;
- `weapons.Register/Get/GetStored` и `scripted_ents.Register/Get`, запись в
  `list.Get("Weapon")` и `list.Get("SpawnableEntities")`;
- загрузчик `LuaRuntime::load_scripts(game_dir)`: выполняет `lua/weapons/*.lua`
  (со свежей `SWEP`, заранее созданными `Primary`/`Secondary`) и
  `lua/entities/*.lua` (с `ENT`), ошибка одного файла не роняет остальные;
  размер файла ограничен 1 МиБ;
- заглушки, без которых скрипты не запускаются: AddCSLuaFile, Model, Sound,
  include, DEFINE_BASECLASS, Material, game.AddParticles; GMod-синтаксис
  проходит через препроцессор (`!` → `not`);
- клиент загружает скрипты при старте: на настоящих ассетах — 3 оружия
  (fists/flechettegun/medkit), 5 энтити; `weapons.Get('weapon_fists')` читает
  Primary.ClipSize и ViewModel.

Тест `real_gmod_weapons_and_entities_load` (пропускается без ассетов).
Осталось: `continue` в препроцессоре (ломает widget_bones), include базовых
классов, привязка SWEP к вьюмодели и Q-меню.

26 сьютов зелёные.
2026-10-24 12:12:29 +03:00
811be825e2 Дневник: декали сделаны
Some checks are pending
CI / checks (push) Waiting to run
2026-10-24 11:50:54 +03:00
b347bc380f Декали infodecal: квады на ближайшей грани и альфа-проход
Some checks are pending
CI / checks (push) Waiting to run
- сборка мира: для каждого infodecal берётся ближайшая плоскость грани (замер:
  у всех 19 декалей расстояние 0), центр проецируется на неё, квадрат строится
  по осям текстуры грани (32×32 юнита), материал — `decals/<имя>`;
- рендер: материалы с префиксом `decals/` идут отдельным проходом с
  ALPHA_BLENDING, без записи глубины, после непрозрачного мира; новый шейдер
  `fs_decal` берёт цвет из обычного освещения, альфу — из текстуры;
- декаль освещается ambient'ом в своей точке (`model_light_at`).
Проверено кадром: граффити с прозрачностью лежит на кирпичной стене.

25 сьютов и 29 картовых тестов зелёные.
2026-10-24 11:50:32 +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
fc2e981bfc Освещение моделей от запечённого света + стабильный каскад теней
Some checks are pending
CI / checks (push) Waiting to run
Пользователь: «диван в светлой комнате тёмный, чуть ближе к свету — светлый,
хотя пол одинаковый» и «свет обновляется резко».

Причины две:
1. Модели вообще не получали свет карты: у них `lightmap_uv` указывал на белый
   тексель атласа, то есть ambient + солнце без привязки к месту. Теперь при
   сборке мира собираются `light_samples` (центр грани + средний свет её блока
   лайтмапы), а модели берут ambient в своей точке с плавным ядром по
   расстоянию (радиус 256, ядро 1/(d²+256)), с нижним порогом 0,35 — одна
   ближайшая грань давала скачки «тёмный/светлый». `lightmap_uv.x < 0` —
   метка шейдеру брать свет из вершины; свет обновляется и при движении
   (rewrite_instance и play_model_animation).
2. Каскад теней был 2600 юнитов вокруг камеры: объект у стены выходил за его
   границу и «вспыхивал» при приближении камеры. Теперь 4200 юнитов и карта
   4096² — вся игровая зона gm_construct попадает в тень.

Все сьюты зелёные.
2026-10-23 18:42:33 +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
8c869e709e Физика: углы Source ↔ кватернион тела совпадают с рендером
Some checks are pending
CI / checks (push) Waiting to run
`quat_from_angles` собирал поворот как Rz(roll)·Ry(yaw)·Rx(pitch), а рендер
(`rotate_model_vector`) — как Rz(yaw)·Ry(pitch)·Rx(roll). Из-за этого меш
двигался по одной ориентации, а тело — по другой: арбуз катился, но выглядел
как юла (жалоба пользователя).

Теперь конверсия в обе стороны точная; добавлен тест
`physics_angles_match_render_rotation` — пять наборов углов, проверка
кватернион→вектор против рендера и обратно.

Плюс тест `real_map_props_stay_stable`: 10 секунд физики на gm_construct —
ни один проп не поднимается выше 20 юнитов и не разгоняется выше 500 ед/с
(«пропы летают» от выталкивания геометрией не подтвердилось — 0 из 4).
2026-10-23 18:06:45 +03:00
4f8a989398 Тест: sort_by_key вместо sort_by (clippy)
Some checks are pending
CI / checks (push) Waiting to run
2026-10-23 18:03:02 +03:00
256cbdc9a0 Вьюмодель: не играть @idle мировой модели (поза брошенного оружия)
Some checks are pending
CI / checks (push) Waiting to run
Пользователь видел пистолет «повёрнутым на 90° влево». Причина: клиент
выбирал для вьюмодели анимацию `idle`, а у `w_pistol` единственная `idle` —
это `@idle` мировой модели, где RAWROT2/RAWPOS задают позу **брошенного на
землю** пистолета (ствол вбок, меш на боку). В руках эта поза не годится.

Теперь для вьюмодели берутся только `idle01`/`idle01empty` (у моделей `v_*`);
если их нет — остаёмся в позе покоя, которую доворачивает `model_yaw` (-90°).

Замер в осях камеры (новый диагностический лог SOURCE_RS_VIEW_DEBUG):
до правки «вперёд 1,7 / вправо 10,6» (ствол вбок), после — «вперёд 10,6 /
вправо 1,7» (ствол вперёд). Кадр подтверждает: пистолет смотрит вперёд, виден
сзади, рукоять вниз.
2026-10-23 18:02:37 +03:00
059d3da8f4 Дневник: самодостаточный APK проверен с чистого листа (распаковка + карта) 2026-10-23 17:10:23 +03:00
0428b3654b fs pack: копировать gameinfo.txt — маркер установки
Some checks are pending
CI / checks (push) Waiting to run
tier::paths::detect признаёт корень ассетов по garrysmod_dir.vpk или
garrysmod/gameinfo.txt. В минимальном наборе нет ни VPK, ни gameinfo, поэтому
клиент с --assets <pack> отвечал «ассеты Garry's Mod не найдены», хотя все
файлы на месте. Теперь fs pack пишет gameinfo.txt из VFS, и распакованный
APK-набор загружается без полной установки.
2026-10-23 17:08:15 +03:00
075fed97d5 Документация: встроенные ассеты в APK — замер и инструкция 2026-10-23 17:04:55 +03:00
d42b581180 fs pack: минимальный набор ассетов карты + встроенные ассеты в APK
Замер: для gm_construct реально читаются 335 файлов из VPK на 27,0 МБ
(277 материалов, 23 модели, 35 Lua-скриптов; крупнейшее — m_anm.ani 6,8 МБ)
плюс карта 35,0 МБ. Итого 62,1 МБ сырых, 31 МБ в tar.gz — против 6,7 ГБ
полной установки. Этого набора достаточно: клиент запускается с ним, карта
и пистолет полностью текстурированы (проверено кадром).

- VirtualFs: запись успешных чтений (start_recording/take_recorded);
- команда `gmod-rs fs pack <карта> --out <каталог>`: собирает мир, грузит
  модели игрока и оружия, автозагрузку Lua и копирует прочитанные файлы в
  структуру garrysmod/... плюс карту — получается самодостаточный корень
  ассетов для `--assets`;
- android/build.sh: если есть android/assets/gmod (или GMOD_RS_ANDROID_ASSETS),
  каталог кладётся в APK как assets/gmod;
- MainActivity: при первом запуске распаковывает assets/gmod в
  files/gmod_server/server, если карты там ещё нет — APK самодостаточен,
  adb push не нужен;
- .gitignore: android/assets/ (ассеты GMod не коммитим).

Все сьюты зелёные.
2026-10-23 17:03:50 +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
b28d6f9f25 Android: паника не проходит через границу SDL_main
catch_unwind вокруг запуска клиента: аддон или драйвер не должны ронять
процесс через FFI-барьер (правило AGENTS.md №2).
2026-10-23 14:21:51 +03:00
8d572b8190 Android: ландшафт, HUD над системными панелями, прыжок по тапу
- MainActivity фиксирует ландшафт (SDL перебивал манифест FULL_USER) и
  передаёт путь к ассетам через getExternalFilesDir;
- короткий тап по JUMP не теряется между кадрами (jump_pending);
- HUD сдвинут внутрь от статус-бара и нижней панели;
- логгер на Android собирается без неиспользуемой ветки.
2026-10-23 14:18:45 +03:00