diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/appendices/knowledge-boundaries.md | 20 | ||||
| -rw-r--r-- | docs/reference/atmosphere.md | 65 | ||||
| -rw-r--r-- | docs/reference/environment-effects.md | 35 | ||||
| -rw-r--r-- | docs/reference/render-frame.md | 41 | ||||
| -rw-r--r-- | docs/tomes/05-render.md | 123 |
5 files changed, 246 insertions, 38 deletions
diff --git a/docs/appendices/knowledge-boundaries.md b/docs/appendices/knowledge-boundaries.md index 7f049fc..0df1e5a 100644 --- a/docs/appendices/knowledge-boundaries.md +++ b/docs/appendices/knowledge-boundaries.md @@ -11,7 +11,8 @@ authoring API без динамического подтверждения. state transitions и окончательный порядок части transparent/FX/shadow subpasses. Дистанционная кривая тумана по квадратам границ подтверждена в живом CShade. -Выбор проходов по native shade-state и GPU fog color пока заменён общим туманом мира с цветом горизонта; +Обычный GPU fog color из горизонта подтверждён исполнением; выбор проходов по +native shade-state и их временные изменения цвета пока заменены общим туманом мира; это приближение считается как `render_coverage fog_pass_eligibility_approx`. После ограничения цели рельефом интерактивная камера проверяет движение @@ -55,7 +56,18 @@ MSH-запрос со свободной preview-камерой; это не пÐ Данные type 16 ещё не декодированы. Внутренний ракурс «Конструктора» после настоящего входа игрока подтверждает исчезновение чёрной поверхности в проходе; -для всей приёмки R2 ещё нужны пары обеих миссий на трёх дистанциях. +наружные окна обеих миссий сверены на расстояниях около 60/85/110, а ближние +окна — с внутренней стороны. Окно «Конструктора» на правом краю кадра при +дистанции около 85 совпадает; внутренние дальние ракурсы и остальные случаи +отсечения ещё не приняты. Общей готовности R2 эти пары не доказывают. + +Старые захваты без `geometry_projection` используют состояние камеры после +обхода мира, которое может отсекать близкие поверхности. Это отмечается +`captured_projection_unverified`; для приёмки таких поверхностей нужен новый захват. + +Захваты без `sky_uv_clock` не подтверждают начальное положение облаков, +звёзд и фона. Replay использует elapsed clock приложения и считает +это как `captured_sky_phase_unverified`; для приёмки неба нужен новый захват. Закрывающий эксперимент: запустить оригинал в совместимой Windows/DirectX среде, перехватить DirectDraw/Direct3D calls и surface flips, сохранить state @@ -67,6 +79,10 @@ Control Op1 пока использует цвет и радиус без слу это явное приближение считается в `render_coverage control_op1_jitter_unsupported`. Масштаб радиуса длиной преобразованного направления и нижняя замена 0,01 восстановлены. +Control Op2 воспроизводит modes 0/2; modes 1/3, body+76 и неподдержанные +time modes/привязки считаются как `render_coverage control_op2_unsupported`. +Управление фазой экземпляра командами Control остаётся до его подключения в runtime. + Размеры команд, resource references, lifecycle, flags families и используемые time modes известны. Не закрыто значение каждого поля body opcodes 1--10, отсутствующий во всех проверенных каталогах opcode 6 и точные формулы редких diff --git a/docs/reference/atmosphere.md b/docs/reference/atmosphere.md index 65ed176..dba20c2 100644 --- a/docs/reference/atmosphere.md +++ b/docs/reference/atmosphere.md @@ -50,7 +50,25 @@ key_time = floor(duration * (key_hour * 60 + key_minute) * 60 / 86400) theta = (clamp((now - birth_start) / lifetime, 0, 1) * 1.2 - 0.1) * pi ``` -Затем к нему применяется матрица рождения. У солнца и луны независимые +Затем к нему применяется матрица рождения как к вектору-столбцу. Если `A` и +`B` — углы рождения, а `x = cos(theta)` и `z = -sin(theta)`, результат равен + +```text +[x*cos(A) + z*sin(A)*sin(B), + x*sin(A) - z*cos(A)*sin(B), + z*cos(B)] +``` + +Порядок важен для всего освещения: транспонированное умножение переносит +солнце на другую сторону мира и меняет освещённые склоны, блики и тени. +В GOG `Terrain.dll` (SHA-256 +`af87d1b2e728a0be73c52be3b44cc196ab46da7799f25a15d40f8c9b0b425ead`) +матрицу строит RVA `0x4A550`, а вектор преобразует `CSun::Update`, RVA +`0x4B570`. Живой замер после 282184 мс при длительности 525000 мс и углах +`[90, 30]` дал направление `[-0.4950135, -0.1408745, -0.8573891]`; +Rust-тест проверяет этот результат с допуском `3e-6`. + +У солнца и луны независимые интервалы и независимые направления: при отсутствии активной луны нельзя подменять её направление противоположностью солнца. `SunFrame::color` — это единственный primary directional RGB. Второй источник получает направление @@ -131,6 +149,31 @@ gradient и clouds row 2. Для nebula/stars/clouds используются UV directional RGB из `sky.packed[0]`. Это четыре фиксированных pass, а не три независимых цветных слоя. +Все три текстурных слоя движутся. Обновление GOG `CSky` +(`Terrain.dll`, RVA `0x48B90`, запись UV `0x4942A–0x495B7`; +[сборка и SHA-256](original-binaries.md)) берёт разность текущего `u32` clock +и сохранённого поля объекта `+8` с переполнением. Это отдельные миллисекунды +жизни неба, а не абсолютная фаза суток из SKE. + +| Слой | Период | Добавка к исходным UV | +|---|---:|---| +| Фон | 300 000 мс | одинаковый сдвиг U и V | +| Звёзды | 100 000 мс | одинаковый сдвиг U и V | +| Облака | 50 000 мс | сдвиг только U | + +Добавка равна остатку от деления elapsed clock на период, умноженному на +нативный коэффициент `f32` (`3.3333333e-6`, `1e-5`, `2e-5`). Вершинные UV +каждый раз вычисляются от позиции купола заново; накопление приращений +создавало бы дрейф. `SkyMesh::update_uvs` получает текущие миллисекунды кадра +и сохраняет геометрию купола. В работающем оригинале проверены UV всех трёх +слоёв, включая разность clock с переполнением. + +Захват сохраняет `sky_uv_clock` на входе в рендер неба, проверяя его по +фактическим UV вершины зенита и clock того же кадра. Replay удерживает эту +фазу вместе с фазой суток. Без такого поля текстуры движутся по elapsed clock +приложения; начальная фаза оригинала неизвестна и явно считается как +`captured_sky_phase_unverified`. + Цвет первого screen-gradient и последнего пояса вычисляет `SkyFrame::screen_gradient(yaw_radians, glare_rgb_delta)`. Native `CSky` сначала округляет `(yaw + pi) * 180 / pi` режимом nearest-even, выбирает сектор @@ -167,21 +210,27 @@ RGB palette 12 с минимумом `80/255` на канал и alpha `150/255` ## Туман и свет -Нативное обновление `CSky` записывает две дальности и один packed RGB. +Нативное обновление `CSky` записывает две дальности и нижнюю границу освещения. `FogFrame` повторяет этот контракт: ```text fog.start = sky.values[0] * 700 fog.end = sky.values[1] * 700 -fog.color = RGB(sky.packed[1]) / 255 +lighting_floor = RGB(sky.packed[1]) / 255 ``` Поля не скрывают порядок исходных параметров: значения сохраняются в -`SkySample`, а преобразование выполняется только в `fog_frame`. Vulkan path -передаёт цвет и дальности в frame uniforms; vertex shader вычисляет линейный -коэффициент по расстоянию до камеры, а fragment shader смешивает RGB материала -с fog color в ветвях combiner, которые используют туман. Конкретный draw state -может обходить это смешивание. +`SkySample`, а преобразование выполняется только в `fog_frame`. Нижняя граница +освещения ограничивает результат расчёта света снизу; она не задаёт цвет тумана. +Обычный цвет тумана выбирается из `sky.colors[0..4]` по направлению камеры, +как цвет горизонта. Это подтверждено в `CSky::Render` и живым совпадением +его значения с `D3DRENDERSTATE_FOGCOLOR` в том же игровом тике. + +Vulkan path передаёт эти параметры раздельно. Vertex shader вычисляет +коэффициент по квадратам расстояния и границ, а fragment shader смешивает +RGB материала с цветом тумана. Конкретный native draw state может обходить +смешивание или временно менять его цвет, например на чёрный для отдельного +прохода. Полный выбор таких проходов ещё не воспроизведён. Цвет primary света солнца — RGB palette 12, нормированный делением на 255 и умноженный на trailing parameter 2. `SunFrame::direction` — нормированный diff --git a/docs/reference/environment-effects.md b/docs/reference/environment-effects.md index 45d7725..f3ecfb4 100644 --- a/docs/reference/environment-effects.md +++ b/docs/reference/environment-effects.md @@ -218,6 +218,41 @@ OneShot -> воспроизвести разовый звук молни отдельный spatial source, применяет DirectSound range/pan и текущий listener. Отсутствие устройства вывода не должно останавливать симуляцию. +### Звуки Control + +Команда Op2 может принадлежать не погоде, а экземпляру эффекта на узле модели. +`fparkan-fx::ambient_sound` хранит её временное окно и предыдущее значение +фазы. В режиме 0 разовый звук начинается при строгом пересечении начала окна: +`previous <= start && start < phase`, если фаза движется вперёд. Значение +`phase == 1` для этой проверки заменяется нулём и подготавливает следующий +цикл. Режим 2 включает loop на обеих границах окна; внутри окна уже работающий +источник обновляется, а не запускается заново. Выход за окно останавливает его. + +Позиция интерполируется между двумя локальными точками и проходит через ту же +матрицу владельца, что и графические команды. Минимальная и максимальная +дистанции остаются постоянными; частота интерполируется по фазе окна. +Каждая команда получает отдельный источник звука, даже если ресурс совпадает +с ресурсом соседней команды. Дождь сохраняет свой loop. Перемещение слушателя, +потеря фокуса и завершение приложения затрагивают все эти источники. + +Эти ветви восстановлены в GOG `Effect.dll` (сборка из +[таблицы бинарников](original-binaries.md)): init RVA `0x12D10`, update +`0x12EB0`. Живой loop `sounds.lib:f_defl.wav` передаёт в звуковой backend +диапазон `12/80`, частоту `1` и мировую позицию из матрицы владельца. +В режиме 2 признак loop берётся из первого DWORD body, а не из command flag. + +Обычное создание эффекта Control сохраняет time mode заголовка и задаёт +постоянную фазу нулём. Raw selector строки служит ID экземпляра, а не номером +режима времени. Поэтому `f_comp_sfx` с time mode 0 и началом окна `0.001` +остаётся тихим до управляющей команды Control. Preview такие команды ещё не +исполняет. Периодические звуки с time mode 2 уже следуют общим часам и текущей +позе узла. `render_coverage control_op2_supported` считает подготовленные +команды, а `control_sound_start/stop` показывает фактические переходы. + +Неизвестные backend modes 1/3, ненулевое поле body+76, неподдержанное время +или привязка владельца пропускаются с `render_coverage control_op2_unsupported`. +Они не заменяются разовым звуком или произвольным loop. + ## Цвет солнца Солнце берёт цвет спрайта и прозрачность бликов из `palette[10]` расписания diff --git a/docs/reference/render-frame.md b/docs/reference/render-frame.md index b2a6a93..df3f248 100644 --- a/docs/reference/render-frame.md +++ b/docs/reference/render-frame.md @@ -85,9 +85,13 @@ RVA `0x1B0F0`, заменяет diffuse alpha пакета без type 16 с bat квадраты границ по этому наблюдению. GLSL вычисляет квадрат расстояния вершины, после чего растеризатор интерполирует коэффициент по треугольнику. `native_fog.effective.sink_rgb` совпадает с `sky.packed[1]`, нижней границей -освещения CShade. Это не подтверждает значение GPU fog color. Приложение пока -сохраняет цвет экранного горизонта; выбор цвета и проходов требует отдельной -проверки GPU-состояния. +освещения CShade. Обычный GPU fog color подтверждён отдельно: `CSky::Render` +по RVA `0x48341` задаёт renderer default из цвета горизонта. В живом замере +`0xFF404064` дошёл до `IDirect3DDevice7::SetRenderState(34)` в том же игровом +тике. В другом ракурсе палитра `[0xFFA8A5CE, 0xFFABA9D5, 0xFFBAA9D5, +0xFFABA9D5]` дала default `0xFFA9A7D1`. Это не покрывает временные изменения +цвета отдельных проходов: например, native render path устанавливает чёрный +цвет и затем восстанавливает сохранённый. Выбор проходов по native shade-state пока приближён общим туманом мира и считается как `render_coverage fog_pass_eligibility_approx`. @@ -134,21 +138,17 @@ RVA `0xF6E0`, преобразует направление текущей маÑ глубину. За фиксированными диапазонами окружения сохраняются их явные состояния. Для MSH без потока цветов type 16 диапазон, цвет которого вычисляет CPU, при -raw-флаге `Batch20` `0x0008` получает переопределение alpha только в одном -подтверждённом случае. В GOG `Terrain.dll` с SHA-256 -`af87d1b2e728a0be73c52be3b44cc196ab46da7799f25a15d40f8c9b0b425ead` проверка -по RVA `0x1B0F0` задаёт alpha `0`, когда установлен bit `0x0040` и сброшен -`0x0010`; поэтому для `0x0148` alpha равна нулю. Для `0x0108` и `0x0118` -превью сохраняет alpha MAT0; для `0x0058` действует приоритет bit `0x0010`, -также оставляя alpha MAT0. При наличии type 16 или отсутствии `0x0008` -переопределения нет. Оно относится к одному draw range и не меняет общий +raw-флаге `Batch20` `0x0008` получает переопределение alpha по +[дистанционной кривой портала](#прозрачность-порталов-msh). Bit `0x0010` +имеет приоритет; иначе bit `0x0040` задаёт нулевую alpha, а оставшаяся +ветвь использует границы 75/95 с поправкой на FOV. При наличии type 16 или +отсутствии `0x0008` переопределения нет. Оно относится к одному draw range и не меняет общий материал; alpha `0` временно задаёт число индексов `0`, а при очистке переопределения исходное число индексов восстанавливается. -Общий fade по расстоянию пока не реализован. Открыты точная метрика расстояния, -выбор первого CPU-принятого треугольника диапазона и эквивалентность frustum- и -culling-отбору оригинала. Поэтому состояния `0x0108` и `0x0118` оставляют alpha -MAT0, пока эти входные данные не установлены. +Fade обновляется с камерой каждый кадр. Открыт выбор первой принятой вершины +после native CPU clipping: приложение пока берёт первую индексированную +вершину диапазона и считает это приближение в `render_coverage`. В превью с фиксированной камерой диапазоны туманности и звёзд, купола и облаков явно помечаются как прозрачный фон. Непрозрачные диапазоны мира остаются на @@ -225,6 +225,17 @@ RVA `0x4EBE0` использует соглашение о вызовах `__std `right - left` на `bottom - top`. Размер окна превью закреплён: если при запуске или после ненулевого изменения окна физический размер не совпадает с заданным, приложение завершает работу, а не растягивает изображение. + +Проекция во время рисования может отличаться от восстановленного состояния +камеры после обхода мира. В GOG у `CStridedPrimitive::RenderVB` (Terrain RVA +`0x1D110`) renderer содержит `near = 0.5`, а на границе World3D RVA `0x13CE4` +уже `near = 5`. Поэтому захват отдельно сохраняет `geometry_projection` при +первом вызове геометрии с той же мировой матрицей камеры. Поток, поколение, +renderer, viewport, far и FOV сверяются с последующей границей; только +подтверждённая запись используется при воспроизведении. Иначе пол в трёх +единицах от камеры может исчезнуть из-за чужой ближней плоскости. Старые JSON +без этой записи сохраняют прежнее поведение с явным счётчиком +`captured_projection_unverified`; их нужно переснять для проверки близких поверхностей. Сравнивайте результат с фрагментом исходного PNG по прямоугольнику `[left, top, right, bottom]`. В этом режиме `FreeFlightCamera` не создаётся, но при повторной отрисовке обновляются окружение и проецируемые тени для diff --git a/docs/tomes/05-render.md b/docs/tomes/05-render.md index 72b0685..c1563fc 100644 --- a/docs/tomes/05-render.md +++ b/docs/tomes/05-render.md @@ -845,6 +845,17 @@ projected shadows. Радиус источника масштабируется синий свет завода почти незаметным. Неположительный итог заменяется на 0,01. Случайные добавки к цвету и радиусу пока явно считаются в `render_coverage control_op1_jitter_unsupported`. + +Эффект может одновременно рисовать поверхность и издавать звук. Для Op2 +приложение создаёт отдельный звуковой источник у каждого экземпляра команды: +несколько одинаковых механизмов слышны одновременно и не заменяют дождь. +Позиция следует анимируемому узлу, а фаза включает, обновляет и останавливает +loop. Обычная строка Control создаёт экземпляр с нулевой постоянной фазой; +звуки с таким режимом ожидают отдельной управляющей команды. Периодические +звуки работают по часам уже в preview. Устройство вывода остаётся в приложении, +а правила переходов и параметров — в `fparkan-fx::ambient_sound` +([подробнее](../reference/environment-effects.md#звуки-control)). + Туман также зависит от расстояния нелинейно: между ближней и дальней границами движок интерполирует по квадрату дистанции. При границах 0 и 700 точка в 350 получает четверть тумана, а не половину. Это сохраняет контраст среднего плана. @@ -998,6 +1009,12 @@ sun-ray queries поэтому используют исходные коордÐ она не равна одному лишь обратному преобразованию камеры. В проверенном GOG контексте Ngi32 использовались `n=0.5`, `z=700`, `f=1.3`. +Ближняя плоскость относится к конкретному проходу. После рисования движок +может вернуть прежние настройки камеры: захват только в конце мира видел +`n=5` и при воспроизведении вырезал пол перед игроком. Формат захвата поэтому +хранит проверенную проекцию вызова геометрии отдельно от состояния камеры; +подробности приведены в [описании кадра](../reference/render-frame.md#предпросмотр-по-захвату-камеры-d3d7). + У GOG `Terrain.dll + 0x7355c` хранит указатель на активную внешнюю камеру. Её selector 0 возвращает 16 слов начиная с `+0x20`, перенос находится в словах 3, 7, 11. Интерфейс `LoadCamera` расположен отдельно, по `+0x134` объекта. @@ -1013,11 +1030,39 @@ Node38, а диапазоны граней — в 68-байтовых Slot68. В проверяются на единственное назначение, а выходящий за массив, повторный или неподдерживаемый диапазон считается ошибкой загрузки. -Первый рендер карты, terrain-запросы высоты и пересечения, receiver-ы теней и -обрезка контуром зданий используют только LOD0. Отбор сохраняет порядок -исходных граней, а контурный проход отдельно переводит индекс спроецированной -грани обратно в исходный. LOD1 пока не подставляется по расстоянию: такой -переход не реализован. +Рисуемый уровень выбирается по ячейкам, а не по расстоянию до отдельного +треугольника. Начало сетки находится в первых двух float заголовка Slot68, +размер ячейки — в границах первого слота LOD0. Атрибут 2 потока Node38 задаёт +число ячеек по X; индекс ячейки равен `x * count_y + y`. Приложение проверяет, +что границы всех слотов образуют эту регулярную сетку. Все 33 карты GOG +проходят такую проверку, включая вырожденные грани на общей границе ячеек: +принадлежность грани берётся из слота, а не угадывается по её центру. + +Координаты камеры переводятся в целые координаты сетки округлением вниз. +Если `dx * dx + dy * dy >= 9` и горизонтальный FOV не меньше 0,5 радиана, +выбирается LOD1. При увеличении масштаба, отсутствии дальнего слота или +наличии геометрического patch остаётся LOD0. Камера за границей карты не +прижимается к крайней ячейке для этого расчёта. Правило восстановлено по +GOG `Terrain.dll` ([сборка и SHA-256](../reference/original-binaries.md)): +`CLandscape::Render`, RVA `0x11340`, выбор уровня `0x11878–0x11A03`. +Исполнение оригинала подтверждает выбор дальнего слота, а getter камеры +`Ngi32.dll` RVA `0x4D960` возвращает именно FOV. + +Основания строений сохраняют подробную геометрию. Нативная регистрация +patch (`Terrain.dll` RVA `0x12E20`) проверяет его треугольники против каждой +ячейки через `mrnParaIntersectsPoly`. Поэтому учитываются и соседние ячейки, +которых касается край треугольника. Перед проверкой координаты нормализуются +в `f32` относительно центра ячейки; замена этой операции сравнением мировых +границ меняет результат на общих рёбрах. На «Конструкторе» игнорируемый +ресурсный тест сверяет все 22 ячейки с принудительным LOD0 с набором, +полученным в живом оригинале. + +Vulkan получает оба уровня один раз. Диапазоны индексов разделены по +материалу и исходной ячейке; на каждом кадре включается только выбранный +уровень. Сгенерированные отмостки остаются включены. Terrain-запросы высоты +и пересечения, receiver-ы теней и обрезка контуром зданий используют +канонический LOD0. Отбор сохраняет порядок исходных граней, а контурный проход +отдельно переводит индекс спроецированной грани обратно в исходный. ```rust let assignments = mesh.face_lod_assignments()?; @@ -1035,9 +1080,10 @@ for (face, assignment) in mesh.faces.iter().zip(assignments.iter()) { исходные фрагменты рельефа вместе с треугольниками отмостки в одном наборе. `TerrainWorld::with_basement_foundation` принимает этот набор во владение и устанавливает те же координаты и маски в BVH высотных запросов и пересечений. -Предпросмотр миссии на Vulkan пока строит только LOD0: связывание BAS-записей -с этой подготовкой ещё не подключено, поэтому наличие API не означает, что -геометрия миссии уже обрезана. +Предпросмотр миссии связывает BAS-записи с этой подготовкой до загрузки +материалов. Полученная поверхность общая для изображения, теней и запросов; +поправка размещения основания один раз применяется к корню строения и его +присоединённым компонентам. Внешнее кольцо должно лежать на области LOD0, а каждый его сегмент — оставаться покрытым текущей поверхностью. Непокрытый сегмент или пересекающиеся контуры @@ -1048,13 +1094,33 @@ for (face, assignment) in mesh.faces.iter().zip(assignments.iter()) { ## Материалы ландшафта -Рядом с `Land.msh` лежат `Land1.wea` и `Land2.wea`. Старший байт -`TerrainFace28.material_tag` выбирает материал первой таблицы, младший — -второй. `0xff` в старшем байте означает отсутствие первого слоя. +Рядом с `Land.msh` лежат `Land1.wea` и `Land2.wea`. Младший байт +`TerrainFace28.material_tag` выбирает базовый материал `Land1.wea`, старший — +дополнительный материал той же таблицы. `0xff` в старшем байте означает +отсутствие дополнительного слоя. `Land2.wea` содержит соответствующие этим +двум строкам detail-материалы. В AutoDemo встречаются теги `0102`, `0203`, `ff01`, `ff02`, `ff03`. Менеджер World3D получает селектор `(table_index << 16) | material_index` и возвращает выбранную фазу MAT0 размером 76 байт. +Таблица, строка и блок анимации — разные части запроса. В быстром проходе +с двумя текстурами `CShade` запрашивает для Land2 блок анимации **1**. Он может +выбирать другую текстуру, а не только другие коэффициенты освещения: +например, в «Конструкторе» это `L09M.0` вместо `L09.0`. Подмена блока первым +приводит к умножению базового изображения на неподходящую detail-текстуру и +меняет яркость и контраст земли. `TerrainMaterialSelection` сохраняет номер +блока отдельно от packed material key; приложение использует его при загрузке +дескриптора и в последующих кадрах. Если второго блока нет, общий MAT0 sampler +возвращается к первому, как оригинальный `GetMaterialPhase`. + +В GOG `Terrain.dll` (сборка из [таблицы бинарников](../reference/original-binaries.md)) +быстрая ветвь находится в RVA `0x1A7AC–0x1A7FB`. Живое исполнение передаёт +handle `0x00010002`, block `1` и устанавливает режим текстур `9`; первая +текстура умножается на diffuse, вторая — на результат с множителем два. +Ближний дополнительный проход с порогом `260 / FOV` относится к fallback без +этого режима. На проверенном устройстве он не выбирается, поэтому переносить +его дистанционный порог в быстрый проход нельзя. + Type 18 в `Land.msh` оригинал называет microtexture mapping chunk; он содержит четырёхбайтовые записи. Type 11 хранит пары `u16`, выбранные таблицей terrain slots. Их первое поле — ключ shade cache, а не номер строки @@ -1085,6 +1151,32 @@ WEAR и MAT0 до объединения геометрии. Индексы GPU `u16`. Terrain передаёт в shader базовый, detail и overlay stages, lightmap и animation phase; object batches сохраняют их legacy blend/depth/alpha state. +При первоначальной загрузке одинаковые текстуры разных material slots +используют один GPU image. Ключ включает размеры, RGBA8 и всю авторскую +mip-цепочку: совпадения только базового изображения недостаточно. Хеш ускоряет +поиск, а полное сравнение данных исключает ошибочное объединение при коллизии. +Слоты сохраняют собственные descriptors, UV и параметры материала. При первой +смене общей текстуры renderer создаёт отдельный image только для изменяемого +слота; следующие фазы обновляют его на месте. Поэтому анимация одного материала +не меняет соседний. Строка `render_texture_upload` показывает число слотов, +начальных GPU images и отделений при обновлении. CPU-копии исходных пикселей +в материалах эта оптимизация не объединяет. + +Рельеф образует неизменяемое начало общего vertex buffer. Control-анимация, +FX и окружение обновляют следующие вершины, а новые индексы нужны только +геометрии проекционных теней в конце index buffer. Поэтому кадр записывает +лишь эти изменяемые части. При росте буфера новая память получает все данные, +включая неизменяемое начало; уменьшение числа теней не уменьшает выделенную +ёмкость. Это сохраняет номера вершин, диапазоны отрисовки и порядок проходов. + +Перед записью общих ресурсов renderer ждёт fences всех отправленных кадров. +Одного fence текущего кадрового слота недостаточно: другой кадр ещё может +читать ту же память. Ожидание всего устройства пока остаётся на пересоздании +swapchain и набора pipelines, диагностическом readback и завершении работы. Загрузка изменённых +текстур пока использует отдельную синхронную transfer-команду. Строка +`render_geometry_upload` показывает записанные vertex/index bytes и объём, +который потребовался бы при полной перезаливке, без начальной загрузки. + После загрузки world path создаёт интерактивную free-flight камеру (`WASD`, `E/Q`, `Shift`, `Ctrl`, RMB relative-look), обновляет listener и на каждом кадре собирает environment frame. Для каждого отрисовываемого строения @@ -1121,10 +1213,15 @@ drawable размер `(right - left) × (bottom - top)` и отклоняет Ð `FreeFlightCamera` в этом режиме не создаётся, но redraw продолжает обновлять environment и projected shadows от captured camera. Перед environment update и его audio events listener получает position, forward и up captured camera; -начальная сортировка world draw использует её же position. Phase capture может +начальная сортировка world draw использует её же position. Облака в обычном +запуске движутся с отдельным 50-секундным циклом UV; фон и звёзды имеют свои +периоды. Изменение цвета неба по расписанию не заменяет это движение. +Phase capture может содержать наблюдаемый mission path и проверенный `atmosphere_seconds`; viewer использует эту абсолютную фазу по умолчанию, если не указан явный CLI override, -и удерживает sample атмосферы на ней через readback frames. Фазы FX следуют +и удерживает sample атмосферы на ней через readback frames. Дополнительный +`sky_uv_clock` фиксирует положение движущихся текстур на том же кадре; +без него их начальная фаза не подтверждена. Фазы FX следуют выбранному control clock: без `--control-clock-ms` он обновляется по elapsed time, а с явным значением readback frames переигрывают заданный clock. Это не полное simulation time: `weather`, RNG и |
