diff options
Diffstat (limited to 'docs/reference/environment-effects.md')
| -rw-r--r-- | docs/reference/environment-effects.md | 238 |
1 files changed, 186 insertions, 52 deletions
diff --git a/docs/reference/environment-effects.md b/docs/reference/environment-effects.md index 799a969..23115af 100644 --- a/docs/reference/environment-effects.md +++ b/docs/reference/environment-effects.md @@ -165,13 +165,32 @@ opcode 1; обе границы включены, а коэффициент вы Позиция, направление, цвет и range интерполируются по концам команды. Позиция и направление переводятся через основу родителя; направление после -этого нормализуется, а нулевой вектор остаётся нулевым. Range сохраняет -авторское значение без ограничения снизу. Типы Op1 `1`, `2`, `5`, `6` и `7` +этого нормализуется, а нулевой вектор остаётся нулевым. Радиус умножается на +длину преобразованного направления; неположительный результат заменяется на +`0,01`. Типы Op1 `1`, `2`, `5`, `6` и `7` выбирают point-light selector `1`; их native flags сохраняются. Типы `3` и `4` выбирают другие классы записей LightManager и не попадают в generic список точечного света. У `LightningEffect` выборка тех же полей использует отдельные ограничение по длительности FXID и правила безопасной обработки. +Generic Control Op1 добавляет к интерполированным RGBA и радиусу равномерный +шум `U × (amplitude / 65536) − amplitude / 2`, где `U` — очередное 16-битное +слово native FX-генератора. Цветовые амплитуды лежат в body `92..108`, +амплитуда радиуса — в `116..120`. Радиус с этой добавкой затем умножается на +длину преобразованного направления и ограничивается снизу `0,01`. +В [эталонной Effect.dll](original-binaries.md) RVA `0xF6E0` расходует слова +в порядке A, B, G, R, радиус: R использует общий поток DLL, остальные четыре +выборки — отдельный поток Op1. Нулевая амплитуда и выход за интервал команды +не отменяют расход этих слов. Четыре активные выборки RGBA и состояния обоих +генераторов после пяти вызовов совпали побитно с DLL при CW `0x007F`; +сравнение радиуса учитывает известное приближение native FastProc длины. + +Приложение сохраняет состояния между обновлениями сцены, не пересчитывая их +при повторной отправке кадра. Начальные seeds просмотра равны `1/1`; +инициализация и общий порядок выборок остальных FX ещё не воспроизведены. +Эта граница учитывается в `control_op1_rng_schedule_approx`: мерцание +подключено, но его конкретная последовательность ещё не служит эталоном parity. + ## Молния и звуковые события `decode_env_lightning_fxid` извлекает из FXID длительность, ссылки opcode 3 и @@ -218,6 +237,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]` расписания @@ -236,11 +290,19 @@ packed: [key.colors[10], key.colors[14]], `AtmosphereFrame` пока не несёт этот бит обновления, поэтому sampler выдаёт интерполированный цвет при каждом запросе. -## Экранная геометрия солнца и луны +## Отрисовка солнца, луны и бликов -`CSun::Render` сначала переводит направление небесного тела в пиксели текущего -viewport, затем собирает quad с native XYZRHW-позицией. При положительной высоте -viewport Vulkan пиксели переводятся в clip-space так: +`CSun::Render` получает текущий viewport, проецирует направление солнца или +луны и собирает quad уже в его пикселях. `Terrain.dll` VA `0x1004AA40` передаёт +эту геометрию через shade callback; native FVF `0x1c4` задаёт +`XYZRHW | DIFFUSE | SPECULAR | TEX1`. Конвертер `0x10020700` сохраняет входные +`x`, `y` и `z`, а в `RHW` записывает `1.0`; исходный `z` равен `0.999`. Callback +включает depth test и выключает запись depth, поэтому более близкая поверхность +может закрыть спрайт, а сам спрайт не меняет глубину для последующих слоёв. + +В Vulkan ряды 3 и 4 сохраняют этот contract отдельным bit 8 material mode. +CPU каждый кадр преобразует viewport-пиксели к clip space текущего размера +swapchain: ```rust position = [ @@ -250,13 +312,17 @@ position = [ ]; ``` -Vertex shader пропускает `clip_from_world` для строк солнца и луны. CPU каждый -кадр пересчитывает центр и размер quad по текущему viewport; старые пиксельные -координаты не переносятся на новый размер. World-space billboards бликов -остаются отдельным путём. Материал экранного quad не освещается, vertex fog -отключён, а sky far-depth bit к нему не применяется. +Положительная высота Vulkan viewport отображает эти координаты в тот же +top-down экран. Vertex shader пропускает мировую матрицу только для этого +режима: -Для мировой геометрии +```glsl +vec4 clip_position = screen_tl + ? vec4(in_position, 1.0) + : frame.clip_from_world * vec4(in_position, 1.0); +``` + +Материал остаётся unlit, fog factor равен нулю. Для мировой геометрии оригинал задаёт viewport `MinZ = 0.1`, `MaxZ = 0.99`: после отсечения проекционная глубина отображается в этот диапазон: @@ -280,35 +346,42 @@ RVA `0x7170`, участок `0x71E7–0x723C`, копирует MinZ/MaxZ в re [таблице бинарников](original-binaries.md). Нативные значения этих двух полей имеют биты `0x3DCCCCCD` и `0x3F7D70A4`. - -## Геометрия визуальных 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`. +Sky-background bit 4 продолжает помещать слои на дальнюю плоскость. Отдельные +flare rows 5 и 6 пока используют world-space billboard builder: для них не +подтверждён этот же native TL путь. + +## Эффекты Control + +Погодное расписание `sky.ske` — только один источник эффектов. У объектов +миссии таблица Control (`.ctl`) связывает строки с FXID-командами. Текущий +preview выбирает начальную группу Control и исполняет только строки без +predicate, chain и state gate. Условия игровых событий и переходы между +группами принадлежат полному игровому Control runtime и здесь не моделируются. + +Type 4 row задаёт локальную basis эффекта через три точки CTL point list. Для +валидной привязки первая точка также задаёт owner node. Путь MSH выбирает позу +этого узла из текущей pose hierarchy; для внешнего mounted-компонента к ней +добавляется поза socket. После этого применяются object transform, header +scale и локальная матрица FX. Подробнее о порядке матриц см. +[описание MSH](msh.md#control-fx-и-owner-transform). + +Renderer preview поддерживает FXID-команды Op3 modes 0–2, Op4, Op7, Op8 и Op9. +Op3 mode0 строит camera-facing поверхность из анимируемых локальных позиции и +масштаба; Op7/Op8 обновляют отдельные элементы particle pool; Op9 создаёт +индексированную полусферическую сетку. Op1 вычисляется отдельно как список +point lights: selector 1 без `flags & 0x80000000` освещает мир, а с этим битом — +собственное строение или юнит. Локальный источник нельзя удалять из освещения +MSH вместе с исключением из общего списка рельефа. Preview сохраняет исходные +flags для projected shadows и передаёт в renderer область действия корневого +объекта. Область ноль означает общий свет; у локального источника и диапазонов +его модели одинаковый ненулевой идентификатор. + +Body discriminator Op3 читается из первого DWORD `FxCommand.raw_body`; offsets +в evaluator-е относительны к данным после четырёхбайтового command word. Mode 1 +добавляет анимируемую ось к camera-facing basis, mode 2 использует native +D110 basis. Для modes 0 и 1 preview предполагает render context 0, который +проходит общий gate `Effect.dll` `0x106C0`; их API не выбирает другой context. +Evaluator mode 2 принимает индекс context и учитывает gate при расчёте `active`. У Op3 mode 1 ось задаёт длину прямоугольника, а не его нормаль. Для локального вектора к камере `v` и анимируемой оси `a` столбцы базиса имеют @@ -359,27 +432,88 @@ RVA `0x93D0`, ветка mode 1 с `0x9739`, сборка столбцов `0x97 семена отдельных экземпляров лишь детерминированно заменяют process-global состояние и не воспроизводят общий порядок вызовов. +`f_gener_ball` evaluator покрывает Op4 mode 0 с положительным знаменателем в +`body+0xC4` и Op9 mode 2. Для Op9 selectors `body+0xC4` со значениями 0, 1 и 2 +отображаются в native flag bases 3, 5 и 6. Нулевой `body+0xC8` проецирует UV на +XY, ненулевой выбирает статические quad/cap координаты. Op4 вычисляет локальное +расстояние камеры тем же component-wise projection, что использует native path. + +FX-команды одной Control-строки используют общие часы экземпляра +`fparkan_fx::clock::FxInstanceClock`. Время хранится в миллисекундах `f32`, +отдельно от длительности документа: mode 0 возвращает постоянную фазу +(при создании ноль), mode 1 — `(now − origin) / duration`, mode 2 повторяет +цикл, mode 3 возвращает `1 − (now − origin) / duration`. +Header flags `0x200` и `0x20` преобразуют незажатую фазу; command path +`0x8120` зажимает результат только после преобразований. +Вызов `set_time` меняет начало отсчёта и режим экземпляра, сохраняя режим +документа. Запрос перезапуска (`Effect.dll` RVA `0x7B40`, instance bit `0x20`) +срабатывает после конца текущего времени жизни: следующий update начинает +новый цикл в своём времени и возвращает режим документа. Это отдельный флаг, +не header `0x20`, складывающий фазу треугольником. Правила режимов, gates +`0x80/0x100` и установления времени сверены вызовом эталонной DLL с x87 CW +`0x007F`; отложенный переход закреплён Rust-тестом по ветви update `0x8120`. +Все экземпляры статического preview пока создаются в момент ноль; геометрия, +свет и звук получают одну фазу на обновление сцены. Команды Control ещё не +меняют эти часы, а нативная очередь обновления с шагом 50 ms не моделируется. +В [эталонной Effect.dll](original-binaries.md) RVA `0x5C60` режимы 4–17 +читают состояние владельца или провайдера; режим 14 тоже входит в эту группу. +Пропуски при расширении выбранных начальных Control-строк учитываются по +командам: `control_fx_requires_game_state` для этих режимов и +`control_fx_unsupported` для остальных непреобразованных команд. Диагностика +сохраняет причину, ресурс, объект, компонент, строку и номер команды. +`control_fx_invalid_documents` отдельно считает неразобранные FXID, где число +команд неизвестно. Счётчики суммируются по компонентам миссии; они не заменяют +учёт ещё не выбранных Control-групп и условных строк в фазе G. +Effect renderer проверяет header bit `0x800` только при +render selector 0; native AniMesh component path был захвачен с selector 1, +поэтому preview использует selector 1 и не подавляет такие visual commands. +Этот capture также показал `0xD00` FX entries в 53-entry manager. Динамическое +состояние manager `A8` пока заменено нулём; экземпляры с gates `0x80/0x100` +считаются в `control_fx_manager_flag_stub`. Header bit `0x10` относится к activation lifecycle и сам по себе +не отключает draw. +Header jitter и random shift хранятся один раз на Control row и совместно +применяются к его sibling-командам; отдельный детерминированный поток каждой +строки сохраняет эту связь, но не повторяет порядок глобального native RNG. +Неразобранные группы, event predicates и неразрешённые owner attachments +пропускаются. + +Для visual FX material берётся из resource reference команды. MAT0 выбирает +фазу по normalized FX phase, независимо от wall-clock анимации world materials. +Command opacity передаётся vertex alpha, а FX MAT0 alpha не умножает её второй +раз; texture alpha продолжает участвовать в shader. Текущая preview-ветка +использует SourceAlpha и read-only depth state и допускает только MAT0-фазы, +проходящие локальный predicate additive-RGB approximation. Семантика native +`flag40` и directional-light ветки остаётся открытой. + +Generated Op9 mesh разделяется на отдельный draw range для каждого native +quad и cap. Каждый range получает ключ по камере до первой индексированной +вершины, в порядке убывания расстояния. Равные ключи используют midpoint +insertion из native queue, поэтому их порядок не является стабильным по +первоначальному времени поступления. Preview использует category 0 как +highest-detail mesh; projected-size LOD и полное визуальное совпадение размера +и формы пока не реализованы. + ## Частицы Op8 `fparkan-fx::smoke` разбирает body Op8, хранит кольцевой пул команды и выдаёт quad-ы с ориентацией к камере для активных слотов. Нативная нормализованная -фаза окна ограничивается диапазоном 0–1 и задаёт интервал проверки следующего события; -сравнение строгое (`schedule_base + interval < update_clock`). Все события -catch-up в одном обновлении проверяются по тому же нормализованному интервалу; -после события нативный код продвигает базу расписания интервалом, вычисленным -по сырой фазе эффекта, а возраст слота считает по нормализованному интервалу. -Когда фаза равна единственной границе окна, деление `0/0` даёт NaN, который -нативный unordered-путь сравнения ограничивает нулём. +фаза окна ограничивается диапазоном 0–1 и задаёт интервал проверки следующего +события; сравнение строгое (`schedule_base + interval < update_clock`). Все +события catch-up в одном обновлении проверяются по тому же нормализованному +интервалу; после события нативный код продвигает базу расписания интервалом, +вычисленным по сырой фазе эффекта, а возраст слота считает по нормализованному +интервалу. Когда фаза равна единственной границе окна, деление `0/0` даёт NaN, +который нативный unordered-путь сравнения ограничивает нулём. Нулевая база расписания служит признаком начального состояния: обновление сохраняет текущую опору эмиттера и базу времени, не создавая частицу. Если первое время равно нулю, следующее обновление повторно захватывает опору. При событии скорость опоры считается от сохранённой позиции до текущей за время от базы расписания; слот смещает сохранённую опору на `скорость * due_interval`. -Обновление без событий не переносит сохранённую опору к текущему владельцу. -Откат часов корректирует только базу расписания до текущего времени: слоты, -опора и три потока случайных -чисел не сбрасываются. +При нулевой скорости оригинал подставляет ось X владельца и для направления, +и для этого смещения. Обновление без событий не переносит сохранённую опору к +текущему владельцу. Откат часов корректирует только базу расписания до текущего +времени: слоты, опора и три потока случайных чисел не сбрасываются. Evaluator принимает отдельные начальные состояния для эмиттера, знаковых отклонений и материала. Переход каждого потока совпадает с общим native LFSR, |
