diff options
Diffstat (limited to 'docs/tomes')
| -rw-r--r-- | docs/tomes/01-guide.md | 371 | ||||
| -rw-r--r-- | docs/tomes/02-architecture.md | 472 | ||||
| -rw-r--r-- | docs/tomes/03-resources.md | 561 | ||||
| -rw-r--r-- | docs/tomes/04-world.md | 648 | ||||
| -rw-r--r-- | docs/tomes/05-render.md | 863 | ||||
| -rw-r--r-- | docs/tomes/06-behavior.md | 769 | ||||
| -rw-r--r-- | docs/tomes/07-implementation.md | 674 | ||||
| -rw-r--r-- | docs/tomes/08-evidence.md | 1087 |
8 files changed, 5445 insertions, 0 deletions
diff --git a/docs/tomes/01-guide.md b/docs/tomes/01-guide.md new file mode 100644 index 0000000..cc4e4c0 --- /dev/null +++ b/docs/tomes/01-guide.md @@ -0,0 +1,371 @@ +# I. Путеводитель и методика + +Первый том задаёт язык и правила всей документации. Он объясняет, как читать +технические главы, какие термины используются для игрового runtime, как +разделяются уровни уверенности и какие требования предъявляются к реализации, +которая должна работать с оригинальными данными без потери информации. + +Документация рассчитана на разработчика, который уже умеет читать C/C++, +байтовые форматы, PE-модули и графические pipeline, но не обязательно знаком с +Iron3D. Поэтому этот том не описывает один конкретный crate, package или +физическое деление будущего кода. Он фиксирует контракты: что должно быть +прочитано, сохранено, рассчитано и показано. + +## Назначение книги + +Книга ведёт от общей архитектуры Iron3D к точным форматам данных и алгоритмам +исполнения. Практическая цель -- реализация, способная открыть оригинальный +каталог *Parkan: Iron Strategy*, загрузить миссию, создать мир, провести +игровой шаг и сформировать кадр. + +Форматы в главах описываются как байтовые контракты. Если указано поле +`+0x10`, это означает расположение в потоке или структуре данных, а не +разрешение читать файл прямым `reinterpret_cast`. Для постоянных layouts +используются offsets, проверки размеров, bounded cursor и явное сохранение +неизвестных байтов. Для versioned и variable-length записей приоритет имеет +последовательный parser с контролем границ. + +Игровое поведение описывается не только размером структур. Совместимая +реализация должна учитывать порядок событий, время, fallback-правила, +идентификаторы объектов, численные ограничения, состояние материалов, +границы кадра и правила завершения операций. + +## Маршруты чтения + +**Читатель, новый для игровой разработки**, начинает с базовых понятий этого +тома, затем переходит к архитектуре, игровому циклу и вводу в рендер. После +этого имеет смысл читать главы о миссиях, мире и ресурсных форматах. + +**Разработчик совместимого движка** читает тома II-VII линейно. Технические +главы имеют одинаковую логику: назначение подсистемы, данные на диске, +представление в памяти, алгоритм работы, проверки и требования к новой +реализации. + +**Аналитик оригинальной программы** использует этот том вместе с разделами о +доказательной базе, ABI, результатах корпусных проверок и границах знания. +Факты, согласованные выводы и открытые вопросы должны оставаться разделёнными: +это позволяет расширять реализацию без подмены проверенных контрактов +удобными догадками. + +## Состав документации + +1. **Путеводитель и методика** -- язык предметной области, правила чтения и + процедура проверки. +2. [**Запуск, архитектура и игровой цикл**](02-architecture.md) -- от + `iron_3d.exe` до расчёта и вывода кадра. +3. [**Ресурсная система и форматы**](03-resources.md) -- архивы, кэши, реестры + и служебные данные. +4. [**Мир, миссии и игровой runtime**](04-world.md) -- TMA, ландшафт, ареалы и + создание объектов. +5. [**Геометрия, материалы и рендер**](05-render.md) -- от вершины модели до + изображения на экране. +6. [**Поведение, управление, звук и сеть**](06-behavior.md) -- интерактивные + подсистемы. +7. [**Руководство по полной реализации**](07-implementation.md) -- предлагаемая + архитектура и порядок работ. +8. [**Справочник и доказательная база**](08-evidence.md) -- ABI, + конфигурация, статистика и открытые вопросы. + +Дополнительные краткие определения собраны в +[глоссарии](../appendices/glossary.md). Технические области, где контракт ещё +не закрыт полностью, перечислены в +[границах знания](../appendices/knowledge-boundaries.md). + +## Условные обозначения + +`+0x10` означает смещение поля относительно начала структуры или записи. +`RVA 0x13B60` -- адрес относительно базы PE-модуля. `u16`, `u32`, `i16` и +`float32` обозначают типы фиксированной ширины. `LE` означает little-endian. +`payload` -- полезные данные записи после метаданных контейнера. `EOF` -- точное +завершение файла или ограниченного блока. + +Если в тексте указан hash, RVA или ordinal, значение относится к явно +обозначенному binary profile. Адреса разных сборок не объединяются по имени +функции. При публикации функции нужны минимум модуль, SHA-256 сборки и RVA. + +Размеры структур выражаются в байтах. Счётчики и offsets считаются частью +формата, даже когда их можно восстановить из длины файла. Padding, reserved +поля, неизвестные хвосты и gaps не нормализуются без доказанного правила. + +## Совместимость + +Слово "совместимость" в этой книге имеет несколько уровней. + +**Reader** умеет открыть файл, проверить границы, извлечь известные поля и +сохранить неизвестные bytes так, чтобы данные можно было записать обратно. + +**Viewer** умеет показать ресурс: модель, texture, material, эффект или карту. +Viewer может быть полезен для анализа, но он не доказывает поведение runtime. + +**Runtime** умеет создать мир, зарегистрировать объекты, исполнять события, +обновлять время, применять контроллеры, выбирать видимое состояние и передавать +его рендеру. + +**Полноценный движок** дополнительно воспроизводит порядок операций, численные +правила, fallback-поведение, resource lifetime, reference ownership, pause, +manual input, сетевые идентификаторы, boundaries кадра и состояние +интерактивных подсистем. + +Поэтому файл может быть "прочитан правильно", но всё ещё не быть реализованным +на уровне движка. Например, reader MSH может восстановить вершины и индексы, +viewer может нарисовать mesh, а runtime обязан ещё сохранить material slots, +animation state, bounds, LOD, visibility, collision и связи с объектом мира. + +## Движок как программа длительного действия + +Обычная прикладная программа получает запрос, вычисляет результат и заканчивает +работу. Игра живёт в цикле: прочитать ввод, обновить состояние мира, +сформировать звук и изображение, показать кадр и повторить. Движок -- набор +подсистем и соглашений, которые делают этот цикл устойчивым. + +**Simulation** отвечает на вопрос "что произошло в мире": куда переместился +объект, кого он видит, сколько у него здоровья, сработал ли эффект, изменился +ли маршрут или приказ. **Rendering** отвечает на другой вопрос: "как текущее +состояние показать". В корректной архитектуре рендер не решает игровые правила, +а читает подготовленное состояние. + +**Tick** -- один шаг расчёта. **Frame** -- одно изображение. Они могут +выполняться с разной частотой: игра способна рассчитать несколько шагов между +двумя показами или временно не рисовать, не останавливая логику. Поэтому время, +накопление input, порядок callbacks и момент удаления объектов считаются частью +контракта. + +## Мир, сцена и объект + +**Мир** -- долгоживущее состояние миссии: ландшафт, объекты, время, погода, +принадлежность к кланам и глобальные сервисы. **Сцена** -- представление той +части мира, которую можно обработать для текущей камеры. **Игровой объект** -- +сущность с идентификатором, положением, набором свойств и поведением. + +В Iron3D объектами управляет World3D. Объекты регистрируются в общей очереди, +получают события, участвуют в расчёте и могут быть удалены отложенно, чтобы не +разрушить обход коллекции посреди шага. Это важнее, чем конкретный контейнер в +новой реализации: совместимость определяется моментом наблюдаемого добавления, +обновления и удаления. + +Мир не равен renderer scene graph. Один объект может иметь runtime state, +controller, сетевой mirror, визуальную модель, collision bounds и script state. +Часть этих данных нужна для gameplay, часть -- для вывода, часть -- для +сохранения и воспроизведения. + +## Ресурс, модель и материал + +**Ресурс** -- именованный блок данных, который можно найти и загрузить. Архивы +`NRes` и `RsLi` содержат таблицы таких блоков. Имя, индекс, размер, offset, +compression method и fallback-правило являются частью контракта загрузки. + +**Модель** описывает форму объекта. Она состоит из вершин, индексов, узлов, +групп треугольников, слотов материалов и auxiliary streams. **Vertex** хранит +положение и обычно дополнительные атрибуты: нормаль для освещения и +UV-координату для выборки texture. **Triangle** -- три вершины, образующие +примитив. **Index buffer** хранит номера вершин и позволяет переиспользовать их +между треугольниками. **Batch** -- непрерывный диапазон индексов, который +рисуется одним материалом и одним набором состояний. + +**Материал** описывает способ отображения поверхности: texture references, +цвет, прозрачность, режимы смешивания и анимацию параметров. **Texture** -- +изображение в памяти графической системы. **Mip-уровни** -- уменьшенные копии +изображения для дальних объектов. **Lightmap** -- дополнительная texture с +заранее рассчитанным освещением. + +Runtime должен связывать эти уровни по цепочке: миссия выбирает объект, объект +ссылается на prototype, prototype приводит к модели, модель -- к WEAR, +материалам, textures и lightmaps. Ошибка на любом участке этой цепочки может +не проявиться в parser-е, но проявится в игровом кадре. + +## Пространственные понятия + +**Transform** переводит точку из локальных координат модели в координаты мира, +камеры и экрана. **Иерархия узлов** позволяет одному элементу наследовать +движение другого. **LOD** выбирает менее подробную геометрию вдали. **Culling** +отбрасывает то, что не видно. **Bounds** -- упрощённая оболочка объекта, +обычно сфера или AABB, используемая для быстрых тестов. + +**Collision** отвечает на геометрические пересечения. **Navigation** ищет +допустимый маршрут. В Iron3D эти задачи разделены: Control обслуживает +физическую модель и столкновения, а ArealMap хранит пространственные области и +связи между ними. + +Важно не смешивать визуальные и игровые упрощения. Render bounds могут быть +достаточны для отсечения, но не обязаны совпадать с collision shape. Навигация +может использовать areal graph, который не является ни mesh-ем модели, ни +геометрией ландшафта в renderer-е. + +## Графический конвейер + +Процессор выбирает видимые объекты, готовит матрицы, материалы и списки +примитивов. Графический backend передаёт вершины, индексы, textures и state +драйверу. Видеокарта преобразует вершины в координаты экрана, разбивает +треугольники на фрагменты, проверяет глубину, смешивает цвет и записывает +результат в буфер кадра. После завершения буфер становится видимым +пользователю. + +Для совместимости важны не только данные draw call. Контракт включает frame +boundaries, viewport, camera state, порядок world traversal, material resolve, +shadow/transparent/FX subpasses, завершение renderer-а, восстановление state и +callbacks после рендера. Если часть имён vtable slots ещё не доказана, новая +реализация должна фиксировать крупный порядок операций и оставлять +детализацию проверяемой. + +## Практический словарь реализации + +**Handle** -- компактная ссылка на управляемый объект. **Cache** -- сохранённый +результат загрузки или декодирования. **Reference count** -- число владельцев +ресурса. **Fallback** -- предписанный запасной вариант при отсутствии данных. +**Invariant** -- условие, которое всегда должно быть истинным для корректного +файла или runtime-состояния. **Determinism** -- повторяемость результата при +одинаковых входных данных и порядке событий. + +**Strict mode** -- режим parser-а, который принимает только корректный файл: +верные magic, версии, размеры, ranges, индексы и точный EOF. **Lossless mode** +-- режим чтения/записи, который сохраняет неизвестные поля, padding, gaps и raw +payload без нормализации. **Quirk** -- именованное отклонение, разрешённое +только после проверки на реальных данных или исполняемом коде. + +Эти слова используются как технические термины. Если глава называет значение +fallback-ом, invariant-ом или quirk-ом, это должно иметь проверяемое +последствие в reader-е, writer-е или runtime. + +## Как читать C/C++-схемы структур + +Структуры в главах описывают байтовый layout, а не переносимый C++ object +model. Если поля на диске идут без padding, reader должен читать их по offsets +либо использовать явно проверенный packed layout. Прямое отображение native +struct допустимо только при доказанном размере, выравнивании и endian-правиле. + +`sizeof` обязательно проверяется `static_assert` или эквивалентным compile-time +test. Это особенно важно для records, где 32-битное поле начинается после +нечётного числа 16-битных или 8-битных полей: стандартное выравнивание +современного compiler-а может вставить скрытые bytes и изменить offsets. + +Для variable-length форматов предпочтителен bounded cursor: + +1. Прочитать header и проверить минимальный размер. +2. Проверить, что offsets и sizes лежат внутри текущего блока. +3. Прочитать таблицы до объявленного count, не до "пока получается". +4. Проверить ссылки между таблицами. +5. Дойти до точного EOF или сохранить явно разрешённый trailing payload. + +Writer пересчитывает только производные значения: размеры, offsets, число +записей, сортировочные таблицы и padding, если правило доказано. Unknown fields +и reserved ranges сохраняются побайтно. + +## Иерархия доказательств + +Документация использует четыре уровня уверенности. + +**Прямое наблюдение** -- поле, значение или последовательность видны в +инструкции программы, таблице PE, экспорте, строке, обработчике файла или в +самом ресурсе. Это самый сильный уровень. + +**Корпусное подтверждение** -- правило проверено на всех подходящих файлах +одного или нескольких явно названных наборов: демоверсии, Части 1 и Части 2. +Например, базовый корпус содержит 435 моделей MSH, 518 textures Texm и 923 +эффекта FXID, прошедших структурные проверки без ошибок; полные части расширяют +эту матрицу вариантов. + +**Согласованный вывод** -- назначение восстановлено по нескольким независимым +признакам: вызывающим функциям, vtable slots, строкам ошибок, диапазонам +значений и связям между форматами. Такой вывод пригоден для реализации, но его +численные детали следует проверять тестами. + +**Открытый вопрос** -- данные можно читать и сохранять, однако предметный смысл +поля или редкой ветки не доказан. Такие bytes нельзя обнулять, +переупорядочивать или превращать в authoring API. + +Уровень уверенности должен быть виден из формулировки. "Поле равно" означает +проверенный layout или значение. "Вероятно отвечает за" означает согласованный +вывод. "Неизвестно" означает сохранять без изменения и не строить вокруг этого +публичный контракт. + +## Проверенные материалы + +Локальный набор проверки включает демоверсию, полные каталоги Частей 1 и 2, +исполняемые файлы, 15 DLL каждой сборки и игровые ресурсы. DLL из +первоначального архива и DLL демоверсии совпали по SHA-256: `15/15`, поэтому +выводы по этому коду и demo-ресурсам образуют один доказательный профиль. + +Исполняемый файл демоверсии `iron_3d.exe` имеет размер 36 864 байта, PE32/x86, +entry RVA `0x141E`, image base `0x400000` и SHA-256 +`b0a8b0db1c3a8698c4d4604d89c655496bd91ac1f8859a455e8a45838aebfbd6`. + +Исполняемые файлы Частей 1 и 2 также имеют размер 36 864 байта и побайтно +совпадают между собой, но относятся к другому binary profile: entry RVA +`0x147E`, SHA-256 +`f476af85c034a4b4f34f49d0806e4dff397b5da0ee26d382a7674231144979f7`. + +Полные каталоги Частей 1 и 2 суммарно включают 60 TMA, 1 101 unit DAT, 254 +NRes-файла и 14 975 NRes entries. Все контейнеры и TMA прошли bounded parser до +точного EOF; полный достижимый граф обеих частей разрешился без ошибок. + +## Процедура проверки + +Проверка строится как воспроизводимая цепочка: + +1. Снять PE-метаданные, хэши, импорты, экспорты, ordinals, RTTI и строки. +2. Построить граф вызовов между модулями и отметить фабрики подсистем. +3. Разобрать функции запуска, загрузчики файлов, главный цикл и критические + vtable-вызовы. +4. Проверить форматы независимыми reader-скриптами с контролем границ и точного + завершения файла. +5. Построить цепочку миссия -> объект -> прототип -> модель -> материал -> + texture. +6. Сравнить счётчики, диапазоны, ссылки и размеры на всём доступном корпусе. + +Ключевой результат сквозной проверки демо-миссий: все 201 объектов шести +миссий разрешились в 501 запрос прототипов, затем в 501 модель, 501 таблицу +WEAR, 3 879 слотов материалов и 5 085 ссылок на textures или lightmaps. Ошибок +в фактически исполняемом пути нет. + +## Что не считается доказательством + +Удобное имя поля не доказывает его назначение. Совпадение layout с текущей +реализацией не доказывает поведение оригинального runtime. Успешный viewer не +доказывает writer. Успешный reader одного файла не доказывает формат всего +корпуса. Совпадение ABI не доказывает побайтную идентичность всех сборок. + +Если локальные данные и предположение расходятся, приоритет имеют исполняемый +код, реальные ресурсы и взаимные invariants между форматами. Неизвестное поле +лучше оставить без имени, чем дать ему ложное предметное значение. + +## Требования к воспроизводимости + +Каждая новая реализация должна иметь strict parser mode, lossless roundtrip +mode и набор corpus tests. Неизвестные поля сохраняются побайтно. Любое +присвоенное полю имя должно сопровождаться наблюдаемым поведением или тестом. +Численные правила -- округление, порядок умножения, RNG и время -- считаются +частью формата исполнения, даже если файл читается правильно. + +Минимальный отчёт проверки должен фиксировать: + +1. build profile и hashes модулей; +2. путь или ключ ресурса; +3. размер входного файла и hash входных bytes; +4. версию parser-а или commit реализации; +5. список включённых quirks; +6. число прочитанных записей и точку EOF; +7. ошибки, предупреждения и unknown ranges; +8. результат roundtrip, если writer участвует в проверке. + +Для runtime-проверок дополнительно нужны mission key, configuration, device +profile, начальное состояние, input/time script и trace значимых callbacks. + +## Разделение профилей + +Binary profile описывает исполняемый код: PE-метаданные, exports/imports, +ordinals, hashes, RVA и layout функций. Corpus profile описывает набор файлов: +каталог, миссии, ресурсы, размеры, counts, variants и статистику parser-а. + +Эти профили нельзя смешивать без явной пометки. Один и тот же формат может +иметь общий смысл в разных сборках, но отличаться редкими ветками, адресами +функций или набором встреченных вариантов. Один и тот же address может иметь +смысл только внутри конкретного module hash. + +При расширении документации новое утверждение должно отвечать на три вопроса: + +1. Где это видно напрямую? +2. На каком корпусе это проверено? +3. Что должна сделать реализация, если правило нарушено? + +Если на один из вопросов нет ответа, утверждение остаётся согласованным выводом +или открытым вопросом, а не закрытым контрактом. diff --git a/docs/tomes/02-architecture.md b/docs/tomes/02-architecture.md new file mode 100644 index 0000000..b44bd51 --- /dev/null +++ b/docs/tomes/02-architecture.md @@ -0,0 +1,472 @@ +# II. Запуск, архитектура и игровой цикл + +Этот том описывает путь от запуска `iron_3d.exe` до устойчивого кадра: +загрузку `iron3d.dll`, создание shell/game objects, поднятие платформенных +сервисов, запуск World3D, расчёт simulation step, безопасное удаление объектов, +рендер и завершение программы. + +Главная особенность Iron3D -- это не один монолитный engine object, а связка +небольшого Win32 bootstrap и набора DLL, которые обмениваются фабриками, +singleton-интерфейсами и C++ vtable. Совместимая реализация может изменить +физическое деление на библиотеки, но не может произвольно менять порядок +инициализации, object identity, правила владения, fallback ресурсов и порядок +событий. + +```text +iron_3d.exe + -> iron3d.dll + -> services.dll + -> World3D.dll + -> Terrain.dll + -> Ngi32.dll + -> AniMesh.dll / ArealMap.dll / Effect.dll + -> ai.dll / Behavior.dll / Wizard.dll + -> Control.dll / MisLoad.dll / Net.dll / Joystick.dll +``` + +## Карта модулей + +Во внешней архитектуре обнаружено пятнадцать DLL. Экспортов сравнительно мало: +они обычно создают объект, возвращают singleton или дают доступ к уже поднятой +подсистеме. Основная работа выполняется через C++-интерфейсы, поэтому порядок +виртуальных слотов является частью ABI, особенно для compatibility shim эпохи +MSVC6. + +```text +iron_3d.exe + | + v +iron3d.dll -- композиция игры, shell и главный цикл + | + +-- services.dll -- доступ к display, GUI, ресурсам, звуку, таймеру и сети + +-- World3D.dll -- объекты, очередь, время, камера и кадр + +-- Terrain.dll -- ландшафт, свет, атмосфера и визуальный слой мира + +-- ai.dll / Behavior.dll / Wizard.dll + +-- Control.dll / Effect.dll / MisLoad.dll + +-- Net.dll / Joystick.dll + +-- Ngi32.dll -- ресурсы, графика, звук, математика и CPU dispatch +``` + +Циклы импортов между DLL ожидаемы. Terrain создаёт визуальные объекты и +обращается к World3D, а World3D получает world-interface из Terrain. Это не +значит, что обе библиотеки совместно владеют всем состоянием. Реальные границы +задаются интерфейсами, refcount, очередью объектов и порядком shutdown. + +Практичная новая структура может быть внутренним набором модулей `platform`, +`resources`, `world`, `mission`, `terrain`, `render`, `animation`, `effects`, +`behavior`, `physics`, `audio` и `network`. Важно сохранить не DLL-границы, а +контракты: имена ресурсов, порядок поиска, fallback-ветки, object ID, момент +создания mirror objects, численное поведение и последовательность событий. + +## Роли модулей + +`iron3d.dll` создаёт shell и game objects, читает `iron_3d.ini`, поднимает +display, sound, CD-audio, network и настройки World3D, загружает миссионные и +UI-конфигурации, содержит message pump и вызывает расчёт/рендер игры. + +`services.dll` работает как service locator. Через него запрашиваются display, +GUI, network manager, resource manager, sound server и timer. Этот слой отделяет +высокоуровневую игру от деталей создания устройств. + +`World3D.dll` -- центральный runtime: очередь объектов, идентификаторы, +события, отложенное удаление, game time, pause, manual input, камера, +material/texture/lightmap managers, сетевые mirrors, расчёт и 3D-проход. + +`Terrain.dll` отвечает не только за землю. В его область входят ландшафт, +здания, визуальный слой мира, камера, shade/state layer, primitive buffers, +сортировочные слои, источники света, тени, microtextures, атмосфера, дождь, +молнии, солнце и flares. + +`Ngi32.dll` содержит низкоуровневые сервисы: DirectDraw/Direct3D-era renderer, +DirectSound, readers `NRes`/`RsLi`, память, часы, математику, пересечения, +определение CPU и таблицу быстрых процедур `g_FastProc`. + +Предметные DLL закрывают отдельные области. `AniMesh.dll` загружает модели и +агентов. `ArealMap.dll` строит spatial graph и маршруты. `Behavior.dll` +реализует поведение юнитов. `ai.dll` содержит стратегический AI и миссионные +сценарии. `Wizard.dll` корректирует локальное движение. `Control.dll` +обслуживает физическую модель и столкновения. `Effect.dll` создаёт runtime-FX. +`MisLoad.dll` читает миссионные данные. `Net.dll` инкапсулирует DirectPlay. +`Joystick.dll` работает через DirectInput. + +## Поток данных + +Миссия не создаёт готовый кадр напрямую. Данные проходят через несколько +уровней: описание объекта, прототипы, ресурсы, runtime-object, контроллеры, +simulation state, render items и только затем платформенный renderer. + +```text +mission data + -> object identity and properties + -> prototype registry + -> model/material/texture/effect resources + -> World3D object + domain controllers + -> simulation state + -> visible render items + -> Ngi32 render interface + -> DirectX-era device +``` + +Этот поток объясняет, почему нельзя объединять физический архив, metadata entry, +декодированный payload и готовый runtime-кэш. У каждого уровня свой срок жизни, +собственный refcount и собственные ошибки. Детали ресурсного конвейера описаны +в [Томе III](03-resources.md), а сборка мира из миссии -- в [Томе IV](04-world.md). + +## Bootstrap + +`iron_3d.exe` -- небольшой PE32/x86 bootstrap размером 36 864 байта. Основная +игровая логика находится в `iron3d.dll`. Исполняемый файл создаёт Win32-процесс, +подготавливает окружение, загружает библиотеку и получает восемь публичных +точек входа: + +```text +createShell deleteShell +createGame deleteGame +createSubsystems deleteSubsystems +getIGame getIShell +``` + +Эти функции образуют внешнюю границу игры. `createShell` создаёт оболочку +интерфейса и меню, `createGame` -- объект игровой логики, `createSubsystems` -- +аппаратные и runtime-сервисы. Getter-функции возвращают уже созданные объекты. + +Запуск удобно читать как конечный автомат: + +```text +PROCESS_CREATED + -> LIBRARY_READY + -> ENTRYPOINTS_READY + -> SHELL_CREATED + -> GAME_CREATED + -> SUBSYSTEMS_READY + -> MAIN_LOOP + -> SUBSYSTEMS_CLOSED + -> GAME_DELETED + -> SHELL_DELETED +``` + +Каждый переход имеет обратное действие. Если display, sound или другой +обязательный сервис не создан, главный цикл не начинается, но уже созданные +объекты освобождаются в обратном порядке. Новая оболочка запуска должна +работать из каталога оригинальной установки, сохранять смысл относительных +путей, создавать окно до графической подсистемы и закрывать частично поднятые +сервисы без предположения, что init дошёл до конца. + +Bootstrap обеих полных частей побайтно одинаков, хотя файл второй части может +иметь другое имя: + +```text +size 36 864 +entry RVA 0x147E +SHA-256 f476af85c034a4b4f34f49d0806e4dff397b5da0ee26d382a7674231144979f7 +``` + +Следовательно, различия полных частей начинаются после передачи управления DLL +и игровым данным. Адреса executable демоверсии относятся к другой binary +profile и не должны переноситься на полные версии без проверки hash. + +## Инициализация подсистем + +Iron3D разделяет создание высокоуровневых объектов и создание подсистем. +`createShell` конструирует оболочку пользовательского интерфейса, `createGame` +создаёт объект игры, а `createSubsystems` связывает их с display, sound, +network и World3D. + +Высокоуровневая последовательность выглядит так: + +```text +прочитать iron_3d.ini + -> получить display service + -> создать окно и графическое устройство + -> проверить доступность 3D-драйвера + -> выбрать CURRENT_D3DCARD + -> получить sound service и настроить громкость + -> создать network instance и передать application GUID + -> создать World3D game settings +``` + +Ошибка отсутствующего 3D-устройства обрабатывается отдельно от ошибок ресурсов: +это разные стадии запуска. Конфигурация влияет не только на разрешение. В +runtime попадают графическая карта, громкость эффектов, CD-audio, режим +CD-sound, сетевое приложение и World3D settings. Application GUID сетевой +подсистемы: + +```text +{3C1D1F01-A870-11D1-8400-000021B14415} +``` + +Один и тот же GUID передаётся сетевому объекту и service layer. Если он +разойдётся, экземпляры игры станут логически разными приложениями, даже при +исправном транспорте. + +## `stdInitGame` + +После платформенных сервисов World3D создаёт внутренний runtime: + +1. Создаёт глобальную очередь объектов. +2. Сохраняет window handle и режим игры. +3. При нужном режиме ограничивает курсор областью окна. +4. Получает или создаёт 3D sound object. +5. Загружает реестр адресов компонентов из `Comp.ini`. +6. Получает или создаёт 3D renderer. +7. Читает профиль возможностей renderer. +8. Загружает component type 6. +9. Для multiplayer создаёт NetWatcher. +10. Получает world-interface из Terrain. +11. Устанавливает исходные параметры света и тумана. + +Порядок важен. World objects не должны появляться до queue, ресурсы рендера -- +до renderer, сетевые mirror objects -- до NetWatcher. В новой реализации у +каждого этапа должен быть явный признак успешного создания, чтобы shutdown мог +безопасно разобрать неполный init. + +## Завершение + +Shutdown идёт в обратном направлении: прекращаются игровые расчёты и сетевые +наблюдатели, разбираются отложенные операции, освобождаются world objects и +менеджеры, затем renderer и sound, затем game settings и platform services. +Ограничение курсора снимается, глобальные ссылки очищаются. + +Полезный протокол завершения: + +1. Запретить новые события и новые объекты. +2. Дождаться выхода из calculation/render traversal. +3. Разобрать очередь deferred operations. +4. Отсоединить объекты от очереди, контроллеров и менеджеров. +5. Освободить managers и singletons после их consumers. +6. Закрыть устройства и платформенные сервисы. + +Такой порядок защищает от dangling-ссылок между World3D, Terrain, renderer, +sound и сетевым слоем. + +## Главный цикл + +Главный цикл -- не одна функция `update_and_render`, а расписание, связывающее +Win32 messages, input, игровые события, таймеры, сеть и renderer. Системная +очередь сообщает об активации окна, вводе, изменении состояния процесса и +выходе. Очередь World3D рассчитывает игровые объекты. У этих очередей разные +правила времени и владения, поэтому их нельзя смешивать в один контейнер. + +Подтверждённые точки вызова в одном из профилей: + +```text +stdCalculateGame RVA 0x5FA94, 0x604C1, 0x6086B +ClearManualEventsList RVA 0x6052F +stdRenderGame RVA 0x60B2F +UpdateManualEventsList в обработчике сообщений около RVA 0xA3759 +``` + +Смысловой skeleton: + +```c +while (running) { + stdCalculateGame(); + clear_keyboard_snapshot(); + update_shell_and_mode(); + ClearManualEventsList(); + process_window_messages(); + update_timers_ui_gameplay_network(); + if (mode_requires_extra_step) stdCalculateGame(); + if (render_enabled) { + stdSetCurrentCamera(camera); + stdRenderGame(camera); + } else { + sleep_briefly(); + } + update_post_render_state(); +} +``` + +Ввод из window messages накапливается между расчётными шагами. Если читать +клавиатуру только внутри рендера, события будут теряться при пропущенных кадрах +или отключённом выводе. + +## `stdCalculateGame` + +Calculation pass сначала очищает или подготавливает список manual events, +увеличивает внутренний depth/counter и опрашивает input device. Если устройство +временно потеряно, выполняется повторное получение доступа и чтение повторяется. +Затем при незамороженной игре выставляется признак `in_calculation` и вызывается +основной traversal очереди объектов. + +```text +prepare input/events + -> enter calculation + -> dispatch queue events + -> objects update behavior and transforms + -> leave calculation + -> apply deferred operations + -> occasional cache maintenance +``` + +После traversal разбирается deferred-delete list. Объект может запросить +собственное удаление во время события, но память освобождается только после +завершения обхода. Периодически также очищаются давно неиспользуемые ресурсы и +объекты по порогам часов порядка 20 и 60 секунд. + +Совместимый runtime должен иметь явный traversal depth или флаг +`in_calculation`. Нельзя полагаться на то, что контейнер выдержит удаление +текущего элемента из обработчика события. + +## Жизненный цикл кадра + +Рендер читает состояние, подготовленное расчётом. Кадр начинается до renderer-а: +message pump уже накопил ввод, World3D уже обновил объекты, отложенные операции +и анимации, после чего выбирается камера и обновляется listener звука. + +```text +system messages and input + -> simulation calculation + -> deferred object operations + -> animation and transforms + -> camera and sound listener + -> visibility and render queues + -> materials and draw passes + -> renderer completion + -> end-of-render callbacks and UI +``` + +В `World3D::stdRenderGame` виден крупный каркас: установка camera/viewport, +renderer frame boundaries, traversal мира, завершение world/shade path, +renderer completion, снятие `in_render`, восстановление viewport и рассылка +end-of-render callbacks. Эти callbacks позволяют объектам безопасно обновить +временные ресурсы после того, как draw-команды больше их не используют. + +Один calculation step не обязан соответствовать одному изображению. Главный +цикл допускает дополнительный вызов `stdCalculateGame` и режим, в котором +расчёт продолжается без вывода кадра. Поэтому нужно хранить отдельно: + +1. монотонные платформенные часы; +2. игровое время с pause и масштабированием; +3. длительность текущего calculation step; +4. локальное время анимации и FX; +5. реальные часы обслуживания кэшей. + +Игровую логику нельзя выводить из render delta: изменение частоты кадров тогда +изменит движение, камеру и сценарные таймеры. Подробности render item и рисков +кадровой совместимости вынесены в справочник [Render frame](../reference/render-frame.md). + +## World3D + +World3D связывает игровые объекты, события, время, ввод, камеру, сетевые +отражения и визуальное представление. Он не содержит всю предметную логику: +движение делегируется Behavior/Wizard, физика -- Control, мир -- Terrain. Его +задача -- общая идентичность, порядок вызовов и безопасный жизненный цикл. + +`CreateQueue` создаёт singleton-объект размером 20 байт, а `GetQueue` +возвращает его. Очередь служит центральным маршрутизатором событий и операций +над объектами. + +Публичный слой предоставляет отдельные функции для локальных и сетевых +объектов: + +```text +CreateObject +AddObjectToGame +AddNewObjectToGame +CreateMirrorObject +AddMirrorObjectToGame +AddNewMirrorToGame +``` + +Разделение "создать" и "добавить в игру" означает два этапа: сначала выделить и +настроить instance, затем зарегистрировать его в общей системе. Это позволяет +loader-у заполнить свойства до появления объекта в расчётной очереди. + +## Идентичность объектов + +Object ID кодирует не только порядковый номер. Проверки диапазонов показывают +разбиение на номер игрока, класс и индекс. Mirror object представляет объект, +владельцем которого является другой участник. Локальный runtime хранит его +видимое состояние, но источник авторитетных изменений находится удалённо. + +```c +struct ObjectId { + uint32_t raw; + uint16_t owner_player; + uint16_t class_and_index; +}; +``` + +Точное битовое разбиение нужно брать из сетевых функций. На уровне API уже +сейчас полезно разделить логические свойства: `is_local`, `is_mirror`, `owner`, +`class` и `index`. + +Минимальный runtime-object должен хранить identifier, type, owner, transform, +active state, ordered property bag, ссылки на controllers, участие в расчёте и +рендере, сетевой статус и флаг отложенного удаления. Специализированные DLL +могут быть представлены компонентами, но порядок их вызовов задаёт World3D. + +## Отложенное удаление + +`DeleteGameObject` проверяет, идёт ли calculation pass. Если обход активен и +удаление не принудительное, объект помещается в deferred list. `KillGameObject` +отправляет запрос через очередь, а не освобождает память напрямую. + +```c +void request_delete(Object* o) { + if (world.in_calculation) { + world.deferred_delete.push_back(o); + o->pending_delete = true; + } else { + world.detach_and_release(o); + } +} +``` + +Это защищает итераторы, связи и текущий стек вызовов. Любая новая подсистема, +способная удалить объект из обработчика события, обязана пользоваться тем же +механизмом. + +Регистрация в очереди и владение памятью -- разные понятия. Удаление из мира не +всегда означает немедленное освобождение instance: часть объектов и managers +использует intrusive reference count, а renderer, sound и resource managers +могут возвращать уже существующий singleton с увеличенным счётчиком. Поэтому +global manager закрывается после всех объектов, которые на него ссылаются. + +## Детерминизм + +Даже при одинаковых формулах результат зависит от порядка. Стабильный runtime +сохраняет последовательность queue traversal, момент формирования input +snapshot, порядок сетевых сообщений, обработку deferred operations и порядок +обращений к RNG. Оптимизация и многопоточность допустимы только при +детерминированном объединении результатов. + +Для переносимой реализации полезно разделить scheduler phases и immutable +render snapshot. Это архитектурная рекомендация для новой реализации, а не +утверждение о точном layout исходных C++ classes. + +## Стабильность между сборками + +Внешняя архитектура полных Частей 1 и 2 сохраняет те же пятнадцать DLL, 313 +exports, имена, ordinals и import sets. Побайтно идентичны: + +```text +ai.dll, Behavior.dll, Joystick.dll, MisLoad.dll, Net.dll, +Ngi32.dll, Terrain.dll, Wizard.dll, World3D.dll +``` + +Пересобраны: + +```text +AniMesh.dll, ArealMap.dll, Control.dll, Effect.dll, +iron3d.dll, services.dll +``` + +Это разделяет переносимость выводов. World3D lifecycle, Terrain, NRes/RsLi +readers, mission loader, AI/Behavior/Wizard, DirectPlay wrapper и joystick +adapter подтверждаются одной машинной реализацией. Model/agent runtime, +collision, effects, shell/composition и service layer требуют отдельного +сравнения поведения Частей 1 и 2. + +Для `World3D.dll` Частей 1 и 2 применим общий hash: + +```text +World3D.dll SHA-256 +17e4a3089b2583a8cf2356c9db0390b1aba138356a09130d79b4e7e4791da61e +``` + +RVA внутри `iron3d.dll` нельзя считать общими без проверки конкретного файла: +эта DLL пересобрана между частями, а демоверсия имеет отдельный binary profile. +Смысловая последовательность цикла переносится как контракт scheduler-а, но +адреса остаются build-specific. diff --git a/docs/tomes/03-resources.md b/docs/tomes/03-resources.md new file mode 100644 index 0000000..acf97a8 --- /dev/null +++ b/docs/tomes/03-resources.md @@ -0,0 +1,561 @@ +# III. Ресурсная система и форматы + +Ресурсная система Iron3D переводит имена из миссий и прототипов в объекты, +которыми пользуются подсистемы мира, рендера, анимации, звука, эффектов и +управления. В этом пути участвуют несколько разных сущностей: файл на диске, +открытый архив, запись каталога, подготовленный payload и готовый runtime-объект. +Их нельзя смешивать, потому что у каждого уровня свой срок жизни, свои правила +кэширования и свой набор проверок. + +Основной контейнер ресурсов -- [NRes](../reference/nres.md). Он используется как +внешний архив (`objects.rlb`, `Material.lib`, `Textures.lib`) и как внутренний +контейнер модели `*.msh`. Второй библиотечный формат -- [RsLi](../reference/rsli.md): +его каталог находится в начале файла, а payload может храниться raw, через +потоковое преобразование, LZSS, адаптивный Huffman + LZSS или raw Deflate. +Визуальная часть прототипа дальше проходит через [MSH](../reference/msh.md), +[WEAR/MAT0](../reference/materials.md) и [Texm](../reference/texm.md), но этот +том описывает именно ресурсный слой: как найти, проверить, раскрыть и сохранить +данные до передачи их предметным подсистемам. + +```text +TMA или unit DAT + -> логический ключ + -> objects.rlb + -> archive.rlb :: model.msh + -> model.wea + -> Material.lib :: MAT0 + -> Textures.lib / LightMap.lib :: Texm +``` + +На демо-корпусе эта цепочка проверена целиком для всех реально размещённых +объектов. При этом полная таблица прототипов может содержать ссылки на контент, +которого нет в урезанной поставке. Диагностика должна различать недостижимую +ссылку в общем реестре и ресурс, реально требуемый выбранной миссией. + +## Ресурсный конвейер + +Загрузка ресурса состоит из последовательных стадий: + +1. Разрешить относительный путь с учётом глобального resource path и текущего + каталога игры. +2. Открыть архив или вернуть уже открытый archive object из кэша. +3. Найти запись каталога по имени, не меняя исходный порядок каталога. +4. Проверить bounds, размер payload и способ хранения. +5. Подготовить bytes: распаковать, применить потоковое преобразование или + вернуть raw-диапазон. +6. Разобрать предметный формат и создать объект подсистемы. +7. Сохранить готовый объект в отдельном кэше, если формат допускает повторное + использование. + +Эти стадии дают четыре независимых уровня кэша: + +1. Открытые архивы. +2. Каталоги имён, offsets и размеров. +3. Подготовленные блоки данных. +4. Кэши моделей, материалов, текстур, lightmaps, эффектов и служебных объектов. + +Повторное открытие того же нормализованного пути возвращает существующий +archive object и увеличивает счётчик владельцев. Готовая texture или model при +этом может жить дольше file handle и иметь собственную политику удаления. Кэш +предметного объекта не должен напрямую закрывать архив: он зависит от данных, +но не владеет файлом как ресурсом операционной системы. + +## Имена и пути + +Большинство игровых имён сравнивается без учёта регистра в ASCII-диапазоне. Это +не Unicode case folding. Для совместимости достаточно нормализовать `A..Z` в +`a..z`, а для RsLi-поиска -- переводить запрос в uppercase ASCII и укладывать его +в фиксированный ключ. + +Фиксированные строки читаются bounded parser-ом: строковая часть заканчивается +на первом NUL, но оставшийся хвост поля сохраняется. Нельзя очищать хвосты, +пересобирать регистр, заменять смешанные разделители или заранее переводить все +пути в абсолютные имена. Старые данные используют исторические имена библиотек, +разный регистр исходных путей и фиксированные поля, где после терминатора могут +оставаться значимые для roundtrip bytes. + +## Строгий и совместимый режимы + +Строгий reader нужен тестам, редактору и проверке корпуса. Он валидирует +структуру до выдачи любого `EntryView`: magic, версию, счётчики, арифметические +переполнения, bounds, sort permutation, alignment и точное завершение payload. +Если формат требует NUL-терминатор, строгий режим проверяет его именно в пределах +фиксированного поля. + +Совместимый reader повторяет только известные особенности оригинала: + +- линейный поиск при повреждённой сортировочной таблице; +- RsLi-исключение `deflate_eof_plus_one` для `sprites.lib::INTERF8.TEX`; +- material fallbacks, подтверждённые ресурсной цепочкой; +- отсутствие геометрии у системных и солнечных объектов, где mesh pass не + требуется. + +Режим совместимости не должен скрывать произвольные ошибки. Каждое послабление +оформляется как именованное правило и покрывается отдельным тестом. Если quirk +применим только к Deflate-записи, он не распространяется на LZSS, Huffman или +raw-диапазоны. + +## NRes + +`NRes` хранит произвольные именованные payload и их атрибуты. Каталог расположен +в конце файла, поэтому начало каталога вычисляется из полного размера файла и +числа записей. + +```text +[Header: 16 байт] +[Data region: payload с выравниванием] +[Directory: entry_count x 64 байта] +``` + +Все числа little-endian. + +```c +struct NResHeader16 { + char magic[4]; // "NRes" + uint32_t version; // 0x00000100 + int32_t entry_count; // >= 0 + uint32_t total_size; // равен фактическому размеру файла +}; +``` + +Производные значения: + +```text +directory_size = entry_count * 64 +directory_offset = total_size - directory_size +``` + +Reader проверяет, что `directory_offset >= 16`, умножение не переполнено, а +каталог заканчивается точно на `total_size`. + +### Запись каталога NRes + +```c +#pragma pack(push, 1) +struct NResEntry64 { + uint32_t type_id; // +0x00 + uint32_t attr1; // +0x04 + uint32_t attr2; // +0x08 + uint32_t size; // +0x0C + uint32_t attr3; // +0x10 + char name[36]; // +0x14 + uint32_t data_offset; // +0x38 + uint32_t sort_index; // +0x3C +}; +#pragma pack(pop) +``` + +Имя содержит не более 35 полезных байт и завершающий ноль. Writer запрещает +внутренний NUL и слишком длинное имя, но сохраняет неизвестные атрибуты +`attr1`, `attr2`, `attr3` без нормализации. Их смысл зависит от конкретного +типа ресурса и не может быть выведен из контейнера. + +Поле `sort_index` задаёт отображение из позиции в отсортированном списке в +исходный индекс записи. Каталог остаётся в исходном порядке. Поиск идёт по +отсортированному отображению, но возвращает исходную запись. При сохранении +writer строит массив исходных индексов, сортирует его по ASCII-case-insensitive +именам и записывает результат в `sort_index`. Если отображение нельзя использовать +или оно не является перестановкой в строгом режиме, совместимый путь переходит к +последовательному сравнению имён. + +### Размещение данных NRes + +Каждый active payload должен лежать после 16-байтового заголовка и полностью до +начала каталога. Канонические игровые файлы выравнивают начало следующего +payload до границы 8 байт нулевым заполнением. + +Порядок canonical save: + +1. Записать временный заголовок. +2. Записать payload всех записей в текущем порядке. +3. После каждого блока добавить нули до кратности 8. +4. Построить таблицу поиска имён. +5. Дописать каталог. +6. Записать окончательный `total_size`. + +Строгий reader выполняет проверки до выдачи записи: + +- `magic == "NRes"` и `version == 0x100`; +- `entry_count >= 0`, а `entry_count * 64` вычисляется без переполнения; +- `total_size` равен фактической длине файла; +- `directory_offset = total_size - entry_count * 64` не меньше 16; +- для каждой записи `data_offset >= 16` и `data_offset + size <= directory_offset`; +- поле имени содержит NUL в пределах 36 байт; +- каждый `sort_index < entry_count`; +- в строгом режиме все `sort_index` образуют перестановку `0..N-1`. + +Нулевое заполнение до границы 8 байт -- подтверждённое поведение игровых +архивов и canonical writer-а. Reader не должен считать ненулевой gap частью +соседнего payload, но lossless-редактор сохраняет исходные bytes, если файл +открыт не в режиме канонической пересборки. + +### Неплотная data region + +Проверка 120 NRes-файлов / 6 804 entries Части 1 и 134 файлов / 8 171 entries +Части 2 не выявила нарушений magic, version, total size, bounds, sort +permutation, ASCII-order, 8-byte alignment или перекрытий активных payload. +Однако `Textures.lib` Части 2 содержит большой ненулевой диапазон в data region, +который не адресуется ни одной записью каталога. Первый активный payload +начинается значительно позже начала файла, а каталог и все активные entries +остаются корректными. + +Следовательно, parser не должен требовать плотного покрытия data region. Нужно +различать три вида диапазонов: + +- `active payload` -- bytes, на которые указывает запись каталога; +- `gap/padding` -- bytes между активными диапазонами; +- `unindexed preserved region` -- произвольные bytes, не принадлежащие ни одной + записи. + +Canonical compact writer может исключить unindexed region только при явной +операции repack. Lossless editor сохраняет её побайтно вместе с исходным +порядком entries и gaps. + +## RsLi + +`RsLi` -- библиотечный архив с каталогом в начале файла. Записи могут храниться +в исходном виде или проходить один из поддержанных путей подготовки. + +```text +[Header: 32 байта] +[Entry table: entry_count x 32 байта] +[Payloads] +[необязательный trailer] +``` + +Заголовок начинается с двух байт `NL`. Версия равна `1`, число записей хранится +как знаковое 16-битное значение. Поле по смещению `0x0E` может содержать +`0xABBA`: это означает, что отображение сортировки уже подготовлено. + +Подтверждённые поля header: + +```text ++0x00 char[2] "NL" ++0x02 u8 reserved, в корпусе 0 ++0x03 u8 version, в корпусе 1 ++0x04 i16 entry_count ++0x0E u16 presorted_flag, значение 0xABBA ++0x14 u32 xor_seed +``` + +Остальные bytes заголовка сохраняются без нормализации. + +### Запись каталога RsLi + +После подготовки таблицы каждая запись имеет layout 32 байта: + +```c +struct RsLiEntry32 { + char name[12]; + uint8_t service[4]; + int16_t flags; + int16_t sort_to_original; + uint32_t unpacked_size; + uint32_t data_offset_raw; + uint32_t packed_size; +}; +``` + +Имя обычно хранится в uppercase ASCII. Четыре служебных байта после имени +сохраняются без изменения. `sort_to_original` играет ту же роль, что и +`sort_index` в NRes: связывает отсортированную позицию с исходной записью. + +Таблица на диске проходит обратимое побайтовое преобразование. Начальное +состояние берётся из младших 16 бит `xor_seed`. Если обозначить два байта +состояния как `lo` и `hi`, для каждого входного байта выполняется: + +```text +lo = hi XOR ((lo << 1) mod 256) +out = in XOR lo +hi = lo XOR (hi >> 1) +``` + +Операция симметрична: один и тот же цикл используется для подготовки и +восстановления. Состояние непрерывно проходит по всей таблице; его нельзя +перезапускать на каждой записи. + +### Способы хранения RsLi + +Способ определяется выражением `flags & 0x1E0`: + +```text +0x000 исходный блок +0x020 только потоковое байтовое преобразование +0x040 LZSS +0x060 преобразование, затем LZSS +0x080 адаптивный Huffman, затем LZSS +0x0A0 преобразование, адаптивный Huffman и LZSS +0x100 raw Deflate без оболочки zlib +``` + +Reader обязан различать все значения, а неизвестную маску отклонять как +неподдерживаемую. После любого пути должно быть получено ровно `unpacked_size` +байт. Методы `0x080` и `0x0A0` подтверждены decoder-кодом и синтетическими +тестами, но живых payload этих веток в проверенных RsLi-файлах не найдено. + +Параметры LZSS: + +- размер кольцевого окна -- `4096`; +- начальное заполнение -- байт `0x20`; +- начальная позиция -- `0xFEE`; +- управляющие признаки читаются от младшего бита к старшему; +- двухбайтовая ссылка кодирует 12-битную позицию и длину `n + 3`; +- восстановленные bytes сразу записываются обратно в кольцевое окно. + +В конце файла может находиться шестибайтовый media overlay trailer: два символа +`AO` и 32-битное значение `overlay`. В таком режиме фактическая позиция блока +равна `data_offset_raw + overlay`. Reader сначала проверяет, что overlay не +выходит за размер отображённого файла, затем проверяет весь диапазон записи. + +### Поиск, кэш и проверки RsLi + +Запрос имени переводится в uppercase ASCII и укладывается в фиксированный ключ. +При признаке `0xABBA` используется сохранённое отображение сортировки. Если +признака нет, loader строит его после чтения каталога. Некорректный индекс +приводит к последовательному поиску. + +Файл открывается через memory mapping. Runtime-запись хранит указатель на +упакованный диапазон, размеры и необязательный указатель на подготовленные +данные. Первый обычный `load` создаёт буфер и сохраняет результат; повторный +возвращает его из кэша. Быстрый путь может вернуть указатель непосредственно в +mapped file только для исходного блока. + +Reader проверяет: + +- сигнатуру `NL`, служебный байт и версию; +- неотрицательное число записей; +- размещение всей таблицы в файле; +- что сохранённое отображение сортировки является перестановкой; +- что эффективный диапазон каждого блока не выходит за конец файла; +- что способ хранения известен; +- что после подготовки получено ровно `unpacked_size` байт. + +В demo-каталоге и полных каталогах обеих частей наблюдаются два RsLi-файла: + +```text +gamefont.rlb 2 entries, все 0x040 LZSS +sprites.lib 24 entries, все 0x100 raw Deflate +``` + +Последняя запись `sprites.lib::INTERF8.TEX` объявляет packed range, который +заканчивается на один байт после физического EOF. Совместимый путь читает на +один байт меньше; строгий путь регистрирует именованный quirk +`deflate_eof_plus_one`. Это исключение не распространяется на другие записи, +методы или произвольные выходы за конец файла. + +Writer, который редактирует существующий архив, сохраняет все служебные bytes +заголовка и записей. Выбор оптимального способа упаковки для новых файлов +является отдельной политикой и не должен менять уже существующие entries без +явного запроса. + +## Реестр объектов + +Имя объекта в миссии является логическим ключом. Связь этого ключа с файлами +модели, материалов и служебных данных хранится в `objects.rlb`, который сам +использует формат NRes. Имя записи каталога -- ключ прототипа. Payload записи +состоит из записей по 64 байта: + +```c +struct ObjectRef64 { + char archive_name[32]; + char resource_name[32]; +}; +``` + +Payload каждой записи `objects.rlb` обязан быть кратен 64 байтам. Это +проверяется до чтения первой ссылки. Оба поля читаются как строки до первого +NUL, но полный 32-байтовый блок сохраняется при редактировании без очистки +хвоста. + +Разрешение прототипа: + +1. Найти entry реестра по логическому ключу без учёта ASCII-регистра. +2. Прочитать все `ObjectRef64` в исходном порядке. +3. Если ссылка указывает обратно в `objects.rlb`, рекурсивно раскрыть указанный + родительский prototype. +4. Объединить effective references родителя с локальными references дочерней + записи, сохранив порядок и происхождение. +5. Выбрать первую существующую ссылку с расширением `.msh`, открыть указанный + архив и найти модель по имени. +6. Загружать `.bas` как отдельный служебный ресурс сооружения, а не как замену + MSH. +7. Если effective prototype не содержит MSH, считать объект негеометрическим, + если это допускает его назначение. + +Resolver обязан детектировать циклы наследования, ограничивать глубину и +кэшировать результат раскрытия. В обеих частях fortification-прототипы используют +явного родителя из `objects.rlb`: родитель предоставляет MSH/WEAR/CPT/NDP/CTL, +а дочерняя запись добавляет собственный BASE. Негеометрический объект не является +ошибкой сам по себе: системные и солнечные сущности могут участвовать в логике +или эффектах без mesh pass. + +Контракт реализации: + +- сохранять порядок ссылок внутри прототипа; +- не выводить имя модели из имени entry, если имеется явная ссылка; +- проверять существование указанного архива и ресурса независимо; +- отделять статус «негеометрический объект» от статуса «повреждённая ссылка»; +- кэшировать результат разрешения ключа, но инвалидировать его при замене архива; +- в diagnostic mode строить полный граф зависимостей и отмечать узлы, достижимые + из выбранной миссии. + +В demo-варианте `objects.rlb` содержит 590 прототипов. У 554 есть прямая ссылка +на MSH; 549 таких ссылок разрешаются в доступных demo-архивах. Ещё 34 прототипа +раскрываются через родительскую запись `objects.rlb` и дополняются локальным +BASE. Семь записей не дают геометрию, а 41 ссылка всего реестра указывает на +контент, которого нет в урезанной поставке. Для 501 запросов прототипов, +порождаемых шестью demo-миссиями, найдены прототип, MSH и WEAR. + +## Unit DAT + +Запись миссии может ссылаться не на один ключ, а на unit-файл `*.dat`. Такой файл +перечисляет компоненты сложного игрового объекта. + +```text +TMA object + -> путь к unit DAT + -> список component keys + -> несколько entries objects.rlb + -> модели, WEAR, control points, effects и другие ресурсы +``` + +Это объясняет, почему один размещённый unit может состоять из корпуса, башен, +оружия, эффектов и служебных частей. В демоверсии найдено 425 unit-файлов и +5 219 записей; все разобраны без ошибок. Наблюдаемый тип записи равен `1`, а +архив назначения -- `objects.rlb`. В 5 205 из 5 219 фиксированных полей имени +обнаружены ненулевые bytes после строкового терминатора; reader использует +строковую часть, а lossless writer сохраняет весь исходный блок. + +Размер каждого unit DAT удовлетворяет формуле: + +```text +file_size = 8 + record_count * 112 +``` + +Первые два байта header равны `F1 F0`. Оставшиеся шесть bytes имеют несколько +наблюдаемых вариантов; их семантика пока не названа и они сохраняются как +`header_opaque[6]`. + +```c +#pragma pack(push, 1) +struct UnitDatRecord112 { + char archive_name[32]; // +0x00 + char resource_name[32]; // +0x20 + uint32_t kind; // +0x40, в корпусе всегда 1 + int32_t parent_or_link; // +0x44 + char description[32]; // +0x48 + uint32_t tail0; // +0x68, opaque + uint32_t tail1; // +0x6C, opaque +}; +#pragma pack(pop) +``` + +Во всех проверенных records `archive_name == "objects.rlb"` и `kind == 1`. +Поле `parent_or_link` встречается как `-1`, `0`, `1` и другие небольшие индексы +и связывает компоненты составного unit; точная предметная классификация ссылки +ещё не закрыта. `description` -- человекочитаемое описание компонента. В Части 2 +есть поля `description[32]`, полностью заполненные без NUL; это валидная bounded +string длиной 32 байта. Требование обязательного terminator применяется только +к полям, где оно доказано форматом. `tail0` и `tail1` нельзя нормализовать. + +Проверено 425 файлов / 5 219 records Части 1 и 676 файлов / 8 145 records +Части 2. Все соответствуют формуле размера, `kind == 1` и +`archive_name == "objects.rlb"`. + +## Вспомогательные форматы + +MSH, материал и текстура отвечают за видимую форму. Полноценный прототип +дополнительно хранит точки крепления, зависимости, управляющие параметры, +области взаимодействия и ссылки на эффекты. Эти данные распределены между +несколькими небольшими форматами. + +Для них действует строгая граница знания: framing, counts и валидность корпуса +могут быть подтверждены parser-ом, тогда как предметный смысл части полей +остаётся неизвестным. Reader предоставляет typed view для доказанных полей и +raw bytes для остальных. Инструмент должен показывать статус поля: +`layout-confirmed`, `consumer-inferred` или `opaque`. + +### CTPT + +В demo-корпусе найдено 284 CTPT-ресурса и 3 599 точек; все прочитаны без ошибок. +Имена показывают назначение слоя: `TurretCenter`, `TurretDirect`, +`CameraCenter`, `TargetDirect`, `Root`, `Sfx_1`, `Sign_Entrance1`, `Width`, +`Height`, `Dir`. + +CTPT хранит локальные marker-точки модели. После применения transform такая точка +становится позицией или направлением в мире. Оружие может использовать её для +дула или оси башни, камера -- для привязки обзора, эффект -- для точки появления. +Конкретное назначение определяется именем и consumer-ом, а не одним общим флагом. +Первое 32-битное поле чаще равно `0`; встречаются `0x80000000` и редкий +вариант. До установления точной семантики оно хранится как `flags_raw`. + +### NDPR + +Проверено 494 NDPR-ресурса и 1 915 записей. Они ссылаются на `animals.rlb`, +`system.rlb`, `static.rlb`, `turrets.rlb`, `weapon.rlb` или используют пустое +имя архива. В 89 записях присутствует связанный эффект. Пустое имя архива +разрешается относительно текущего контекста. Reader хранит ссылку и остальные +параметры раздельно; writer сохраняет исходный порядок. + +### EXPL и reference arrays + +Проверено 144 ресурса EXPL: 26 используют версию 1, 54 -- версию 2, 64 -- +версию 3. Reader выбирает layout по version field и требует точного завершения +payload. Полная field-level семантика всех версий пока не доказана, поэтому +version-specific opaque sections сохраняются. + +Отдельная проверенная группа из 585 ресурсов содержит 2 956 однотипных +ссылочных records. Их границы и counts закрыты, однако единое предметное имя +всего семейства не подтверждено всеми consumers. В API безопаснее использовать +нейтральное `ReferenceArray` и конкретизировать назначение на уровне типа entry. + +### SUND и CTLD + +Два ресурса SUND содержат суммарно 12 ключей. Их следует загружать как параметры +системного объекта, а не как геометрию. + +Для CTLD проверено 531 payload. Размеры и сочетания счётчиков сильно различаются, +поэтому parser должен быть версионно- и счётчик-ориентированным, а неизвестные +секции -- храниться в исходном виде. + +### TRF, ANI и SKE + +В демоверсии обнаружены 5 файлов TRF, 38 preload-записей, 8 ANI-ресурсов и +6 SKE-ресурсов. Все проходят структурный разбор. Эти семейства участвуют в +подготовке компонентов и анимационных или управляющих данных до создания +runtime-объекта. + +Поскольку живой корпус невелик, редактор не должен синтезировать новые варианты +этих форматов по догадке. Безопасный режим -- читать доказанные счётчики и +ссылки, предоставлять raw-view неизвестных секций и обеспечивать побайтовое +сохранение неизменённых данных. + +### BASE + +Проверено 30 BASE-ресурсов; каждый содержит ровно один polygon record и проходит +структурную проверку. BASE payload и ссылка `.bas` в `objects.rlb` выполняют +связанные, но разные роли: + +- наличие ссылки `.bas` позволяет registry resolver-у искать одноимённый + `<stem>.msh` в том же архиве; +- сам BASE payload загружается отдельной подсистемой сооружений и не заменяет + MSH geometry. + +Resolver не должен интерпретировать bytes BASE как mesh. Writer сохраняет +polygon record и неизвестные поля 1:1, пока полный gameplay-контракт BASE не +подтверждён. + +## Правило сохранения + +Lossless editor сохраняет неизвестные поля, хвосты фиксированных строк, +служебные bytes, gaps, padding и unindexed regions. Writer пересчитывает только +явно производные значения: размеры, offsets, число записей, сортировочную +перестановку и padding. Такая дисциплина позволяет редактировать известную +часть ресурса, не разрушая данные, смысл которых пока не установлен. + +Canonical repack допустим только как явная операция. Он может исключать +неиндексируемые диапазоны, пересортировывать таблицы и пересобирать padding, но +не должен быть побочным эффектом обычного редактирования. Если пользователь +открыл существующий архив и изменил один известный атрибут, все остальные bytes, +не являющиеся производными от этого изменения, должны пройти roundtrip без +потери. diff --git a/docs/tomes/04-world.md b/docs/tomes/04-world.md new file mode 100644 index 0000000..82d9e47 --- /dev/null +++ b/docs/tomes/04-world.md @@ -0,0 +1,648 @@ +# IV. Мир, миссии и игровой runtime + +Миссия в Iron3D не является готовым снимком мира. Она задаёт исходные данные: +маршруты, кланы, размещённые объекты, свойства, ссылку на ландшафт и +дополнительные записи. Runtime строит из этого карту, пространственные +структуры, очередь `World3D`, визуальные представления, controllers и связи с +ресурсной системой. + +Для совместимой реализации важно не смешивать три слоя: + +1. **Disk data** -- `data.tma`, `Land.msh`, `Land.map`, `BuildDat.lst` и + связанные resource archives. +2. **Prepared data** -- разобранные paths, clans, terrain streams, areal graph, + prototype graph, material и texture handles. +3. **Runtime objects** -- World3D instances, domain controllers, spatial + registration, AI/scripts, timers и расчётный tick. + +Граница между этими слоями нужна для диагностики и отката. Ошибка в достижимой +цепочке размещённого объекта должна остановить создание миссии до публикации +объекта в очереди событий. Недостижимая запись общего архива может быть +inventory warning и не обязана блокировать текущую карту. + +## `data.tma`: данные миссии + +`data.tma` -- основное описание расстановки и логической конфигурации миссии. +Он не содержит всю геометрию, материалы или AI-код. Файл перечисляет paths, +clans, objects, свойства и ссылки на внешние прототипы. Подробный справочный +контракт формата вынесен в [TMA](../reference/tma.md), но глава использует его +как часть сквозного runtime pipeline. + +TMA читается строго последовательно bounded cursor-ом. Записи имеют переменную +длину, поэтому offsets следующих секций получаются только после разбора +предыдущих. Секции нельзя искать по сигнатурам: порядок управляется счётчиками, +длинами и mode-dependent ветками. + +Главный критерий корректности -- `cursor.offset == file_size` после последней +записи. Неописанный хвост, переполнение при вычислении размеров, отрицательный +или чрезмерный count и выход за bounds являются ошибками parser-а, а не +материалом для эвристического восстановления. + +### Верхний уровень + +Все переменные строки в проверенных TMA используют length-prefixed primitive: + +```c +struct LpString { + uint32_t byte_length; + uint8_t bytes[byte_length]; +}; +``` + +Завершающий NUL не является обязательной частью framing. Reader продвигается +ровно на `4 + byte_length`. Текст можно декодировать как legacy ANSI/CP1251 для +человекочитаемого представления, но исходные bytes сохраняются для lossless +режима. + +Подтверждённый верхний уровень: + +```text +u32 format_version // 1 +u32 path_count +PathRecord paths[path_count] +u32 clan_section_version // 6 +u32 clan_count +ClanRecord clans[clan_count] +u32 object_section_version // 10 +u32 object_count +PlacedObject objects[object_count] +LpString land_path +u32 mission_flag +LpString description_raw +u32 extra_section_version // 1 +u32 extra_count +ExtraRecord28 extras[extra_count] +``` + +Имена `clan_section_version`, `object_section_version` и +`extra_section_version` описывают устойчивое положение полей в контракте. Они +не доказывают исходные имена C++-структур. Strict mode проверяет известные +значения, compatible mode сохраняет raw value и сообщает диагностический +контекст. + +### Paths + +```c +struct PathRecord { + int32_t path_id; + uint32_t point_count; + float points[point_count][3]; +}; +``` + +Paths идут сразу после `path_count` без имён и padding. `path_id` не обязан +совпадать с физической позицией записи: script/gameplay reference должен +использовать сохранённый ID, а не индекс массива. + +Перед выделением массива проверяются `point_count`, умножение `point_count * +12` и наличие всего диапазона в файле. Координаты хранятся как little-endian +`float32` triples в общей системе координат мира. + +### Clans + +Clan section задаёт участников миссии, их ресурсные связи, позиционные anchors +и таблицы отношений. Общая prefix-часть: + +```text +LpString name +i32 raw_id +f32 anchor_x +f32 anchor_y +u32 mode +mode-dependent body +relation table +``` + +Для обычных modes `1..3` тело содержит две пары: + +```text +LpString resource_path +i32 resource_tag +LpString resource_path +i32 resource_tag +``` + +После них идёт relation table: + +```text +u32 relation_count +repeat relation_count: + LpString other_clan_name + i32 relation_value +``` + +Первая ресурсная строка обычно указывает на script/formula base, вторая -- на +TRF или пустой ресурс. Tags различаются между кланами и должны сохраняться как +raw-поля, пока их потребительская семантика не закрыта. + +Mode `0` имеет отдельный count-driven layout: + +```text +LpString first_resource +u32 spatial_group_count +repeat spatial_group_count: + u32 record_count + repeat record_count: + float raw_spatial[5] +LpString second_resource +i32 second_tag +u32 relation_count +relations... +``` + +Внутренний `record_count` в известных живых образцах равен `1`, но parser читает +объявленное значение. Нельзя разбирать mode `0` как обычные две resource +references: это сдвигает cursor и ломает последующую relation table. + +### PlacedObject и свойства + +Ключевое поле размещённого объекта -- `resource_name`. Оно имеет два рабочих +варианта: + +1. прямой логический ключ прототипа, который ищется в `objects.rlb`; +2. путь к unit DAT, из которого получается список компонентных ключей. + +Доказанное framing объектной записи: + +```text +u32 raw_kind +u32 class_or_flags +LpString resource_name +u32 raw_after_resource +u32 identity_or_clan_raw +f32 position[3] +f32 orientation[3] +f32 scale[3] +LpString instance_name +u32 raw_after_name +i32 link0 +i32 link1 +u32 property_schema_version // 1 +u32 property_count +Property properties[property_count] +``` + +`orientation[3]` названа по наблюдаемому использованию как transform-поле, но +точный Euler order должен подтверждаться pose/render parity. `scale` в +большинстве записей равен `(1,1,1)`. `instance_name` может быть пустым у +unit-ссылки или содержать stem размещённого прототипа. + +Свойства хранятся как ordered property bag: + +```text +Property: + u32 raw_value[4] + LpString name +``` + +Порядок, повторяемость имени и raw 16-byte value важнее удобного словаря. +Разные consumers интерпретируют четыре слова как integer, float, default или +range data в зависимости от имени свойства. Typed view допустим только для +доказанных property names; базовый parser обязан сохранить исходный порядок. + +В раннем проверенном корпусе на каждом из 201 размещённого объекта встречаются +`Invulnerability` и `Life state`. Для 48 unit-ссылок дополнительно наблюдаются +`LogicalID`, `ClanID`, `Type`, `MaxSpeedPercent`, `MaximumOre`, `CurrentOre`, +`ChargeRadius`, `FreeBotNum`, `FreeTechnoNum`, `FreeConstructionTime` и +`FreeResearchTime`. Имя `NOT USED` встречается массово и сохраняется как +обычное поле, несмотря на исторический смысл названия. + +### Epilogue и extras + +После объектов идут путь к ландшафту, флаг миссии, raw-описание и trailing +section. `description_raw` не всегда является чистым текстом: внутри +объявленной длины встречаются служебные bytes и остатки путей. Поэтому decoded +view является вспомогательным, а не каноническим представлением. + +```c +struct ExtraRecord28 { + float position[3]; + uint32_t raw[4]; +}; +``` + +Последние четыре слова `ExtraRecord28` пока не нормализуются. Reader хранит их +как raw data и не позволяет extra record поглотить начало следующей секции или +файловый хвост. + +Покрытие полных каталогов: + +```text +Часть 1: 29 TMA, 34 paths, 101 clans, 864 objects, 28 extra records +Часть 2: 31 TMA, 61 paths, 91 clans, 885 objects, 41 extra records +``` + +Версии стабильны: верхний уровень `1`, clan section `6`, object section `10`, +property schema `1`, trailing section `1`. У всех размещённых объектов +`class_or_flags == 0x80000002`. + +## Сквозная загрузка миссии + +`data.tma` описывает размещение, но видимый runtime-объект появляется только +после прохождения dependency graph. Простая загрузка файлов с похожим stem +работает на отдельных объектах, но ломается на составных unit DAT, изменённых +именах моделей и наследовании прототипов через `objects.rlb`. + +Сквозная цепочка: + +```text +TMA object + -> direct prototype key или unit DAT + -> component key + -> objects.rlb entry + -> MSH и WEAR + -> material slots + -> MAT0 phases + -> Texm и lightmap + -> prepared World3D instance +``` + +Контейнеры и графические форматы описаны отдельно в [NRes](../reference/nres.md), +[MSH](../reference/msh.md), [WEAR и MAT0](../reference/materials.md) и +[Texm](../reference/texm.md). В этой главе они рассматриваются как ребра +создания мира. + +### Фазы loader-а + +1. **Mission context.** Выбрать каталог миссии, прочитать конфигурацию и + определить карту. +2. **World foundation.** Загрузить `Land.msh`, `Land.map`, `BuildDat.lst` и + создать spatial managers. +3. **Mission description.** Разобрать TMA, paths и clans, но пока не публиковать + объекты. +4. **Prototype resolution.** Для каждой размещённой сущности раскрыть прямой + ключ или unit DAT и построить component list. +5. **Resource preparation.** Открыть требуемые RLB/LIB, проверить MSH, WEAR, + MAT0, textures, lightmaps и effects. +6. **Instance construction.** Создать World3D objects и domain controllers, + заполнить transform, ownership и properties. +7. **Registration.** Только после успешной настройки добавить instances в + queue и spatial structures. +8. **Scenario start.** Подключить AI/scripts, активировать timers и разрешить + первый calculation tick. + +Разделение construction и registration предотвращает появление наполовину +созданного объекта в очереди событий. Если ошибка возникает до регистрации, +pending objects освобождаются без рассылки gameplay-событий. После регистрации +откат выполняется через обычный lifecycle очереди. + +### Статистика dependency graph + +Для ранних шести миссий 201 размещённый объект даёт 48 ссылок на unit-файлы и +153 прямых ключа. Unit-файлы раскрываются в 348 компонентов. Всего получается +501 запрос прототипа; для каждого достижимого запроса найдены запись реестра, +MSH и WEAR. + +Полный dependency graph частей 1 и 2: + +```text +Часть 1 +864 placed objects +463 unit references -> 4 300 components +4 701 prototype/MSH/WEAR requests +36 954 material slots +48 806 texture requests + 139 lightmaps +failures 0 + +Часть 2 +885 placed objects +561 unit references -> 5 521 components +5 845 prototype/MSH/WEAR requests +50 888 material slots +68 603 texture requests + 214 lightmaps +failures 0 +``` + +`failures 0` означает, что для каждой достижимой ветви найдены prototype, +effective MSH/WEAR, MAT0, Texm и lightmap. Это не означает, что во всём +глобальном каталоге нет недостижимых или служебных записей. + +Метрики нужно помечать областью. Чистая object chain шести ранних миссий даёт +3 873 material slots и 5 049 texture requests. Mission total включает по одной +environment WEAR-таблице на миссию и становится 3 879 material slots и 5 067 +texture references. + +### Диагностика ошибок + +Ошибка привязывается к конкретному ребру графа: + +- миссия ссылается на отсутствующий unit-файл; +- unit DAT раскрывается в component key, которого нет в реестре; +- prototype найден, но его MSH отсутствует в ожидаемом archive; +- WEAR указывает на неизвестный MAT0; +- MAT0 phase ссылается на отсутствующий Texm или lightmap; +- prepared object не прошёл валидацию transform/properties. + +Сообщение вида `resource not found` недостаточно для восстановления каталога. +Диагностика должна содержать исходный placed object, раскрытый ключ, archive, +entry и тип связи. + +## `Land.msh`: ландшафт как специализированная модель + +`Land.msh` является [NRes](../reference/nres.md)-архивом, но его содержимое +отличается от обычной объектной MSH. Он хранит геометрию поверхности, таблицы +участков и ускорители пространственных запросов. Видимые buffers являются лишь +частью данных: CPU-подсистемам остаются нужны adjacency, surface classes и +cell accelerator streams. + +Во всех проверенных картах порядок типов одинаков: + +```text +1, 2, 3, 4, 5, 18, 14, 11, 21 +``` + +Типы `1`, `3`, `4` и `5` совместимы по базовому представлению с узлами, +позициями, нормалями и UV обычной модели. Типы `11` и `21` специфичны для +terrain; `14` и `18` являются дополнительными потоками. + +### Streams и размеры элементов + +```text +type 1 38 байт node/slot mapping +type 3 12 байт float3 positions +type 4 4 байта packed normals +type 5 4 байта packed UV +type 11 4 байта cell accelerator data +type 14 4 байта auxiliary stream +type 18 4 байта auxiliary stream +type 21 28 байт terrain face +``` + +Для этих streams `attr1` соответствует числу элементов, а `attr3` -- stride. +Тип `2` начинается заголовком размером `0x8C`, после которого идут slot records +по 68 байт. Число slots вычисляется как `(size - 0x8C) / 68`; reader проверяет +делимость, bounds и отсутствие хвоста. + +### `TerrainFace28` + +Запись type `21` связывает triangles, соседей и surface metadata: + +```text ++0x00 .. +0x07 flags и служебные поля ++0x08 u16 vertex0 ++0x0A u16 vertex1 ++0x0C u16 vertex2 ++0x0E u16 neighbor0 ++0x10 u16 neighbor1 ++0x12 u16 neighbor2 ++0x14 .. +0x1B material/class/edge fields +``` + +Каждый vertex index обязан быть меньше числа позиций type `3`. Neighbor равен +`0xFFFF` либо указывает на другой элемент type `21`. Последние восемь bytes +сохраняются без нормализации до полного закрытия предметной семантики. + +### Маски поверхности + +Runtime использует полную 32-битную маску face и два compact-представления. +Основное 16-битное поле собирается из отдельных битов полной маски; второе +шестибитное поле хранит material classes. Это не усечение младших битов. + +Для совместимого writer-а нужны явные функции `full_to_compact()` и +`compact_to_full()`. Неизвестные биты полной маски сохраняются отдельно, иначе +обратное преобразование потеряет информацию. + +Основное соответствие: + +```text +full 00000001 -> compact 0001 +full 00000008 -> compact 0002 +full 00000010 -> compact 0004 +full 00000020 -> compact 0008 +full 00001000 -> compact 0010 +full 00004000 -> compact 0020 +full 00000002 -> compact 0040 +full 00000400 -> compact 0080 +full 00000800 -> compact 0100 +full 00020000 -> compact 0200 +full 00002000 -> compact 0400 +full 00000200 -> compact 0800 +full 00000004 -> compact 1000 +full 00000040 -> compact 2000 +full 00200000 -> compact 8000 +``` + +Для шестибитного material-поля используются full-биты `0x100`, `0x8000`, +`0x10000`, `0x40000`, `0x80000` и `0x80`; они переходят соответственно в +compact-биты `1`, `2`, `4`, `8`, `0x10`, `0x20`. + +### Проверенное покрытие + +```text +AutoMAP 3 051 вершина, 3 174 faces +PROL 11 125 вершин, 9 234 faces +Tut_1 8 827 вершин, 8 290 faces +Tut_2 9 456 вершин, 8 996 faces +Tut_3 9 833 вершины, 8 560 faces +Tut_4 9 022 вершины, 8 612 faces +``` + +Расширенное покрытие: + +```text +Часть 1: 33 карты, 299 450 vertices, 275 882 faces +Часть 2: 32 карты, 188 024 vertices, 184 454 faces +``` + +Во всех 65 картах порядок типов равен `[1,2,3,4,5,18,14,11,21]`. Strides, +count-driven размеры, vertex indices, neighbor indices и payload bounds +валидны. Различия карт являются различиями данных, а не новым вариантом +loader-а. + +## `Land.map` и ArealMap + +`Land.map` хранит логическое разбиение пространства на связанные области. Это +NRes-архив с одной записью type `12`. Payload содержит переменное число +ареалов, links и grid быстрого поиска. + +Ареал -- участок мира с геометрической границей и метаданными. Граф соседств +позволяет искать маршрут между крупными областями вместо обхода каждой +terrain-вершины. Grid отвечает на быстрый вопрос: какие области потенциально +находятся рядом с координатой. + +### Prefix ареала + +```c +struct ArealPrefix56 { + float anchor_x; + float anchor_y; + float anchor_z; + float reserved_12; + float area_metric; + float normal_x; + float normal_y; + float normal_z; + uint32_t logic_flag; + uint32_t reserved_36; + uint32_t class_id; + uint32_t reserved_44; + uint32_t vertex_count; + uint32_t poly_count; +}; +``` + +После prefix идут `float3 vertices[vertex_count]`. Нормаль в проверенных +записях имеет длину, практически равную единице. Поля `reserved_12`, +`reserved_36` и `reserved_44` в живом корпусе равны нулю, но writer сохраняет +их без нормализации. + +### Links и polygon blocks + +За вершинами хранится массив: + +```c +struct EdgeLink8 { + int32_t area_ref; + int32_t edge_ref; +}; +``` + +Пара `(-1, -1)` означает отсутствие соседа. Иначе `area_ref` указывает на +другую область, а `edge_ref` -- на соответствующее ребро. Число пар равно +`vertex_count + 3 * poly_count`. + +После links для каждого polygon читается `u32 n`, затем block размером +`4 * (3*n + 1)` bytes. Во всех 65 проверенных картах `poly_count == 0`. +Framing ветки восстановлен по loader path, но предметное поведение polygon +blocks не получает статус corpus-verified. + +### Grid быстрого поиска + +После всех ареалов записаны `cellsX` и `cellsY`. Далее для каждой ячейки идут +`u16 hitCount` и `hitCount` номеров областей. Runtime уплотняет это в одно +32-битное значение: старшие 10 бит содержат число попаданий, младшие 22 -- +начальный индекс в общем пуле. + +Grid не является точной геометрической проверкой. Он возвращает короткий список +candidates, после чего выполняется проверка принадлежности области. При +загрузке каждый area ID обязан быть меньше общего числа ареалов. + +Покрытие: + +```text +Ранние шесть карт: 3 811 areals, grid 128 x 128 +Часть 1: 33 карты, 34 662 areals, 197 698 areal vertices +Часть 2: 32 карты, 18 984 areals, 114 968 areal vertices +``` + +Во всех картах grid равен `128 x 128`. Максимальное число candidates в ячейке +-- 20 для Части 1 и 14 для Части 2. Все area/edge references находятся в +диапазоне, normals имеют единичную длину в пределах float32-погрешности, parser +заканчивается точно на конце payload. + +## Пространственные задачи runtime + +Движок решает три похожих, но независимых вопроса: + +- **видимость** -- нужно ли рисовать объект для текущей камеры; +- **столкновение** -- пересекается ли движение с поверхностью или другим телом; +- **навигация** -- через какие области допустимо провести маршрут. + +Terrain, Control и ArealMap используют общие координаты мира, но разные +структуры данных. Нельзя заменять навигационный граф видимыми triangles или +вычислять collision только по границе areal. Render frame описан отдельно в +[Render frame](../reference/render-frame.md); здесь важна подготовка world data, +которую renderer получает уже после загрузки миссии. + +### Поиск области + +Координата переводится в ячейку grid из `Land.map`. Ячейка даёт список +candidate areas, затем выполняется точная геометрическая проверка. Такой запрос +не перебирает все области карты и не зависит от количества terrain faces. + +Если координата попадает в несколько candidates, выбор должен учитывать +геометрию boundary и class/logic flags, а не только первый ID из grid cell. +Если область не найдена, caller получает явный miss и решает, допустим ли +fallback к ближайшей области. + +### Маршрут + +После определения начальной и целевой областей маршрут строится по графу +соседств. Результат высокого уровня -- последовательность areal IDs. Из неё +формируется локальный corridor, внутри которого movement controller выбирает +конкретное движение по поверхности. + +Такое разделение оставляет навигацию устойчивой к деталям terrain mesh: +изменение density triangles не должно менять high-level route, пока areal graph +и links остаются теми же. + +### Категории зон объектов + +`BuildDat.lst` связывает 12 имён категорий с 32-битными масками: + +```text +Bunker_Small 80010000 +Bunker_Medium 80020000 +Bunker_Large 80040000 +Generator 80000002 +Mine 80000004 +Storage 80000008 +Plant 80000010 +Hangar 80000040 +MainTeleport 80000200 +Institute 80000400 +Tower_Medium 80100000 +Tower_Large 80200000 +``` + +Файл читается секционно. Неизвестное имя, дублирование или нарушенная структура +не должны тихо превращаться в нулевую маску. Нулевая маска является +диагностируемым состоянием, а не универсальным default. + +## Создание мира + +Инициализация карты должна быть staged pipeline, а не набором независимых +autoload-ов: + +1. открыть `Land.msh` и построить geometry/spatial данные terrain; +2. открыть `Land.map` и создать areals, links и cell grid; +3. загрузить категории `BuildDat.lst`; +4. создать world managers для поверхности, областей, света и атмосферы; +5. разобрать TMA, paths и clans; +6. раскрыть object resources через unit DAT и `objects.rlb`; +7. подготовить MSH, WEAR, MAT0, Texm, lightmap и FXID dependencies; +8. создать World3D objects и domain controllers в pending state; +9. проверить cross references между components, controllers и spatial data; +10. зарегистрировать visual, physical и behavior components; +11. подключить AI/scripts и разрешить первый calculation tick. + +Минимальный псевдокод объектной части: + +```c +for (const PlacedObject& placed : mission.objects) { + vector<string> keys = expand_resource_name(placed.resource_name); + + for (const string& key : keys) { + Prototype p = registry.resolve(key); + PreparedVisual v = prepare_visual(p); + Object* o = construct_component(p, v, placed.properties); + + o->set_world_transform(placed.transform); + pending_registration.push_back(o); + } +} + +validate_cross_references(pending_registration); +register_all(pending_registration); +``` + +`prepare_visual` использует явные ссылки прототипа и правила fallback ресурсной +системы. Она не должна угадывать модель по имени placed object, если prototype +уже задаёт другой effective MSH/WEAR. + +## Инварианты реализации + +- Reader всех count-driven структур проверяет overflow до выделения памяти. +- Parser TMA, `Land.msh` и `Land.map` завершает работу точно на конце своего + payload. +- Неизвестные поля, reserved bytes, raw strings и property values сохраняются + lossless. +- Object properties остаются ordered property bag; сортировка имён запрещена. +- Clan relations и area links проверяются на диапазон, но физический порядок + записей сохраняется. +- Terrain vertex indices, face neighbors и areal references валидируются до + публикации spatial managers. +- Достижимый missing resource останавливает mission load до регистрации + объектов; недостижимая запись общего каталога остаётся диагностикой. +- Calculation tick включается только после успешной сборки terrain, areal graph, + managers, object queue и scenario bindings. diff --git a/docs/tomes/05-render.md b/docs/tomes/05-render.md new file mode 100644 index 0000000..6804fa6 --- /dev/null +++ b/docs/tomes/05-render.md @@ -0,0 +1,863 @@ +# V. Геометрия, материалы и рендер + +Этот том описывает путь от загруженного игрового состояния до pixels в back +buffer. Renderer не решает игровые правила: он получает transforms, geometry, +материалы, свет, эффекты, камеру и список видимых объектов, затем превращает +их в упорядоченный набор draw calls и fixed-function states. + +Графический pipeline FParkan держится на нескольких слоях данных: + +```text +MSH node/slot/batch + -> Batch20.material_index + -> строка WEAR + -> имя MAT0 + -> активная phase + -> textureName и lightmap slot + -> Texm payload + -> LegacyRenderState + -> draw item кадра +``` + +Важное практическое правило: форматы ресурсов, runtime-состояние renderer-а и +современный backend являются разными уровнями. Файл можно прочитать правильно и +всё равно получить неверный кадр из-за другой сортировки, другого mip-skip, +другой ветки material fallback или другого округления animation time. + +## Контур рендера + +Изображение является последней стадией длинного цикла. До renderer-а уже +накоплен ввод, рассчитан simulation step, применены отложенные операции, +обновлены animation states, выбрана camera и выставлен listener для 3D sound. + +```text +system messages and input + -> simulation calculation + -> deferred object operations + -> animation and transforms + -> camera and sound listener + -> visibility and render queues + -> materials and draw passes + -> renderer completion + -> end-of-render callbacks and UI +``` + +CPU делает отбор объектов, сэмплирует animation, собирает matrices, выбирает +LOD/slot, группирует batches и готовит состояния. Графический pipeline +преобразует вершины из model space в screen space, rasterizes triangles, +проверяет depth, применяет texture stages, lighting, alpha test/blend и пишет +pixels. + +Координатный путь вершины: + +```text +local/model space + -> world space + -> view/camera space + -> clip space + -> normalized device coordinates + -> viewport pixels +``` + +Порядок умножения матриц и соглашение о layout должны быть едины во всём +движке. Ошибка транспонирования часто выглядит как сломанная анимация, хотя +ключи модели прочитаны верно. + +## Граница Ngi32 + +`Ngi32.dll` является платформенной границей Iron3D-era renderer-а. Она создаёт +графический и звуковой interfaces, перечисляет устройства, хранит capability +profile, предоставляет память, часы и быстрые математические процедуры. +Высокоуровневые DLL должны обращаться к interface Ngi32, а не напрямую к +конкретному DirectDraw/Direct3D device. + +`iron_3d.ini` задаёт выбранный `CURRENT_D3DCARD`. Display layer перечисляет +drivers и video modes, проверяет поддержку 3D, переводит native capabilities во +внутренний профиль и создаёт render object. `niCreate3DRender` принимает +выбранный driver/mode, window handle и flags владения, динамически получает +функции DirectDraw/Direct3D семейства 5-7 и публикует refcounted renderer. +`niGet3DRender` возвращает уже созданный объект и увеличивает число владельцев. + +```text +enumerate adapters and video modes + -> choose CURRENT_D3DCARD + -> translate native capabilities + -> create DirectDraw surfaces and 3D interface + -> construct engine renderer + -> publish global refcounted pointer +``` + +Старый API работает как state machine. Перед draw подсистема terrain/shade +выбирает matrices, texture stages, filtering, depth test/write, culling, alpha +test, blending и vertex format. Современный backend может собрать это в +immutable pipeline key и реализовать через shaders, но compatibility layer +должен видеть исходную fixed-function модель. + +```c +struct LegacyRenderState { + Mat4 world, view, projection; + TextureStage stages[2]; + BlendMode blend; + DepthMode depth; + CullMode cull; + bool alpha_test; + uint8_t alpha_ref; + VertexFormat vertex_format; +}; +``` + +Эта структура является переносимой моделью наблюдаемого контракта, а не +утверждением о точном layout оригинального объекта renderer-а. + +Отдельная часть ABI -- таблица `g_FastProc`. При запуске выбираются scalar, +MMX, Katmai/SSE, 3DNow или PPro-реализации процедур, а `niGetProcAddress(index)` +возвращает pointer из изменяемой таблицы. Номер slot является частью ABI: +signature менять нельзя. Различия scalar/SIMD округления способны менять +animation sampling, culling, particles и даже gameplay-adjacent decisions. + +## MSH как граф модели + +`*.msh` является nested NRes, а не одной монолитной структурой. Geometry, +nodes, slots, batches, animation и служебные streams лежат в отдельных entries +и связываются по `type_id`. Физический порядок entries сохраняется для +roundtrip, но reader не должен выводить из него смысловую связь. + +Карта основных entries: + +```text +type 1 узлы и выбор slot, обычно stride 38 +type 2 header 0x8C + slots по 68 байт +type 3 positions float3, stride 12 +type 4 packed normals, stride 4 +type 5 packed UV0, stride 4 +type 6 index buffer, u16 +type 7 triangle descriptors, stride 16 +type 8 animation keys, stride 24 +type 9 служебный поток модели +type 10 строки и имена узлов +type 13 draw batches, stride 20 +type 15 дополнительный поток, stride 8 +type 17 вспомогательные данные +type 18 редкий поток, stride 4 +type 19 animation frame map, u16 +type 20 редкая вспомогательная таблица +``` + +Базовый набор types стабилен для проверенных моделей Частей 1 и 2. Расширенный +вариант добавляет types 18 и 20. Редкий вариант `MTCHECK.MSH` имеет +альтернативный атрибут type 1; его payload нужно поддерживать copy-through до +закрытия layout. + +### Узлы и slots + +Type 1 обычно состоит из записей по 38 байт: + +```c +struct Node38 { + uint16_t hdr0; + uint16_t parent_or_link; + uint16_t anim_map_start; + uint16_t fallback_key; + uint16_t slot_index[15]; +}; +``` + +`slot_index` образует матрицу `3 LOD x 5 groups`. Выбор выполняется как +`slot_index[lod * 5 + group]`; `0xFFFF` означает отсутствие geometry для этой +комбинации. Поле `parent_or_link` участвует в иерархии или связи узлов, но +название остаётся описательным. + +Type 2 начинается с header `0x8C`, затем содержит slots по 68 байт: + +```c +struct Slot68 { + uint16_t tri_start; + uint16_t tri_count; + uint16_t batch_start; + uint16_t batch_count; + float aabb_min[3]; + float aabb_max[3]; + float sphere_center[3]; + float sphere_radius; + uint32_t opaque[5]; +}; +``` + +Slot связывает диапазон triangle descriptors, диапазон draw batches, AABB и +sphere bounds. AABB удобен для более точных осевых тестов, sphere -- для +быстрого отбрасывания. Последние пять слов сохраняются без интерпретации. + +Обязательные проверки: + +- `type 2` имеет размер не меньше `0x8C`; +- остаток после header кратен 68; +- каждый `slot_index` либо `0xFFFF`, либо меньше числа slots; +- `tri_start + tri_count` не выходит за type 7; +- `batch_start + batch_count` не выходит за type 13. + +### Vertex streams, triangles и batches + +Основные vertex streams: + +```text +type 3: position = три float32 +type 4: normal = четыре int8 +type 5: UV0 = два int16 +type 6: index = uint16 +``` + +Normal XYZ декодируется как signed component / `127.0` с clamp в `[-1, 1]`. +Четвёртый byte normal stream не отбрасывается при roundtrip. UV декодируется +как `packed / 1024.0`. Index buffer адресует вершины относительно `base_vertex` +batch-а, поэтому проверка допустимости всегда использует +`base_vertex + index < vertex_count`. + +Type 7 хранит descriptors triangles: + +```c +struct TriDesc16 { + uint16_t tri_flags; + uint16_t link0; + uint16_t link1; + uint16_t link2; + int16_t nx; + int16_t ny; + int16_t nz; + uint16_t sel_packed; +}; +``` + +Descriptors используются коллизией, выбором и связями triangles. `sel_packed` +содержит три двухбитовых selector-а; значение `3` преобразуется в отсутствие +ссылки (`0xFFFF`). Полная семантика links и flags не закрывается одним layout. + +Type 13 задаёт draw ranges: + +```c +#pragma pack(push, 1) +struct Batch20 { + uint16_t batch_flags; // +0x00 + uint16_t material_index; // +0x02 + uint16_t opaque4; // +0x04 + uint16_t opaque6; // +0x06 + uint16_t index_count; // +0x08 + uint32_t index_start; // +0x0A + uint16_t opaque14; // +0x0E + uint32_t base_vertex; // +0x10 +}; +#pragma pack(pop) +static_assert(sizeof(Batch20) == 20); +``` + +`material_index` выбирает строку WEAR. `index_start`, `index_count` и +`base_vertex` описывают один indexed draw. Неизвестные поля могут влиять на +редкие проходы или state grouping, поэтому writer сохраняет их 1:1. + +Типовой обход модели: + +```c +for (Node& node : model.nodes) { + Matrix node_world = parent_world * local_transform(node); + uint16_t sid = node.slot_index[lod * 5 + group]; + if (sid == 0xFFFF) continue; + + Slot& slot = model.slots[sid]; + if (camera.culls(transform(slot.bounds, node_world))) continue; + + for (uint32_t i = 0; i < slot.batch_count; ++i) { + Batch& b = model.batches[slot.batch_start + i]; + bind_wear_material(b.material_index); + draw_indexed(b.base_vertex, b.index_start, b.index_count); + } +} +``` + +В реальном кадре между culling и draw добавляются material resolve, lightmap, +render queues и сортировка, но связи данных остаются такими. + +## Иерархия и анимация + +Анимация MSH меняет локальный transform узлов. Geometry streams не изменяются: +для каждого узла на кадр строится matrix из position и quaternion. Дочерний +узел наследует transform родителя, поэтому изменение корпуса переносит башню, +точки крепления и все связанные slots. + +Связка состоит из: + +- type 8: пул animation keys; +- type 19: карта кадров; +- `anim_map_start` и `fallback_key` в `Node38`; +- parent links, задающих порядок умножения matrices. + +Ключ type 8 занимает 24 байта: + +```c +struct AnimKey24 { + float position[3]; + float time; + int16_t qx; + int16_t qy; + int16_t qz; + int16_t qw; +}; +``` + +Quaternion components декодируются как signed value / `32767.0`. На диске +порядок полей XYZ-W, но runtime math использует логическое `[w, x, y, z]`. +Безусловная современная нормализация после чтения не добавляется без parity +проверки: она может изменить крайние кадры. + +Type 19 является массивом `uint16_t`; его `attr2` задаёт общее число кадров +timeline. Для конкретного узла `anim_map_start` указывает на блок длиной +`frame_count` либо равен `0xFFFF`. + +Выбор ключа: + +1. вычислить frame index из времени; +2. если frame вне диапазона, взять `fallback_key`; +3. если `anim_map_start == 0xFFFF`, взять `fallback_key`; +4. иначе прочитать `map_words[anim_map_start + frame]`; +5. если значение не меньше `fallback_key`, снова использовать fallback; +6. иначе использовать mapped key и следующий key для interpolation. + +Fallback возвращается без interpolation. Это защищает статические узлы и конец +track-а. + +Для времени между двумя keys: + +```text +alpha = (t - k0.time) / (k1.time - k0.time) +position = lerp(k0.position, k1.position, alpha) +rotation = shortest-path quaternion blend +``` + +Перед quaternion blend проверяется dot product. Если стороны находятся в +противоположных полусферах, знак второй стороны меняется, чтобы пройти по +короткому пути. При точном совпадении времени возвращается соответствующий key +без вычисления alpha. + +Объект может переходить между двумя animation states. Тогда для каждого узла +сэмплируются позы A и B, затем position смешивается линейно, а quaternion -- +через shortest-path blend. Если одна сторона невалидна, используется другая. + +```c +Pose sample_node(Node n, float t); +Pose blend_pose(Pose a, Pose b, float weight); +Mat4 local = quaternion_matrix(pose.rotation); +local.set_translation(pose.position); +world[n] = world[parent(n)] * local; +``` + +Для parity особенно важны x87-compatible округление при выборе frame index и +порядок операций. Одинаковая формула на SSE может выбрать соседний кадр возле +границы. + +Проверки animation data: + +- размер type 8 кратен 24; +- размер type 19 кратен 2; +- каждый `fallback_key` меньше числа keys; +- блок карты узла полностью помещается в type 19; +- времена keys внутри track возрастают; +- parent links не образуют cycle; +- quaternion components читаются как signed 16-bit. + +## WEAR и MAT0 + +MSH batch хранит только числовой `material_index`. WEAR переводит позиционный +slot в имя материала. MAT0 по этому имени описывает phases, parameters, +texture names и animation blocks. Такое разделение позволяет одной geometry +использовать разные appearances. + +```text +Batch20.material_index + -> строка WEAR + -> имя MAT0 + -> активная phase + -> textureName и render parameters +``` + +### WEAR + +WEAR имеет type ID `0x52414557` и обычно хранится как `*.wea` рядом с моделью. +Формат текстовый: + +```text +<wearCount> +<legacyId> <materialName> +... wearCount строк + +[пустая строка] +[LIGHTMAPS +<lightmapCount> +<legacyId> <lightmapName> +... lightmapCount строк] +``` + +`legacyId` читается и сохраняется, но material выбирается по позиции строки и +имени. Пустая строка перед `LIGHTMAPS` является частью совместимого framing: +parser paths по-разному обрабатывают переход, и отсутствие разделителя ломает +совместимость. Material handle кодируется как `(table_index << 16) | +wear_index`; manager поддерживает ограниченное число wear tables. + +Fallback material resolve строго разделён: + +1. имя из WEAR; +2. `DEFAULT`; +3. entry 0; +4. для lightmap отсутствие означает slot `-1`, а не замену обычной texture. + +Пустое имя texture внутри phase означает намеренно untextured surface. +Lightmap ищется в отдельном cache и не подменяется diffuse texture. + +### MAT0 + +MAT0 имеет type ID `0x3054414D` и обычно находится в `Material.lib`. `attr1` +содержит runtime flags, `attr2` -- версию payload. Versioned metadata читается +cursor-ом: старые версии получают runtime defaults, но reader не пытается +насильно читать поля новой версии. + +```c +#pragma pack(push, 1) +struct Mat0PrefixV4Plus { + uint16_t phase_count; // +0x00 + uint16_t animation_block_count; // +0x02, меньше 20 + uint8_t metadata_a; // +0x04, attr2 >= 2 + uint8_t metadata_b; // +0x05, attr2 >= 2 + uint32_t metadata_c_raw; // +0x06, attr2 >= 3 + uint32_t metadata_d_raw; // +0x0A, attr2 >= 4 +}; + +struct Phase34 { + uint8_t parameters[18]; + char texture_name[16]; +}; +#pragma pack(pop) +static_assert(sizeof(Phase34) == 34); +``` + +Если `attr2 < 2`, metadata A/B получают default `255`; при `attr2 < 3` +значение C соответствует `1.0f`; при `attr2 < 4` D равно 0. C/D сохраняются +как raw 32-bit values до полного подтверждения интерпретации. Phase parameters +сохраняются как 18 raw bytes даже там, где часть bytes уже имеет понятный +смысл. + +Каждая phase разворачивается в runtime-запись примерно 76 байт: коэффициенты +цвета, освещения и прозрачности, texture slot и служебные поля. Material time +выбирает одну или две phases; только часть полей интерполируется, остальные +копируются из активной записи. + +Animation block MAT0 имеет плотный framing без 4-byte tail alignment: + +```text +u32 header_raw +u16 key_count +repeat key_count: + u16 k0 + u16 k1 + u16 k2 +``` + +Младшие три бита `header_raw` задают числовой mode, остальные образуют mask +interpolation. Наблюдаются modes 0, 1, 2 и 3, связанные с семействами loop, +ping-pong, one-shot/clamp и random-offset, но точные boundary cases остаются +предметом runtime parity. Поле `k2` сохраняется всегда. + +Проверки MAT0: + +- `animation_block_count < 20`; +- все versioned metadata помещаются в payload; +- секция phases имеет ровно `phase_count * 34` байта; +- `texture_name` ограничено 16 байтами; +- каждый animation block и его keys помещаются в payload; +- parser заканчивает чтение на точном конце записи. + +Material manager кэширует разобранный MAT0 и texture handles. Current phase +лучше вычислять на экземпляр материала, если random offset или локальное время +различаются между объектами; immutable phase data остаются общими. + +## Texm: текстуры, mip-уровни и атласы + +`Texm` -- основной формат изображений. Он хранится в `Textures.lib`, +`LightMap.lib` и других NRes-архивах. Payload содержит header, необязательную +palette, mip chain и иногда `Page` chunk для atlas rectangles. + +```c +struct TexmHeader32 { + uint32_t magic; // 'Texm' + uint32_t width; + uint32_t height; + uint32_t mip_count; + uint32_t flags4; + uint32_t flags5; + uint32_t unknown6; + uint32_t format; +}; +``` + +Подтверждённые formats: + +```text +0 Indexed8 + palette 256 x 4 байта +565 R5 G6 B5 +556 R5 G5 B6 +4444 A4 R4 G4 B4 +88 L8 A8 +888 RGB8 в четырёхбайтовом element +8888 A8 R8 G8 B8 +``` + +Formats 556 и 88 являются loader-confirmed, но не corpus-verified для +доступных игровых payload. CPU decoder расширяет короткие каналы до 8 bit через +повторение значимых bit, а не простым shift. Для 888 служебный четвёртый byte +сохраняется при roundtrip. + +Layout: + +```text +TexmHeader32 +[palette 1024 байта, только для format 0] +level 0 pixels +level 1 pixels +... +level mip_count-1 pixels +[optional Page chunk] +``` + +Размер уровня `i` вычисляется из `max(1, width >> i)` и +`max(1, height >> i)`. Bytes per pixel: 1 для indexed; 2 для 565, 556, 4444 и +88; 4 для 888 и 8888. Parser суммирует размеры с проверкой overflow до чтения. + +`Page` chunk: + +```c +struct PageHeader8 { + uint32_t magic; // 'Page' + uint32_t rect_count; +}; + +struct PageRect8 { + int16_t x; + int16_t width; + int16_t y; + int16_t height; +}; +``` + +Chunk обязан иметь размер `8 + rect_count * 8`; произвольный tail не +допускается. Rectangles задаются в pixel space базового mip. Если loader +пропускает верхние mip-уровни, rectangles масштабируются вместе с новым base +level. + +Mip-skip является поведением loader-а, а не offline-изменением файла. После +skip меняются runtime width, height, mip count и pointer на первый загружаемый +уровень. Современный renderer должен повторить выбор base level или +эквивалентно эмулировать его upload policy; использование полной texture при +тех же UV меняет резкость и atlas coordinates. + +Indexed texture требует связанную palette. Часть palettes выбирается по suffix +имени: буква `A..Z` и вариант пустой или `0..9`, всего 286 возможных slots. +Невалидный suffix диагностируется явно. + +Обычные textures и lightmaps находятся в разных managers. Обычный cache +отслеживает refcount и время неиспользования, а eviction выполняется +отложенно. Lightmap lifetime связан с world/mission и не должен попадать под +ту же политику удаления. + +Строгий Texm parser проверяет положительные dimensions, положительный +`mip_count`, известный format, точный размер palette/mip chain, корректный +`Page` и отсутствие лишних bytes. `flags4`, `flags5` и `unknown6` сохраняются +1:1; участие `flags5` в mip-skip подтверждено, но полная семантика всех bits не +закрыта. + +## Свет, тени, атмосфера и сортировка + +Свет является отдельной world-подсистемой. Terrain layer создаёт +`LightManager`, `Shader` и primitive managers. Это не один глобальный +коэффициент яркости: world управляет point lights, lightmaps, shadows, +atmospheric objects и sort phases. Материал сообщает свойства поверхности, а +CShade превращает их в states renderer-а. + +Подтверждённые точки: `CreateLightManager`, `CreateShader`, +`CreateAtmosphere`, `CreatePrimitives`, `CreatePrimitives2`, +`CShade::StartMeshRender`, `CShade::EndMeshRender` и +`CShade::ConfigureTextureAndAlphaBlendModes`. + +CShade получает active MAT0 phase, capability profile устройства и pass +context. Он выбирает texture mode, alpha blending, depth/cull behavior и способ +освещения. Наличие fallback вроде `TEXTUREMODE_MODULATE not supported` +означает, что material нельзя напрямую преобразовать в современный PBR. +Сначала строится legacy state, затем он сопоставляется shader permutation. + +CLightManager выдаёт numeric IDs источникам и проверяет допустимое количество. +Ветка `EmulatePointLights()` позволяет воспроизводить point lights даже при +ограничениях hardware lighting. Неизвестный type light должен давать отдельную +ошибку. + +Lightmap не является обычной diffuse texture. WEAR содержит отдельный блок +`LIGHTMAPS`, manager открывает `LightMap.lib`, а shade path подаёт lightmap +отдельным slot или texture stage. Замена lightmap предварительным умножением в +diffuse texture ломает LOD, atlas coordinates и динамическую модуляцию. + +Тени проходят отдельным render pass. Terrain содержит пути для теней зданий и +роботов, ограничения максимального числа, detail level и smoothing. Доказаны +shadow manager/pass, настройки detail/smoothing/count и зависимость от +Terrain/CShade; полная формула projection geometry для каждого caster требует +dynamic trace. Unknown settings из `shade.cfg` читаются и сохраняются по +именам, а не заменяются произвольными modern defaults. + +Atmosphere manager создаёт world objects для фоновых и погодных явлений. +Отдельно подтверждены lightning, sun render, flare, `env_lightning`, rain +background sound и обязательные ссылки на lightning effect. Эти объекты +обновляются по игровому времени, но часть параметров зависит от camera: flare +требует screen position и occlusion test, rain -- области рядом с observer, +sound -- listener. Их нельзя один раз запечь в terrain. + +RNG для lightning, atmosphere phases и FX должен иметь стабильный порядок. +Даже правильный средний интервал не даёт повторяемый кадр, если random values +запрашиваются в другой последовательности. + +Согласованная модель sort phases: + +```text +opaque terrain and models + -> lightmapped/state-grouped passes + -> shadows and projected primitives + -> alpha-tested surfaces + -> transparent objects/effects back-to-front + -> atmosphere, flares and overlays +``` + +Точный взаимный порядок отдельных FX, shadow и atmosphere subpasses требует +capture. Новый renderer должен хранить явный `RenderPhase` и стабильный +secondary sort key, а не сортировать всё только по material ID. + +## FXID: система эффектов + +FXID -- не готовая картинка, а описание небольшого runtime command stream. +Header задаёт lifetime, time mode, random shifts и transform. Затем идут +команды разных types. При создании manager превращает disk-команды в runtime +objects; во время кадра они обновляются и выпускают sounds, particles, +materials или projected primitives. + +Type ID равен `0x44495846`. Header занимает 60 байт: + +```c +struct FxHeader60 { + uint32_t command_count; + uint32_t time_mode; + float duration_seconds; + float phase_jitter; + uint32_t flags; + uint32_t settings_id; + float random_shift[3]; + float pivot[3]; + float scale[3]; +}; +``` + +Поток команд начинается строго с offset `0x3C`. `duration_seconds` +преобразуется runtime-ом во внутреннюю шкалу времени. `phase_jitter` и +`random_shift` используются только при соответствующих flags. Pivot задаёт +локальную точку опоры, scale -- базовый масштаб экземпляра. Unknown flags и +settings ID сохраняются. + +Каждая команда начинается с `uint32_t command_word`: + +```text +opcode = command_word & 0xFF +enabled = (command_word >> 8) & 1 +``` + +Bits 9-31 являются частью данных и сохраняются. Между командами нет +выравнивания. Размер команды, включая word: + +```text +opcode 1 224 байта +opcode 2 148 байт +opcode 3 200 байт +opcode 4 204 байта +opcode 5 112 байт +opcode 6 4 байта +opcode 7 208 байт +opcode 8 248 байт +opcode 9 208 байт +opcode 10 208 байт +``` + +Parser использует opcode только для выбора фиксированного размера. Неизвестный +opcode отклоняется: попытка угадать длину потеряет синхронизацию всего stream. + +Opcodes 2, 3, 4, 5, 7, 8, 9 и 10 содержат pair fixed strings: + +```c +struct FxResourceRef64 { + char archive[32]; + char name[32]; +}; +``` + +Имена сравниваются case-insensitive по ASCII, а tail после первого nul byte +сохраняется. Resolve выполняется при создании command object или лениво при +первом запуске, но ошибка должна включать имя эффекта, номер команды, archive +и resource name. + +Базовый normalized age: + +```text +tn = (now - start_time) / (end_time - start_time) +``` + +`time_mode` выбирает источник коэффициента: constant, forward/reverse age, +cyclic phase, external world state и варианты с ограничением относительно +предыдущего значения. Точные формулы редких modes являются parity-задачей. +Flags могут умножать alpha на lifetime, применять triangular remap, случайно +сдвигать phase/space, инвертировать active-state, фильтровать по времени суток +или включать manager gates. + +Lifecycle: + +```text +create instance + -> copy header and external transform + -> calculate end time and random offsets + -> create command objects in disk order + -> resolve required resources + -> Start + +on each calculation/render frame + -> evaluate time coefficient and gates + -> update commands in stable order + -> emit active primitives or sounds + -> collect render batches + -> handle Stop / Restart / end-of-life +``` + +Update и emit разделяются. Simulation может продолжаться в кадре без render, а +emit не должен повторно менять игровое состояние. Для authoring безопасно +типизировать header и resource references, а body редких commands сохранять raw +до подтверждения field-level semantics. + +## Полный кадр + +Крупный вход в world render проходит через `World3D::stdRenderGame`. Доказан +следующий порядок boundary операций: + +1. передать camera в Terrain через `stdSetCurrentCamera2` и сохранить её как + текущую; +2. получить camera/view/viewport interfaces через virtual queries; +3. обновить положение и ориентацию 3D sound listener; +4. настроить renderer viewport и matrices; +5. вызвать два renderer boundary slots перед traversal; +6. установить глобальный флаг `in_render`; +7. вызвать главный virtual метод camera/world traversal; +8. выполнить дополнительную post queue при включённом режиме; +9. завершить world/shade pass; +10. вызвать renderer completion slot; +11. снять `in_render`, восстановить viewport и разослать end-of-render. + +Семантические имена нескольких slots перед и после traversal не подтверждены, +поэтому в compatibility code их лучше временно называть +`frame_boundary_0`, `frame_boundary_1`, `frame_boundary_2`. + +Обход видимого мира: + +```text +проверить active/visible state + -> выбрать LOD по расстоянию и настройкам + -> получить node matrices из animation state + -> выбрать slot для каждого node/group + -> преобразовать bounds в world space + -> выполнить culling + -> добавить batches в подходящую render queue +``` + +Material/texture resolve желательно выполнять после visibility и slot +selection, чтобы невидимые объекты не меняли порядок обращений к caches и не +создавали лишние side effects. Невидимость объекта и отсутствие slot являются +разными причинами пропуска и диагностируются отдельно. + +Подготовленный draw item содержит: + +```text +node world matrix +batch flags and index range +WEAR material handle +MAT0 active phase and coefficients +texture handle +optional lightmap handle +render phase and sorting key +legacy pipeline state +``` + +Draw item должен ссылаться на immutable данные кадра. Изменение phase или +texture cache посреди прохода не должно менять уже собранную очередь. + +Согласованная декомпозиция внутренних render phases: + +1. подготовка frame state, camera и viewport; +2. непрозрачный terrain; +3. непрозрачные object batches; +4. lightmap и дополнительные material passes; +5. projected primitives и тени; +6. alpha-tested geometry; +7. transparent objects и FX в сортировочных слоях; +8. atmosphere, sun, flare и weather; +9. renderer completion boundary; +10. end-of-render callbacks; +11. shell/UI и post-render state. + +Точный взаимный порядок пунктов 4-8 и связь completion slot с физическим +DirectDraw flip/present требуют dynamic capture. Сортировка внутри каждой фазы +должна быть стабильной: для opaque первичен pipeline/material key, для +transparent -- distance layer и depth order, затем stable insertion ID. + +Геометрический draw использует streams type 3/4/5, optional streams, index +buffer type 6, `base_vertex`, `index_start` и `index_count`. Матрица узла +устанавливается как world transform, затем CShade привязывает texture stages и +fixed-function state. + +```c +set_world_matrix(item.node_world); +bind_vertex_streams(model.streams); +bind_index_buffer(model.indices); +apply_legacy_state(item.pipeline); +bind_texture(0, item.texture); +bind_texture(1, item.lightmap); +draw_indexed(item.batch.base_vertex, + item.batch.index_start, + item.batch.index_count); +``` + +После последнего world pass renderer закрывает сцену и выводит back buffer. +World3D снимает `in_render`, восстанавливает временный viewport state и вызывает +`on_end_render` у active objects. Только после этого допустимо освобождать +temporary vertex buffers или заменять render representation. UI/shell +обслуживается верхним уровнем после возврата из world-render path; для +диагностики полезно уметь сохранять world-only command list и финальный +framebuffer отдельно. + +## Проверки паритета + +Главные риски совпадения кадра: + +- x87 extended precision и правила округления; +- различия scalar/SIMD slots `g_FastProc`; +- порядок objects, batches и transparent primitives; +- depth write/test, cull, alpha test и blend transitions; +- mip-skip, palette и `Page` coordinates; +- material fallback и выбор phase; +- последовательность RNG для FX и atmosphere; +- capability fallback конкретного устройства; +- quantization времени и дополнительный simulation step; +- eager/lazy resource resolve и cache side effects. + +Минимальный deterministic frame capture должен включать camera state, viewport, +visible object IDs, выбранные LOD/group/slot, draw-item list, material и texture +handles, pipeline keys, matrices, render phase, sort key, причины culling и +hashes промежуточных buffers. Без такой трассировки нельзя уверенно отделить +ошибку формата MSH от ошибки state machine renderer-а или сортировки. + +Связанные справочные страницы с таблицами форматов: [MSH](../reference/msh.md), +[materials](../reference/materials.md), [Texm](../reference/texm.md) и +[render frame](../reference/render-frame.md). diff --git a/docs/tomes/06-behavior.md b/docs/tomes/06-behavior.md new file mode 100644 index 0000000..93ec301 --- /dev/null +++ b/docs/tomes/06-behavior.md @@ -0,0 +1,769 @@ +# VI. Поведение, управление, звук и сеть + +Шестой том описывает подсистемы, которые превращают загруженный мир в +реагирующую игру: AI, Behavior, Wizard, Control, ввод, камеру, звук и сеть. +Эти области нельзя восстанавливать только по структуре файлов. Для них важны +порядок кадра, ownership объектов, timing событий и доказуемые границы между +решением, движением, presentation и транспортом. + +Ключевой принцип: reader compatibility не равна gameplay compatibility. +Корректно разобранный ресурс ещё не доказывает, что runtime выбирает ту же +цель, строит тот же маршрут, применяет ту же collision correction, создаёт тот +же sound event или отправляет тот же network payload. Поэтому все утверждения +ниже разделяют подтверждённую структуру, восстановленный архитектурный +контракт и открытые участки, требующие динамической трассировки. + +```text +AI / mission script + -> стратегическая цель, условия, команды миссии +Behavior + -> состояние объекта, target, global/local path +Wizard + -> локальная коррекция траектории +Control + -> physical step, collision proxy, итоговый transform +World3D + -> очередь событий, ownership, deferred deletion +Render / Sound / Net + -> представление, listener, mirrors и сообщения +``` + +Связанные главы: [мир и миссии](04-world.md), [геометрия и рендер](05-render.md) +и справочный [render frame](../reference/render-frame.md). + +## AI, Behavior и Wizard + +Iron3D разделяет стратегическое принятие решений, поведение конкретного объекта +и локальную коррекцию движения. Это разделение должно сохраниться в новой +реализации: стратегический AI не меняет transform напрямую, а collision manager +не выбирает игровую цель. + +```text +ai.dll / SuperAI + -> цель клана, миссии и группы +Behavior.dll + -> состояние юнита, target, global path, local corridor +Wizard.dll + -> ближайшая допустимая траектория +Control.dll + -> физическое движение и столкновения +``` + +### Behavior + +`CreateBehaviour` создаёт controller для отдельного игрового объекта. +`CreateDistributor` восстановлен по consumers как посредник распределения +команд или ресурсов; это высокоуверенный архитектурный вывод, а не доказанное +имя внутреннего класса. Behavior получает `IArealMap` через AI/клановый +контекст, ведёт radar/target state, строит global path, превращает его в local +corridor и передаёт движение Wizard. + +Ошибочные состояния проверяются явно: + +1. отсутствует system map; +2. отсутствует terrain interface; +3. active behavior не имеет `IArealMap`; +4. объект попал в non-reachable area; +5. объект пытается выйти из non-walkable area; +6. path generator вошёл в infinite cycle. + +Эти случаи являются fatal или diagnostic conditions. Совместимая реализация не +должна тихо исправлять их teleport-ом, потому что такое исправление скрывает +ошибку areal graph, terrain query или state machine. + +### Параметры Behavior.ini + +Подтверждены настройки: + +```text +PathFind_BuildingHitDist +PathFind_BuildingNearestDist +PathFind_NearBuildSpeedPercent +PathFind_CorridorRadius +PathFind_NearDoorCoeff +PathFind_fStepOffBuilding +PathFind_MaxAccel +PathFind_MaxRotation +PathFind_fStepDist +PathFind_MinPointInTrajectory +Network_ResourceTransferMaxDelay +``` + +Они задают геометрию corridor, дистанции реакции на здания, снижение скорости +возле препятствий, пределы ускорения и поворота, дискретизацию trajectory и +сетевой timeout передачи ресурсов. Значения читаются как runtime-конфигурация, +а не компилируются в код. Parser должен поддерживать комментарии `//`, пробелы +вокруг `=` и CRLF. + +Файл также содержит logging/debug switches: `Behavior.log`, уровни ошибок, +show vectors и z-buffer debug. Эти переключатели полезны не только для +совместимости, но и как модель современных trace flags. + +### Wizard + +Wizard получает желаемое направление и corridor, анализирует ближайшие +ограничения и выдаёт скорректированную локальную траекторию. Behavior может +очищать её через `ClearWizardPath` при смене цели, повреждении global path или +переходе объекта в неактивное состояние. + +Нужно различать четыре уровня движения: + +- **global path** -- последовательность areals; +- **local path** -- точки или сегменты внутри corridor; +- **wizard path** -- краткосрочное движение с учётом ближайших препятствий; +- **physical step** -- фактически разрешённое Control перемещение. + +Хранение всего маршрута одним массивом лишает систему возможности локально +обойти препятствие без полного повторного поиска. Граница Behavior/Wizard +существует именно для того, чтобы краткосрочная геометрическая коррекция не +ломала стратегический path state. + +### SuperAI и миссионные сценарии + +`CreateSuperAI` создаёт центральный controller клана; `GetSuperAI` возвращает +его. AI загружает файлы из `MISSIONS\SCRIPTS\`, проверяет версию и пишет ошибки +в `ai.log`. Несовпадение версии является отдельной ошибкой, а не неизвестной +командой. + +Сценарный корпус содержит binary `.scr`, formula exports `.fml`, таблицу +переменных `varset.var` и `.trf`-данные. `.scr` хранит именованные секции и +события, например `Init`, `Mission`, `Problems0`, `Fort_Task_Complete` и +`Hero_Teleported`, вместе с числовыми ссылками на compiled instructions. +`.fml` является текстовым экспортом formula set. `varset.var` декларативно +описывает типы, defaults, ranges и строки через макросоподобные формы +`VAR(...)` и `STRING(...)`. + +Безопасная runtime-модель: + +```text +load script bundle + -> validate version and symbol tables + -> create global/formula variables + -> bind named events to instruction offsets + -> instantiate SuperAI per clan + -> dispatch MISSION_START and object events + -> update timers/conditions each simulation tick + -> enqueue game commands through World3D/Behavior +``` + +Сценарий не должен владеть игровым объектом напрямую. Он хранит logical/object +IDs и отправляет команды через игровые interfaces, чтобы удаление объекта или +сетевой mirror не оставили dangling pointer. + +Полная grammar compiled instructions и точное значение всех opcodes остаются +открытым направлением. До появления decompiler-а `.scr` binary body сохраняется +lossless, а доказанные symbol/event tables документируются отдельно. + +### TRF и preload-данные + +TRF-файлы проходят структурный разбор. `auto.trf`, `data.trf` и tutorial +variants имеют сигнатуру [NRes](../reference/nres.md) и содержат большие +таблицы имён игровых прототипов: оружия, башен, сооружений и других объектов. +Также найдены preload-записи, ANI и SKE resources. + +По содержимому, порядку загрузки и consumers TRF с высокой вероятностью +предоставляет AI/сценарному слою заранее подготовленную таблицу типов и +связанных данных. Framing и имена подтверждены corpus-ом, но полная семантика +каждой TRF-записи ещё не закрыта. Имена должны разрешаться через тот же +resource registry, что и миссионные объекты. + +### Стабильность AI-слоя + +`ai.dll`, `Behavior.dll` и `Wizard.dll` побайтно идентичны в Частях 1 и 2. Это +подтверждает, что разделение SuperAI -> Behavior -> Wizard и бинарная +реализация этих трёх уровней не менялись. + +Сценарный корпус: + +```text +Часть 1: 58 SCR, 58 FML, 29 TRF +Часть 2: 59 SCR, 59 FML, 44 TRF +``` + +Все TRF являются структурно валидными NRes. Неизменность DLL усиливает вывод о +стабильной VM, но не закрывает instruction grammar `.scr`: для неё нужен +dispatcher/jump-table decompiler. Дополнительные сценарные данные расширяют +differential corpus, но не заменяют анализ VM. + +## Control, физика и коллизии + +Control превращает желаемое движение в физически допустимое изменение +состояния. World3D владеет жизненным циклом объекта; Terrain предоставляет +поверхность и world queries; Behavior/Wizard задают намерение; Control создаёт +physical controller и collision representation. + +Публичная поверхность: + +```text +InitializeSettings +LoadControlSystem +LoadPhysicalModel +CreateCollManager +CreateCollObject +``` + +Модуль импортирует World3D queue/object functions, `Terrain::GetWorld`, часы, +тригонометрию и `g_FastProc`. Это подтверждает его положение между gameplay +object и геометрией мира. + +### Control system и physical model + +`LoadControlSystem` загружает настройки controller-а: ограничения скорости, +ускорения, поворота и режимы управления. `LoadPhysicalModel` загружает форму и +параметры, используемые для столкновений. Visible MSH не обязан совпадать с +collision representation: для физики часто нужна более простая и устойчивая +форма. + +Практичная runtime-модель: + +```c +struct PhysicalState { + Transform transform; + Vec3 linear_velocity; + Vec3 angular_velocity; + float requested_speed; + float requested_turn; + uint32_t flags; +}; + +struct CollisionProxy { + ObjectId owner; + ShapeSet shapes; + Bounds broad_phase_bounds; + uint32_t category_mask; +}; +``` + +Названия полей здесь описывают контракт совместимой реализации, а не точный +layout исходного C++-объекта. + +### Collision pipeline + +Один расчётный шаг удобно разделить так: + +1. controller получает желаемые `speed`/`turn` от Behavior или manual input; +2. вычисляет кандидатный transform на основе `dt`; +3. обновляет broad-phase bounds collision object; +4. collision manager находит потенциальные пары и terrain candidates; +5. narrow phase вычисляет контакт или допустимый остаток перемещения; +6. physical state корректируется; +7. World3D получает итоговый transform; +8. событие `GMSG_COLLISION_DETECTED` отправляется в согласованной фазе. + +Позиция collision event после narrow phase является рекомендуемой фазой +реализации и согласуется с назначением сообщения, но точный call-site +относительно всех correction steps требует динамической трассировки Control. +Удаление объекта из обработчика остаётся отложенным по правилам World3D. +Collision manager не должен хранить прямую незащищённую ссылку на объект, +который уже pending-delete. + +### CTLD и physical resources + +Реестр прототипов ссылается на `*.ctl`, `*.cpt` и связанные control resources. +В Части 1 структурно проверен 531 CTLD payload без ошибок. Размеры и пять +внутренних счётчиков образуют множество вариантов: наиболее частый размер +392 байта с pattern `(0,0,0,1,0)`, но встречаются блоки от примерно 212 до +1868 байт и более сложные комбинации. + +CTLD является составным count-driven форматом, а не фиксированной struct. +Parser должен: + +- прочитать prefix и все счётчики с проверкой переполнения; +- вычислить границы секций по их counts; +- сохранять неизвестные records в typed raw containers; +- требовать точного завершения payload; +- не использовать размер одного популярного варианта как универсальный layout. + +Полная предметная семантика всех секций ещё не доказана, но существующие файлы +можно безопасно читать, индексировать и сохранять. + +### Terrain queries и movement handoff + +Control получает world-interface Terrain и использует поверхность, faces и +ускорители для высоты, нормали и пересечений. Навигационный маршрут сообщает, +куда двигаться, но итоговый transform определяется по физической поверхности. +При переходе через склон controller должен согласовать горизонтальный шаг, +высоту и ориентацию с terrain normal. + +Порядок операций должен быть детерминированным: пары collision objects +сортируются по стабильному ID, contacts обрабатываются в фиксированной +последовательности, а интеграция использует одну политику `dt` и округления. +Иначе одинаковая миссия постепенно расходится даже без сети. + +### Различия Control в Части 2 + +`Control.dll` пересобрана при неизменных размере, imports и пяти именах/ordinals +exports; RVA всех пяти exports изменились. Форматы и cross-module boundary +сохранились, но точное physical/collision behavior нельзя считать побайтно тем +же. + +CTLD-корпус расширен с 531 до 623 payload. Новых framing errors не найдено; +большинство общих CTLD изменено вместе с переработанными моделями. Это +подтверждает count-driven parser, но не закрывает предметную семантику shape +records и contact solver. + +Differential test обеих частей должен воспроизводить движение без препятствий, +slope following, pair collision, timing collision event и удаление объекта в +callback. Сравниваются transforms и contact events по tick, а не только факт +успешной загрузки. + +## Ввод, камера и управление + +World3D нормализует клавиатуру, мышь и joystick в общие scan codes и manual +commands. Win32 message handler вызывает `UpdateManualEventsList`; перед +обработкой новой порции сообщений основной цикл вызывает +`ClearManualEventsList`. Снимок клавиатуры очищается отдельно через +`stdClearKeyboard`. + +Публичная поверхность включает `WinMsg2ScanCode`, converters для +keyboard/mouse/joystick/predicate, `ScanCode2Str`, `ManualCommand2Str`, +`stdIsKeyPressed`, lock/unlock keyboard и чтение mouse shift. Это позволяет +хранить конфигурацию управления независимо от физического устройства. + +### Event, state и axis + +Ввод имеет минимум три семантики: + +- **edge event** -- нажатие или отпускание в текущей порции сообщений; +- **held state** -- клавиша остаётся нажатой между кадрами; +- **analog value** -- смещение мыши или положение joystick axis. + +Manual command дополняет источник коэффициентом, режимом wrap, dead +zone/threshold и временной характеристикой. Строки camera bindings показывают +команды `MCMD_STATE`, `MCMD_ANGLE_X`, `MCMD_ANGLE_Y`, режимы `MAN_WRAP` и +`MAN_NOTWRAP`, а также параметры ускорения в миллисекундах. + +Simulation читает подготовленный input snapshot. Renderer не должен +самостоятельно опрашивать OS, иначе одно и то же нажатие будет зависеть от +частоты кадров. + +### Joystick через DirectInput + +`Joystick.dll` экспортирует: + +```text +QueryJoy +CreateJoy +ReleaseJoy +SetJoyRange +PeekJoyMessage +GetJoyCaps +``` + +`QueryJoy` обнаруживает устройство, `CreateJoy` получает интерфейс DirectInput, +`SetJoyRange` нормализует оси в диапазон движка, `PeekJoyMessage` выдаёт +очередное унифицированное событие. + +При потере устройства чтение может вернуть ошибку acquired state. Интерфейс +следует повторно получить, очистить устаревшее состояние и продолжить. +Hot-unplug не должен оставлять последнюю ось навсегда отклонённой. +`GetInstalledJoyNames` и `SetActiveJoy` в World3D связывают device list с +game-facing выбором. + +### Два camera interface + +World3D предоставляет `stdSetCurrentCamera`/`stdGetCurrentCamera`: это камера +как часть игрового состояния. Terrain имеет +`stdSetCurrentCamera2`/`stdGetCurrentCamera2`: concrete camera, которую world +renderer использует для matrices, viewport и visibility. + +`LoadCamera` экспортирован обоими модулями. По call graph World3D-вариант +играет роль component bridge, а Terrain-вариант связан с concrete +camera/world implementation. Это архитектурный вывод: точные class names и +layout не восстановлены. + +Минимальные данные камеры: + +```text +world position and orientation +view matrix +projection parameters / field of view +near and far planes +viewport rectangle +camera mode and target object +manual angles/state +``` + +Такая граница позволяет game code работать с абстрактной камерой, не зная +внутреннего renderer representation. + +### Camera commands и порядок кадра + +Подтверждены команды `CMD_CAMERA_LEFT`, `CMD_CAMERA_RIGHT`, `CMD_CAMERA_UP`, +`CMD_CAMERA_DOWN`, `CMD_CAMERA_CENTER`, `CMD_CAMERA_INFRARED`, а также +spotlight и внешние/миссионные camera modes. Горизонтальный угол использует +wrap, вертикальный -- ограниченный диапазон. Center плавно возвращает обе оси к +заданному значению. + +Порядок кадра: + +1. собрать manual events; +2. обновить camera controller во время calculation; +3. вычислить итоговый transform и ограничения; +4. перед render установить current camera; +5. передать её Terrain и sound listener; +6. после кадра сохранить mode-specific state. + +Camera smoothing должно использовать игровое время или специально +подтверждённые часы. Привязка к render delta делает управление разным при 30 и +144 FPS. + +## Звуковая подсистема + +Ngi32 создаёт низкоуровневый DirectSound backend. `services.dll` публикует +`ISoundServer`. Game, Terrain и FX работают уже через эти интерфейсы: +воспроизводят 2D/3D sources, меняют volume и связывают listener с camera. + +Публичные функции Ngi32: + +```text +niCreate3DSound +niGet3DSound +niGet3DSoundCaps +niMuteSound +``` + +Backend динамически вызывает `DirectSoundEnumerateA` и `DirectSoundCreate`; +параметр `DisableDSound` может полностью отключить этот путь. + +### Устройство и capabilities + +Конфигурация учитывает `3D Sound`, качество, reverse sound, частоту buffer, +режим постоянного воспроизведения и автоматический выбор лучшего устройства. +Эти значения преобразуются во внутренний capability/profile object до создания +sources. + +Код содержит отдельный no-device state и строку `3D Sound was not initialized`. +Отсутствие 3D sound обрабатывается отдельно от ошибок simulation/resources. +Новый runtime не должен позволять отсутствию звука разрушать simulation и +обязан возвращать звуковым командам явный no-device result. + +Общий sound object разделяется между подсистемами и использует счётчик +владельцев. Закрывать DirectSound следует после остановки всех sources и +atmosphere/FX managers. + +### Sound resources и SWAV + +Основная библиотека называется `sounds.lib`; `mission.cfg` также создаёт +именованные sound resources и variations. Legacy API `rsLoadWave` загружает +waveform из archive. Импорт `MSACM32` подтверждает путь преобразования сжатых +wave-данных в формат playback buffer. + +Resource identity состоит из library и name. Один sound asset может иметь +несколько runtime sources с различными position, volume, pitch/flags и временем +запуска. Поэтому кэшировать следует decoded sample/buffer, а source object +создавать на событие. + +FX opcode 2 хранит `archive[32] + name[32]` и обычно создаёт sound command. +Atmosphere использует отдельные loop/variation sources, например rain +background. Миссионный слой содержит voice events для завершения или провала +задания. + +Проверенный SWAV-корпус: + +```text +Часть 1: 399 — 306 MS ADPCM, 93 PCM +Часть 2: 540 — 446 MS ADPCM, 93 PCM, 1 empty entry +``` + +Все непустые записи имеют RIFF/WAVE framing и частоту 22 050 Hz. В Части 2 +entry `ALIEN_ME.WAV` имеет размер 0. Это присутствующий archive key без +decodable waveform. + +Sound loader должен различать: + +- `entry_missing`; +- `entry_empty`; +- `wave_invalid`; +- `decoded_sample`. + +Нулевой payload не передаётся RIFF parser-у и не должен приводить к чтению +header за границей. + +### 3D listener и sources + +Перед world traversal `stdRenderGame` обновляет listener из camera transform. +Listener содержит position, orientation и, при наличии, velocity. Source +содержит world position и параметры затухания. Spatialization выполняется +backend-ом либо совместимой программной моделью. + +```text +camera transform + -> listener position/front/up +object or effect transform + -> source position +sample + source parameters + -> DirectSound 3D buffer +``` + +Прямо подтверждено обновление listener в начале `stdRenderGame`, до world +traversal. Sound events могут создаваться и в calculation/FX path, поэтому +нельзя утверждать, что listener предшествует созданию каждого source. Важно, +что spatial backend получает camera state текущего отображаемого кадра до +завершения его обработки. Перенос listener update после world render создаст +как минимум однокадровое рассогласование presentation. + +### Громкость, mute и CD-аудио + +`iron3d.dll` применяет отдельные настройки эффектов и CD sound. Параметр +`FORCE_CD_SOUND` меняет политику выбора музыкального источника. `niMuteSound` +должен временно остановить вывод без разрушения sample cache и logical playback +state. + +В новой реализации полезно разделить buses: master, effects, ambient, voice и +music/CD. Это проектное решение совместимого backend-а, а не доказанный layout +оригинального mixer-а. Оно позволяет применять старые коэффициенты, не +переписывая individual source volume. + +### Граница service layer + +`Ngi32.dll` с DirectSound/backend code не изменилась между Частями 1 и 2, но +`services.dll` пересобрана и уменьшилась на 4 096 байт. Поэтому low-level +decoder/device path подтверждается одной машинной реализацией, а service +lifecycle, GUI/audio wiring и defaults требуют раздельной трассировки обеих +частей. + +## Сетевая подсистема + +Net инкапсулирует DirectPlay4A и lobby/service-provider API. World3D строит над +транспортом player identity, mirror objects и игровые сообщения. Эти уровни +следует разделять: DirectPlay отвечает за доставку bytes между players, +World3D -- за смысл сообщения и владение объектом. + +Application GUID: + +```text +{3C1D1F01-A870-11D1-8400-000021B14415} +``` + +Он передаётся network instance и service layer. Экземпляры с другим GUID не +принадлежат одному логическому приложению. + +### Lifecycle соединения + +Публичные функции Net покрывают полный цикл: + +```text +CreateNetworkInstance + -> select/use service provider + -> setup connection + -> enumerate or create session + -> join/create session + -> create local player + -> send/receive messages and player data + -> destroy player + -> close session + -> close connection +``` + +Поддерживаются providers эпохи DirectPlay: TCP/IP, IPX и modem/lobby варианты, +если они установлены в системе. Функции явно проверяют, что DirectPlay enabled +до enumeration, session и player operations. Неверный порядок вызовов должен +возвращать понятную ошибку, а не разыменовывать пустой interface. + +### Sessions, players и адреса + +Net предоставляет enumeration service providers и sessions, выбор host/join, +player name/password/data, latency, максимальный размер сообщения, размер +очереди, server player info и provider address. Lobby launch обрабатывается +отдельной веткой. + +Внутренняя модель должна хранить как минимум: + +```c +struct NetPlayer { + TransportPlayerId transport_id; + uint16_t game_player_number; + string name; + RawBytes player_data; + bool is_local; + bool is_host; +}; +``` + +Transport ID нельзя использовать как постоянный `ObjectId`. NetWatcher связывает +временный DirectPlay identifier с номером игрока и World3D entities. + +### Игровые сообщения World3D + +Подтверждённые имена message surface: + +```text +GMSG_CREATE_REMOTE_PLAYER +GMSG_APPEND_RESOURCE +GMSG_CHANGE_OBJECT_OWNER +GMSG_SET_PLAYER_DATA +GMSG_MISSION_DATA_PATH +GMSG_TAKE_OBJECT +GMSG_TEXT_FOR_PLAYER +GMSG_SYNC_STATE +GMSG_CREATE_MIRROR +GMSG_PAUSE_REMOTE_PLAYER +GMSG_CONFIRM_PLAYER_DATA +GMSG_KILL_PLAYER +SYSMSG_SET_TIME +SYSMSG_SET_PLAYER_NUMBER +GMSG_END_MESSAGE_SEQ +GMSG_REMOVE_RESOURCE +``` + +`GMSG_COLLISION_DETECTED` относится к общей очереди, но не обязательно +передаётся по сети. Message ID, payload size и delivery policy должны быть +частью явной schema. Нельзя сериализовать C++ pointers или native padding. + +### Mirror objects и ownership + +Удалённо принадлежащий объект представлен local mirror instance. Он участвует в +рендере и spatial queries, но authority над его созданием, ключевыми properties +и удалением находится у owner player. Сообщение смены владельца обновляет эту +границу; оно не должно создавать второй объект с тем же ID. + +Типовой путь: + +```text +remote create message + -> validate player and ObjectId + -> resolve prototype/resources + -> CreateMirrorObject + -> apply initial state + -> AddMirrorObjectToGame + -> subsequent sync messages update mirror +``` + +При потере player NetWatcher инициирует предписанное удаление или transfer +ownership через World3D queue. Мгновенное освобождение во время receive callback +запрещено по тем же причинам, что и в calculation pass. + +### Сжатие и wire compatibility + +`netZipData` и `netUnZipData` образуют встроенный слой упаковки payload. Он +находится выше транспорта: переход с DirectPlay на UDP/ENet не отменяет +необходимость воспроизводить формат упакованного сообщения, если требуется +соединение с оригинальной игрой. + +Полный wire schema, framing и алгоритм сжатия пока не доказаны packet +capture-ом. Поэтому нужны два режима: + +- **native compatibility** -- отдельный adapter, реализуемый после трассировки + оригинальных packets; +- **modern multiplayer** -- новая versioned protocol schema, использующая ту же + game-message семантику, но не заявляющая совместимость с DirectPlay client. + +Эти режимы нельзя незаметно смешивать. До доказательства native wire +compatibility современный transport должен быть versioned и отделён от слоя, +который претендует на совместимость с оригинальным клиентом. + +### Стабильность сетевого слоя + +`Net.dll` и `World3D.dll` побайтно идентичны в обеих частях. Application GUID, +DirectPlay wrapper, mirror-object API и World3D message surface относятся к +одной машинной реализации. + +Это подтверждает отсутствие отдельной сетевой реализации для Части 2, но не +закрывает wire schema: без packet/send-receive capture по-прежнему неизвестны +точное framing, reliability flags, payload layouts и алгоритм `netZipData` для +native interoperability. + +Для binary regression достаточно одного профиля неизменённых DLL, но message +captures должны включать контент обеих частей, потому что prototype/resource IDs +и mission data различаются. + +## Контракты реализации + +Совместимая реализация должна фиксировать не только результат, но и момент его +появления в кадре. Для Behavior, Control, input, sound и network особенно важны +tick boundaries: одна и та же команда, применённая на один tick раньше или +позже, меняет дальнейшую симуляцию. + +### Trace-события + +Минимальный trace для этого тома: + +- input snapshot: edge events, held state, analog values; +- camera state: mode, target, angles, matrices, viewport; +- Behavior: target, areal, global path revision, local corridor; +- Wizard: requested vector, constraints, wizard path; +- Control: candidate transform, contacts, correction, final transform; +- World3D queue: message name, ObjectId, dispatch phase, deferred deletion; +- sound: sample key, source owner, position, event tick, listener state; +- network: player mapping, message ID, payload length, delivery policy. + +Для рендера это связывается с [render frame](../reference/render-frame.md): +camera и listener должны попадать в trace до world traversal, иначе нельзя +отделить ошибку presentation от ошибки управления. + +### Проверки Behavior и сценариев + +- script version mismatch даёт отдельную ошибку; +- event table читается lossless; +- VM body сохраняется без потери неизвестных bytes; +- отсутствующий `IArealMap` не замалчивается; +- non-walkable/non-reachable states дают diagnostic condition; +- одинаковый input log воспроизводит одинаковый sequence Behavior commands; +- resource names из TRF разрешаются через общий registry. + +### Проверки Control + +- движение без препятствий; +- slope/terrain-following; +- симметричные pair-collision tests с переставленными IDs; +- contact event отправляется один раз в предписанной фазе; +- удаление объекта в collision callback безопасно; +- replay одинакового input log даёт одинаковые transforms; +- collision proxy перестраивается после смены component/model state. + +### Проверки input и камеры + +- edge event не повторяется как held state; +- mouse/joystick axis сбрасывается по правилам snapshot; +- hot-unplug joystick не оставляет старое отклонение; +- camera horizontal angle wraps, vertical angle clamps; +- center command использует подтверждённое время, а не render FPS; +- Terrain и sound получают одну и ту же camera frame. + +### Проверки звука + +- backend может отсутствовать без нарушения simulation; +- один decoded sample переиспользуется несколькими sources; +- `entry_missing`, `entry_empty` и `wave_invalid` различаются; +- listener совпадает с camera frame; +- loop source корректно переживает pause/resume; +- mute не сбрасывает position и time; +- missing sound resource содержит полную диагностическую цепочку; +- deterministic test сравнивает список sound events, а не waveform устройства. + +### Проверки сети + +- нельзя создавать queue с активной сетью и нулевым player ID; +- session/player operations до enable/setup возвращают ошибку; +- сообщения проверяют длину до чтения payload; +- sequence/end markers обрабатываются в стабильном порядке; +- duplicate create mirror не создаёт второй instance; +- ownership change атомарно обновляет routing; +- pause/time messages применяются в одной simulation boundary; +- resource transfer имеет timeout `Network_ResourceTransferMaxDelay`; +- disconnect не оставляет objects с несуществующим owner; +- replay записанного message log даёт одинаковое World3D state. + +`resnet.log` и `NetWatch.log` следует поддерживать как отдельные каналы: первый +относится к transport/resource exchange, второй -- к связи players и game +objects. + +## Границы знания + +Подтверждены внешние interfaces, часть runtime order, значимые строки, +конфигурационные параметры, corpus-level counts и стабильность ряда DLL между +двумя частями. Открытыми остаются: + +- instruction grammar `.scr` и semantics всех VM opcodes; +- точная семантика всех TRF-записей; +- полный layout CTLD shape records; +- contact solver и порядок всех correction steps; +- class layout камер, контроллеров, sound service и network watcher; +- DirectPlay wire framing, reliability flags и payload schema; +- алгоритм `netZipData`/`netUnZipData`; +- точные defaults service layer там, где DLL пересобраны. + +Эти границы должны оставаться видимыми в документации и тестах. Если новая +реализация вводит удобный современный abstraction layer, он обязан быть +отделён от утверждений о native compatibility и покрыт отдельным trace. diff --git a/docs/tomes/07-implementation.md b/docs/tomes/07-implementation.md new file mode 100644 index 0000000..968d61b --- /dev/null +++ b/docs/tomes/07-implementation.md @@ -0,0 +1,674 @@ +# VII. Руководство по полной реализации + +Этот том описывает инженерный путь к совместимому движку FParkan. Он опирается +на доказанные форматы и runtime-контракты, но не требует повторять физическое +деление оригинала на пятнадцать DLL. Повторить нужно наблюдаемое поведение: +форматы, имена, fallback, object IDs, порядок событий, численную политику, +границы кадра, сохранения и воспроизводимость прохождения. + +Предложенные ниже modules, handles, snapshots, queues и scheduler phases являются +целевой архитектурой новой реализации, а не восстановленным внутренним layout +оригинального Iron3D. Главная практическая цель: запускаться из неизменённого +оригинального каталога игры, проходить corpus gates для демоверсии, Части 1 и +Части 2, а затем измеримо двигаться от archive compatibility к полной игровой +совместимости. + +## Целевая архитектура + +Практичная форма новой реализации -- модульный монолит с узкими интерфейсами и +отдельными platform adapters. Внутренние границы должны соответствовать ролям +Iron3D, а не обязательно его DLL. Это упрощает перенос на современные платформы +и оставляет возможность поддерживать разные compatibility profiles для разных +сборок данных. + +```text +application запуск, окно, конфигурация, shutdown +platform filesystem, clocks, input, threads, dynamic libraries +resources NRes, RsLi, paths, archives, cache and diagnostics +assets MSH, WEAR, MAT0, Texm, FXID and auxiliary formats +mission TMA, unit DAT, prototype graph, scenario data +world ObjectId, queue, lifecycle, time, messages, mirrors +terrain Land.msh, Land.map, surface and spatial queries +navigation areals, graph search, corridors +behavior unit state machines, target and path requests +physics control systems, collision proxies and contacts +animation pose sampling, hierarchy and blending +audio sample cache, sources, listener and buses +render legacy-state compatibility and modern backend +network game message schema plus transport adapters +tools validators, extractors, viewers, captures and editors +``` + +Каждый модуль зависит от нижележащих интерфейсов, а не от concrete managers. +Behavior видит `INavigation` и `IPhysicsCommandSink`, но не включает headers +renderer-а. Render получает immutable snapshot, а не mutable world. Network +receive не меняет мир напрямую: validated messages попадают в очередь следующей +calculation boundary. + +### Центральные идентичности + +Resource identity хранит и исходное написание, и нормализованный ASCII-key для +поиска: + +```c +struct ResourceKey { + NormalizedRelativePath archive; + FixedAsciiName name; + uint32_t type_id; +}; +``` + +Normalization сохраняет исходную строку для diagnostics и roundtrip, а отдельный +ASCII-casefold key используется только для lookup. Эта граница важна для +архивов [NRes](../reference/nres.md), таблиц [RsLi](../reference/rsli.md), +prototype references и fallback-путей материалов. + +Object identity разделяет внутреннюю защиту от dangling references и исходную +сетевую/script-семантику: + +```c +struct ObjectHandle { uint32_t generation; uint32_t slot; }; +struct OriginalObjectId { uint32_t raw; }; +``` + +`ObjectHandle` нужен для безопасного внутреннего владения, deferred deletion и +weak references. `OriginalObjectId` сохраняет наблюдаемую семантику исходной +игры: scripts, mirrors, network messages и savegame references должны видеть +логический ID, а не адрес объекта или номер slot в новом allocator-е. + +Frame snapshot отделяет simulation от render. Simulation пишет mutable state; +renderer читает опубликованное состояние или строго ограниченную фазу +`in_render`. Deferred deletion применяется между фазами, а не во время traversal. +Командный контур renderer-а должен сверяться с [описанием кадра](../reference/render-frame.md) +до pixel comparison. + +### Владение ресурсами + +Ресурс проходит несколько уровней: + +```text +ArchiveHandle -> EntryView -> DecodedBlob -> ParsedAsset -> RuntimeResource +``` + +`EntryView` ссылается на metadata архива, `DecodedBlob` владеет подготовленными +bytes, `ParsedAsset` является CPU-представлением, `RuntimeResource` может +дополнительно владеть GPU/audio objects. Eviction верхнего уровня не закрывает +архив, если он ещё нужен другому entry. Ссылки идут вниз только через явные +handles. + +Для shared objects допустимы reference counting или generation handles. +Intrusive refcount нужен только в ABI-shim; внутренний современный код +предпочтительно держит понятное владение и weak handles. Архивы, decoded blobs, +CPU assets и GPU resources имеют отдельные бюджеты и отдельные diagnostics. + +### Backend adapters + +Render, audio, input и network получают отдельные adapters. Legacy compatibility +state живёт выше Vulkan, D3D11 или Metal backend; DirectPlay compatibility живёт +отдельно от modern transport. Так можно заменить платформу, не меняя форматы, +игровую семантику и regression corpus. + +Backend adapter не должен быть местом, где исправляются данные. Если +[MSH](../reference/msh.md), [MAT0](../reference/materials.md) или +[Texm](../reference/texm.md) требуют fallback, это фиксируется в asset/runtime +слое и попадает в trace. Backend получает уже выбранные resources, states и +draw items. + +### Scheduler phases + +```text +collect_platform_events +build_input_snapshot +advance_game_clock +calculate_world_queue +apply_deferred_operations +update_navigation_physics_animation_fx +publish_render_snapshot +render_world +render_ui +end_frame_callbacks +maintenance_and_eviction +``` + +Фазы имеют стабильный порядок и запрещённые операции. Registry mutation +запрещена во время world traversal, GPU upload не изменяет simulation state, а +maintenance не влияет на gameplay. Script timers, material animation и FX +lifetime относятся к game time, если обратное не доказано. + +Сначала реализуется однопоточный эталон. Параллелизм добавляется только внутри +фаз с детерминированным merge: decoding независимых assets, culling chunks или +подготовка immutable draw items. Это снижает риск скрытых race conditions и +расхождений replay. + +### Структурированные ошибки + +Каждая ошибка должна содержать фазу, путь, archive entry, object/prototype key, +offset и цепочку причины. + +```text +MissionLoadError + mission: Campaign.00/Mission.02 + object: 17 + resource_name: UNITS/.../unit.dat + component: e_tur_... + prototype: objects.rlb::e_tur_... + cause: model archive missing +``` + +Логическое отсутствие необязательного lightmap, отсутствующий entry в архиве, +неизвестное opaque поле, выход ссылки за диапазон и повреждённый offset имеют +разный severity и разные способы исправления. Ошибка данных должна быть +actionable chain, а не строка вида `failed to load resource`. + +## Порядок работ + +Движок строится от данных к поведению и от детерминированных CPU-компонентов к +аппаратным. Каждый этап заканчивается исполняемым инструментом и тестовым +критерием. Нельзя начинать полноценный gameplay, пока ресурсный граф и +model/material path не дают воспроизводимый результат. + +### Этап 0. Corpus harness + +- индексировать оригинальный каталог и вычислить hashes; +- реализовать bounded binary cursor и structured diagnostics; +- создать CLI для массового запуска parser-ов; +- сохранять JSON-отчёт с counts, variants, warnings и failures; +- зафиксировать демоверсию, Часть 1 и Часть 2 как независимые baselines. + +Готовность: повторный запуск на каждом неизменённом каталоге даёт идентичный +отчёт. Любой parser умеет завершиться контролируемой ошибкой с offset и +контекстом, а не crash или allocation по непроверенному count. + +### Этап 1. Архивы и пути + +- реализовать strict/lossless [NRes](../reference/nres.md) reader/writer; +- реализовать [RsLi](../reference/rsli.md) mapping, table transform, lookup, + LZSS и Deflate; +- добавить адаптивный decoder для методов `0x080` и `0x0A0`; +- воспроизвести overlay и известные compatibility quirks; +- реализовать archive-handle cache и ASCII name policy. + +Готовность: неизменённые архивы проходят byte-identical roundtrip; поиск всех +имён совпадает с каталогом; malformed corpus отклоняется без выхода за память. +NRes с ненулевым unindexed region обязательно остаётся regression case. + +### Этап 2. Граф ресурсов + +- разобрать `objects.rlb` и unit DAT; +- построить resolver прямой MSH, рекурсивного parent prototype через + `objects.rlb` и отдельного BASE payload; +- реализовать dependency graph с reachability от миссии; +- добавить parsers CTPT, NDPR и остальных служебных форматов в lossless-режиме; +- создать инспектор прототипа, показывающий все связанные ресурсы. + +Готовность: 201 demo-объект раскрывается в 501 прототип. Затем все миссии +Частей 1 и 2 дают 4 701 и 5 845 prototype requests без failures. Недостижимые +отсутствующие ресурсы отмечаются отдельно от критических ошибок в reachable +graph. + +### Этап 3. Статический asset viewer + +- реализовать [MSH](../reference/msh.md) core streams, slots и batches; +- декодировать Texm во все подтверждённые pixel formats; +- разобрать WEAR и [MAT0](../reference/materials.md) с точными fallback; +- построить современный renderer compatibility layer; +- добавить wireframe, normals, bounds, LOD/group и material debug views. + +Готовность: открываются 435/511 моделей, 518/631 textures и 905/1 127 materials +Частей 1/2; batch/index bounds не нарушаются; viewer показывает корректно +текстурированную статическую модель из исходного архива. Красивый viewer всё ещё +означает только asset compatibility, а не готовую игру. + +### Этап 4. Анимация и эффекты + +- реализовать MSH type 8/type 19 sampling и hierarchy; +- добавить x87-compatible reference path для чувствительных формул; +- реализовать material phase animation; +- разобрать FXID header/commands и runtime instances; +- сначала поддержать все opcodes, встречающиеся в корпусе, сохраняя raw body; +- добавить deterministic RNG stream и effect capture. + +Готовность: frame-by-frame poses совпадают с golden reference своей части; все +923/1 065 FXID создаются без parser errors; перезапуск одинакового effect seed +даёт идентичный список emitted primitives. + +### Этап 5. Карта и мир + +- реализовать `Land.msh` и corrected `TerrainFace28` layout; +- построить terrain rendering и CPU surface queries; +- реализовать `Land.map`, cell grid и graph links; +- визуализировать areals и найденные маршруты; +- разобрать [TMA](../reference/tma.md) и выполнять staged mission loading; +- создать World3D queue, ObjectId и deferred deletion. + +Готовность: 65 карт и 60 TMA Частей 1 и 2 загружаются до EOF; все areal links +валидны; objects появляются в правильных transforms; мир выдерживает расчётные +шаги без рендера. + +### Этап 6. Gameplay controllers + +- подключить input snapshot и camera controller; +- реализовать navigation corridor, Behavior state machine и Wizard boundary; +- создать physical controller и collision manager; +- загрузить control resources в lossless typed model; +- внедрить game time, pause, event queue и end-of-frame callbacks; +- подключить AI layer и symbol/event layer сценариев. + +Готовность: юнит получает цель, строит маршрут, движется по terrain, реагирует +на collision и исполняет базовые миссионные события в детерминированном replay. +На этом этапе вводится differential branch для изменённых `AniMesh`, `Control` и +`Effect`; неизменённые DLL используют общий reference path. + +### Этап 7. Полный кадр, звук и UI + +- реализовать render phases, sorting, lighting, shadows и atmosphere; +- подключить 3D listener, sample cache, FX sounds и mission audio; +- воспроизвести shell/UI loading и post-world pass; +- добавить frame capture до UI и после UI; +- зафиксировать capability fallback profiles. + +Готовность: миссия визуально и звуково проходима; каждый draw и sound event +имеет trace; одинаковый replay создаёт одинаковые command lists. На этом этапе +вводится differential branch для `iron3d` и `services`. + +### Этап 8. Сеть, сохранения и динамическая совместимость + +- реализовать modern transport над versioned game-message schema; +- отдельно исследовать DirectPlay wire и `netZipData` для native compatibility; +- добавить mirrors, ownership transfer и disconnect cleanup; +- восстановить save/campaign state и dispatcher; +- выполнить динамические captures оригинала для render states, script VM и + physics edge cases. + +Готовность: одиночная кампания запускается из оригинального каталога, +сохраняется и продолжается; multiplayer replay согласован между peers; full +corpus не создаёт новых parser variants без явной регистрации. + +## Тестовый контур + +Совместимость нельзя подтвердить одним screenshot. Нужны тесты на уровне bytes, +структур, ссылок, simulation state, команд renderer-а и конечного изображения. +Каждый слой локализует свой класс ошибки. + +```text +unit tests + -> parser/property tests + -> corpus validation + -> cross-resource integration + -> deterministic simulation replay + -> render/audio command captures + -> pixel and gameplay parity +``` + +Failure верхнего уровня всегда должен позволять спуститься к меньшему тесту и +понять причину. + +### Unit, property и fuzz tests + +Для каждого binary primitive проверяются little-endian чтение, bounded strings, +checked arithmetic и cursor boundaries. Для структур -- минимальный размер, +максимальные counts, пустые arrays, нулевые варианты и редкие branches. + +Property tests генерируют случайные корректные NRes/RsLi/WEAR records, +выполняют encode -> decode и сравнивают семантику. Fuzz tests изменяют длины, +offsets, counts и termination bytes и требуют контролируемой ошибки без crash и +чрезмерного выделения памяти. + +Критические алгоритмы имеют отдельные vectors: ASCII casefold, NRes permutation +search, RsLi byte transform, LZSS backreferences, quaternion shortest path, +matrix composition и terrain mask remap. + +### Corpus validation + +Каждый файл оригинального каталога проходит parser своего семейства. Отчёт +содержит hash, variant, counts, warnings, errors и точный offset сбоя. Baseline +демоверсии: + +```text +MSH 435 +MAT0 905 +Texm 518 +FXID 923 +WEAR 457 +Land.msh 6 +Land.map 6 +TMA 6 +unit DAT 425 +errors 0 +``` + +Изменение parser-а принимается только если baseline остаётся стабильной либо +новый variant зарегистрирован с образцом и объяснением. Warnings должны быть +именованными: «неизвестное opaque поле» не равно «выход ссылки за диапазон». + +### Cross-resource integration + +Интеграционный тест начинается с миссии и проходит весь dependency graph: +object -> prototype -> MSH -> WEAR -> MAT0 -> Texm/lightmap/FXID. Он не +ограничивается тем, что файлы существуют: material slot должен указывать на +допустимый MAT0, phase -- на допустимую texture, model batch -- на существующий +WEAR index. + +Demo mission total: 201 objects -> 501 prototypes -> 501 object MSH/WEAR. +Чистый object graph даёт 3 873 material slots и 5 049 texture requests; после +включения environment WEAR итог равен 3 879 material slots, 5 067 textures и +18 lightmaps, failures 0. Такой тест ловит ошибки casefold, suffix, fallback и +путей, которые отдельный parser не замечает. + +Для каждого отсутствующего узла отчёт хранит полный parent chain, чтобы +различать broken global archive и реально достижимый mission failure. + +### Deterministic simulation replay + +Записывается начальная миссия, seed, input events, network messages и значения +внешних часов. На контрольных ticks сохраняется canonical state hash: + +```text +sorted ObjectId list +transforms and velocities +critical properties and owners +AI/behavior state IDs +active effect state +game clock and RNG states +``` + +Pointer addresses, allocator order и GPU handles в hash не входят. Два запуска с +одинаковым log должны давать одинаковый state hash на каждом checkpoint. Первое +расхождение гораздо информативнее финального разного результата миссии. + +### Render command parity + +До pixel comparison сравнивается command list: + +```text +camera matrices and viewport +visible ObjectIds +render phase and stable order +model/node/slot/batch IDs +material phase and texture handles +legacy pipeline states +index ranges and transforms +``` + +Если command lists совпадают, но pixels различаются, проблема находится в +shader/backend, sampling или численной точности. Если command lists уже +различаются, pixel diff лишь скрывает более раннюю ошибку. + +Golden captures следует хранить отдельно для статической модели, анимации, +terrain, transparent FX, shadows, lightmap и atmosphere. + +### Pixel, audio и network tests + +Pixel tests используют фиксированное разрешение, camera, device profile, seed и +timeline. Сравниваются exact pixels для CPU/reference path и tolerance metrics +для GPU path, но tolerance не должна скрывать переставленные прозрачные +primitives. + +Audio tests сравнивают список sound events, sample IDs, positions, loop flags и +gains; waveform зависит от mixer/device и является вторичным уровнем. Network +tests воспроизводят captured message sequences, проверяют mirrors, ownership и +disconnect. Для native DirectPlay compatibility дополнительно нужен packet-level +corpus. + +## Regression baselines + +Corpus validation формирует три независимых отчёта: демоверсия, Часть 1 и +Часть 2. Каждый сохраняет manifest файлов, hashes executable/DLL, variants, +warnings, global archive health и mission reachability. + +Ключевые corpus gates: + +```text +NRes: 120 файлов / 6 804 entries и 134 / 8 171 для Частей 1/2 +TMA: 29 миссий / 864 objects / 28 extras и 31 / 885 / 41 +MSH: 435 и 511 моделей +MAT0: 905 и 1 127 материалов +Texm: 518 и 631 текстура +FXID: 923 и 1 065 эффектов +full reachability: 4 701 и 5 845 prototype requests, failures 0 +``` + +Расширенные mission-reachability totals: + +```text +Часть 1: 29 TMA, 864 objects, 4 701 prototypes, + 36 954 materials, 48 806 textures, 139 lightmaps, failures 0 +Часть 2: 31 TMA, 885 objects, 5 845 prototypes, + 50 888 materials, 68 603 textures, 214 lightmaps, failures 0 +``` + +Обязательные regression cases: + +- NRes с ненулевым unindexed region; +- prototype inheritance через `objects.rlb`; +- unit DAT `description[32]` без NUL; +- TMA epilogue и `extra_count` 0--4; +- empty SWAV entry; +- stale save-slot metadata без payload; +- build-scoped RVA lookup. + +Byte-identical asset comparison выполняется только внутри одного корпуса. Между +Частями 1 и 2 сравниваются semantic invariants и decoded representation, +поскольку многие assets пересобраны. + +## Точность, скорость и повторяемость + +Совместимый движок должен быть корректным, повторяемым и достаточно быстрым. +Эти свойства нельзя получать одним и тем же приёмом. Сначала создаётся простой +эталонный путь, затем он измеряется и оптимизируется без изменения результата. + +Главные источники расхождений: x87 extended precision, преобразование float в +integer, порядок операций, старые SIMD implementations, нестабильная сортировка, +RNG и использование разных часов. + +### x87 и округление + +Оригинальный x86-код мог хранить промежуточные значения в 80-битных регистрах +x87, а в память записывать 32-битный float. Современный compiler чаще использует +SSE с округлением после каждой операции. Различие заметно на границах animation +frame, culling plane и collision threshold. + +Для критических формул нужен reference mode: + +- фиксированный порядок операций без reassociation; +- запрещённый fast-math; +- явные преобразования и проверенный режим округления; +- тесты возле half-integer и epsilon boundaries; +- при необходимости extended intermediate через `long double` на проверенной + платформе. + +Не требуется эмулировать x87 во всём движке. Нужно локализовать функции, где +малое отличие меняет дискретное решение, и держать для них scalar reference path. + +### RNG как часть состояния + +FX, atmosphere и, вероятно, AI используют случайные значения. Один глобальный +RNG легко расходится, если новая реализация запрашивает дополнительное число для +визуальной оптимизации. Для трассировки полезны именованные streams: + +```text +world/gameplay RNG +AI/script RNG +FX instance RNG +atmosphere RNG +non-deterministic cosmetic RNG +``` + +Для native parity может потребоваться один общий алгоритм и точная sequence. До +подтверждения capture каждый stream хранит seed и счётчик вызовов в trace. +Cosmetic stream не входит в simulation hash. + +### Стабильный порядок + +Коллекции не должны зависеть от адресов, unordered containers или порядка +завершения worker threads. Для объектов, collision pairs, opaque/transparent +draws и network messages задаются явные stable keys: + +- objects -- queue insertion sequence или OriginalObjectId; +- collision pairs -- упорядоченная пара IDs; +- opaque draws -- phase, pipeline key, material, stable insertion ID; +- transparent draws -- layer, quantized distance, stable insertion ID; +- network messages -- sequence и sender. + +Даже когда математический результат коммутативен, side effects, cache accesses и +RNG делают порядок наблюдаемым. + +### Часы и fixed-step + +Monotonic platform clock хранится отдельно от game clock. Pause и time scaling +применяются к game clock. Simulation работает с фиксированным или точно +воспроизводимым шагом, а render может интерполировать presentation state, не +изменяя authoritative world. + +Maintenance timers кэшей используют реальные часы или отдельную подтверждённую +шкалу; их срабатывание не должно менять gameplay. При перегрузке лучше выполнить +ограниченное число simulation steps и явно зафиксировать dropped presentation +frames, чем передать огромный `dt` в AI/physics. + +### Оптимизация без потери эталона + +1. Сохранить scalar reference implementation. +2. Добавить profiler counters на decoding, culling, sorting, animation, upload + и draw. +3. Оптимизировать только измеренный bottleneck. +4. Сравнить SIMD/parallel результат с reference на полном corpus. +5. Оставить runtime switch для отключения оптимизации при диагностике. + +`g_FastProc` удобно моделировать как таблицу function objects: все slots сначала +указывают на scalar path, затем безопасные slots заменяются SIMD-вариантами +после self-test на старте. + +### Кэш и память + +Архивы, decoded blobs, CPU assets и GPU resources имеют отдельные budgets. +Eviction разрешена только для объектов с нулевым external refcount и после +безопасной frame fence. Original delayed cleanup порядка десятков секунд можно +воспроизвести policy-параметрами, не сканируя все entries каждый кадр. + +Основные показатели: число открытых архивов, decoded bytes, resident +textures/lightmaps, models, active FX, draw items и deferred-delete size. Любой +неограниченно растущий счётчик является regression. Производительность считается +достаточной только после корректности: стабильные 60 FPS с неверным LOD или +пропущенными эффектами не являются успехом. + +## Release gates + +Версия не выпускается, если: + +- появился новый corpus error; +- изменился byte roundtrip неизменённых ресурсов; +- dependency graph получил failure в достижимом пути; +- deterministic replay расходится; +- command capture изменился без ожидаемого changelog; +- parser допускает allocation по непроверенному count; +- новая оптимизация не имеет scalar reference comparison. + +Каждое исправление регистрирует минимальный regression asset или synthetic +vector. Если новый behavior намеренно отличается от предыдущего, изменение +должно иметь compatibility profile, corpus sample и объяснение, почему старый +baseline был неполным или неверным. + +## Уровни совместимости + +Слово «совместимый» используется только с уровнем: + +1. **Archive-compatible** -- открывает и сохраняет контейнеры. +2. **Asset-compatible** -- декодирует модели, материалы, текстуры и эффекты. +3. **Mission-compatible** -- загружает карту и создаёт все объекты. +4. **Runtime-compatible** -- исполняет время, события, поведение и физику. +5. **Presentation-compatible** -- воспроизводит рендер и звук. +6. **Game-compatible** -- позволяет пройти миссии, сохраняться и продолжать. +7. **Native-interoperable** -- взаимодействует с оригинальной сетью и внешним + ABI. + +Viewer с красивой моделью находится только на втором уровне. + +### Обязательные критерии запуска и данных + +- приложение запускается из неизменённого оригинального каталога; +- относительные пути, регистр и legacy encodings разрешаются по исходным + правилам; +- все требуемые NRes/RsLi открываются без предварительной конвертации; +- parsers проверяют границы и не используют неопределённые bytes как указатели; +- неизвестные поля сохраняются lossless; +- все mission-reachable prototype, model, material, texture, lightmap и effect + references разрешаются; +- отсутствие необязательного ресурса следует документированному fallback, а не + случайному default. + +### Обязательные критерии мира + +- TMA разбирается до точного EOF; +- `Land.msh` и `Land.map` создают корректную поверхность и areal graph; +- ObjectId, owner и mirror semantics устойчивы; +- queue traversal и deferred deletion безопасны; +- pause, game time и simulation steps повторяемы; +- AI/Behavior/Wizard/Control взаимодействуют через заданные границы; +- collision и navigation не подменяют друг друга; +- script events используют logical IDs и переживают удаление объектов; +- deterministic replay совпадает на контрольных ticks. + +### Обязательные критерии presentation + +- static и animated MSH используют правильные slots, batches и transforms; +- WEAR/MAT0/Texm fallback и phase timing совпадают; +- mip-skip, palettes, Page atlases и lightmaps работают; +- render phases, depth/cull/blend state и transparent order подтверждены + captures; +- FXID commands и RNG дают устойчивый результат; +- camera и 3D sound listener синхронизированы; +- atmosphere, тени, солнце и flares не являются декоративными заглушками; +- UI и world rendering имеют правильную границу; +- golden command captures стабильны, pixel parity измеряется на фиксированных + сценах. + +### Обязательные критерии полной игры + +- все доступные миссии стартуют, завершаются и корректно сообщают + success/failure; +- campaign dispatcher сохраняет прогресс; +- savegame восстанавливает world, script, AI, RNG и clocks, а не только + placement; +- input remapping, pause, camera modes, sound и настройки работают из UI; +- длительный прогон не накапливает objects, resources или audio sources; +- ошибки данных показывают actionable chain; +- производительность приемлема без отключения подсистем; +- демоверсия, Часть 1 и Часть 2 проходят один и тот же тестовый контур с + раздельными manifests и эталонами. + +### Native interoperability + +Самый строгий уровень дополнительно требует совпадения x86 ABI экспортов, vtable +slots и calling conventions для подключаемых оригинальных модулей, а также +DirectPlay wire/framing и compression. Этот уровень независим от возможности +играть в новом standalone runtime. + +Проект может честно заявлять game compatibility без native DLL/network +interoperability, но это должно быть явно указано. Аналогично pixel-perfect режим +может быть отдельным compatibility profile поверх функционально корректного +renderer-а. + +### Совместимость нескольких наборов данных + +Критерий полной совместимости применяется отдельно к демоверсии, Части 1 и +Части 2. Прохождение одного набора не позволяет заявлять поддержку остальных. + +Обязательное различие: + +- **format compatibility** -- один parser принимает все три набора; +- **content compatibility** -- конкретная миссия разрешает весь reachable graph; +- **behavior compatibility** -- runtime совпадает с соответствующей сборкой + изменённых DLL; +- **cross-version support** -- один новый движок выбирает корректные данные и + defaults по fingerprint установки. + +Content fingerprint включает hashes executable/DLL и manifest ключевых архивов. +Он не используется для запрета модификаций, но выбирает compatibility profile и +делает отклонение диагностируемым. + +## Definition of done + +Полное документирование и реализация считаются завершёнными только когда каждый +критерий связан с главой спецификации, executable test и хотя бы одним +corpus/golden case. Утверждение без проверяемого критерия остаётся +исследовательской заметкой, а не контрактом. diff --git a/docs/tomes/08-evidence.md b/docs/tomes/08-evidence.md new file mode 100644 index 0000000..fab5805 --- /dev/null +++ b/docs/tomes/08-evidence.md @@ -0,0 +1,1087 @@ +# VIII. Справочник и доказательная база + +Восьмой том фиксирует, на чём держится книга: ABI, exports/imports, файловая +поверхность, статистика корпусов, открытые вопросы, критерии доказанности и +словарь терминов. Это самостоятельная справочная глава: она не заменяет +профильные статьи о форматах, но задаёт общий контракт, по которому проверяются +реализация, parser-ы, compatibility layer и будущие динамические эксперименты. + +## Как читать доказательства + +Доказательством считается наблюдение, которое можно повторить на конкретном +файле, сборке или трассе. Вывод может объединять несколько наблюдений, но он +должен сохранять происхождение данных: демоверсия, полная Часть 1 и полная +Часть 2 не смешиваются в один безымянный corpus. + +Для каждого утверждения полезно различать четыре уровня: + +- `layout-confirmed`: известны offset, size, count, bounds и правила безопасного + чтения; +- `corpus-verified`: branch или вариант реально встречается в доступных игровых + данных; +- `code-confirmed`: branch виден в бинарном коде, но отсутствует в доступном + corpus; +- `behavior-confirmed`: поведение подтверждено исполнением оригинальной + программы, трассой API/vtable или controlled differential test. + +Если поле не имеет доказанного предметного смысла, документация хранит его как +opaque field. Это не мешает lossless read/write, но запрещает строить writer, +который очищает, переименовывает или пересчитывает такое поле на основании +правдоподобной догадки. + +## ABI и границы модулей + +### Базовый binary profile + +Все исследованные модули -- 32-битные PE для x86, собранные C++-компилятором +эпохи MSVC6. Публичная граница сочетает именованные exports, фабрики C++- +объектов, singleton getters и дальнейшие вызовы через vtable. + +Для binary shim необходимо учитывать: + +- `__cdecl` и `__stdcall` у свободных функций; +- `__thiscall` у методов, где `this` передаётся в `ECX`; +- очистку stack, видимую по `ret N`; +- точный порядок virtual slots; +- multiple-interface pointer adjustments; +- 4-byte alignment и native little-endian types; +- отсутствие безопасного ABI для STL-контейнеров между современным и старым + compiler-ом. + +Внутренний новый движок не обязан использовать этот ABI. Он нужен только +compatibility layer, который принимает старые DLL-facing interfaces, старый +порядок slots и старые ownership rules. + +### Публичная поверхность DLL + +В 15 DLL обнаружено 313 exports: + +```text +AniMesh.dll 2 ArealMap.dll 9 +Behavior.dll 3 Control.dll 5 +Effect.dll 2 Joystick.dll 6 +MisLoad.dll 2 Net.dll 37 +Ngi32.dll 145 Terrain.dll 13 +Wizard.dll 1 World3D.dll 72 +ai.dll 2 iron3d.dll 8 +services.dll 6 +``` + +Демоверсия содержит 1 126 imported function slots, а полные Части 1 и 2 -- +1 134. Они включают Win32 runtime, DirectX и межмодульные связи. Большое число +exports `Ngi32.dll` состоит из активного объектного API, математических/resource +functions и legacy compatibility stubs. + +Compatibility headers должны фиксировать symbol, ordinal, decorated или +undecorated name и signature конкретной сборки. Смысловое имя недостаточно: +порядок exports и calling convention входят в бинарный контракт. + +### Композиционный и сервисный слой + +`iron3d.dll` экспортирует восемь функций: + +```text +createShell deleteShell +createGame deleteGame +createSubsystems deleteSubsystems +getIGame getIShell +``` + +`services.dll` публикует шесть getters: + +```text +getDisplay +getGUIServer +getNetManager +getResManager +getSoundServer +getTimer +``` + +Эти getters возвращают shared interfaces. Caller не должен конструировать +concrete implementation или уничтожать singleton напрямую. Для совместимости +важны не только адреса функций, но и порядок startup/shutdown, owner/refcount +transitions и реакция на failure paths: отсутствие sound device, ошибка display, +прерванная загрузка миссии и normal shutdown. + +### Предметные фабрики + +```text +AniMesh: LoadAgent, LoadAniMesh +ArealMap: CreateArealMap, CreateSystemArealMap, GetSystemArealMap, + CreateHallWay, CreateObjectFromScheme, CreateObjectsForDebug, + CalcFullResearchCost, Debug_TestSchemeType, ShowDebugVector +Behavior: CreateBehaviour, CreateDistributor, PressDebugKey +Control: InitializeSettings, LoadControlSystem, LoadPhysicalModel, + CreateCollManager, CreateCollObject +Effect: InitializeSettings, CreateFxManager +MisLoad: CreateMissionData, LoadResearch +AI: CreateSuperAI, GetSuperAI +Wizard: CreateWizard +Terrain: CreateAtmosphere, CreateLightManager, CreatePrimitives, + CreatePrimitives2, CreateShader, GetShade, GetWorld, + LoadCamera, stdGetCurrentCamera2, stdSetCurrentCamera2 +``` + +Фабрика возвращает interface pointer. Конкретный размер объекта и layout +остаются внутренними; внешнему коду важны vtable, QueryInterface-подобная +negotiation, lifetime methods и правила владения. + +### World3D export families + +72 exports `World3D.dll` группируются по назначению: + +```text +lifecycle: stdInitGame, stdCloseGame, stdCalculateGame, stdRenderGame +objects: CreateObject, AddObjectToGame, AddNewObjectToGame, + CreateMirrorObject, AddMirrorObjectToGame, AddNewMirrorToGame, + DeleteGameObject, KillGameObject, CreateQueue, GetQueue +camera: LoadCamera, stdSetCurrentCamera, stdGetCurrentCamera +input: UpdateManualEventsList, ClearManualEventsList, stdClearKeyboard, + converters, scan/string functions, key lock/query, mouse shift +clock: SetGameTime, PauseGameTime, ResumeGameTime, GetGameTime family +network: netCreateNetWatcher, GetNetPlayerNum and mirror/player helpers +resources/render: material, texture, lightmap and end-of-render helpers +settings/state: CreateGameSettings, SetGameRender, SetStateForGameObjects +``` + +World3D является главным местом, где внешний ABI превращается в game loop: +input обновляет manual events, calculation проходит queue/world traversal, +deferred deletion откладывает фактическое уничтожение объектов, render читает +подготовленный snapshot, а end-of-render helpers закрывают временные ресурсы. + +### Net и Joystick + +`Net.dll` экспортирует создание instance/interface и 33 операции transport +lifecycle: provider/session enumeration, setup, create/join/close, player +operations, send/receive, latency, addresses, queue size, lobby и +`netZipData`/`netUnZipData`. + +`Joystick.dll` имеет компактную границу: + +```text +QueryJoy +CreateJoy +ReleaseJoy +SetJoyRange +PeekJoyMessage +GetJoyCaps +``` + +Эти модули легче всего заменить adapter-ами, потому что их публичная +поверхность достаточно узкая. Для native interoperability сохраняются исходные +signatures; modern runtime может использовать внутренние typed interfaces. + +### Ngi32 export families + +145 exports `Ngi32.dll` включают: + +```text +resource archives: niOpenResFile, niOpenResFileEx, niOpenResInMem, + niCreateResFile, rsOpenLib, rsFind, rsLoad +renderer: niGetD3DDriverAmount, niSelectD3DDriver, + niGetD3DDriverCaps, niGetD3DVideoModeList, + niCreate3DRender, niGet3DRender, niGetMaxTextureSize +audio: niCreate3DSound, niGet3DSound, niGet3DSoundCaps, + niMuteSound, rsLoadWave +platform: allocation, clocks, fixed-memory helpers +math/geometry: plane, ray, polygon and volume intersection routines +CPU dispatch: g_FastProc, niGetProcAddress and feature detection +legacy ABI: n3d*, vrt*, bsp* compatibility entries +``` + +Экспорт переменной `g_FastProc` требует особого shim: consumer получает адрес +таблицы, а не результат функции. + +### Подтверждённые RVA + +Адреса указаны как RVA конкретной исследованной сборки: + +```text +World3D stdCalculateGame 0x13910 +World3D stdRenderGame 0x13B60 +World3D sendEndOfRender 0x13D20 +World3D UpdateManualEvents 0x10E10 +World3D ClearManualEvents 0x11180 +World3D DeleteGameObject 0x087B0 +Ngi32 g_FastProc 0x3A058 +``` + +`iron3d.dll` вызывает calculation около RVA `0x5FA94`, `0x604C1`, `0x6086B`, +render около `0x60B2F`, а manual-event update находится в Win32 message path +около `0xA3759`. + +RVA используются только для сопоставления и трассировки этой версии. Runtime +implementation не должна встраивать их как постоянные игровые идентификаторы. +Таблица внутренних RVA хранится по SHA-256 конкретного модуля. + +Подтверждённые hashes неизменённых DLL: + +```text +World3D.dll 17e4a3089b2583a8cf2356c9db0390b1aba138356a09130d79b4e7e4791da61e +Ngi32.dll bab9840d94f4e4e74ffc26677724fa896cf4823845504d09a9e025f80016edf5 +``` + +### Vtable и interface negotiation + +Вызовы вида `object->vfunc(offset)` доказывают порядок slots, даже когда имя +метода неизвестно. Renderer slots около `+0x28`, `+0x30`, `+0x34` окружают +world traversal; camera и viewport получаются через selector-based interface +calls; shared objects используют ранний slot как AddRef-подобную операцию. + +Правила реконструкции: + +1. Зафиксировать byte offset slot и число аргументов. +2. Найти все call sites и типы передаваемых значений. +3. Отделить доказанное поведение от назначенного имени. +4. Построить C-compatible shim vtable с точным порядком. +5. Внутри adapter-а перевести вызов в современный typed interface. + +Нельзя добавлять virtual destructor в начало reconstructed interface: это +сдвинет все slots. + +### ABI-матрица Частей 1 и 2 + +Во всех пятнадцати DLL совпадают export names, ordinals и import sets. Общее +число exports остаётся 313. Обе полные части содержат 1 134 imported function +slots; значение 1 126 относится к демоверсии и хранится отдельно. + +Побайтно идентичны девять DLL: + +```text +ai.dll +Behavior.dll +Joystick.dll +MisLoad.dll +Net.dll +Ngi32.dll +Terrain.dll +Wizard.dll +World3D.dll +``` + +Пересобраны `AniMesh.dll`, `ArealMap.dll`, `Control.dll`, `Effect.dll`, +`iron3d.dll`, `services.dll`. + +Изменение export RVA: + +```text +AniMesh 2 / 2 +Control 5 / 5 +iron3d 8 / 8 +services 6 / 6 +ArealMap 0 / 9 +Effect 0 / 2 +``` + +Нулевое изменение export RVA не доказывает идентичность тела функции: +`ArealMap.dll` и `Effect.dll` имеют изменённый `.text` при прежних адресах +exports. Compatibility headers фиксируют внешний ABI один раз, но внутренняя +таблица адресов, тестов и semantic deltas выбирается по build fingerprint. + +## Файловая поверхность + +### Каталог как внешний API + +Оригинальная установка -- не просто набор assets. Имена файлов, относительные +пути, регистр, конфигурационные ключи и разделение библиотек образуют внешний +контракт. Совместимый движок должен принимать каталог без переименования и +предварительной распаковки. + +Основные root-файлы включают executable и 15 DLL, `Iron_3D.ini`, `Comp.ini`, +`Behavior.ini`, `ArealMap.ini`, `BuildDat.lst`, input/preload descriptions и +набор `.rlb/.lib` архивов: + +```text +objects.rlb +system.rlb +static.rlb +effects.rlb +Material.lib +Textures.lib +LightMap.lib +Palettes.lib +sounds.lib +voices.lib +``` + +Parser конфигураций должен сохранять неизвестные keys и секции, поддерживать +quoted strings, хранить provenance значения и отличать absent key от explicit +default. + +### `Iron_3D.ini` + +Демоверсия содержит секции `[CS]`, `[MULTIPLAYER]`, `[TEMP]` и +`[LEVEL_RATIO]`. + +```text +DISPLAY_WIDTH=640 DISPLAY_HEIGHT=480 +BITDEPTH=16 CURRENT_D3DCARD=0 +WINDOW_MODE=0 FORCE_SOFTWARE_CURSOR=1 +RENDER_QUALITY=2 REFLECTIONS=0 +EMBOSS_BUMP=0 EMBM=0 +PLAY_CD_MUSIC=1 MOUSE_SENS=100 +JOY_SENS=100 MOUSE_REV_Y=0 +JOY_REV_Y=0 JOY_ENABLE=0 +SUBTITLES=1 +``` + +`FORCE_CD_SOUND` хранит строку пути. Multiplayer задаёт default IP, login и +password. `[TEMP]` содержит normalization и offence/defence ranges, +`[LEVEL_RATIO]` -- коэффициенты сложности `0.5`, `0.7`, `1.0`. + +Parser не должен считать имена регистрозависимыми без отдельного +доказательства. Effective value, raw value и факт присутствия ключа хранятся +раздельно. + +### `Comp.ini`: реестр компонентов + +Формат строки: + +```text +<CID> <DLL-name> <Function-name> [comment] +``` + +Подтверждённая таблица: + +```text +0 terrain.dll LoadLandscape +1 terrain.dll LoadBuilding +2 terrain.dll LoadCamera +3 animesh.dll LoadAgent +4 animesh.dll LoadAgent +5 terrain.dll CreateAtmosphere +6 terrain.dll CreateShader +7 misload.dll LoadResearch +``` + +World3D использует этот файл как динамический component registry. Standalone +runtime может сопоставить CID внутренним фабрикам, но compatibility loader +должен поддерживать исходные DLL/function strings и комментарии `//`. + +### `Behavior.ini` и `ArealMap.ini` + +Demo `Behavior.ini` задаёт logging, debug rendering и controller switches: + +```text +LogFile=Behavior.log SaveLog=0 +MaxErrorLevel=1 DefErrorLevel=2 +LookBugMode=0 ShowVectors=0 +NoZBuffer=0 LockBehaviour=0 +UseDebugKey=1 GiveDefaultOrder=0 +DefaultOrderPhase=10 DeterminMode=0 +ImmortalHero=0 UseWizard=1 +``` + +Код Behavior также ищет дополнительные `PathFind_*` и network parameters. В +demo-файле они отсутствуют, следовательно используются compiled defaults или +другой источник; нельзя приписывать им произвольные значения. + +`ArealMap.ini` содержит log switches, `ShowAreals`, `Areal_NoZBuffer`, +`HallWay_NoZBuffer`, `EdgeUp` и `RunBehDebug`. + +### Миссии, UI и сохранения + +Типичный каталог миссии содержит: + +```text +data.tma +mission.cfg +briefing.cfg +messages.cfg +``` + +`mission.cfg` -- текстовое описание именованных resource objects. Блок +начинается `object <name>`, содержит `desc`, `library`, `libtype`, числовой +`type` и произвольные именованные параметры, затем `end`. В демоверсии через +него определяются ambient music loops/variations и другие mission services. + +`briefing.cfg` и `messages.cfg` относятся к пользовательскому представлению и +текстовым событиям. Binary TMA остаётся источником placement и properties; эти +файлы дополняют, а не заменяют его. + +Отдельные поверхности: + +```text +MISSIONS/SCRIPTS/*.scr, *.fml, *.trf, varset.var +MISSIONS/dispatcher.ini +ui/shell_ctrls.cfg +ui/menu_resources.cfg +ui/cursor.cfg +ui/game_resources.cfg +ui/hq.cfg +DATA/TextRes.cfg +SAVE/saveslots.cfg +``` + +Dispatcher демоверсии содержит секцию `[COMPLETE]`; полные части расширяют +campaign state и набор миссионных файлов. UI-config следует читать отдельным +generic object/config parser-ом, сохраняя порядок блоков и неизвестные fields. +`TextRes.cfg` связывает ключи с локализованными строками. + +Save slot list не является полным savegame state. Для полной совместимости +нужно отдельно восстановить binary save payload, campaign dispatcher и +serialization world/script/AI/RNG. + +### Правила файловой совместимости + +- Поддерживать `/` и `\` во входных legacy paths. +- Разрешать paths относительно root игры и mission context. +- Сохранять исходное написание для log и roundtrip. +- Использовать ASCII case-insensitive lookup внутри архивов. +- Учитывать CP1251/ANSI строки там, где встречается локализованный текст. +- Не применять Unicode normalization к фиксированным resource names. +- Различать физически отсутствующий файл и отсутствующий entry в существующем + архиве. +- Не требовать одинакового регистра имени файла на case-sensitive системах: + resolver строит индекс каталога. + +Все найденные конфигурации должны иметь schema с defaults, provenance и +признаком `present`. Это позволяет отличить исходный default от явно заданного +пользователем значения. + +### Различия файловой поверхности Частей 1 и 2 + +Часть 2 добавляет `ui_factory.lib` -- NRes с шестью Texm entries. +`ui/minimap.lib` увеличен примерно с 6,95 до 10,10 МБ. `gamefont.rlb` и +`sprites.lib` побайтно совпадают между частями. + +`Iron_3D.ini` Части 2 добавляет ключи `SFX_VOLUME`, `CD_VOLUME`, +`DEBUG_KEYS_ON`, меняет некоторые defaults (`MOUSE_SENS`, `MAP_ALPHA128`) и +локализует строки login/password. Это подтверждает правило schema + +provenance: parser хранит не только effective value, но и признак присутствия +ключа в конкретной сборке. + +`BuildDat.lst` Части 2 использует более полные пути под +`UNITS\BUILDS\AI\...`; category masks при этом остаются логическим контрактом, +а physical path -- частью content profile. + +`TextRes.cfg` и `TextRes.dll` значительно расширены. Localized text, resource +identifier и path normalization должны оставаться разными слоями: локализация +текста не меняет ASCII-casefold policy имён entries. + +## Результаты проверки корпусов + +### Demo baseline + +Демоверсия содержит `iron_3d.exe`, те же 15 DLL и сокращённый набор +миссий/ресурсов. Все 15 DLL совпали с первоначально исследованными файлами по +SHA-256. Поэтому executable, бинарный код DLL и demo-assets относятся к одной +совместимой технологической сборке. + +```text +modules: 16, из них DLL: 15 +DLL exports: 313 +DLL imports: 1126 +DLL identity: 15/15 +``` + +`iron_3d.exe`: 36 864 байта, PE32/x86, image base `0x400000`, entry RVA +`0x141E`, timestamp 28 июня 2001 года, SHA-256 +`b0a8b0db1c3a8698c4d4604d89c655496bd91ac1f8859a455e8a45838aebfbd6`. + +### Миссии и сквозные ссылки + +Шесть TMA разобраны до точного EOF: суммарно 20 paths, 15 clans, 201 placed +objects и 1 extra record. 48 объектов ссылаются на unit DAT, 153 -- на прямые +prototype keys. Unit-файлы раскрыли 348 компонентов. + +Сквозной результат: + +```text +501 prototype requests 501 resolved +501 MSH requests 501 resolved +501 WEAR requests 501 resolved +3879 material slots 3879 resolved +5067 texture requests 5067 resolved +18 lightmap requests 18 resolved +failures 0 +``` + +Это самое сильное интеграционное подтверждение текущего корпуса: имена, +архивы, ASCII casefold и fallback согласуются между реальными форматами. + +### Реестр и unit DAT + +`objects.rlb` содержит 590 prototype entries: + +```text +554 имеют прямую MSH-ссылку +549 прямых MSH разрешаются в demo-каталоге +34 раскрываются через родительский prototype и локальный BASE +7 не дают доступной геометрии +41 ссылка общего реестра указывает на отсутствующий demo-content +``` + +Негеометрические или неразрешённые глобальные entries: + +```text +sun_01 +sun_02 +ws_al_01 +ws_al_02 +ws_fl_01 +ws_hm_01 +ws_hm_02 +``` + +Они не входят в фактически требуемую цепочку проверенных миссий. + +Проверено 425 unit DAT, 5 219 records, errors 0. Все records имеют kind 1 и +archive `objects.rlb`; в 5 205 name fields есть ненулевые хвостовые байты после +string terminator. Такой tail является данными, а не мусором, если цель -- +lossless roundtrip. + +### Модели + +Проверено 435 MSH без errors/warnings; 157 анимированных. Диапазоны: 1-38 +nodes, 1-112 slots, 12-9 686 vertices, 1-439 batches. + +```text +414 моделей: types [1,2,3,4,5,15,13,6,7,8,19,9,10,17] +21 модель: [1,2,3,4,5,18,15,13,6,7,8,19,9,10,17,20] +``` + +Type 17 непуст у 29 моделей; type 20 встречается у 21. Редкий variant type 1 +найден в `system.rlb::MTCHECK.MSH`. + +Повторная проверка terrain исправила layout face: vertex indices находятся с +`+0x08`, neighbor indices с `+0x0E`. Эта локальная проверка имеет приоритет над +ранними черновыми описаниями. + +### Материалы и текстуры + +Проверено 457 WEAR, 905 MAT0 и 518 Texm без ошибок. У всех MAT0 `attr2 = 6`. +531 материал содержит одну phase; максимальное число phases -- 29. У 860 +материалов один animation block, у 43 -- два, у 2 -- восемь. + +Распределение Texm по форматам: + +```text +indexed 15 +565 155 +4444 59 +888 52 +8888 237 +``` + +Форматы 556 и 88 присутствуют в loader-е, но не встречаются в demo-assets. +65 текстур содержат `Page`; размеры лежат от `8x8` до `256x256`. Все 385 +уникальных texture references из MAT0 разрешаются. + +### Эффекты + +Проверено 923 FXID без ошибок. Наиболее часты команды 3, 7, 1 и 2. Команда 6 в +данных демоверсии не встречается. Наблюдаются режимы времени 0, 1, 2, 4, 5, +14, 15, 16 и 17. + +### Карты + +Шесть `Land.msh` и шесть `Land.map` проходят проверку без ошибок. Всего 3 811 +ареалов; grid всегда `128x128`, максимальное число candidates в ячейке -- 10, +`poly_count` во всех записях равен нулю. + +```text +AutoMAP 3051 vertices, 3174 faces, 343 areas +PROL 11125 vertices, 9234 faces, 731 area +Tut_1 8827 vertices, 8290 faces, 378 areas +Tut_2 9456 vertices, 8996 faces, 900 areas +Tut_3 9833 vertices, 8560 faces, 722 areas +Tut_4 9022 vertices, 8612 faces, 737 areas +``` + +Максимальное отклонение длины areal normal от единицы около `1.05e-7`. + +### Вспомогательные форматы + +```text +CTPT 284 resources, 3599 points, errors 0 +NDPR 494 resources, 1915 records, errors 0 +BASE 30 resources, errors 0 +EXPL 144 resources, versions 1/2/3, errors 0 +reference arrays 585 resources, 2956 records, errors 0 +SUND 2 resources, 12 keys, errors 0 +CTLD 531 payloads, errors 0 +TRF 5 files, errors 0 +preload 38 entries +ANI 8 resources +SKE 6 resources +``` + +CTPT names подтверждают attachment semantics: `TurretCenter`, `TurretDirect`, +`CameraCenter`, `TargetDirect`, `Root`, `Sfx`, `Width`, `Height`, `Dir` и +другие. + +### Как читать статистику + +Нулевое число parser errors подтверждает layout и диапазонные инварианты на +имеющихся variants, но не автоматически раскрывает предметный смысл каждого +opaque field. Отсутствие opcode или poly branch в corpus означает, что эту +ветку нельзя считать corpus-verified. + +Особенно важно различать весь архив и достижимый runtime path. В `objects.rlb` +есть ссылки на вырезанный demo-content, однако шесть миссий не требуют их. +Поэтому quality gate имеет два отчёта: global archive health и mission +reachability. + +### Полные каталоги Частей 1 и 2 + +Статистика демоверсии остаётся неизменной. Полные Части 1 и 2 образуют два +самостоятельных профиля с отдельными manifests, hashes и golden data. + +Часть 1: + +```text +files 1 017, bytes 197 056 957 +NRes 120 / 6 804 entries +TMA 29 / 864 objects / 28 extras +unit DAT 425 / 5 219 records +objects.rlb 590 prototypes +MSH 435, MAT0 905, Texm 518, FXID 923 +Land maps 33 / 34 662 areals +reachable prototypes 4 701 +materials 36 954, textures 48 806, lightmaps 139 +reachability failures 0 +``` + +Часть 2: + +```text +files 1 302, bytes 358 004 931 +NRes 134 / 8 171 entries +TMA 31 / 885 objects / 41 extras +unit DAT 676 / 8 145 records +objects.rlb 683 prototypes +MSH 511, MAT0 1 127, Texm 631, FXID 1 065 +Land maps 32 / 18 984 areals +reachable prototypes 5 845 +materials 50 888, textures 68 603, lightmaps 214 +reachability failures 0 +``` + +Bootstrap Частей 1 и 2 идентичен. Девять DLL идентичны, шесть пересобраны при +сохранённом ABI. Активные NRes entries сравниваются так: 3 733 идентичны, 2 503 +имеют изменённый payload, 1 934 добавлены в Части 2, 567 удалены. Это +показывает стабильность форматов при существенной переработке content, +особенно MSH, CTLD и FXID. + +## Границы знания + +### Закрытые или практически закрытые области + +- Startup bootstrap и восемь exports `iron3d.dll`. +- Карта 15 DLL, exports/imports и основные interface boundaries. +- NRes layout, поиск и writer rules. +- RsLi header, table transform, lookup, mapping и используемые decode paths. +- TMA всех 60 проверенных миссий, unit DAT и `objects.rlb` resolution. +- MSH core/animation range contracts. +- WEAR, MAT0, Texm и FXID framing. +- `Land.msh`/`Land.map` и areal grid. +- World3D calculation/render order и deferred deletion. +- Сквозная mission-to-texture цепочка. + +Полная проверка доступных каталогов усилила NRes active ranges, recursive +prototype inheritance через `objects.rlb`, bounded non-NUL unit descriptions, +полный TMA epilogue, extra records и Clan mode 0, MSH/MAT0/Texm/FXID variant +matrix Частей 1 и 2, 65 `Land.msh`/`Land.map`, полный reachable graph 60 +миссий, stability matrix пятнадцати DLL, empty SWAV и stale save-slot metadata. + +### Render-state и pixel parity + +Доказан порядок frame boundaries, world traversal, material resolve и крупных +проходов. Не доказаны символами точные названия renderer vtable slots +`+0x28/+0x30/+0x34`, полный набор state transitions CShade и окончательный +взаимный порядок некоторых transparent/FX/shadow subpasses. + +Pixel parity требует эталонных кадров оригинала с фиксированными camera, +timing, seed, разрешением и capability profile. Вместе с изображением +необходимо сохранять command/state trace; иначе pixel difference не позволяет +отличить ошибку формата от ошибки backend-а. + +Минимальный capture должен фиксировать resolution, bit depth, selected driver, +device capabilities, camera matrices, mission, game time, seed, input log, +scene boundaries, transforms, render states, texture-stage states, texture +binds, viewport, clear, draw calls и `Blt/Flip`. Сначала сравниваются command +lists; pixel diff имеет смысл только после совпадения geometry/state sequence. + +### FXID field-level semantics + +Размеры команд, resource references, lifecycle, flags families и используемые +time modes известны. Не закрыто значение каждого поля body opcodes 1-10, +отсутствующий во всех проверенных каталогах opcode 6 и точные формулы редких +time modes. + +Закрывающий эксперимент: создать инструмент, который изменяет по одному полю +копии эффекта, воспроизводить его в контролируемой сцене и логировать runtime +command object, emitted primitives и sound events. Одновременно reads в +`Effect.dll` сопоставляются с offsets body. + +### Script VM + +Сценарные packages, symbol names, event sections, variable declarations и +version check доступны. Полная instruction grammar `.scr`, semantics всех +opcodes и serialization состояния VM ещё не восстановлены. + +План реконструкции: + +1. Найти loader `.scr`, version check, границы bytecode, таблицы + strings/symbols/events. +2. Найти dispatcher loop по повторяющемуся чтению opcode и indirect branch или + jump table. +3. Для каждого handler определить instruction size, operands, чтения/записи VM + state, stack effect, branch target и world side effects. +4. Hook-нуть dispatcher и писать запись `package,event,ip,opcode,raw + operands,state before,state after,next ip`. +5. Построить disassembler и CFG; branch target обязан попадать на + подтверждённую границу инструкции. +6. Закрывать opcode после статического handler contract, одного динамического + trace и одного regression script. + +После opcode table отдельно восстанавливаются serialization IP, call/event +frames, variables, timers и RNG. + +### Physical/control formats + +CTLD и связанные resources структурно читаются, count patterns и variants +известны. Не названы все секции, shape types, coefficients и точный contact +solver. То же относится к редким MSH types 17/20 и части CTPT/NDPR flags. + +Закрывающий эксперимент: трассировать `LoadControlSystem`, +`LoadPhysicalModel` и создание collision objects на нескольких прототипах; +записать offsets, созданные shape instances и реакции на контролируемое +движение. Изменение одного resource field должно связываться с одним +наблюдаемым параметром. + +### Сеть + +DirectPlay lifecycle и имена игровых сообщений известны. Точные framing, +payload schema, reliability flags и алгоритм `netZipData` пока не подтверждены +записью сетевого обмена. Поэтому совместимость с оригинальным сетевым клиентом +ещё не доказана. + +Для закрытия нужны два оригинальных клиента в изолированной среде и логирование +`netZipData`, `netUnZipData`, DirectPlay Send/Receive и World3D message +enqueue/dequeue. Native interoperability подтверждается только успешным +обменом original client <-> compatibility implementation в обе стороны. + +### Редкие или отсутствующие corpus-ветки + +- `Land.map poly_count > 0`: layout читается из loader-а, но ни одна из 65 + проверенных карт не содержит живой записи. +- RsLi adaptive methods `0x080`/`0x0A0`: decoder path известен, однако + демоверсия и обе полные части их не используют. +- Texm formats 556 и 88: loader поддерживает их, но ни один проверенный Texm не + использует эти значения. +- FX opcode 6: размер известен, однако живой command отсутствует во всём + доступном corpus. +- Некоторые material flags и MSH auxiliary streams встречаются слишком редко + для полного authoring contract. + +Такие ветки реализуются строго по бинарному коду и synthetic tests, а статус +corpus-verified получают только после появления реального файла. + +### Сохранения и campaign state + +`saveslots.cfg` и `missions/dispatcher.ini` найдены, но полный бинарный +savegame payload, serialization World3D/AI/script/RNG и правила миграции версии +не восстановлены. Без этого нельзя честно заявлять полную campaign +compatibility. + +Минимальный набор сохранений для каждой части: + +```text +S0 сразу после старта миссии +S1 тот же state без simulation step +S2 изменена только позиция одного объекта +S3 изменено только здоровье/свойство +S4 активен один Behavior order/path +S5 активен один FX и timer +S6 изменена одна script variable +S7 изменён research/economy state +S8 перед/после mission completion +S9 pause и non-default game time +``` + +Без самих binary save payload возможно описать обязательный state и найти код +сериализации, но невозможно доказать disk layout и roundtrip. + +### Shell, HUD, шрифты и локализация + +Граница shell подтверждена экспортами `createShell`/`getIShell`, `IGUIServer`, +верхнеуровневым UI-pass и файлами `ui/*.cfg`, `DATA/TextRes.cfg`, +`gamefont.rlb` и `sprites.lib`. RsLi framing двух библиотек закрыт, но widget +tree, layout rules, font glyph metrics, sprite command semantics, +focus/navigation и полный HUD state machine пока не восстановлены до +field-level спецификации. + +До закрытия новая реализация может построить функционально эквивалентный UI +поверх известных ресурсов, но не заявлять native layout/behavior parity. + +### Исследования, экономика и игровые свойства + +Экспорты `LoadResearch`, `CalcFullResearchCost`, TRF/preload resources и TMA +properties доказывают отдельный слой исследований, стоимости, добычи и +производственных параметров. Сквозные имена (`MaximumOre`, `CurrentOre`, +`FreeResearchTime`, `FreeConstructionTime` и другие) доступны, однако формулы +стоимости, dependency graph технологий, inventory/economy transitions и точная +типизация всех 16-byte property values не закрыты. + +Закрывающий эксперимент: сопоставить `LoadResearch`/`CalcFullResearchCost` с +ресурсами и UI, снять изменения state на контролируемых покупках/исследованиях +и построить typed schema свойств по consumers, не по одному имени. + +### Условия динамического этапа + +Полное закрытие оставшихся вопросов технически возможно, но не только по +статическим архивам. Нужна среда, способная запускать оригинальный 32-битный +код, и набор эталонных наблюдений: + +1. Изолированная 32-битная Windows VM или отдельная машина с исходными + DirectDraw/Direct3D/DirectSound/DirectPlay interfaces. +2. Два неизменённых игровых каталога и manifest SHA-256 для executable, DLL, + конфигураций и ключевых архивов. +3. Отладчик с hardware/software breakpoints, просмотром x87/SSE state и + сохранением memory dumps. +4. API/vtable hooking для Win32 file I/O, DirectDraw/Direct3D, DirectSound и + DirectPlay; hooks должны писать binary trace, не изменяя порядок вызовов. +5. Управляемые clocks, input log и RNG seed либо trace всех вызовов источника + случайности. +6. Автоматический launcher, который восстанавливает snapshot VM, запускает один + test case, собирает логи и завершает процесс без ручного вмешательства. + +Для каждого capture сохраняются profile сборки, hash модулей, +mission/resource key, конфигурация, device profile, начальное состояние, +input/time script и версии инструментов. + +### Критерий закрытия открытого вопроса + +Для каждого открытого вопроса должны существовать: + +- build fingerprint и адреса наблюдаемых функций; +- raw trace и автоматический parser trace-а; +- минимальный воспроизводимый input/resource/save/message; +- формальный контракт или явно ограниченная гипотеза; +- differential test для Частей 1 и 2, если модуль изменён; +- обновление тематической статьи; +- regression case, запускаемый без ручного анализа. + +До выполнения этих условий статический контракт пригоден для реализации, но +утверждение о полном поведенческом или native-паритете не публикуется. + +## Глоссарий + +### Бинарные файлы и reverse engineering + +**PE (Portable Executable)** -- формат исполняемых файлов Windows: EXE и DLL. +Он содержит заголовки, секции, таблицы импортов и экспортов, relocations и +адрес точки входа. + +**Image base** -- предпочтительный адрес начала загруженного PE-образа. +**VA** -- виртуальный адрес в процессе. **RVA** -- адрес относительно image +base. Адрес функции в памяти обычно равен `image_base + RVA`. + +**Import** -- внешняя функция или переменная, которую модуль получает из другой +DLL. **Export** -- символ, предоставляемый другим модулям. Имя, ordinal и +calling convention вместе образуют часть бинарного контракта. + +**ABI** -- соглашение о двоичном взаимодействии: размещение аргументов, возврат +значений, очистка stack, layout структур, порядок virtual methods и правила +владения. + +**Calling convention** -- часть ABI, определяющая передачу аргументов и очистку +stack. Для исследованного 32-битного кода важны `__cdecl`, `__stdcall` и +`__thiscall`. + +**Vtable** -- массив указателей на virtual methods C++-объекта. Запись +`vtable +0x34` означает вызов указателя по байтовому смещению `0x34` от начала +таблицы. + +**Static analysis** исследует файл без его исполнения: disassembly, strings, +imports, call graph и data flow. **Dynamic analysis** наблюдает работающую +программу: breakpoints, traces, API hooks, memory state и packet/frame captures. + +**Evidence** -- наблюдение, которое можно повторить. **Inference** -- вывод, +объединяющий несколько наблюдений. **Hypothesis** -- рабочее предположение, ещё +не подтверждённое достаточным экспериментом. + +### Форматы данных и ресурсы + +**Archive** -- контейнер, объединяющий множество ресурсов. **Entry** -- запись +его каталога. **Payload** -- полезные bytes конкретной записи. + +**Magic** -- короткая сигнатура формата, например `NRes` или `Texm`. +**Version** -- номер варианта layout. Проверка одной magic без проверки version +и размеров недостаточна. + +**Offset** -- положение данных относительно начала файла или структуры. +**Size** -- число занимаемых bytes. **Stride** -- размер одного элемента +массива. **Alignment** -- требование начинать данные на address или offset, +кратном заданному числу. + +**Little-endian** -- порядок, в котором младший byte многобайтного числа +расположен первым. Все основные числовые поля исследованных форматов Iron3D +используют этот порядок. + +**Fixed-size string** -- поле заранее известной длины. Полезная строка +заканчивается первым NUL, но оставшиеся bytes поля могут содержать служебный +хвост и должны сохраняться. + +**Opaque field** -- поле с доказанными offset и размером, но не установленным +предметным смыслом. Его безопасно читать и копировать, но нельзя очищать или +переосмысливать без эксперимента. + +**Invariant** -- условие, которое обязано выполняться: диапазон находится +внутри payload, индекс указывает на существующий элемент, число записей +соответствует размеру секции. + +**Strict reader** отклоняет любое нарушение контракта. **Compatibility reader** +дополнительно воспроизводит только известные особенности оригинала, например +именованный fallback. Compatibility mode не означает игнорирование произвольной +порчи. + +**Roundtrip** -- последовательность decode -> encode. **Byte-identical +roundtrip** создаёт файл, полностью совпадающий с исходным. **Lossless editor** +может изменить известное поле, сохранив все остальные bytes и порядок записей. + +**Fallback** -- явно предписанный запасной путь, например материал `DEFAULT`, +затем entry 0. **Heuristic** -- догадка по похожим данным; она не должна +незаметно заменять доказанный fallback. + +### Игровой runtime + +**Engine** -- программная среда, которая загружает данные, ведёт время, +исполняет мир и формирует изображение/звук. **Game** -- конкретные правила, +миссии и содержимое, работающие поверх engine services. + +**World** -- долгоживущее состояние миссии: objects, terrain, время, кланы и +managers. **Scene** -- представление части мира для конкретной обработки, чаще +всего текущей камеры. + +**Game object** -- сущность с идентичностью, transform, properties и lifecycle. +**Component/controller** -- специализированная часть поведения: animation, +physics, AI или rendering representation. + +**Simulation** отвечает за изменение мира. **Tick** -- один расчётный шаг +simulation. **Frame** -- одно подготовленное изображение. Число ticks и frames +за единицу времени не обязано совпадать. + +**Game loop** -- повторяющийся порядок ввода, расчёта, рендера и обслуживания. +**Scheduler phase** -- явно ограниченный участок loop, где разрешены +определённые операции. + +**Event/message** -- типизированное сообщение между objects или subsystems. +**Queue traversal** -- стабильный обход зарегистрированных объектов. +**Deferred deletion** -- перенос фактического удаления до безопасной границы +после traversal. + +**Determinism** -- одинаковый результат при одинаковом initial state, input, +времени и порядке событий. **Replay** -- повторное исполнение записанной +последовательности входов/сообщений для проверки determinism. + +**Authority** -- subsystem или network peer, которому разрешено окончательно +менять состояние объекта. **Mirror object** -- локальное представление объекта, +authority которого находится у другого player. + +### Геометрия, анимация и рендеринг + +**Mesh** -- набор vertex/index streams и draw-групп, описывающий форму. +**Node** -- элемент hierarchy модели со своим local transform. **Slot** в MSH +-- выбранная геометрическая группа для комбинации node, LOD и group; он также +хранит bounds и диапазоны batches. + +**Batch** -- непрерывный индексный диапазон с одним material slot и общим +render state. **Transform** переводит данные между local, world, view и clip +spaces. Порядок умножения matrices является частью контракта. + +**Quaternion** -- четырёхкомпонентное представление вращения. **Keyframe** -- +pose в определённое время. **Sampling** выбирает pose для времени, а +**blending** смешивает animation states. + +**Bounds** -- упрощённый объём для быстрых тестов. **AABB** -- пара +minimum/maximum по осям. **Bounding sphere** -- center и radius. + +**Renderer** -- subsystem, преобразующая подготовленную сцену в изображение. +**Backend** -- реализация renderer поверх конкретного API или устройства. + +**Draw call** -- команда нарисовать диапазон primitives с текущими resources и +states. **Material** -- правила отображения поверхности: texture, коэффициенты, +прозрачность и режимы pipeline. **Material phase** -- одно временное состояние +анимированного материала. + +**Texture** -- двумерный массив texels. **UV coordinates** -- координаты +выборки. **Mip chain** -- последовательность уменьшенных уровней texture. +**Lightmap** -- texture с заранее рассчитанным вкладом освещения. + +**Fixed-function pipeline** -- старый графический pipeline, где приложение +выбирает predefined transform, lighting, texture-stage и blend states вместо +пользовательских shaders. + +**Depth buffer** хранит глубину уже принятой поверхности. **Alpha test** +полностью принимает или отвергает fragment. **Blending** смешивает новый цвет с +framebuffer. + +**Back buffer** -- скрытый framebuffer. **Present/flip** делает завершённый +кадр видимым. **Pixel parity** -- совпадение конечного изображения при +фиксированных условиях. + +### Навигация, физика, звук и сеть + +**Areal** -- логическая область карты с границей, class/flags и связями с +соседями. **Areal graph** -- граф, вершинами которого служат области, а рёбрами +-- допустимые переходы. **Cell grid** -- пространственный индекс для candidate +areas или objects. + +**Pathfinding** -- поиск маршрута по графу. **A\*** использует стоимость уже +пройденного пути и оценку расстояния до цели. Навигационная проходимость, +отсутствие collision и видимость -- разные свойства. + +**Collision proxy** -- упрощённое представление объекта для столкновений. +**Broad phase** быстро находит потенциальные пары; **narrow phase** выполняет +точную проверку и вычисляет contact. + +**Sample** -- декодированные звуковые данные. **Source** -- экземпляр +воспроизведения с position, gain, loop state и временем. **Listener** -- позиция +и ориентация слушателя для 3D spatialization. + +**Transport** -- механизм доставки bytes между peers. **Protocol** -- framing, +message types, порядок и правила подтверждения. **Serialization** -- +преобразование typed state в byte sequence. + +**Reliable delivery** гарантирует доставку/порядок в пределах выбранной модели; +**unreliable delivery** допускает потери ради задержки. **Wire compatibility** +-- способность обмениваться данными с оригинальным клиентом, а не только +воспроизводить ту же игровую семантику в новом протоколе. + +## Связанные локальные справки + +- [NRes](../reference/nres.md) +- [RsLi](../reference/rsli.md) +- [TMA](../reference/tma.md) +- [MSH](../reference/msh.md) +- [Texm](../reference/texm.md) +- [Materials](../reference/materials.md) +- [Render frame](../reference/render-frame.md) +- [Границы знания](../appendices/knowledge-boundaries.md) +- [Глоссарий](../appendices/glossary.md) + +## Дополнительное чтение + +Эти материалы помогают понять PE, ABI, сжатие, graphics pipeline, game loop и +навигацию. Они не являются доказательством поведения Iron3D: детали движка +принимаются только после проверки его бинарного кода и игровых ресурсов. + +- [Microsoft PE/COFF specification](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format) +- [Microsoft x86 calling conventions](https://learn.microsoft.com/en-us/cpp/build/x86-calling-conventions) +- [Intel Software Developer Manuals](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html) +- [Ghidra documentation](https://ghidra-sre.org/) +- [RFC 1951: DEFLATE](https://www.rfc-editor.org/rfc/rfc1951) +- [zlib manual](https://zlib.net/manual.html) +- [Kaitai Struct user guide](https://doc.kaitai.io/user_guide.html) +- [Microsoft Direct3D documentation](https://learn.microsoft.com/en-us/windows/win32/direct3d) +- [Vulkan specification](https://registry.khronos.org/vulkan/specs/1.4-extensions/html/vkspec.html) +- [Real-Time Rendering resources](https://www.realtimerendering.com/) +- [LearnOpenGL](https://learnopengl.com/) +- [Scratchapixel](https://www.scratchapixel.com/) +- [Game Programming Patterns](https://gameprogrammingpatterns.com/) +- [Fix Your Timestep](https://gafferongames.com/post/fix_your_timestep/) +- [Red Blob Games: A*](https://www.redblobgames.com/pathfinding/a-star/introduction.html) |
