From 09fcd350234656f726988cd6ee46840ed99b618d Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Sat, 31 Oct 2026 17:18:47 +0400 Subject: feat(render): integrate Control effects and animated world scene --- docs/reference/environment-effects.md | 155 +++++++++++++++++++++++----------- docs/reference/msh.md | 45 ++++++++++ docs/reference/render-frame.md | 73 +++++++++------- docs/tomes/05-render.md | 151 ++++++++++++++++++--------------- 4 files changed, 275 insertions(+), 149 deletions(-) (limited to 'docs') diff --git a/docs/reference/environment-effects.md b/docs/reference/environment-effects.md index 799a969..45d7725 100644 --- a/docs/reference/environment-effects.md +++ b/docs/reference/environment-effects.md @@ -236,11 +236,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 +258,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 +292,39 @@ 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` входят в общий +diffuse light set. Записи с этим битом сохраняют native flags для специальных +путей и projected shadows, но не добавляются в общий diffuse список. + +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 +375,66 @@ 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-команды используют общий preview clock и ограниченный набор time modes: +mode 0 оставляет фазу равной нулю, mode 1 вычисляет `elapsed / duration` без +зажатия, mode 2 повторяет цикл. Header flags `0x200` и `0x20` преобразуют эту +фазу; command path `0x8120` зажимает результат только после преобразований. +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`, связанное с header gate и фактической FX phase, пока не +моделируется. 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, diff --git a/docs/reference/msh.md b/docs/reference/msh.md index 0a8a2cb..130a203 100644 --- a/docs/reference/msh.md +++ b/docs/reference/msh.md @@ -95,6 +95,51 @@ translation суммируется, а rotations умножаются; посл запускает игровой Control/AI и не обещает полного совпадения с runtime или всеми x87 rounding modes. +## Control clock и pose компонентов + +Статический Vulkan preview начинает с позы, вычисленной из сохранённых `.ctl` +bindings. Пока не задан `--static-animation-frame`, при каждом новом сэмпле +сцены приложение сэмплирует поддержанные tag 21 записи для каждого экземпляра +компонента и заново строит его MSH node hierarchy; time и transition используют +общий runtime clock. Динамический sampler принимает tag 21 mode `0`, +controller flags `5` (raw value `0xFFFFFFFF` трактуется как `5`), +разрешённый gate provider и static row mapping: оба +variable index равны `0xFFFFFFFF`, previous-row flag не установлен, transition +rate конечен и положителен. + +Fallback к статической CTLD-позе всего экземпляра компонента срабатывает только +при следующих ошибках sampler: `UnsupportedTag21Mode`, +`UnsupportedVariableMapping`, `MissingGateProvider`, +`GateProviderKeyOutOfRange`, `MissingGateProviderValue` и +`GateProviderCountMismatch`. Он заменяет весь динамический результат экземпляра, +а не одну запись. Остальные sampling errors завершают обновление сцены; например, +`UnsupportedPreviousRowMapping` и `InvalidTransitionRate` не включают fallback. + +`--control-clock-ms ` воспроизводит общий MSH/FX clock линейно: первый из +`--frames` успешно представленных кадров получает `0`, последний — заданное +время. Replay clock привязан к числу представленных кадров: повторная подготовка +того же кадра, включая swapchain recreation, использует прежний sample. +`--static-animation-frame ` отключает динамическую MSH выборку и задаёт один +frame всем компонентам. Этот override не включает AI и не превращает preview +в полный игровой Control runtime. + +## Control FX и owner transform + +Для выбранной Control effect group runtime берёт строки без predicate, chain и +state gate. Type 4 row offsets выбирают три точки из CTL point list; позиции +точек задают локальное начало, а их направления — локальную basis. К basis +применяются FX header scale и локальные random shifts. Затем матрица проходит +через mission rotation/translation и object scale, optional mounted-component +pose, owner-node pose и локальную матрицу эффекта. Поэтому FX mounted-компонента +следует её текущему MSH owner pose, а не остаётся привязанным к mission root. + +Когда point record задаёт допустимый owner node, его pose разрешается из MSH +hierarchy. Для mounted external components node pose вычисляется относительно +component root и применяется вместе с уже рассчитанным mount pose. Internal +`INTO` child controller/equipment links пока не интерпретируются как MSH socket; +если owner pose нельзя разрешить, его FX пропускается. Нативное сочетание child +component scale, equipment attachment и CPT basis остаётся неустановленным. + ## Vertex streams Основные vertex streams имеют фиксированный source index. У type 3 `attr1` diff --git a/docs/reference/render-frame.md b/docs/reference/render-frame.md index 10de209..0a0fb4d 100644 --- a/docs/reference/render-frame.md +++ b/docs/reference/render-frame.md @@ -24,22 +24,22 @@ затенения, вызывается завершение рендерера, снимается `in_render`, затем рассылается `end-of-render`. -При retry кадра после пересоздания swapchain сохраняется прежний pending -sample, включая уже вычисленные позы и состояния FX и материалов. Для нового -drawable обновляются aspect, проекция и геометрия, зависящая от viewport; pose -берётся из pending camera. Новые события управления камерой остаются в -input controller и применяются следующим sample. Экранные billboards погоды -повторно проецируются из закэшированных мировых головы и хвоста, без второго -шага симуляции окружения и повторной отправки звуковых событий. Пауза нулевого -drawable не добавляет время. - -Pending frame хранит пакет MAT0 для мира и, при наличии GPU-окружения, отдельный -пакет неба. Пакеты содержат выбранные селектор и категорию конвейера, фазовые -коэффициенты материала, четыре UV-преобразования и кортеж фаз дополнительных -текстур. Повторное применение пакета при retry или нескольких resize не выбирает -фазу заново и не продвигает RNG. Перед отрисовкой сначала применяются материалы -мира и их порядок прозрачности, затем MAT0 неба, после чего viewport-зависимый -проход неба записывает свои переопределения. +В интерактивном preview часы материалов и окружения прибавляют полное время +между обновлениями. Ограничение шага в 100 мс остаётся у движения свободной +камеры и не обрезает эти часы. В режиме replay окружение получает разницу +последних применённых значений общего control clock. + +Если swapchain просит повторить ещё не представленный кадр, preview сохраняет +его общий clock, уже вычисленные позы, FX/RNG и фазы материалов. При изменении +drawable обновляются камера, aspect и viewport, после чего sky, environment +geometry и projected shadows пересобираются для нового размера. +Положение и ориентация камеры берутся из сохранённого состояния +ожидающего кадра; новый ввод мыши применяется при следующем сэмпле. Сэмпл +погоды остаётся прежним: экранные billboards дождя и снега повторно +проецируются из закэшированных мировых головы и хвоста, без второго шага +симуляции окружения или повторной отправки звуковых событий. Следующий такт +выбирается после успешного представления кадра; пауза нулевого drawable не +добавляет время. ## Элемент отрисовки @@ -117,6 +117,10 @@ RVA `0x13D90`. Эти данные подтверждают только пор путь превью принимает входные данные с матрицей `selector0_words`, полученные через [процедуру захвата нативного кадра](../../tools/native-frame-capture/README.md). +Формат захвата сохраняет локальную матрицу `selector0_words` и, когда она +доступна, записывает `effective_world_matrix_words` с маркером пространства +`world`. + Процедура захвата записывает наблюдавшийся путь миссии и значение `atmosphere_seconds`, если проверка потока, поколения и часов прошла успешно. В превью с фиксированной камерой эта абсолютная фаза расписания используется, @@ -163,14 +167,26 @@ RVA `0x4EBE0` использует соглашение о вызовах `__std события, слушателю задаются захваченные позиция, направление вперёд и верхняя ось; начальная сортировка диапазонов мира использует ту же позицию. +Если в JSON присутствует пара `effective_world_matrix_space: "world"` и +`effective_world_matrix_words`, превью использует мировую матрицу. Матрица +`selector0_words` остаётся локальной и используется как запасной вариант только +при отсутствии мировых полей, если `parented_camera` не равно `true`. Для +родительской камеры без эффективной мировой матрицы вход отклоняется. + Захват может содержать наблюдавшийся путь миссии и проверенное значение `atmosphere_seconds`; последнее используется только при отсутствии явного параметра командной строки. Это значение фиксирует фазу расписания атмосферы -при сравнении с неподвижной камерой, тогда как FX продолжает получать реальное +при сравнении с неподвижной камерой, тогда как FX по умолчанию получает реальное время кадра. `simulation_time_seconds`, погода, RNG и полное состояние миссии остаются неизвестными, поэтому совпадение камеры и фазы не подтверждает визуального совпадения с нативным кадром. +Параметр `--control-clock-ms` воспроизводит единые часы MSH/FX от нуля до +выбранного значения в миллисекундах за указанное число кадров. Без этого +параметра часы идут в реальном времени. Для `--readback-sequence-out` этот +управляемый режим обязателен. Он не восстанавливает +`simulation_time_seconds`, погоду, RNG или полное состояние миссии. + Для неба превью вычисляет направление как `atan2(forward.x, forward.y)`. Оно совпадает со вторым выходным значением, вычисляемым в `Terrain.dll` по RVA `0x4F9C0`; при выборе сектора экранного градиента `CSky` добавляет π по RVA @@ -183,15 +199,9 @@ RVA `0x4EBE0` использует соглашение о вызовах `__std `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 и матрица -камеры применяются при следующей отрисовке. +`2 * atan(tan(horizontal_fov / 2) / a)`. Поэтому горизонтальный угол остаётся +`1.3` рад после изменения размера окна; новый viewport и матрица камеры +применяются при следующей отрисовке. Проекция направления не сравнивает косинус угла с ближней дистанцией: она отбрасывает только лучи за камерой и почти параллельные плоскости изображения @@ -254,6 +264,11 @@ D3D7 в `fparkan-render`, а затем один раз меняет знак в - переход на запасной набор возможностей устройства; - квантование времени симуляции. -Для отладки требуется детерминированный захват кадра: состояние камеры, -идентификаторы видимых объектов, список элементов отрисовки, ключи конвейера, -матрицы и хэши промежуточных буферов. +Параметр `--readback-sequence-out` сохраняет необработанные байты каждого +отправленного кадра; в имя файла записываются индекс кадра, значение общих +управляемых часов и размер изображения. Байты соответствуют выбранному формату +изображения swapchain Vulkan и не гарантируют порядок каналов R, G, B, A. Числовое +формат выводится как `readback_format`; расширение `.rgba` само по себе порядок +каналов не определяет. Последовательность не содержит структурированных ID +видимых объектов, списка элементов отрисовки, ключей конвейера, матриц или +хэшей промежуточных буферов. diff --git a/docs/tomes/05-render.md b/docs/tomes/05-render.md index ef7dc52..212f2ca 100644 --- a/docs/tomes/05-render.md +++ b/docs/tomes/05-render.md @@ -362,30 +362,14 @@ local.set_translation(pose.position); world[n] = world[parent(n)] * local; ``` -Статический Vulkan preview берёт CTLD `.ctl` из явной ссылки прототипа. -Поддерживаемые Control timeline с tag 21 сэмплируются при запуске на native -clock `0`; разрешённые NDP providers передают конечные type-1 значения. -Неподдерживаемый режим или variable mapping, а также отсутствующее или -неразрешённое gate value оставляют компонент на статических CTLD bindings. В -timeline участвуют только включённые строки; при повторном node ID поздняя -строка заменяет раннюю. - -Без `--static-animation-frame` preview сохраняет экземпляр Control и при -каждом redraw сэмплирует поддерживаемый timeline на текущем native clock. -Обновлённая Node38-поза перепроецирует видимую геометрию в заранее выделенный -vertex span, оставляя индексы и draw ranges неизменными. Если в пути Unit DAT -компонента есть такой Control, заново вычисляется его EXTO mount socket и -перепроецируется дочерняя геометрия; дочерний mount использует preorder-порядок -Unit DAT. Внутренние INTO-компоненты здесь не считаются глобальными MSH -socket mounts. Обновлённые вершины и shadow-треугольники попадают в общий -mesh upload перед следующим кадром. Culling sphere восстанавливается от -начальной native sphere и расширяется до текущих shadow-треугольников; это -`shortcut:`, потому что native обновление bounding sphere для анимированной -геометрии пока не установлено. Это путь сэмплирования Control-позы, не AI или -анимация эффектов. - -Явный `--static-animation-frame N` переопределяет timeline и задаёт `N` всем -узлам, пропуская runtime Control-сэмпл. +Статический Vulkan preview сначала строит начальную позу по CTLD `.ctl`. +В ней участвуют только включённые строки; при повторе node ID поздняя строка +заменяет раннюю, а узлы без настройки остаются на кадре `0`. Без +`--static-animation-frame` preview сэмплирует поддержанные Control timelines +при каждом новом сэмпле сцены. Флаг `--static-animation-frame N` задаёт `N` +всем узлам и пропускает runtime Control sample. Это визуальный preview позы, +а не полный Control/AI runtime. + MSH sampler выбирает type 19 frame округлением `(time - 0.5)` к ближайшему целому с ties-to-even, затем интерполирует выбранный type 8 key и следующий key в исходном `time`; это portable-вариант native x87 conversion в default @@ -646,15 +630,15 @@ diffuse texture ломает LOD, atlas coordinates и динамическую Vulkan preview строит проецируемые тени для объектов, отбрасывающих тени, отбрасывает сферы вне шести clip-плоскостей с радиусом `sphere + 100` и сохраняет порядок видимых кандидатов; frame builder обрабатывает не более 20 -объектов. Текущая реализация выделяет страницы в 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: +объектов, отбрасывающих тени. Текущая реализация выделяет страницы в 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. @@ -823,6 +807,39 @@ emit не должен повторно менять игровое состоя типизировать header и resource references, а body редких commands сохранять raw до подтверждения field-level semantics. +В `fparkan-game` выбранные Control FX проходят отдельный transparent path. +Начальная CTL group запускает только строки без predicates, chain или state +gate. Type 4 point list задаёт локальную матрицу; текущие MSH owner и mounted +node poses входят в transform до локальной FX basis. Реализованные visuals +охватывают Op3 modes 0–2, Op4, Op7/8 particle pools и Op9 generated mesh. Op1 +selector 1 добавляет point lights без native flag `0x80000000` в общий diffuse +список; остальные active records сохраняют flags для специальных путей и +projected shadows. Effect renderer подавляет header bit `0x800` только при +render selector 0. Native AniMesh component path захвачен с selector 1, а в +его 53-entry manager присутствуют FX entries с header `0xD00`; preview +использует selector 1 и пропускает такие visual commands. Изменения manager +`A8`, которые задают runtime phase/header gate, пока не моделируются. Header +bit `0x10` относится к activation lifecycle и не выключает draw. + +Для visual effect renderer использует MAT0, указанный самой командой. Его +phase выбирается по normalized FX phase, а не по wall-clock clock материала +мира. FX opacity остаётся в vertex alpha, MAT0 alpha для этой FX binding +фиксируется на единице, texture alpha остаётся в shader. Preview передаёт FX +через SourceAlpha/read-only-depth state. MAT0 phases проходят локальный +eligibility predicate additive-RGB approximation; native `flag40` и +directional-light branch остаются неразрешёнными. + +Op9 генерирует индексированную полусферу с отдельными vertex и UV данными и +отправляет каждый quad и cap как отдельный draw range. Native packet distance +callback берёт первую индексированную вершину (`Terrain.dll:0x246A0` через +`0x247B0`), преобразует её согласно режиму packet и использует евклидово +расстояние до камеры. Queue размещает большие расстояния раньше меньших; +равный ключ вставляется в найденный binary-search midpoint, поэтому порядок +равных packets не стабилен по времени поступления. Preview повторяет этот ключ +и insertion order. Renderer category 0 остаётся highest-detail mesh; native +category вычисляется из projected sphere size и quality table, поэтому +фиксированная категория не является native LOD parity. + ## Полный кадр Крупный вход в world render проходит через `World3D::stdRenderGame`. Доказан @@ -1035,54 +1052,48 @@ WEAR и MAT0 до объединения геометрии. Индексы GPU animation phase; object batches сохраняют их legacy blend/depth/alpha state. После загрузки world path создаёт интерактивную free-flight камеру (`WASD`, -`E/Q`, `Shift`, `Ctrl`, RMB relative-look), обновляет listener и собирает -environment frame для каждого подготовленного кадра. Event loop один раз -сэмплирует камеру, общий animation clock, геометрию и окружение перед запросом -redraw; redraw потребляет это состояние. Если surface требует пересоздать -swapchain, тот же sample времени и состояние мира отправляются повторно. -Перед повторной отправкой projection камеры и viewport-зависимые environment -и shadow geometry обновляются для нового размера без продвижения сцены. -Счётчик и replay clock продвигаются только после результата `Presented`. -После шага движения камера проверяет высоту -`TerrainWorld`: над конечной поверхностью сохраняется начальный просвет, а вне -поверхности высота не ограничивается. - -Для каждого отрисовываемого строения (корневого объекта класса `FORT`) и для -каждого его отрисовываемого компонента создаётся отдельная запись проверки. Она -использует те же текущие матрицы узлов и присоединённых компонентов, что и -геометрия. -После ограничения целевой позиции по TerrainWorld камера проверяет непрерывный -отрезок от прежней позиции до цели; при пересечении она остаётся в прежней -позиции, сохраняя новый поворот. Повторная отправка ожидающего кадра не -применяет движение второй раз. Это ограничение диагностической свободной -камеры, а не правило игрового или следящего режима оригинала. - +`E/Q`, `Shift`, `Ctrl`, RMB relative-look), обновляет listener и на каждом +кадре собирает environment frame. Для каждого отрисовываемого строения +(корневого объекта класса `FORT`) и для каждого его отрисовываемого компонента +создаётся отдельная запись проверки. Она использует те же текущие матрицы узлов +и присоединённых компонентов, что и геометрия. После ограничения целевой +позиции по TerrainWorld камера проверяет непрерывный отрезок от прежней позиции +до цели; при пересечении +она остаётся в прежней позиции, сохраняя новый поворот. Повторная отправка +ожидающего кадра не применяет движение второй раз. Это ограничение +диагностической свободной камеры, а не правило игрового или следящего режима +оригинала. Mission-local `sky.ske`/`sky.wea` выбирают атмосферное расписание и sky materials, `effects.rlb/env_lightning` даёт lightning visual; weather particles, flares, sun/moon, point lights, projected shadows и audio events обновляются вместе с камерой и временем. `--atmosphere-seconds` задаёт старт времени, `--frames 0` оставляет цикл -бесконечным, `--control-clock-ms N` воспроизводит общий clock анимации MSH и -материалов вместе с окружением от нуля до `N` за заданное число кадров и требует -как минимум два кадра. -`--preview-roots` служит только диагностическим ограничителем. +бесконечным, а `--preview-roots` служит только диагностическим ограничителем. `--legacy-camera-capture` выбирает воспроизводимую камеру без free-flight -управления: запись selector 0 задаёт D3D7 transform, clip параметры и native -viewport RECT `[left, top, right, bottom]`. Путь сохраняет оси basis из view -matrix вместе с roll, переносит полученный `clip_from_world` в preview camera, -переводит RECT в физический drawable размер `(right - left) × (bottom - top)` -и отклоняет несовпадение размера при -создании или resize вместо растягивания кадра. Результат нужно сравнивать с -тем же crop исходного PNG `[left, top, right, bottom]`. Контроллер -`FreeFlightCamera` в этом режиме не создаётся. Подготовка кадра обновляет +управления. `selector0_words` содержит локальную матрицу камеры. Если запись +содержит `effective_world_matrix_words` с маркером +`effective_world_matrix_space="world"`, preview использует эту мировую +матрицу. При `parented_camera=true` отсутствие мировой матрицы — ошибка; +локальный selector 0 остаётся legacy fallback только для записей без признака +parented и без мировой матрицы. +Если native helper явно применяет выбранную мировую камеру, setter работает в +mode 2, а restore возвращает исходную локальную матрицу в mode 1. Clip +параметры и native viewport RECT `[left, top, right, bottom]` идут из той же +записи. Путь сохраняет оси basis из view matrix вместе с roll, переносит +полученный `clip_from_world` в preview camera, переводит RECT в физический +drawable размер `(right - left) × (bottom - top)` и отклоняет несовпадение +размера при создании или resize вместо растягивания кадра. Результат нужно +сравнивать с тем же crop исходного PNG `[left, top, right, bottom]`. Контроллер +`FreeFlightCamera` в этом режиме не создаётся, но redraw продолжает обновлять environment и projected shadows от captured camera. Перед environment update и его audio events listener получает position, forward и up captured camera; начальная сортировка world draw использует её же position. Phase capture может содержать наблюдаемый mission path и проверенный `atmosphere_seconds`; viewer использует эту абсолютную фазу по умолчанию, если не указан явный CLI override, -и удерживает sample атмосферы на ней через readback frames. Без -`--control-clock-ms` FX получает реальный frame time; с параметром environment -и материалы используют replay clock. Это не полное simulation time: `weather`, RNG и +и удерживает sample атмосферы на ней через readback frames. Фазы FX следуют +выбранному control clock: без `--control-clock-ms` он обновляется по elapsed +time, а с явным значением readback frames переигрывают заданный clock. Это не +полное simulation time: `weather`, RNG и остальное mission state остаются неизвестными, поэтому совпадение камеры и фазы не доказывает visual parity с native frame. -- cgit v1.2.3