Commit graph

21 commits

Author SHA1 Message Date
f58eafeb46 Клиент: спавн не падает, вьюмодель на глазах, HUD без отладки
Some checks are pending
CI / checks (push) Waiting to run
Падение wgpu на втором спавне («Copy at offset ... overrunning the bounds»):
буферы GPU создавались под размер мира при загрузке и не росли. Появился
WorldRenderer::ensure_capacity (рост вдвое + перезаливка) и refresh_materials
(bind group для материалов, появившихся после спавна, — иначе новые батчи
молча не рисуются).

Вьюмодель:
- обновляется после player.update и camera.pos, а не до них: раньше она
  отставала на кадр при ходьбе и «плыла» при повороте (жалоба пользователя);
- по умолчанию w_pistol.mdl вместо v_pistol.mdl: v_pistol у нас рисуется
  развёрнутым полотном (2157 вершин, 2 меша — разбираться отдельно), а w_pistol
  выглядит пистолетом;
- курс -90°: у модели ствол смотрит вдоль +Y (влево), а не +X — без доворота
  пистолет лежал поперёк кадра;
- дистанция 26, сдвиг вправо/вниз, анимация idle01 (у w_pistol свои кадры
  в MDL).

bone_world_transforms теперь обходит кости рекурсивно: порядок «родитель
раньше ребёнка» в файлах не гарантирован.

HUD: журнал и отладочные строки скрыты по умолчанию (клавиша `), строка
verts/tris убрана.

Плюс тесты на реальных ассетах: кости вьюмодели не разваливаются при
скиннинге (макс. сдвиг 4 юнита), структура моделей оружия, встроенные кадры.

Все сьюты зелёные, clippy чистый.
2026-10-23 13:20:03 +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
257ac0792a A13: туман карты подключён к рендеру
Some checks are pending
CI / checks (push) Waiting to run
Fog читается при сборке мира (приоритет env_fog_controller, иначе worldspawn),
доля тумана считается линейно от fogstart к fogend с потолком fogmaxdensity —
как в движке. В рендере два новых vec4 в uniform камеры (цвет и параметры),
буфер камеры 112 → 144 байта; world.wgsl смешивает цвет с туманом по
расстоянию до камеры.

Замер на gm_construct: туман есть — цвет (0,557 0,643 0,698), start 1024,
end 250000, max_density 1,0, farz −1. Это дальний туман: на 2000 юнитов доля
≈ 0,4%, поэтому кадр с камеры E11 не изменился (трава 73,2, бетон 116,0,
небо 207,6, тоннель 81,1 — как в E20). Механика подключена и проверена; заметен
туман будет у карт с близким fogend.

Осталось: применить туман к воде и к моделям. Все 23 сьюта и 28 картовых
тестов зелёные. Разбор — docs/PARITY.md E24.
2026-10-16 23:18:12 +03:00
86a6cb63f9 Отладочный слой коллизий: видеть, из чего складывается стена
Some checks are pending
CI / checks (push) Waiting to run
Жалобы «невидимая стена», «стою вписанным в параллелепипед вокруг рампы»,
«на стыках комнат стена, которую можно перепрыгнуть, но не запрыгнуть»
невозможно разобрать по картинке: стена невидима по определению. Теперь
коллизию рядом с игроком можно увидеть.

`SOURCE_RS_DEBUG_COLLISION=<радиус>` (по умолчанию 500) включает слой:

- красное — рёбра самих кирпичей, то есть истинная форма по плоскостям
  (рампа выглядит рампой, а не ящиком);
- жёлтое — рёбра габаритов: именно их интервалы берёт `penetration`, поэтому
  расхождение красного с жёлтым и есть «параллелепипед вокруг рампы»;
- голубое — треугольники коллизии рельефа;
- зелёное — тело игрока.

Слой рисуется поверх мира без участия глубины (иначе невидимую стену не
отличить от настоящей) и работает как в живом окне, так и в кадрах пролёта.
Кирипчи строятся из пересечений пар граней, обрезанных остальными
полупространствами: `Brush::vertices_of` разделён на `segments_of`, и рёбра
(core) отдаёт новый `Brush::edges`.

Замеры, сделанные этим слоем и `probe_ramp_movement` (тоже в этом коммите):
в точке игрока `-2438 -966 48` (рампа #1891, 9 граней, 448x128x204 со
склоном) залипания при движении в −Y и по диагоналям объясняются настоящей
кирпичной стеной: кирпич #829 стоит по `y = -1024` и она видна на кадре.
Фантомного выталкивания в этой точке нет. Значит источник «невидимых стен»
в другом месте, и искать его надо обходом с сеткой по всей карте, а не по
центру: старая сетка обхода (x −1500..600, y −800..600) до запада и юга не
доходит вовсе.

В список дефектов (docs/PARITY.md, часть A) добавлены A9..A14: невидимый
параллелепипед у рамп и стыков, перелом света по полигонам (лайтмапа
берётся в вершинах, а не на пиксель), две беды воды (прозрачность слева и
заливка справа, мелкие одинаковые по Y волны), туман и присед без смены
скорости.
2026-10-16 18:52:33 +03:00
4d9a8b580f Рельеф не рисовался: намотка displacement'ов была перевёрнута
Some checks failed
CI / checks (push) Has been cancelled
Жалобы «дисплейсменты вывернуты» и «сквозь них видно только скайбокс»
оказались одной механикой. Меш 3D-скайбокса рисуется без записи глубины и
без отсечения, поэтому всегда заполнял то, что мир не нарисовал, и маскировал
пропуск: вместо травяных холмов gm_construct был виден чужой ландшафт
скайбокса, а из-под карты изнанка рельефа читалась как «трава сверху».

Замер: геометрия рельефа в буфере здоровая (0 вырожденных, 0 нефинитных,
габариты — земля карты), батч выдаётся, но `--only-material 36` даёт пусто и
только `SOURCE_RS_NO_CULL=1` возвращает `#93ff26` на экран. Замер верхней
поверхности над точкой: пол под спавном (z=-144) намотан с кросс-произведением
вниз, трава (z=-148) — вверх. Видимая сверху поверхность — та, у которой
кросс-произведение смотрит внутрь тела; браш-грани приводятся к этому в
`face_winding`, а `build_displacement` подгонял намотку вдоль нормали
плоскости, то есть наоборот.

Порядок индексов в `emit` перевёрнут; нормали вершин оставлены наружу, чтобы
динамический свет не поехал. Коллизия не задета — `add_displacement_tris`
строит нормали по рёбрам, порядок вершин для неё неважен.

Новая диагностика: переключатель `SOURCE_RS_NO_SKY_MESH` (гасит только меш
скайбокса), лог «топ-материал по объёму геометрии» и предупреждение о
батчах с индексом вне списка материалов — рендер пропускает такие молча.
Отмечу: в тестах мир строится с пустым VirtualFs, из-за чего 189 материалов
против 54 в рантайме и индексы расходятся, поэтому диагностика печатается из
сборки мира.

Тесты: real_map_top_surface_orientation (замер), real_map_displacement_is_visible_from_above
(держит правку). Все 22 теста с картой зелёные.
2026-10-16 11:12:48 +03:00
a1b0c03a62 Экспонента лайтмапы возвращена: тени в запечённом свете появились
Some checks are pending
CI / checks (push) Waiting to run
Ошибка была в моём же исправлении из E11. Экспоненту из четвёртого байта там
отбросили, потому что у большинства люкселей байт равен 255, что как i8 даёт
-1 и масштаб 0,5, и картинка выходила вдвое темнее. Взамен подобрали плоский
коэффициент 3,0, и по освещённым поверхностям совпало.

Но затенённые участки выходили втрое светлее эталона, и это было записано как
«не найдено». Причина: VRAD нормализует мантиссу так, что её максимум лежит в
верхней половине байта, а порядок яркости пишет в экспоненту. Отбросив её, мы
сравняли тени с солнцем.

Масштаб подобран так, чтобы освещённые участки остались: 2^(-1) * 4,47 = 2,24.
Тень с экспонентой -3 получает 2^(-3) * 4,47 = 0,56, вчетверо темнее солнца.

Та же камера, что в E11:
  бетон в солнце  GMod 143,6 -> было 147,0, стало 143,7
  тоннель         GMod  92,9 -> было 165,8, стало  86,8
  стена в тени    GMod  36,6 -> было 124,6, стало  70,8
  небо            GMod 199,7 -> 196,9 без изменений

Тоннель — то место, где затенение и должно быть сильным: 165,8 -> 86,8 при
эталонных 92,9. Это и есть появившиеся тени.

Проверено, что динамическое солнце эти участки не освещает: при
SUN_BRIGHTNESS_SCALE 900 и 2500 значения совпали до десятых.

Вода: ряд длин 46/23/11/5,5/2,7 был кратен ×0,5, из-за чего периоды фаз всех
пяти волн синхронизировались и рябь выстраивалась в правильную решётку — на
кадре это читалось как гофрированный металл. Заменено на шесть волн с
несоизмеримыми длинами 340/210/125/73/41/19.

Тест lightmap_ignores_exponent_byte переименован в
lightmap_exponent_byte_makes_shadow_darker и проверяет, что разница экспонент
на две ступени даёт вчетверо.
2026-10-15 10:45:49 +03:00
983019faa7 Тени от солнца и солнце в небе
Теней в движке не было вообще — это и было причиной того, что плоская трава
читалась как «вывернутая», хотя намотка рельефа верна: плоскости 110 граней
смотрят вверх, развёрнуто 0 из 11 776 треугольников. Проверка переворотом
подтвердила: при инверсии становится вверх 8 008 / вниз 10 353 и разворачиваются
все треугольники, то есть явно хуже.

Добавлена карта теней 2048x2048 (Depth32Float), ортографическая проекция на
область радиусом 2 600 юнитов вокруг камеры по направлению на солнце. Отдельный
пасс глубины со стороны источника; вода в него не идёт — она полупрозрачна.
В шейдере луч сравнивается с картой четырьмя сравнениями по соседним текселям
и сглаживается smoothstep, иначе край тени идёт лестницей. Смещение по
нормали на 3 юнита убирает полосу самозатенения.

Солнце в небе: направление пишется в uniform камеры шестым vec4, небо рисует
ядро 2,5 градуса и ореол до 12.

Сверка с GMod на той же камере, что в E11:
  бетон 143,6 -> 147,0 (было 87,8)
  трава 127,1 -> 129,0 (было 77,3)
  небо 199,7 -> 196,9

Расходится стена в тени: GMod 36,4, у нас 124,6. Основной свет даёт запечённая
лайтмапа, а динамическое солнце при SUN_BRIGHTNESS_SCALE = 900 вносит малую
долю, поэтому затенение этой стены у нас не гаснет. Причина не найдена,
записана в PARITY.md E12.5.

Заодно добавлен Mat4::ortho в math.
2026-10-15 09:38:35 +03:00
30be24e38e Вода: затухание по волновому числу и отбраковка по плоской поверхности
Затухание было 1/(1 + d²·0.00012) — на 900 юнитах это 0,01, и озеро с высоты
читалось зеркальной плитой. Алиасинг бьёт только по мелким волнам: дальше
пикселя. Заменено на затухание по волновому числу каждой волны отдельно,
local = 1/(1 + (k·d·0.01)²). Волна длиной 46 юнитов на 900 юнитах ещё видна
(0,4), длиной 2,7 — уже нет (0,002).

discard считался по волновой нормали: на скользящих углах dot(n, view)
проваливался в ноль и поверхность выбивалась дырками, край озера рвался
зубцом. Теперь facing берётся по плоской поверхности — это вопрос геометрии,
а волновая нормаль идёт только в френель и блик.
2026-10-15 08:57:45 +03:00
593282f661 Освещение сходится с GMod: экспонента лайтмапы читалась вдвое темнее
Сверено с настоящим GMod на одной камере: точка (-399.5, -1604.5, 67.9),
yaw 146.33, pitch 3.48, fov 100. Наш кадр снят в той же точке, ракурс совпал.

Первое сравнение: небо сошлось (0,97-1,00), а поверхности были темнее вдвое.
Причина — четвёртый байт люкселя читался как i8 и давал масштаб 2^exp; у
большинства люкселей он равен 255, то есть как i8 это -1 и масштаб 0,5.

Масштаб подобран по эталону на двух разных материалах:
  0,5 -> бетон 70,5 / трава 52,8
  1,0 -> 87,8 / 77,3
  2,0 -> 122,0 / 107,8
  3,0 -> 147,0 / 130,3
  GMod -> 143,6 / 127,1
Один коэффициент попал в эталон на двух материалах, значит это найденная
величина, а не подгонка.

Заодно:
- водa: сумма герстнеровских волн вместо ровной синусоиды — острые гребни и
  широкие ложбины; референсы дают прямое указание «Gerstner, not sine»;
- плавание: вход подавался как направление (-1..1), а не скорость, поэтому
  горизонтальная скорость была 0,8 юнита в секунду вместо 200;
- ноклип: скорость при отпускании клавиш не обнулялась — это инерция; теперь
  обнуляется, а пробел такой же ввод, как «вперёд»;
- добавлен --fov, по умолчанию 100 как в GMod.

Тесты на плавание и на инерцию в ноклипе добавлены.
2026-10-15 08:14:46 +03:00
e91a60d40d Освещение: нормали граней были перевёрнуты, карта не получала света
Симптом: «на видео нету освещения вообще». Предыдущий замер показывал
среднюю лайтмапу 0,22 и делал вывод, что темно «от данных карты». Вывод был
неверен.

Замер ориентаций вершин gm_construct вскрыл причину: строго вверх — 17
вершин, строго вниз — 18 096. У настоящей карты пола смотрят вверх.

Причина: face_normal брала нормаль плоскости с обратным знаком
(`if side == 0 { -normal }`). В VBSP нормаль плоскости грани уже смотрит
наружу от твёрдого тела. Инверсия попала в код при попытке починить
намотку, но намотку чинит отдельная face_winding — она сравнивает
геометрическую нормаль треугольника с нормалью грани и переворачивает обход
сама.

Следствие: солнце и запечённая лайтмапа попадали на изнанку всех
поверхностей. Пол не получал ни того, ни другого.

Стало: вверх 15 195, вниз 2 918. Тест real_map_normals_are_not_inverted
не даёт дефекту вернуться.

Заодно второй настоящий баг в том же месте: в sample_light_luxel ширина
строки блока лайтмапы считалась как extent[0], а блок — это
(extent[0]+1) × (extent[1]+1) люкселей. Каждая строка читалась на люксель
раньше, и к низу блока чтение уезжало на десятки люкселей в сторону.

Долго висевшее «белое пятно» оказалось не дефектом: это комната Construct,
материал GM_CONSTRUCT/COLOR_ROOM с VMT UnlitGeneric и базовой текстурой
color/white — белая по замыслу и неосвещаемая. Спавн игрока внутри неё.

Рябь на воде подобрана замером: частота 0.035 давала плиту, 0.35 —
aliasing на горизонте, остановились на 0.12 и 0.19; третью полосу убрали —
её наклон давал клинья поперёк озера.
2026-10-14 12:15:15 +03:00
fd2c9b8df5 Вода: рябь по мировым координатам, френель и блик от солнца
Был плоский mix(deep, sky, fresnel * 0.85) — вода стояла зеркальной плитой.

- Рябь строится из трёх синусоид разной частоты и направления в мировых
  координатах, с затуханием к горизонту: вдали вода должна быть гладкой, иначе
  горизонт превращается в кашу.
- Френель по возмущённой нормали, а не по исходной.
- Отражение неба зависит от угла: у горизонта светлее и холоднее, сверху
  темнее.
- Узкий блик от солнца по отражённому вектору (степень 220) — у воды блик
  игольчатый, а не матовая подсветка.
- Прозрачность зависит от френеля: вблизи видно дно, гладкая поверхность
  почти зеркало.

Время для анимации приходит из буфера камеры (добавлено пятое vec4), а не
выводится из позиции: иначе картинка дёргалась бы при повороте.
2026-10-14 10:09:17 +03:00
fb98a3ebf1 Солнце светит сверху, ambient берётся из карты
На gm_construct солнце светило строго с горизонта: направление на свет было
(0.588, -0.809, 0) — Z ноль, то есть полы не получали света вообще, а светил
только бок, попавший под рыскание.

Причина: у light_environment в GMod наклон лежит в ОТДЕЛЬНОМ ключе pitch,
а в angles стоит только рыскание. На карте буквально angles = "0 306 0" и
pitch = "-56", а читались только angles.

Теперь направление (-0.329, 0.452, 0.829) — полы получают 83% солнца.
Тест real_map_sun проверяет, что Z солнца больше 0.5.

Заодно небесная добавка берётся из _ambient той же сущности
("114 136 148 420" на gm_construct) вместо жёсткой константы
[0.62, 0.66, 0.74]: карты с собственным небом освещались чужим цветом.
2026-10-14 10:04:36 +03:00
b96b06ccdb Небо гасится за пределами карты: и 2D-скайбокс, и 3D-скайбокс
Жалоба: «если игрок выходит за карту, то скайбокс и 3D-скайбокс не должны
рендериться (иначе не понятны границы карты)».

Причина видна в шейдере 3D-скайбокса:
    world = camera.pos.xyz + (in.pos - sky_camera) * scale
Геометрия неба размещается относительно камеры, поэтому небо всегда заливает
весь фон — пределы карты на нём не видно.

Замер на gm_construct:
  габариты геометрии  (-5640, -4576, -960) .. (2168, 6464, 2976)
  габариты коллизии   (-15360, -15360, -1024) .. (15872, 15360, 15359)
  sky_camera          (-1428, 1645, 10991), масштаб 1/16
  2D-скайбокса нет, только 3D
Габариты коллизии не годятся — включают и 3D-небо, и далёкие террасы,
поэтому основание — габариты геометрии карты.

- world::inside_bounds — одна реализация предиката, её используют и движок,
  и рендерер (рендерер копирует габариты при загрузке).
- WorldRenderer::update_sky_visibility пересчитывает видимость каждый кадр:
  игрок уходит за край во время игры. Проверка по всем трём осям сразу,
  под картой небо тоже исчезает.
- SKY_BOUNDS_MARGIN = 128: габариты считаются по вершинам, а камера стоит
  на высоте глаз над опорой, так что стоящий на краю игрок формально за ними.
- Гасятся оба прохода неба: иначе за краем остался бы виден 3D-неб, а это
  ровно то, что мешало увидеть границу.

Тесты: sky_is_visible_only_inside_the_map (юнит) и real_map_sky_bounds
(замер на настоящей карте).
2026-10-14 07:10:47 +03:00
387abd6454 engine/render: динамический свет из карты (солнце, лампы, прожекторы)
collect_lights() разбирает light_environment (солнце), light, light_spot.
На gm_construct: 65 источников, 1 солнце, 22 прожектора. Шейдер получил
uniform-блок с солнцем, точечными источниками и конусами прожекторов.

Ключевые находки при подборе нормировки (проверено рендерами):
- четвертая компонента _light — это РАДИУС у ламп и ЯРКОСТЬ у солнца;
- яркость нельзя домножать в цвет, иначе возводится в квадрат;
- лайтмапа уже содержит запеченное солнце, динамическое добавляется слабо.

Также добавлен разбор GAME_LUMP в formats (нужен для деталь-текстур) и
заодно выяснено: в gm_construct деталь-текстур нет, под-лумпы prps/prpd —
это геометрия пропов. Текстура GRASS-SAND_13 есть, но не используется ни
одним texinfo — вопрос песка остается открытым (docs/PARITY.md A5).
2026-10-13 08:55:51 +03:00
4da3289180 render/engine: отсечение задних граней с нормализацией намотки
A4 из docs/PARITY.md. Раньше cull_mode был None во всех пайплайнах, и
изнанка брашей рисовалась поверх настоящих поверхностей (на gm_construct
трава была видна сквозь бетонную плиту).

Намотку нельзя задать «как в карте»: в данных она непоследовательна.
Замер на gm_construct показал, что у обычных граней и у displacement'ов
противоположный порядок обхода, а нормаль плоскости у них трактуется
с разным знаком. Поэтому:

- face_normal: нормаль плоскости смотрит внутрь тела, лицевая сторона —
  обратная (side == 0 => -normal);
- face_winding: геометрическая нормаль каждой грани приводится к нормали
  грани, треугольник разворачивается при несовпадении;
- build_displacement: то же для рельефа, причём «наружу» — это нормаль
  БАЗОВОЙ плоскости (на склонах геометрическая нормаль наклонная).

После этого инвариант «геометрическая нормаль = нормаль освещения» держится
везде, и правильная фронт-грань одна: Ccw в NDC, то есть Cw в wgpu/Vulkan
(Source использует левую систему координат).

Проверено рендерами на gm_construct: рельеф виден сверху целиком, плита и
трава вместе, трава корректно перекрыта плитой. Диагностические флаги
SOURCE_RS_NO_CULL и SOURCE_RS_FRONT_FACE=ccw оставлены для сверки.
2026-10-12 17:12:40 +03:00
44b0ece2ab render: вода (SURF_WARP), area-скайбокс с правильным порядком глубины, лайтмапы диспов по UV
- Вода: детект Water-шейдера (/), отдельный полупрозрачный
  проход с френелем; поверхность/изнанка по нормали. Белая дыра на месте воды
  (2D-скайбокс «вниз») устранена.
- 3D-скайбокс: выбор граней по area листа sky_camera (движок: area bits), а не по
  AABB. Disp-грани не попадают в leaf_faces — выбор по листу центра грани; теперь
  в скайбоксе есть большая плоскость flatgrass_2 (пропадала — торчала «стена»
  бетонного пола). Защита >30% граней = скайбокс не выделяем (gm_flatgrass).
- Порядок: 2D-скайбокс фоном -> 3D-меш (depth Always, без записи) -> мир ->
  вода. Скайбокс больше не проступает сквозь дальние стены карты.
- Лайтмапы: блок грани (size+1)^2 с индексом v*width+u; для displacement'ов —
  плотная сетка по UV поверхности (CalcDispSurfCoords), а не проекция мира:
  убрана «шахматка» теней на рельефе. Скайбокс-меш тоже сэмплит лайтмапы.
- Тесты: gm_flatgrass-диспы живут в скайбоксе (z≈-16000), пороги обновлены.
- docs/RECON.md §12: форматы листьев/граней, лайтмапы, скайбокс, вода.
2026-10-12 08:28:28 +03:00
b7a63d9a83 client: главное меню (выбор карты, запуск), команда Run = меню
- menu: список карт из VFS, навигация Up/Down, Enter — загрузка карты,
  Esc — выход, скриншот для тестов; пустой мир + 2D-текст
- create_buffer: пустые буферы wgpu запрещены — минимальный размер
- run.rs удалён (дублировал окно без меню)
2026-10-12 04:33:00 +03:00
5db759c8bb render/client: HUD и 2D-текст (bitmap-шрифт 8x8), фикс capture-прохода
- render::text: атлас ASCII 32..127, вершинный оверлей, шейдер text.wgsl
- WorldRenderer: push_text/clear_text, пайплайн с альфа-смешиванием, depth Always
- capture(): теперь использует общий draw() — раньше скриншоты не включали
  скайбокс и оверлей (важно: скайбокс-меш на скриншотах появился только сейчас)
- map: HUD (FPS, позиция, режим, подсказки, лог событий), тени текста
2026-10-12 04:12:34 +03:00
2aeee1ac0a engine/render: модели энтити (prop_static/prop_dynamic/prop_physics) и 3D-скайбокс
- engine::model: сборка мешей MDL+VVD+VTX (bind pose, LOD 0), материалы через
  cd-каталоги модели, поворот углами Source
- build_world: экземпляры prop_* моделей в общий меш мира (9 инстансов на
  gm_construct), кэш моделей и текстур
- 3D-скайбокс: выделение скайбокс-граней (AABB вокруг sky_camera, <30% карты),
  сжатие 1/scale вокруг камеры, отдельный пайплайн с глубиной у дальней
  плоскости; 2D-скайбокс рисуется только в незакрытых пикселях
- тесты: sky_mesh на gm_construct, пороги дисплейсментов обновлены
2026-10-12 02:42:12 +03:00
b8b5a4c328 engine/render: M3 v0 — рендер мира BSP со скайбоксом
- crates/engine: сборка мира (браши, displacement'ы, лайтмапы в вершинах,
  материалы через VMT/patch/include, кэш текстур, пропуск прозрачных материалов),
  загрузка скайбокса из skyname
- crates/render: wgpu-рендерер мира (VB/IB, батчи, глубина), скайбокс-куб,
  камера-полёт, capture PNG с учётом BGRA, debug-режимы материалов
- client: команда map (диск/VPK, pakfile карты, WASD+мышь, --pos/--angles/--screenshot)
- тесты: gm_construct/gm_flatgrass собираются, 0 заглушек, 41 324 треугольника
- docs: прогресс M3 и заметки (BGRA, pak-VMT, z=w клиппинг)
2026-10-12 00:06:26 +03:00