feat(render): integrate Control effects and animated world scene
Docs Deploy / Build and Deploy MkDocs (push) Successful in 37s

This commit is contained in:
Valentin Popov committed 2026-10-31 17:18:47 +04:00
1 parent 4a589f42fc
commit 09fcd35023
7 files changed
+8333 -2570

No files matched your search

+19 -19
View File
@@ -5,8 +5,10 @@
интерактивную сцену выбранной карты: terrain, все доступные MSH-компоненты интерактивную сцену выбранной карты: terrain, все доступные MSH-компоненты
объектов, материалы, динамические тени, небо, погоду, lightning FX и звуковые объектов, материалы, динамические тени, небо, погоду, lightning FX и звуковые
события. Это просмотр карты с полной графической сценой; прохождение миссии и события. Это просмотр карты с полной графической сценой; прохождение миссии и
игровое поведение юнитов пока не запускаются, а модели показываются в статической игровое поведение юнитов пока не запускаются. Модели начинают с настроек узлов
позе, заданной сохранёнными настройками узлов прототипа. прототипа, а поддерживаемые Control-анимации и эффекты обновляются по общим
часам сцены. Неподдерживаемые Control-треки остаются на сохранённой начальной
позе.
## Запуск ## Запуск
@@ -46,23 +48,21 @@ cargo run --release -p fparkan-game -- `
луна, flares, молнии, дождь и снег, point lights, динамические тени и связанные луна, flares, молнии, дождь и снег, point lights, динамические тени и связанные
звуковые события обновляются в каждом кадре. звуковые события обновляются в каждом кадре.
`--frames N` ограничивает запуск числом представленных кадров для `--frames N` ограничивает запуск числом кадров для smoke/readback-проверок;
smoke/readback-проверок; значение `0` означает бесконечный интерактивный запуск. `--legacy-camera-capture`
значение `0` означает бесконечный интерактивный запуск. `--control-clock-ms N` читает сохранённый захват камеры и запускает воспроизводимый режим без
воспроизводит общий clock анимации MSH и материалов вместе с окружением от нуля free-flight управления. В обычном запуске накопленный runtime clock обновляет
до `N` за `--frames` кадров и требует не менее двух кадров. Пересоздание MSH Control-анимацию, поддержанные Control FX и анимацию материалов.
swapchain повторяет текущий sample времени и состояние мира; перед повторной `--control-clock-ms <u32>` вместе с `--frames N` (не меньше двух) воспроизводит
отправкой обновляются camera projection и viewport-зависимая геометрия. Это не этот clock от нуля до заданного миллисекундного значения за выбранные кадры.
продвигает сцену или replay clock. Счётчик и replay clock переходят дальше
после представленного кадра. `--static-animation-frame <u16>` задаёт один кадр для всех MSH-узлов и
`--legacy-camera-capture` читает сохранённый захват камеры и запускает выключает выбор MSH-позы из Control clock. Без override приложение берёт
воспроизводимый режим без начальные настройки узлов из явных `.ctl` bindings, затем применяет
free-flight управления. `--static-animation-frame <u16>` задаёт один кадр для поддержанные Control-треки. Узлы без настройки остаются на кадре `0`. Это не
всех узлов. Если параметр не указан, приложение берёт сохранённые настройки запускает AI, миссионные события или полное поведение юнитов.
узлов из явной ссылки прототипа на `.ctl`; для узлов без такой настройки `--static-material-phase <u16>` фиксирует фазу MSH и terrain материалов; фаза
используется кадр `0`. Статическая поза не запускает игровой Control, AI или материала Control FX остаётся частью его normalized command timeline.
полное поведение юнитов.
`--static-material-phase <u16>` выбирает фиксированную фазу материала.
`--readback-out target/frame.raw` сохраняет последний кадр GPU. `--validation` `--readback-out target/frame.raw` сохраняет последний кадр GPU. `--validation`
включает Vulkan validation layers и завершает запуск с ошибкой при предупреждении включает Vulkan validation layers и завершает запуск с ошибкой при предупреждении
или ошибке. Readback сохраняет байты в формате surface без преобразования; или ошибке. Readback сохраняет байты в формате surface без преобразования;
+1 -3
View File
@@ -14,6 +14,7 @@ fparkan-resource = { path = "../../crates/fparkan-resource" }
fparkan-render = { path = "../../crates/fparkan-render" } fparkan-render = { path = "../../crates/fparkan-render" }
fparkan-platform-winit = { path = "../../adapters/fparkan-platform-winit" } fparkan-platform-winit = { path = "../../adapters/fparkan-platform-winit" }
fparkan-msh = { path = "../../crates/fparkan-msh" } fparkan-msh = { path = "../../crates/fparkan-msh" }
fparkan-material = { path = "../../crates/fparkan-material" }
fparkan-prototype = { path = "../../crates/fparkan-prototype" } fparkan-prototype = { path = "../../crates/fparkan-prototype" }
fparkan-render-vulkan = { path = "../../adapters/fparkan-render-vulkan" } fparkan-render-vulkan = { path = "../../adapters/fparkan-render-vulkan" }
fparkan-runtime = { path = "../../crates/fparkan-runtime" } fparkan-runtime = { path = "../../crates/fparkan-runtime" }
@@ -24,6 +25,3 @@ rodio = { version = "0.23.0", default-features = false, features = ["playback",
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
winit = { version = "0.30", default-features = false, features = ["rwh_06"] } winit = { version = "0.30", default-features = false, features = ["rwh_06"] }
[dev-dependencies]
fparkan-material = { path = "../../crates/fparkan-material" }
+8044 -2405
View File
File diff suppressed because it is too large. Load diff
+101 -46
View File
@@ -236,11 +236,19 @@ packed: [key.colors[10], key.colors[14]],
`AtmosphereFrame` пока не несёт этот бит обновления, поэтому sampler `AtmosphereFrame` пока не несёт этот бит обновления, поэтому sampler
выдаёт интерполированный цвет при каждом запросе. выдаёт интерполированный цвет при каждом запросе.
## Экранная геометрия солнца и луны ## Отрисовка солнца, луны и бликов
`CSun::Render` сначала переводит направление небесного тела в пиксели текущего `CSun::Render` получает текущий viewport, проецирует направление солнца или
viewport, затем собирает quad с native XYZRHW-позицией. При положительной высоте луны и собирает quad уже в его пикселях. `Terrain.dll` VA `0x1004AA40` передаёт
viewport Vulkan пиксели переводятся в clip-space так: эту геометрию через 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 ```rust
position = [ position = [
@@ -250,13 +258,17 @@ position = [
]; ];
``` ```
Vertex shader пропускает `clip_from_world` для строк солнца и луны. CPU каждый Положительная высота Vulkan viewport отображает эти координаты в тот же
кадр пересчитывает центр и размер quad по текущему viewport; старые пиксельные top-down экран. Vertex shader пропускает мировую матрицу только для этого
координаты не переносятся на новый размер. World-space billboards бликов режима:
остаются отдельным путём. Материал экранного quad не освещается, vertex fog
отключён, а sky far-depth bit к нему не применяется.
Для мировой геометрии ```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`: после отсечения оригинал задаёт viewport `MinZ = 0.1`, `MaxZ = 0.99`: после отсечения
проекционная глубина отображается в этот диапазон: проекционная глубина отображается в этот диапазон:
@@ -280,35 +292,39 @@ RVA `0x7170`, участок `0x71E7–0x723C`, копирует MinZ/MaxZ в re
[таблице бинарников](original-binaries.md). Нативные значения этих двух полей [таблице бинарников](original-binaries.md). Нативные значения этих двух полей
имеют биты `0x3DCCCCCD` и `0x3F7D70A4`. имеют биты `0x3DCCCCCD` и `0x3F7D70A4`.
Sky-background bit 4 продолжает помещать слои на дальнюю плоскость. Отдельные
flare rows 5 и 6 пока используют world-space billboard builder: для них не
подтверждён этот же native TL путь.
## Геометрия визуальных FX-команд ## Эффекты Control
`fparkan-fx::fx_visual` формирует кадры для двух подтверждённых вариантов Погодное расписание `sky.ske` — только один источник эффектов. У объектов
`f_gener_ball`. Op4 принимает mode 0 с положительным знаменателем в `body+0xC4` миссии таблица Control (`.ctl`) связывает строки с FXID-командами. Текущий
и выдаёт camera-facing quad; расстояние камеры считается через native preview выбирает начальную группу Control и исполняет только строки без
component-wise projection родительской матрицы. Op9 принимает mode 2: predicate, chain и state gate. Условия игровых событий и переходы между
селектор `body+0xC4` со значением 0, 1 или 2 отображается в native flag base группами принадлежат полному игровому Control runtime и здесь не моделируются.
3, 5 или 6 и выбирает одну из трёх сеток; категория render flags выбирает её
детализацию. `body+0xC8` выбирает UV path:
нулевое значение проецирует позицию вершины на XY, ненулевое использует
фиксированные координаты quad и cap. Кадр сохраняет MAT0 resource, фазу,
opacity, вершины и triangle indices для renderer.
Evaluator получает готовые command body, phase, camera point и внешнюю Type 4 row задаёт локальную basis эффекта через три точки CTL point list. Для
матрицу. Выбор Control row, manager gates, owner attachment, header jitter и валидной привязки первая точка также задаёт owner node. Путь MSH выбирает позу
global RNG остаются задачей владельца. `fparkan-game` пока использует для Op9 этого узла из текущей pose hierarchy; для внешнего mounted-компонента к ней
категорию 0 с наибольшей детализацией; выбор категории по projected size ещё добавляется поза socket. После этого применяются object transform, header
не реализован. scale и локальная матрица FX. Подробнее о порядке матриц см.
[описание MSH](msh.md#control-fx-и-owner-transform).
Для Op3 поддерживаются body-типы 0, 1 и 2. Поля отсчитываются от Renderer preview поддерживает FXID-команды Op3 modes 0–2, Op4, Op7, Op8 и Op9.
`FxCommand.raw_body`, то есть после 4-байтового command word. Типы 0 и 1 Op3 mode0 строит camera-facing поверхность из анимируемых локальных позиции и
используют общие position/scale/phase/opacity tracks и camera-facing основу; масштаба; Op7/Op8 обновляют отдельные элементы particle pool; Op9 создаёт
тип 1 и тип 2 также интерполируют ось, а тип 2 строит basis через native индексированную полусферическую сетку. Op1 вычисляется отдельно как список
D110-порядок. Все три evaluator-а принимают уже собранную матрицу владельца и point lights: selector 1 и записи без `flags & 0x80000000` входят в общий
не выбирают Control row или attachment. Текущий путь для типов 0 и 1 diffuse light set. Записи с этим битом сохраняют native flags для специальных
предполагает native render context 0: `Effect.dll` проверяет общий gate путей и projected shadows, но не добавляются в общий diffuse список.
`context + 0x60 < 8`, а API этих evaluator-ов не выбирает другой context.
Evaluator типа 2 принимает индекс context явно и учитывает этот gate в `active`. 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 ось задаёт длину прямоугольника, а не его нормаль. Для У Op3 mode 1 ось задаёт длину прямоугольника, а не его нормаль. Для
локального вектора к камере `v` и анимируемой оси `a` столбцы базиса имеют локального вектора к камере `v` и анимируемой оси `a` столбцы базиса имеют
@@ -359,27 +375,66 @@ RVA `0x93D0`, ветка mode 1 с `0x9739`, сборка столбцов `0x97
семена отдельных экземпляров лишь детерминированно заменяют process-global семена отдельных экземпляров лишь детерминированно заменяют 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 ## Частицы Op8
`fparkan-fx::smoke` разбирает body Op8, хранит кольцевой пул команды и выдаёт `fparkan-fx::smoke` разбирает body Op8, хранит кольцевой пул команды и выдаёт
quad-ы с ориентацией к камере для активных слотов. Нативная нормализованная quad-ы с ориентацией к камере для активных слотов. Нативная нормализованная
фаза окна ограничивается диапазоном 0–1 и задаёт интервал проверки следующего события; фаза окна ограничивается диапазоном 0–1 и задаёт интервал проверки следующего
сравнение строгое (`schedule_base + interval < update_clock`). Все события события; сравнение строгое (`schedule_base + interval < update_clock`). Все
catch-up в одном обновлении проверяются по тому же нормализованному интервалу; события catch-up в одном обновлении проверяются по тому же нормализованному
после события нативный код продвигает базу расписания интервалом, вычисленным интервалу; после события нативный код продвигает базу расписания интервалом,
по сырой фазе эффекта, а возраст слота считает по нормализованному интервалу. вычисленным по сырой фазе эффекта, а возраст слота считает по нормализованному
Когда фаза равна единственной границе окна, деление `0/0` даёт NaN, который интервалу. Когда фаза равна единственной границе окна, деление `0/0` даёт NaN,
нативный unordered-путь сравнения ограничивает нулём. который нативный unordered-путь сравнения ограничивает нулём.
Нулевая база расписания служит признаком начального состояния: обновление Нулевая база расписания служит признаком начального состояния: обновление
сохраняет текущую опору эмиттера и базу времени, не создавая частицу. Если сохраняет текущую опору эмиттера и базу времени, не создавая частицу. Если
первое время равно нулю, следующее обновление повторно захватывает опору. При первое время равно нулю, следующее обновление повторно захватывает опору. При
событии скорость опоры считается от сохранённой позиции до текущей за время событии скорость опоры считается от сохранённой позиции до текущей за время
от базы расписания; слот смещает сохранённую опору на `скорость * due_interval`. от базы расписания; слот смещает сохранённую опору на `скорость * due_interval`.
Обновление без событий не переносит сохранённую опору к текущему владельцу. При нулевой скорости оригинал подставляет ось X владельца и для направления,
Откат часов корректирует только базу расписания до текущего времени: слоты, и для этого смещения. Обновление без событий не переносит сохранённую опору к
опора и три потока случайных текущему владельцу. Откат часов корректирует только базу расписания до текущего
чисел не сбрасываются. времени: слоты, опора и три потока случайных чисел не сбрасываются.
Evaluator принимает отдельные начальные состояния для эмиттера, знаковых Evaluator принимает отдельные начальные состояния для эмиттера, знаковых
отклонений и материала. Переход каждого потока совпадает с общим native LFSR, отклонений и материала. Переход каждого потока совпадает с общим native LFSR,
+45
View File
@@ -95,6 +95,51 @@ translation суммируется, а rotations умножаются; посл
запускает игровой Control/AI и не обещает полного совпадения с runtime или запускает игровой Control/AI и не обещает полного совпадения с runtime или
всеми x87 rounding modes. всеми 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 <u32>` воспроизводит общий MSH/FX clock линейно: первый из
`--frames` успешно представленных кадров получает `0`, последний — заданное
время. Replay clock привязан к числу представленных кадров: повторная подготовка
того же кадра, включая swapchain recreation, использует прежний sample.
`--static-animation-frame <u16>` отключает динамическую 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
Основные vertex streams имеют фиксированный source index. У type 3 `attr1` Основные vertex streams имеют фиксированный source index. У type 3 `attr1`
+43 -28
View File
@@ -24,22 +24,22 @@
затенения, вызывается завершение рендерера, снимается `in_render`, затем затенения, вызывается завершение рендерера, снимается `in_render`, затем
рассылается `end-of-render`. рассылается `end-of-render`.
При retry кадра после пересоздания swapchain сохраняется прежний pending В интерактивном preview часы материалов и окружения прибавляют полное время
sample, включая уже вычисленные позы и состояния FX и материалов. Для нового между обновлениями. Ограничение шага в 100 мс остаётся у движения свободной
drawable обновляются aspect, проекция и геометрия, зависящая от viewport; pose камеры и не обрезает эти часы. В режиме replay окружение получает разницу
берётся из pending camera. Новые события управления камерой остаются в последних применённых значений общего control clock.
input controller и применяются следующим sample. Экранные billboards погоды
повторно проецируются из закэшированных мировых головы и хвоста, без второго
шага симуляции окружения и повторной отправки звуковых событий. Пауза нулевого
drawable не добавляет время.
Pending frame хранит пакет MAT0 для мира и, при наличии GPU-окружения, отдельный Если swapchain просит повторить ещё не представленный кадр, preview сохраняет
пакет неба. Пакеты содержат выбранные селектор и категорию конвейера, фазовые его общий clock, уже вычисленные позы, FX/RNG и фазы материалов. При изменении
коэффициенты материала, четыре UV-преобразования и кортеж фаз дополнительных drawable обновляются камера, aspect и viewport, после чего sky, environment
текстур. Повторное применение пакета при retry или нескольких resize не выбирает geometry и projected shadows пересобираются для нового размера.
фазу заново и не продвигает RNG. Перед отрисовкой сначала применяются материалы Положение и ориентация камеры берутся из сохранённого состояния
мира и их порядок прозрачности, затем MAT0 неба, после чего viewport-зависимый ожидающего кадра; новый ввод мыши применяется при следующем сэмпле. Сэмпл
проход неба записывает свои переопределения. погоды остаётся прежним: экранные billboards дождя и снега повторно
проецируются из закэшированных мировых головы и хвоста, без второго шага
симуляции окружения или повторной отправки звуковых событий. Следующий такт
выбирается после успешного представления кадра; пауза нулевого drawable не
добавляет время.
## Элемент отрисовки ## Элемент отрисовки
@@ -117,6 +117,10 @@ RVA `0x13D90`. Эти данные подтверждают только пор
путь превью принимает входные данные с матрицей `selector0_words`, полученные через путь превью принимает входные данные с матрицей `selector0_words`, полученные через
[процедуру захвата нативного кадра](../../tools/native-frame-capture/README.md). [процедуру захвата нативного кадра](../../tools/native-frame-capture/README.md).
Формат захвата сохраняет локальную матрицу `selector0_words` и, когда она
доступна, записывает `effective_world_matrix_words` с маркером пространства
`world`.
Процедура захвата записывает наблюдавшийся путь миссии и значение Процедура захвата записывает наблюдавшийся путь миссии и значение
`atmosphere_seconds`, если проверка потока, поколения и часов прошла успешно. `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`; последнее используется только при отсутствии явного `atmosphere_seconds`; последнее используется только при отсутствии явного
параметра командной строки. Это значение фиксирует фазу расписания атмосферы параметра командной строки. Это значение фиксирует фазу расписания атмосферы
при сравнении с неподвижной камерой, тогда как FX продолжает получать реальное при сравнении с неподвижной камерой, тогда как FX по умолчанию получает реальное
время кадра. `simulation_time_seconds`, погода, RNG и полное состояние миссии время кадра. `simulation_time_seconds`, погода, RNG и полное состояние миссии
остаются неизвестными, поэтому совпадение камеры и фазы не подтверждает остаются неизвестными, поэтому совпадение камеры и фазы не подтверждает
визуального совпадения с нативным кадром. визуального совпадения с нативным кадром.
Параметр `--control-clock-ms` воспроизводит единые часы MSH/FX от нуля до
выбранного значения в миллисекундах за указанное число кадров. Без этого
параметра часы идут в реальном времени. Для `--readback-sequence-out` этот
управляемый режим обязателен. Он не восстанавливает
`simulation_time_seconds`, погоду, RNG или полное состояние миссии.
Для неба превью вычисляет направление как `atan2(forward.x, forward.y)`. Оно Для неба превью вычисляет направление как `atan2(forward.x, forward.y)`. Оно
совпадает со вторым выходным значением, вычисляемым в `Terrain.dll` по RVA совпадает со вторым выходным значением, вычисляемым в `Terrain.dll` по RVA
`0x4F9C0`; при выборе сектора экранного градиента `CSky` добавляет π по RVA `0x4F9C0`; при выборе сектора экранного градиента `CSky` добавляет π по RVA
@@ -183,15 +199,9 @@ RVA `0x4EBE0` использует соглашение о вызовах `__std
`1.3` рад — параметры, наблюдавшиеся в проверенном контексте GOG Ngi32 `1.3` рад — параметры, наблюдавшиеся в проверенном контексте GOG Ngi32
([формула native projection](../tomes/05-render.md#камера-и-размещение-в-оригинале)). ([формула native projection](../tomes/05-render.md#камера-и-размещение-в-оригинале)).
Для отношения ширины к высоте `a` превью вычисляет вертикальный угол Для отношения ширины к высоте `a` превью вычисляет вертикальный угол
```rust `2 * atan(tan(horizontal_fov / 2) / a)`. Поэтому горизонтальный угол остаётся
fn vertical_fov(self, aspect: f32) -> f32 { `1.3` рад после изменения размера окна; новый viewport и матрица камеры
2.0 * ((self.horizontal_fov * 0.5).tan() / aspect).atan() применяются при следующей отрисовке.
}
```
Код заменяет невалидный `aspect` на отношение 16:9; горизонтальный угол
остаётся `1.3` рад после изменения размера окна, а новый viewport и матрица
камеры применяются при следующей отрисовке.
Проекция направления не сравнивает косинус угла с ближней дистанцией: она Проекция направления не сравнивает косинус угла с ближней дистанцией: она
отбрасывает только лучи за камерой и почти параллельные плоскости изображения отбрасывает только лучи за камерой и почти параллельные плоскости изображения
@@ -254,6 +264,11 @@ D3D7 в `fparkan-render`, а затем один раз меняет знак в
- переход на запасной набор возможностей устройства; - переход на запасной набор возможностей устройства;
- квантование времени симуляции. - квантование времени симуляции.
Для отладки требуется детерминированный захват кадра: состояние камеры, Параметр `--readback-sequence-out` сохраняет необработанные байты каждого
идентификаторы видимых объектов, список элементов отрисовки, ключи конвейера, отправленного кадра; в имя файла записываются индекс кадра, значение общих
матрицы и хэши промежуточных буферов. управляемых часов и размер изображения. Байты соответствуют выбранному формату
изображения swapchain Vulkan и не гарантируют порядок каналов R, G, B, A. Числовое
формат выводится как `readback_format`; расширение `.rgba` само по себе порядок
каналов не определяет. Последовательность не содержит структурированных ID
видимых объектов, списка элементов отрисовки, ключей конвейера, матриц или
хэшей промежуточных буферов.
+80 -69
View File
@@ -362,30 +362,14 @@ local.set_translation(pose.position);
world[n] = world[parent(n)] * local; world[n] = world[parent(n)] * local;
``` ```
Статический Vulkan preview берёт CTLD `.ctl` из явной ссылки прототипа. Статический Vulkan preview сначала строит начальную позу по CTLD `.ctl`.
Поддерживаемые Control timeline с tag 21 сэмплируются при запуске на native В ней участвуют только включённые строки; при повторе node ID поздняя строка
clock `0`; разрешённые NDP providers передают конечные type-1 значения. заменяет раннюю, а узлы без настройки остаются на кадре `0`. Без
Неподдерживаемый режим или variable mapping, а также отсутствующее или `--static-animation-frame` preview сэмплирует поддержанные Control timelines
неразрешённое gate value оставляют компонент на статических CTLD bindings. В при каждом новом сэмпле сцены. Флаг `--static-animation-frame N` задаёт `N`
timeline участвуют только включённые строки; при повторном node ID поздняя всем узлам и пропускает runtime Control sample. Это визуальный preview позы,
строка заменяет раннюю. а не полный Control/AI runtime.
Без `--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-сэмпл.
MSH sampler выбирает type 19 frame округлением `(time - 0.5)` к ближайшему MSH sampler выбирает type 19 frame округлением `(time - 0.5)` к ближайшему
целому с ties-to-even, затем интерполирует выбранный type 8 key и следующий целому с ties-to-even, затем интерполирует выбранный type 8 key и следующий
key в исходном `time`; это portable-вариант native x87 conversion в default key в исходном `time`; это portable-вариант native x87 conversion в default
@@ -646,15 +630,15 @@ diffuse texture ломает LOD, atlas coordinates и динамическую
Vulkan preview строит проецируемые тени для объектов, отбрасывающих тени, Vulkan preview строит проецируемые тени для объектов, отбрасывающих тени,
отбрасывает сферы вне шести clip-плоскостей с радиусом `sphere + 100` и отбрасывает сферы вне шести clip-плоскостей с радиусом `sphere + 100` и
сохраняет порядок видимых кандидатов; frame builder обрабатывает не более 20 сохраняет порядок видимых кандидатов; frame builder обрабатывает не более 20
объектов. Текущая реализация выделяет страницы в atlas `256 x 256` с 28 page объектов, отбрасывающих тени. Текущая реализация выделяет страницы в atlas
slots и использует одно сглаживание. Preview применяет native defaults `256 x 256` с 28 page slots и использует одно сглаживание. Preview применяет
`RobShadowsOn = true` и `BldShadowsOn = false`: объект kind `4` проверяет native defaults `RobShadowsOn = true` и `BldShadowsOn = false`: объект kind `4`
первый флаг, остальные kinds — второй. Поэтому роботы BTLU остаются объектами, проверяет первый флаг, остальные kinds — второй. Поэтому роботы BTLU остаются
отбрасывающими тени, а объекты FORT и STAT их не создают. Preview пока не объектами, отбрасывающими тени, а объекты FORT и STAT их не создают. Preview
загружает `shade.cfg`, поэтому значения из пользовательского файла не меняют пока не загружает `shade.cfg`, поэтому значения из пользовательского файла не
список объектов, отбрасывающих тени. Проверка действует только на проецируемые меняют список объектов, отбрасывающих тени. Проверка действует только на
тени: отдельный список объектов для затенения солнца по-прежнему включает проецируемые тени: отдельный список объектов для затенения солнца по-прежнему
FORT, BTLU и STAT. Вызов preview передаёт `fade = 1.0` и LOD defaults: включает FORT, BTLU и STAT. Вызов preview передаёт `fade = 1.0` и LOD defaults:
minimum LOD `1` и detail multiplier `1.0` отдельно для зданий и роботов. minimum LOD `1` и detail multiplier `1.0` отдельно для зданий и роботов.
Проекция учитывает вклад directional и point lights. Проекция учитывает вклад directional и point lights.
@@ -823,6 +807,39 @@ emit не должен повторно менять игровое состоя
типизировать header и resource references, а body редких commands сохранять raw типизировать header и resource references, а body редких commands сохранять raw
до подтверждения field-level semantics. до подтверждения 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`. Доказан Крупный вход в world render проходит через `World3D::stdRenderGame`. Доказан
@@ -1035,54 +1052,48 @@ WEAR и MAT0 до объединения геометрии. Индексы GPU
animation phase; object batches сохраняют их legacy blend/depth/alpha state. animation phase; object batches сохраняют их legacy blend/depth/alpha state.
После загрузки world path создаёт интерактивную free-flight камеру (`WASD`, После загрузки world path создаёт интерактивную free-flight камеру (`WASD`,
`E/Q`, `Shift`, `Ctrl`, RMB relative-look), обновляет listener и собирает `E/Q`, `Shift`, `Ctrl`, RMB relative-look), обновляет listener и на каждом
environment frame для каждого подготовленного кадра. Event loop один раз кадре собирает environment frame. Для каждого отрисовываемого строения
сэмплирует камеру, общий animation clock, геометрию и окружение перед запросом (корневого объекта класса `FORT`) и для каждого его отрисовываемого компонента
redraw; redraw потребляет это состояние. Если surface требует пересоздать создаётся отдельная запись проверки. Она использует те же текущие матрицы узлов
swapchain, тот же sample времени и состояние мира отправляются повторно. и присоединённых компонентов, что и геометрия. После ограничения целевой
Перед повторной отправкой projection камеры и viewport-зависимые environment позиции по TerrainWorld камера проверяет непрерывный отрезок от прежней позиции
и shadow geometry обновляются для нового размера без продвижения сцены. до цели; при пересечении
Счётчик и replay clock продвигаются только после результата `Presented`. она остаётся в прежней позиции, сохраняя новый поворот. Повторная отправка
После шага движения камера проверяет высоту ожидающего кадра не применяет движение второй раз. Это ограничение
`TerrainWorld`: над конечной поверхностью сохраняется начальный просвет, а вне диагностической свободной камеры, а не правило игрового или следящего режима
поверхности высота не ограничивается. оригинала.
Для каждого отрисовываемого строения (корневого объекта класса `FORT`) и для
каждого его отрисовываемого компонента создаётся отдельная запись проверки. Она
использует те же текущие матрицы узлов и присоединённых компонентов, что и
геометрия.
После ограничения целевой позиции по TerrainWorld камера проверяет непрерывный
отрезок от прежней позиции до цели; при пересечении она остаётся в прежней
позиции, сохраняя новый поворот. Повторная отправка ожидающего кадра не
применяет движение второй раз. Это ограничение диагностической свободной
камеры, а не правило игрового или следящего режима оригинала.
Mission-local `sky.ske`/`sky.wea` выбирают Mission-local `sky.ske`/`sky.wea` выбирают
атмосферное расписание и sky materials, `effects.rlb/env_lightning` даёт атмосферное расписание и sky materials, `effects.rlb/env_lightning` даёт
lightning visual; weather particles, flares, sun/moon, point lights, lightning visual; weather particles, flares, sun/moon, point lights,
projected shadows и audio events обновляются вместе с камерой и временем. projected shadows и audio events обновляются вместе с камерой и временем.
`--atmosphere-seconds` задаёт старт времени, `--frames 0` оставляет цикл `--atmosphere-seconds` задаёт старт времени, `--frames 0` оставляет цикл
бесконечным, `--control-clock-ms N` воспроизводит общий clock анимации MSH и бесконечным, а `--preview-roots` служит только диагностическим ограничителем.
материалов вместе с окружением от нуля до `N` за заданное число кадров и требует
как минимум два кадра.
`--preview-roots` служит только диагностическим ограничителем.
`--legacy-camera-capture` выбирает воспроизводимую камеру без free-flight `--legacy-camera-capture` выбирает воспроизводимую камеру без free-flight
управления: запись selector 0 задаёт D3D7 transform, clip параметры и native управления. `selector0_words` содержит локальную матрицу камеры. Если запись
viewport RECT `[left, top, right, bottom]`. Путь сохраняет оси basis из view содержит `effective_world_matrix_words` с маркером
matrix вместе с roll, переносит полученный `clip_from_world` в preview camera, `effective_world_matrix_space="world"`, preview использует эту мировую
переводит RECT в физический drawable размер `(right - left) × (bottom - top)` матрицу. При `parented_camera=true` отсутствие мировой матрицы — ошибка;
и отклоняет несовпадение размера при локальный selector 0 остаётся legacy fallback только для записей без признака
создании или resize вместо растягивания кадра. Результат нужно сравнивать с parented и без мировой матрицы.
тем же crop исходного PNG `[left, top, right, bottom]`. Контроллер Если native helper явно применяет выбранную мировую камеру, setter работает в
`FreeFlightCamera` в этом режиме не создаётся. Подготовка кадра обновляет 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 и environment и projected shadows от captured camera. Перед environment update и
его audio events listener получает position, forward и up captured camera; его audio events listener получает position, forward и up captured camera;
начальная сортировка world draw использует её же position. Phase capture может начальная сортировка world draw использует её же position. Phase capture может
содержать наблюдаемый mission path и проверенный `atmosphere_seconds`; viewer содержать наблюдаемый mission path и проверенный `atmosphere_seconds`; viewer
использует эту абсолютную фазу по умолчанию, если не указан явный CLI override, использует эту абсолютную фазу по умолчанию, если не указан явный CLI override,
и удерживает sample атмосферы на ней через readback frames. Без и удерживает sample атмосферы на ней через readback frames. Фазы FX следуют
`--control-clock-ms` FX получает реальный frame time; с параметром environment выбранному control clock: без `--control-clock-ms` он обновляется по elapsed
и материалы используют replay clock. Это не полное simulation time: `weather`, RNG и time, а с явным значением readback frames переигрывают заданный clock. Это не
полное simulation time: `weather`, RNG и
остальное mission state остаются неизвестными, поэтому совпадение камеры и остальное mission state остаются неизвестными, поэтому совпадение камеры и
фазы не доказывает visual parity с native frame. фазы не доказывает visual parity с native frame.