Files
fparkan/docs/specs/fxid.md

203 lines
8.0 KiB
Markdown
Raw Normal View History

# FXID
`FXID` — бинарный формат эффекта в движке Parkan: Iron Strategy.
Эта страница задаёт контракт формата и исполнения на уровне, достаточном для 1:1 порта рендера/симуляции эффектов и для lossless-инструментов.
Связанные контейнеры: [NRes](nres.md), [RsLi](rsli.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`.
- На полном retail-корпусе `testdata/Parkan - Iron Strategy` проверено `923/923` FXID payload без ошибок.
## 12. Статус покрытия и что осталось до 100%
Закрыто:
1. Контейнер FXID, fixed-size командный поток, opcode-покрытие `1..10`.
2. Базовый runtime-контур исполнения эффекта.
3. Корпусная валидация формата на retail-данных.
Осталось:
1. Полная field-level семантика payload каждого opcode для авторинга новых эффектов «с нуля».
2. Формальная спецификация всех `time_mode` веток на уровне точных числовых формул и edge-case поведения.
3. Полный набор пиксельных parity-тестов FX (оригинал vs новый рендер) на фиксированных сценах.