diff options
Diffstat (limited to 'docs/reference')
| -rw-r--r-- | docs/reference/environment-effects.md | 128 | ||||
| -rw-r--r-- | docs/reference/materials.md | 7 | ||||
| -rw-r--r-- | docs/reference/msh.md | 47 | ||||
| -rw-r--r-- | docs/reference/render-frame.md | 21 |
4 files changed, 203 insertions, 0 deletions
diff --git a/docs/reference/environment-effects.md b/docs/reference/environment-effects.md index 8045d68..0435a6a 100644 --- a/docs/reference/environment-effects.md +++ b/docs/reference/environment-effects.md @@ -104,6 +104,10 @@ N = round_even( координаты, и к каждой оси применяется floor-based wrap; 5. после wrap точка снова переводится в мир, а её хвост сбрасывается на голову. +Повторное обновление на том же времени имеет нулевой `dt`: позиция капли и +сохранённое начало хвоста остаются прежними. Хвост продвигается только при +положительном шаге; wrap по-прежнему сбрасывает его на голову. + Скорости не принадлежат системе координат камеры: ```text @@ -143,6 +147,24 @@ slot `+6c`): ширина `RECT` делится на горизонтальныÐ исходную таблицу знаков и минимальный depth fade `0.1`. Renderer сам решает, как загрузить этот квадрат и как смешать его материал. +## Точечный свет из opcode 1 + +Preview вычисляет generic Op1 отдельно от визуальных команд. Переданный +владельцем `command_time` сравнивается с началом и концом интервала из body +opcode 1; обе границы включены, а коэффициент выборки равен +`(command_time - start) / (end - start)`. Заголовочная длительность FXID не +заменяет этот интервал. Для нулевой ширины адаптер не создаёт кадр света, +поскольку у команды нет конечного коэффициента интерполяции. + +Позиция, направление, цвет и range интерполируются по концам команды. +Позиция и направление переводятся через основу родителя; направление после +этого нормализуется, а нулевой вектор остаётся нулевым. Range сохраняет +авторское значение без ограничения снизу. Типы Op1 `1`, `2`, `5`, `6` и `7` +выбирают point-light selector `1`; их native flags сохраняются. Типы `3` и +`4` выбирают другие классы записей LightManager и не попадают в generic список +точечного света. У `LightningEffect` выборка тех же полей использует отдельные +ограничение по длительности FXID и правила безопасной обработки. + ## Молния и звуковые события `decode_env_lightning_fxid` извлекает из FXID длительность, ссылки opcode 3 и @@ -207,6 +229,112 @@ packed: [key.colors[10], key.colors[14]], `AtmosphereFrame` пока не несёт этот бит обновления, поэтому sampler выдаёт интерполированный цвет при каждом запросе. +## Экранная геометрия солнца и луны + +`CSun::Render` сначала переводит направление небесного тела в пиксели текущего +viewport, затем собирает quad с native XYZRHW-позицией. При положительной высоте +viewport Vulkan пиксели переводятся в clip-space так: + +```rust +position = [ + pixel[0] * (2.0 / width) - 1.0, + pixel[1] * (2.0 / height) - 1.0, + 0.999, +]; +``` + +Vertex shader пропускает `clip_from_world` для строк солнца и луны. CPU каждый +кадр пересчитывает центр и размер quad по текущему viewport; старые пиксельные +координаты не переносятся на новый размер. World-space billboards бликов +остаются отдельным путём. Материал экранного quad не освещается, vertex fog +отключён, а sky far-depth bit к нему не применяется. + +Native `z = 0.999` не означает, что спрайт гарантированно лежит за любой +поверхностью. Vulkan использует `LESS_OR_EQUAL` и не записывает depth для +прозрачного ряда. При near `0.5`, far `700` мировая поверхность на расстоянии +`320` получает depth +`700 / (700 - 0.5) * (1 - 0.5 / 320) ≈ 0.999151`; поэтому экранный `0.999` +чуть ближе этой поверхности и проходит depth test перед ней. Это фиксированное +native значение сохраняется для совместимости, но само по себе не обещает +окклюзию солнца всей геометрией местности. + +## Геометрия визуальных FX-команд + +`fparkan-fx::fx_visual` формирует кадры для двух подтверждённых вариантов +`f_gener_ball`. Op4 принимает mode 0 с положительным знаменателем в `body+0xC4` +и выдаёт camera-facing quad; расстояние камеры считается через native +component-wise projection родительской матрицы. Op9 принимает mode 2: +селектор `body+0xC4` со значением 0, 1 или 2 отображается в native flag base +3, 5 или 6 и выбирает одну из трёх сеток; категория render flags выбирает её +детализацию. `body+0xC8` выбирает UV path: +нулевое значение проецирует позицию вершины на XY, ненулевое использует +фиксированные координаты quad и cap. Кадр сохраняет MAT0 resource, фазу, +opacity, вершины и triangle indices для renderer. + +Evaluator получает готовые command body, phase, camera point и внешнюю +матрицу. Выбор Control row, manager gates, owner attachment, header jitter и +global RNG остаются задачей владельца. `fparkan-game` пока использует для Op9 +категорию 0 с наибольшей детализацией; выбор категории по projected size ещё +не реализован. + +Для Op3 поддерживаются body-типы 0, 1 и 2. Поля отсчитываются от +`FxCommand.raw_body`, то есть после 4-байтового command word. Типы 0 и 1 +используют общие position/scale/phase/opacity tracks и camera-facing основу; +тип 1 и тип 2 также интерполируют ось, а тип 2 строит basis через native +D110-порядок. Все три evaluator-а принимают уже собранную матрицу владельца и +не выбирают Control row или attachment. Текущий путь для типов 0 и 1 +предполагает native render context 0: `Effect.dll` проверяет общий gate +`context + 0x60 < 8`, а API этих evaluator-ов не выбирает другой context. +Evaluator типа 2 принимает индекс context явно и учитывает этот gate в `active`. + +## Частицы Op7 + +`fparkan-fx::op7` вычисляет фиксированный набор частиц Op7 по body команды, +фазе эффекта, матрице владельца, позиции камеры и render context. После +инициализации состояние команды сохраняет случайно изменённые треки позиции +и масштаба. Body задаёт время групп, opacity, фазу материала и вертикальное +ускорение; последнее масштабируется переданным world scalar и применяется к +копии матрицы владельца каждой активной группы. Текущий preview +`fparkan-game` передаёт `world_scalar = 10.0`; чтение действующей настройки +мира пока не подключено. + +Часы активной группы обновляются до применения detail cutoff отдельной +частицы. Cutoff сравнивает ordinal render context с постоянным порогом слота. +`Effect.dll` `0x1300` использует тот же порядок, поэтому скрытие слота не +останавливает часы его активной группы. Поток частиц последовательно задаёт +случайные изменения позиции и масштаба; для порогов видимости используется +отдельный поток. Шаг LFSR совпадает с нативным переходом двух половин, однако +семена отдельных экземпляров лишь детерминированно заменяют process-global +состояние и не воспроизводят общий порядок вызовов. + +## Частицы Op8 + +`fparkan-fx::smoke` разбирает body Op8, хранит кольцевой пул команды и выдаёт +quad-ы с ориентацией к камере для активных слотов. Нативная нормализованная +фаза окна ограничивается диапазоном 0–1 и задаёт интервал проверки следующего события; +сравнение строгое (`schedule_base + interval < update_clock`). Все события +catch-up в одном обновлении проверяются по тому же нормализованному интервалу; +после события нативный код продвигает базу расписания интервалом, вычисленным +по сырой фазе эффекта, а возраст слота считает по нормализованному интервалу. +Когда фаза равна единственной границе окна, деление `0/0` даёт NaN, который +нативный unordered-путь сравнения ограничивает нулём. + +Нулевая база расписания служит признаком начального состояния: обновление +сохраняет текущую опору эмиттера и базу времени, не создавая частицу. Если +первое время равно нулю, следующее обновление повторно захватывает опору. При +событии скорость опоры считается от сохранённой позиции до текущей за время +от базы расписания; слот смещает сохранённую опору на `скорость * due_interval`. +Обновление без событий не переносит сохранённую опору к текущему владельцу. +Откат часов корректирует только базу расписания до текущего времени: слоты, +опора и три потока случайных +чисел не сбрасываются. + +Evaluator принимает отдельные начальные состояния для эмиттера, знаковых +отклонений и материала. Переход каждого потока совпадает с общим native LFSR, +но эти локальные seed не воспроизводят порядок process-global вызовов игры. +Предел 100000 событий catch-up — защитная граница evaluator-а; соответствующего +лимита в исследованном native цикле не обнаружено. + ## Что остаётся за владельцем `EnvironmentSystem` не извлекает камеру из renderer, не разрешает материалы diff --git a/docs/reference/materials.md b/docs/reference/materials.md index c148bc8..435bd6e 100644 --- a/docs/reference/materials.md +++ b/docs/reference/materials.md @@ -132,6 +132,13 @@ ping-pong нечётный цикл идёт в обратном направлÐ sampler `animation_block_index` уже выбран вызывающим runtime из block table; packed WEAR handle и номер строки WEAR являются отдельными значениями. +Для native effect path с явной normalized phase режим блока не вычисляется: +конечное значение вне `0..=1` заменяется на `0.5`, затем phase умножается на +последний `end_time_ms` и усекается до целого tick. Поиск интервала идёт от +последнего ключа к первому. При phase `1.0` tick равен duration, поэтому ни один +интервал не подходит: выбирается key 0 и общий interpolation path может +экстраполировать за пределы его первого интервала. + ## Fallback Material resolve: diff --git a/docs/reference/msh.md b/docs/reference/msh.md index 165b750..a28514d 100644 --- a/docs/reference/msh.md +++ b/docs/reference/msh.md @@ -153,3 +153,50 @@ Byte `material_index` выбирает строку WEAR; high word at `+0x02` Ñ `index_start + index_count` остаётся внутри type 6, а каждый использованный индекс через `base_vertex` попадает в position stream. `vertex_count` и `local_batch_index` доступны renderer для исходного draw contract. + +## Сторона грани в цветовом проходе + +В цветном проходе `Terrain.dll`, функция `0x100296F0`, сравнивает камеру с +каждой гранью по знаку тройного произведения: + +```c +d = dot(eye - v0, cross(v1 - v0, v2 - v0)); +``` + +Проход оставляет грань только при `d > 0`. Raw `Batch20.flags` задаёт два +исключения: `0x0002` пропускает программную проверку стороны, поэтому обратная +грань не отбрасывается этим условием. С `0x4000` проверяется только первая +грань записи: если `d <= 0`, цикл её граней прекращается; если `d > 0`, +оставшиеся грани этой записи уже не проходят повторную программную проверку. +`Terrain.dll!0x100296F0` также настраивает пакет этого цветного пути: flags по +адресу `packet+0x128` равны `0x404` или `0x414`. Путь отправки в +`Terrain.dll!0x1001D660` перед вызовом renderer vtable `+0x48` очищает только +маску `0x1`; `Ngi32.dll!0x10007470` проверяет маску `0x4` этого аргумента. При +установленной маске render state 22 получает `D3DCULL_NONE`, иначе — +`D3DCULL_CW`. Оба значения CShade сохраняют маску `0x4`, поэтому в этом +цветном пути аппаратное отсечение выключено, а набор обычных граней определяет +программный тест `d > 0`. Raw-флаги `0x0002` и `0x4000` меняют этот тест, как +описано выше. + +Статический Vulkan preview сохраняет каждую исходную запись `Batch20` одним +диапазоном рисования: +`append_static_preview_component_with_lightmaps` добавляет исходные индексы без +перестановки, а в диапазон переносит `index_count` и исходный `first_index` с +общим смещением компонента. Поэтому `0x4000` выбирает ту же первую грань, даже +если диапазон получил другой материал для lightmap. + +Поскольку вершины preview переведены в мировые координаты, знак `d` для MSH +диапазона корректируется знаком детерминанта полной матрицы размещения. Повороты +узлов и mounts — единичные кватернионы с determinant `+1`; отрицательный object +scale меняет знак. Renderer Vulkan задаёт `front_face=CLOCKWISE` и viewport с +положительной высотой. Для используемых матриц legacy и free-flight preview +при положительном determinant native-visible `d > 0` относится к Vulkan +back-face; поэтому preview отсекает `FrontFace`. При отрицательном determinant +режим меняется на `BackFace`. +Для `0x0002` и `0x4000` аппаратное отсечение выключено, чтобы сохранить их +программную политику. Отклонённый `0x4000` диапазон получает число индексов 0; +каждый кадр это число заново вычисляется из исходного значения, поэтому после +изменения положения камеры или opacity материала диапазон может вернуться в +draw queue. Проверка первой грани +применяется только к MSH-диапазонам цветовой мировой очереди; terrain и +shadow/sun запросы её не используют. diff --git a/docs/reference/render-frame.md b/docs/reference/render-frame.md index f8aee38..de71603 100644 --- a/docs/reference/render-frame.md +++ b/docs/reference/render-frame.md @@ -143,6 +143,27 @@ RVA `0x4EBE0` использует соглашение о вызовах `__std `0x481C5`. Это преобразование применяется только на границе неба и не меняет рыскание или управление движением `FreeFlightCamera`. +## Камера свободного полёта + +Начальный профиль использует `near = 0.5`, `far = 700` и горизонтальный FOV +`1.3` рад — параметры, наблюдавшиеся в проверенном контексте GOG Ngi32 +([формула native projection](../tomes/05-render.md#камера-и-размещение-в-оригинале)). +Для отношения ширины к высоте `a` превью вычисляет вертикальный угол +```rust +fn vertical_fov(self, aspect: f32) -> f32 { + 2.0 * ((self.horizontal_fov * 0.5).tan() / aspect).atan() +} +``` + +Код заменяет невалидный `aspect` на отношение 16:9; горизонтальный угол +остаётся `1.3` рад после изменения размера окна, а новый viewport и матрица +камеры применяются при следующей отрисовке. + +Проекция направления не сравнивает косинус угла с ближней дистанцией: она +отбрасывает только лучи за камерой и почти параллельные плоскости изображения +(`forward · direction <= 1e-4`). Лучи в передней полусфере могут проецироваться +за край viewport; владелец спрайта отдельно решает, нужно ли его рисовать. + ## Ограничения точного совпадения `VulkanStaticCamera::from_legacy_d3d7` выполняет восстановление вида и проекции |
