Files
fparkan/docs/specs/fxid.md
Valentin Popov 0d7ae6a017
Some checks failed
Test / Lint (push) Failing after 1m10s
Test / Test (push) Has been skipped
Test / Render parity (push) Has been skipped
Документирование и обновление спецификаций
- Обновлены спецификации `runtime-pipeline`, `sound`, `terrain-map-loading`, `texture`, `ui` и `wear`.
- Добавлены разделы о статусе покрытия и оставшихся задачах для достижения 100% завершенности.
- Внесены уточнения по архитектурным ролям, минимальным контрактам и требованиям к toolchain для каждой подсистемы.
- Уточнены форматы данных и правила взаимодействия между компонентами системы.
2026-02-19 11:07:04 +04:00

8.0 KiB
Raw Permalink Blame History

FXID

FXID — бинарный формат эффекта в движке Parkan: Iron Strategy.
Эта страница задаёт контракт формата и исполнения на уровне, достаточном для 1:1 порта рендера/симуляции эффектов и для lossless-инструментов.

Связанные контейнеры: NRes, RsLi.

1. Контейнер

  • Тип ресурса в NRes: 0x44495846 (FXID).
  • Значения attr1/attr2/attr3 в типовых игровых данных стабильны, но при редактуре их нужно сохранять как есть.

2. Бинарный формат

Все значения little-endian.

2.1. Заголовок (60 байт)

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 используется ссылка:

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 новый рендер) на фиксированных сценах.