# FXID `FXID` — бинарный формат эффекта в движке Parkan: Iron Strategy. Эта страница задаёт контракт формата и исполнения на уровне, достаточном для 1:1 порта рендера/симуляции эффектов и для lossless-инструментов. Связанный контейнер: [NRes / RsLi](nres.md). ## 1. Контейнер - Тип ресурса в `NRes`: `0x44495846` (`FXID`). - Значения `attr1/attr2/attr3` в типовых игровых данных стабильны, но при редактуре их нужно сохранять как есть. ## 2. Бинарный формат Все значения little-endian. ### 2.1. Заголовок (60 байт) ```c struct FxHeader60 { uint32_t cmd_count; // 0x00 uint32_t time_mode; // 0x04 float duration_sec; // 0x08 float phase_jitter; // 0x0C uint32_t flags; // 0x10 uint32_t settings_id; // 0x14 float rand_shift_x; // 0x18 float rand_shift_y; // 0x1C float rand_shift_z; // 0x20 float pivot_x; // 0x24 float pivot_y; // 0x28 float pivot_z; // 0x2C float scale_x; // 0x30 float scale_y; // 0x34 float scale_z; // 0x38 }; ``` Поток команд начинается строго с `offset = 0x3C`. ### 2.2. Команда Каждая команда: 1. `uint32 cmd_word` 2. body фиксированного размера, зависящего от `opcode` Поля `cmd_word`: - `opcode = cmd_word & 0xFF` - `enabled = (cmd_word >> 8) & 1` - `bits 9..31` нужно сохранять 1:1 Выравнивания между командами нет. ### 2.3. Размеры команд | Opcode | Размер | |---:|---:| | 1 | 224 | | 2 | 148 | | 3 | 200 | | 4 | 204 | | 5 | 112 | | 6 | 4 | | 7 | 208 | | 8 | 248 | | 9 | 208 | | 10 | 208 | ## 3. Смысл заголовка - `cmd_count`: число команд в потоке. - `time_mode`: способ вычисления текущего коэффициента эффекта. - `duration_sec`: длительность (в рантайме переводится в миллисекунды). - `phase_jitter`: амплитуда случайного фазового сдвига. - `flags`: флаги поведения (видимость, альфа-модификаторы, режимы гейтинга). - `settings_id`: индекс профиля/настроек эффекта. - `rand_shift_*`: случайный пространственный сдвиг. - `pivot_*`: локальная опора. - `scale_*`: базовый масштаб инстанса эффекта. ## 4. Флаги заголовка Практически важные биты: - `0x0001`: случайный сдвиг фазы - `0x0008`: случайный пространственный сдвиг (`rand_shift_*`) - `0x0010`: ветки видимости/окклюзии - `0x0020`: треугольный ремап альфы - `0x0040`: инверсия исходного active-state - `0x0080`, `0x0100`: фильтрация по времени суток - `0x0200`: умножение альфы на нормализованное время жизни - `0x0400`, `0x1000`: дополнительные биты состояния менеджера эффекта - `0x0800`: дополнительный гейтинг Неизвестные биты должны сохраняться без изменений. ## 5. `time_mode` (0..17) База: - `tn = (now - start) / (end - start)` - `prev = предыдущая вычисленная альфа` Поддерживаемые семейства режимов: - константный режим; - линейный (`tn`), обратный (`1-tn`), циклический (`fract(tn)`); - режимы от внешних параметров мира/очереди; - режимы на основе норм векторов состояния; - режимы с ограничением вниз/вверх относительно `prev`. После вычисления: - при `flags & 0x0200` применяется `alpha *= tn`; - при `flags & 0x0020` применяется triangular remap. ## 6. Resource-ссылки внутри команд Для opcode `2/3/4/5/7/8/9/10` используется ссылка: ```c struct ResourceRef64 { char archive[32]; char name[32]; }; ``` Контракт: - строки ASCII, нуль-терминированные; - сравнение имён регистронезависимое; - обычно: - `opcode 2`: `sounds.lib` + `*.wav` - остальные: `material.lib` + имя материала/эффекта. ## 7. Runtime-контракт исполнения На создании инстанса: 1. Заголовок копируется в runtime-состояние. 2. Вычисляется `end_time`. 3. Для каждой команды создаётся runtime-объект по `opcode`. 4. В объект копируется `enabled`. 5. Объект инициализируется контекстом эффекта. На каждом кадре: 1. Вычисляется текущий коэффициент/альфа по `time_mode` и `flags`. 2. Выполняется update каждой команды. 3. Выполняется emit/render часть активных команд. 4. Применяются события Start/Stop/Restart. ## 8. Строгий парсер (рекомендуемый) 1. Проверить `len(payload) >= 60`. 2. Прочитать `cmd_count`. 3. Идти от `ptr = 0x3C`. 4. Для каждой команды: - проверить `ptr + 4 <= len`; - прочитать `opcode`; - проверить, что `opcode` поддержан; - проверить `ptr + size(opcode) <= len`; - сдвинуть `ptr += size(opcode)`. 5. Проверить `ptr == len(payload)`. ## 9. Writer и редактор Для lossless-совместимости: - сохранять все неизвестные поля/биты; - не менять фиксированные размеры команд; - не добавлять padding; - пересчитывать только `cmd_count` и размеры контейнера; - сохранять порядок команд. ## 10. Что требуется для 1:1 переноса 1. Полная поддержка opcode `1..10`. 2. Точный контракт вычисления `time_mode` и `flags`. 3. Точное поведение `ResourceRef64`. 4. Повторяемый RNG и одинаковая политика плавающей точки. ## 11. Статус валидации - Формальные инварианты FXID зафиксированы в `tools/msh_doc_validator.py` и `tools/fxid_abs100_audit.py`. - В текущем рабочем окружении нет полного набора игровых архивов (`testdata` без payload), поэтому массовая повторная проверка корпуса здесь не выполнялась.