aboutsummaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/reference/environment-effects.md128
-rw-r--r--docs/reference/materials.md7
-rw-r--r--docs/reference/msh.md47
-rw-r--r--docs/tomes/05-render.md38
4 files changed, 212 insertions, 8 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/tomes/05-render.md b/docs/tomes/05-render.md
index b04af31..c8bdf4e 100644
--- a/docs/tomes/05-render.md
+++ b/docs/tomes/05-render.md
@@ -625,15 +625,20 @@ Lightmap не является обычной diffuse texture. WEAR содерж
отдельным slot или texture stage. Замена lightmap предварительным умножением в
diffuse texture ломает LOD, atlas coordinates и динамическую модуляцию.
-Vulkan preview строит projected shadows для выбранных зданий и роботов,
-отбрасывает spheres вне шести clip-плоскостей с радиусом `sphere + 100` и
+Vulkan preview строит проецируемые тени для объектов, отбрасывающих тени,
+отбрасывает сферы вне шести clip-плоскостей с радиусом `sphere + 100` и
сохраняет порядок видимых кандидатов; frame builder обрабатывает не более 20
-casters. Текущая реализация выделяет страницы в atlas `256 x 256` с 28 page
-slots и использует одну smoothing pass. Вызов preview передаёт `fade = 1.0` и
-LOD defaults: minimum LOD `1` и detail multiplier `1.0` отдельно для зданий и
-роботов. Rust-путь не загружает `shade.cfg`: эти значения задаются кодом, а
-параметры файла пока не влияют на preview. Проекция учитывает вклад
-directional и point lights.
+объектов. Текущая реализация выделяет страницы в atlas `256 x 256` с 28 page
+slots и использует одно сглаживание. Preview применяет native defaults
+`RobShadowsOn = true` и `BldShadowsOn = false`: объект kind `4` проверяет
+первый флаг, остальные kinds — второй. Поэтому роботы BTLU остаются объектами,
+отбрасывающими тени, а объекты FORT и STAT их не создают. Preview пока не
+загружает `shade.cfg`, поэтому значения из пользовательского файла не меняют
+список объектов, отбрасывающих тени. Проверка действует только на проецируемые
+тени: отдельный список объектов для затенения солнца по-прежнему включает
+FORT, BTLU и STAT. Вызов preview передаёт `fade = 1.0` и LOD defaults:
+minimum LOD `1` и detail multiplier `1.0` отдельно для зданий и роботов.
+Проекция учитывает вклад directional и point lights.
Receiver projection обрезает projected quad по рёбрам треугольника. Native ray
query descriptor в `+0x10` требует наличия всех переданных bits, а descriptor
@@ -955,6 +960,23 @@ for (face, assignment) in mesh.faces.iter().zip(assignments.iter()) {
}
```
+Подготовка BAS вынесена в `fparkan-terrain::build_basement_foundations`.
+Функция получает внешние и внутренние кольца в порядке размещения, строит
+упорядоченные пересечения внешней границы с текущей поверхностью и возвращает
+исходные фрагменты рельефа вместе с треугольниками отмостки в одном наборе.
+`TerrainWorld::with_basement_foundation` принимает этот набор во владение и
+устанавливает те же координаты и маски в BVH высотных запросов и пересечений.
+Предпросмотр миссии на Vulkan пока строит только LOD0: связывание BAS-записей
+с этой подготовкой ещё не подключено, поэтому наличие API не означает, что
+геометрия миссии уже обрезана.
+
+Внешнее кольцо должно лежать на области LOD0, а каждый его сегмент — оставаться
+покрытым текущей поверхностью. Непокрытый сегмент или пересекающиеся контуры
+дают ошибку; карта не остаётся частично обрезанной. Первичная нормализация
+использует исходные грани и соседей LOD0, последующие пересечения и вырезания —
+канонические фрагменты после предыдущих размещений. Соответствие динамических
+соседей, создаваемых нативным движком, пока не моделируется.
+
## Материалы ландшафта
Рядом с `Land.msh` лежат `Land1.wea` и `Land2.wea`. Старший байт