aboutsummaryrefslogtreecommitdiff
path: root/docs/reference/environment-effects.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/reference/environment-effects.md')
-rw-r--r--docs/reference/environment-effects.md146
1 files changed, 146 insertions, 0 deletions
diff --git a/docs/reference/environment-effects.md b/docs/reference/environment-effects.md
index 31024f7..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 и
@@ -189,6 +211,130 @@ OneShot -> воспроизвести разовый звук молни
отдельный spatial source, применяет DirectSound range/pan и текущий listener.
Отсутствие устройства вывода не должно останавливать симуляцию.
+## Цвет солнца
+
+Солнце берёт цвет спрайта и прозрачность бликов из `palette[10]` расписания
+`sky.ske`; `palette[9]` — чёрный specular-компонент. Вторичный направленный
+свет использует `palette[14]`. При выборке расписания это выражено так:
+
+```rust
+packed: [key.colors[10], key.colors[14]],
+```
+
+Этот порядок соответствует живому GOG-замеру: `CSun+0x95c` содержал
+`0x41FF3C00` (интерполяция `palette[10]`), `+0x960` — `0xFF000000`
+(`palette[9]`), а `+0x964` — вторичный свет из `palette[14]`. В `Terrain.dll`
+по VA `0x1004AA92..0x1004AAAB` цвет в `+0x95c` обновляется, только если
+установлен бит 0 результата sky-query; иначе остаётся прежнее значение.
+`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, не разрешает материалы