diff options
| author | Valentin Popov <valentin@popov.link> | 2026-06-22 00:58:51 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-06-22 00:58:51 +0300 |
| commit | 78fc5f1debf1395d5df0bab7cc0dde54351205cb (patch) | |
| tree | ef8f7c72a183723fcbea0b2d1fefd7c28ca7bc18 | |
| parent | 50c2cf4686b53ebd2b76318223096660e92305a4 (diff) | |
| download | fparkan-78fc5f1debf1395d5df0bab7cc0dde54351205cb.tar.xz fparkan-78fc5f1debf1395d5df0bab7cc0dde54351205cb.zip | |
docs: rewrite MkDocs documentation
44 files changed, 6336 insertions, 2586 deletions
diff --git a/docs/appendices/glossary.md b/docs/appendices/glossary.md new file mode 100644 index 0000000..2bef280 --- /dev/null +++ b/docs/appendices/glossary.md @@ -0,0 +1,200 @@ +# Глоссарий + +Глоссарий объясняет термины в том смысле, в котором они используются в этой +книге. Короткое определение не заменяет профильную главу: практический контракт +понятия раскрывается в соответствующем томе или справочной странице. + +## Бинарные файлы и ABI + +**PE (Portable Executable)** -- формат исполняемых файлов Windows: EXE и DLL. +Он содержит заголовки, секции, таблицы импортов и экспортов, relocations и +адрес точки входа. + +**Image base** -- предпочтительный адрес начала загруженного PE-образа. +**VA** -- виртуальный адрес в процессе. **RVA** -- адрес относительно image +base. + +**Import** -- внешняя функция или переменная, которую модуль получает из другой +DLL. **Export** -- символ, предоставляемый другим модулям. Имя, ordinal и +calling convention вместе образуют часть binary contract. + +**ABI** -- соглашение о двоичном взаимодействии: размещение аргументов, возврат +значений, очистка stack, layout структур, порядок virtual methods и правила +владения. + +**Calling convention** -- часть ABI, определяющая передачу аргументов и очистку +stack. Для исследованного 32-bit code важны `__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** -- требование начинать данные на offset, кратном заданному числу. + +**Little-endian** -- порядок, в котором младший byte многобайтного числа +расположен первым. Основные числовые поля форматов Iron3D используют этот +порядок. + +**Fixed-size string** -- поле заранее известной длины. Полезная строка +заканчивается первым NUL, но оставшиеся bytes могут содержать служебный хвост и +должны сохраняться. + +**Opaque field** -- поле с доказанными offset и size, но не установленным +предметным смыслом. Его безопасно читать и копировать, но нельзя очищать или +переосмысливать без эксперимента. + +**Invariant** -- условие, которое обязано выполняться: range лежит внутри +payload, индекс указывает на существующий элемент, count соответствует размеру +секции. + +**Strict reader** отклоняет любое нарушение контракта. **Compatibility reader** +дополнительно воспроизводит только известные особенности оригинала. + +**Fallback** -- явно предписанный запасной путь, например material `DEFAULT`, +затем entry 0. **Heuristic** -- догадка по похожим данным; она не должна +незаметно заменять доказанный fallback. + +**Roundtrip** -- последовательность decode -> encode. **Byte-identical +roundtrip** создаёт файл, полностью совпадающий с исходным. **Lossless editor** +может изменить известное поле, сохранив все остальные bytes и порядок записей. + +## Ресурсы + +**NRes** -- основной контейнер ресурсов с каталогом в конце файла. + +**RsLi** -- библиотечный архив с каталогом в начале файла и несколькими методами +упаковки payload. + +**TMA** -- mission data: paths, clans, placed objects, properties, land path и +extras. + +**MSH** -- модель Iron3D, представленная как NRes с entries для geometry, +nodes, slots, batches, animation и auxiliary streams. + +**WEAR** -- таблица внешнего вида модели, переводящая material index в MAT0 +name и lightmap slots. + +**MAT0** -- материал: phases, parameters, animation blocks и texture references. + +**Texm** -- texture payload с header, palette, mip chain и optional Page atlas. + +**FXID** -- ресурс эффектов: команды, references, lifetime, random/time modes и +runtime instances. + +## Игровой runtime + +**Engine** -- программная среда, которая загружает данные, ведёт время, +исполняет мир и формирует изображение/звук. **Game** -- правила, миссии и +content поверх engine services. + +**World** -- долгоживущее состояние миссии: objects, terrain, время, кланы и +managers. **Scene** -- представление части мира для конкретной обработки, +обычно текущей камеры. + +**Game object** -- сущность с идентичностью, transform, properties и lifecycle. +**Component/controller** -- специализированная часть поведения: animation, +physics, AI или rendering representation. + +**Simulation** отвечает за изменение мира. **Tick** -- один расчётный шаг. +**Frame** -- одно подготовленное изображение. Число ticks и frames за единицу +времени не обязано совпадать. + +**Event/message** -- типизированное сообщение между objects или subsystems. +**Queue traversal** -- стабильный обход зарегистрированных объектов. +**Deferred deletion** -- перенос фактического удаления до безопасной границы. + +**Snapshot** -- согласованное состояние, которое renderer читает без изменения +simulation. **Determinism** -- одинаковый результат при одинаковом initial +state, input, времени и порядке событий. + +**Authority** -- subsystem или network peer, которому разрешено окончательно +менять состояние объекта. **Mirror object** -- локальное представление объекта, +authority которого находится у другого player. + +## Геометрия и рендеринг + +**Vertex** -- вершина geometry. **Index** -- номер вершины. **Triangle** -- +примитив из трёх индексов. + +**Node** -- элемент hierarchy модели со своим local transform. **Slot** в MSH +-- выбранная геометрическая группа для комбинации node, LOD и group. **Batch** +-- непрерывный индексный диапазон с material slot и render state. + +**Transform** переводит данные между coordinate spaces. **Matrix** задаёт +линейное преобразование и translation. Порядок умножения matrices является +частью контракта. + +**Bounds** -- упрощённый объём для быстрых тестов. **AABB** -- min/max по осям. +**Bounding sphere** -- center и radius. + +**Renderer** преобразует подготовленную сцену в изображение. **Backend** -- +реализация поверх конкретного API или устройства. + +**Draw call** -- команда нарисовать диапазон primitives. **Indexed draw** +использует index buffer и base vertex. + +**Material phase** -- одно временное состояние анимированного материала. +**Texture** -- двумерный массив texels. **Mip chain** -- последовательность +уменьшенных уровней texture. **Atlas** -- texture с несколькими под- +изображениями. + +**Fixed-function pipeline** -- старый graphics pipeline, где приложение +выбирает predefined transform, lighting, texture-stage и blend states вместо +пользовательских shaders. + +**Depth test**, **culling**, **alpha test** и **blending** -- render states, +которые влияют на порядок и видимость fragments. + +**Pixel parity** -- совпадение конечного изображения при фиксированных camera, +time, seed, resolution и device profile. + +## Навигация, звук и сеть + +**Areal** -- логическая область карты с границей, class/flags и связями с +соседями. **Areal graph** -- граф областей и переходов. **Cell grid** -- +пространственный индекс для быстрых candidate queries. + +**Pathfinding** -- поиск маршрута по graph. **Corridor** -- локальная полоса, +построенная из последовательности areals. **Local steering** корректирует +ближайший шаг внутри corridor. + +**Collision proxy** -- упрощённое представление объекта для столкновений. +**Broad phase** быстро находит потенциальные пары. **Narrow phase** выполняет +точную проверку и вычисляет contact. + +**Sample** -- декодированные звуковые данные. **Source** -- конкретный +экземпляр воспроизведения с position, gain, loop state и временем. **Listener** +-- положение и ориентация слушателя для 3D spatialization. + +**Transport** -- механизм доставки bytes между peers. **Protocol** -- framing, +message types, порядок и правила подтверждения. **Wire compatibility** -- +способность обмениваться данными с оригинальным клиентом. + +**Serialization** -- преобразование typed state в byte sequence. **Framing** -- +способ отделить одно сообщение от следующего. **Reliable delivery** гарантирует +доставку/порядок в пределах выбранной модели; **unreliable delivery** допускает +потери ради задержки. + +**Player ID** транспорта и **game player number** -- разные идентичности. +**Ownership transfer** меняет authority объекта. **Replication** передаёт +состояние или события remote mirrors. diff --git a/docs/appendices/knowledge-boundaries.md b/docs/appendices/knowledge-boundaries.md new file mode 100644 index 0000000..f9d7d0e --- /dev/null +++ b/docs/appendices/knowledge-boundaries.md @@ -0,0 +1,120 @@ +# Границы знания + +Этот раздел перечисляет области, где контракт ещё не закрыт полностью. Они не +мешают безопасному чтению и lossless сохранению, но не должны превращаться в +authoring API без динамического подтверждения. + +## Render state + +Доказаны frame boundaries, world traversal, material resolve и крупные проходы. +Не доказаны символами точные имена renderer vtable slots, полный набор CShade +state transitions и окончательный порядок части transparent/FX/shadow subpasses. + +Закрывающий эксперимент: запустить оригинал в совместимой Windows/DirectX +среде, перехватить DirectDraw/Direct3D calls и surface flips, сохранить state +log на минимальных сценах с одним типом материала. + +## 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`. + +## Script VM + +Доступны packages, symbols, event sections, variable declarations и version +checks. Полная instruction grammar `.scr`, semantics opcodes и serialization +state ещё не восстановлены. + +Закрывающий эксперимент: найти dispatcher loop в `ai.dll`, сопоставить jump +table с instruction sizes, построить disassembler и сравнить выполнение +коротких scripts с оригиналом. + +## Saves and campaign state + +Найдены `saveslots.cfg` и `missions/dispatcher.ini`, но binary savegame payload, +serialization World3D/AI/script/RNG и migration rules не закрыты. + +Нужны сохранения оригинала в контролируемых состояниях: старт миссии, изменение +позиции, здоровья, order/path, FX/timer, script variable, research/economy, +mission completion, pause и non-default game time. + +## Physical/control formats + +CTLD и связанные resources структурно читаются, count patterns и variants +известны. Не названы все секции, shape types, coefficients и точный contact +solver. То же относится к редким MSH auxiliary streams и части CTPT/NDPR flags. + +Закрывающий эксперимент: трассировать `LoadControlSystem`, +`LoadPhysicalModel`, `CreateCollManager` и создание collision objects; связать +каждый изменяемый field с созданным shape, contact или реакцией на движение. + +## DirectPlay wire + +DirectPlay lifecycle и имена игровых messages известны. Wire framing, payload +schema, reliability flags и `netZipData` требуют записи обмена двух +оригинальных клиентов. + +Native interoperability подтверждается только успешным обменом original client +<-> compatibility implementation в обе стороны. + +## Shell, HUD, шрифты и локализация + +Граница shell подтверждена exports `createShell/getIShell`, `IGUIServer`, +верхнеуровневым UI-pass и файлами `ui/*.cfg`, `DATA/TextRes.cfg`, +`gamefont.rlb` и `sprites.lib`. RsLi framing библиотек закрыт, но widget tree, +layout rules, glyph metrics, sprite command semantics, focus/navigation и HUD +state machine пока не восстановлены до field-level спецификации. + +Закрывающий эксперимент: трассировать загрузку `shell_ctrls.cfg`, +`menu_resources.cfg`, `cursor.cfg`, `game_resources.cfg` и `hq.cfg`, сопоставить +GUI object factories и снять command/event captures для меню, HUD, briefing и +диалогов. + +## Research, economy and properties + +Экспорты `LoadResearch`, `CalcFullResearchCost`, TRF/preload resources и TMA +properties доказывают отдельный слой исследований, стоимости, добычи и +производственных параметров. Формулы стоимости, dependency graph технологий, +inventory/economy transitions и точная типизация всех 16-byte property values +не закрыты. + +Закрывающий эксперимент: сопоставить research functions с ресурсами и UI, +снять изменения state на контролируемых покупках/исследованиях и построить +typed schema свойств по consumers, а не по одному имени. + +## Rare branches + +- `Land.map poly_count > 0`; +- RsLi adaptive methods `0x080` и `0x0A0`; +- Texm formats 556 и 88; +- FX opcode 6; +- редкие material flags и MSH auxiliary streams. + +Такие ветки реализуются по бинарному коду и synthetic tests, а статус +corpus-verified получают только после реального файла или runtime trace. + +## Dynamic-stage requirements + +Оставшиеся вопросы нельзя закрыть только статическими архивами. Нужна +изолированная 32-bit Windows-среда, неизменённые игровые каталоги, manifest +SHA-256, debugger, API/vtable hooks, controlled clocks/input и автоматический +launcher, который восстанавливает snapshot, запускает один test case, собирает +логи и завершает процесс без ручного вмешательства. + +Для каждого capture сохраняются build profile, module hashes, mission/resource +key, configuration, device profile, initial state, input/time script и версии +инструментов. + +## Closure criteria + +Вопрос считается закрытым только при наличии build fingerprint, raw trace, +parser trace-а, минимального воспроизводимого input/resource/save/message, +формального контракта или явно ограниченной гипотезы, differential test для +изменённых DLL, обновления тематической главы и regression case, запускаемого +без ручного анализа. diff --git a/docs/index.md b/docs/index.md index 000ea34..2775ed7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,17 +1,51 @@ -# Welcome to MkDocs +# FParkan -For full documentation visit [mkdocs.org](https://www.mkdocs.org). +FParkan -- самостоятельная техническая книга о восстановлении игрового движка +Iron3D из *Parkan: Iron Strategy*. Она ведёт от запуска оригинальной программы +и карты DLL к форматам ресурсов, загрузке миссии, геометрии, материалам, +рендеру, поведению, звуку, сети и плану чистой совместимой реализации. -## Commands +Сайт оформлен как онлайн-книга: тома читаются последовательно, а справочник +используется как быстрый доступ к форматам, проверочным правилам и границам +доказанного знания. -* `mkdocs new [dir-name]` - Create a new project. -* `mkdocs serve` - Start the live-reloading docs server. -* `mkdocs build` - Build the documentation site. -* `mkdocs -h` - Print help message and exit. +## Как читать -## Project layout +Если вы впервые разбираете игровой движок, начните с тома I и II. Там вводится +лексика, доказательная политика, модульная архитектура и жизненный цикл кадра. - mkdocs.yml # The configuration file. - docs/ - index.md # The documentation homepage. - ... # Other markdown pages, images and other files. +Если нужна реализация совместимого движка, читайте тома III--VII линейно: +ресурсы, миссии, мир, рендер, интерактивные подсистемы и порядок работ. + +Если вы проверяете выводы, переходите к тому VIII и приложениям. Там собраны +уровни уверенности, corpus gates, открытые вопросы и критерии закрытия. + +## Восемь томов + +1. **Путеводитель и методика** -- назначение книги, маршруты чтения, язык + предметной области и правила проверки. +2. **Запуск, архитектура и игровой цикл** -- `iron_3d.exe`, пятнадцать DLL, + сервисы, World3D, очередь объектов и границы кадра. +3. **Ресурсная система и форматы** -- NRes, RsLi, кэши, имена, `objects.rlb`, + unit DAT и сквозное разрешение ресурсов. +4. **Мир, миссии и runtime** -- TMA, ландшафт, ареалы, маршруты, создание мира + и свойства размещённых объектов. +5. **Геометрия, материалы и рендер** -- MSH, анимация, WEAR, MAT0, Texm, FXID, + свет, атмосфера и полный render frame. +6. **Поведение, управление, звук и сеть** -- AI, Behavior, Wizard, Control, + ввод, камера, звук и DirectPlay-слой. +7. **Руководство по полной реализации** -- целевая архитектура, этапы работ, + тестовый контур, точность, скорость и критерий совместимости. +8. **Справочник и доказательная база** -- ABI, конфигурация, статистика + корпусов, границы знания и глоссарий. + +## Политика доказательств + +Специфические утверждения об Iron3D принимаются только после локальной проверки +на исполняемых файлах, DLL, демоверсии, полных каталогах Частей 1 и 2 или на +взаимных инвариантах реальных ресурсов. Внешние описания и текущий код FParkan +могут подсказывать вопросы, но не заменяют проверку. + +Неизвестные поля не получают правдоподобных имён. Пока смысл не закрыт, +документация фиксирует raw layout, границы, безопасное чтение и lossless +сохранение. diff --git a/docs/reference/materials.md b/docs/reference/materials.md new file mode 100644 index 0000000..8146a2c --- /dev/null +++ b/docs/reference/materials.md @@ -0,0 +1,69 @@ +# WEAR и MAT0 + +MSH batch хранит только `material_index`. WEAR переводит этот индекс в имя +материала, а MAT0 по этому имени описывает phases, parameters и texture +references. + +```text +Batch20.material_index + -> WEAR row + -> MAT0 entry + -> active phase + -> textureName +``` + +## WEAR + +WEAR -- текстовый ресурс type ID `0x52414557`, обычно `*.wea` рядом с моделью. + +```text +<wearCount> +<legacyId> <materialName> +... + +[empty line] +[LIGHTMAPS +<lightmapCount> +<legacyId> <lightmapName> +...] +``` + +`legacyId` сохраняется, но выбор выполняется по позиции строки и имени. Между +основной таблицей и `LIGHTMAPS` нужен пустой разделитель. + +## MAT0 + +MAT0 имеет type ID `0x3054414D`, обычно расположен в `Material.lib`. `attr1` +содержит runtime flags, `attr2` -- версию payload. + +```c +#pragma pack(push, 1) +struct Mat0PrefixV4Plus { + uint16_t phase_count; + uint16_t animation_block_count; + uint8_t metadata_a; + uint8_t metadata_b; + uint32_t metadata_c_raw; + uint32_t metadata_d_raw; +}; + +struct Phase34 { + uint8_t parameters[18]; + char texture_name[16]; +}; +#pragma pack(pop) +``` + +Versioned fields читаются только если версия их содержит. Для старых версий +используются runtime defaults, а raw values сохраняются. + +## Fallback + +Material resolve: + +1. имя из WEAR; +2. `DEFAULT`; +3. entry с индексом 0. + +Пустое texture name означает намеренно нетекстурированную поверхность. Lightmap +fallback отдельный: отсутствующий lightmap даёт slot `-1`. diff --git a/docs/reference/msh.md b/docs/reference/msh.md new file mode 100644 index 0000000..25aaad2 --- /dev/null +++ b/docs/reference/msh.md @@ -0,0 +1,82 @@ +# MSH + +Файл `*.msh` является NRes-контейнером. Geometry, узлы, slots, batches, +animation и служебные streams лежат в entries с разными `type_id`. + +## Entry map + +```text +type 1 nodes and slot selection +type 2 header 0x8C + Slot68 records +type 3 positions float3 +type 4 packed normals +type 5 packed UV0 +type 6 index buffer u16 +type 7 triangle descriptors +type 8 animation keys +type 9 service stream +type 10 strings and node names +type 13 Batch20 records +type 15 auxiliary stream +type 17 auxiliary data +type 18 rare stream +type 19 animation frame map +type 20 rare auxiliary table +``` + +Reader ищет entries по type, но сохраняет исходный порядок для roundtrip. + +## Node and slot selection + +Type 1 обычно состоит из records по 38 bytes: + +```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[lod * 5 + group]` выбирает geometry slot. `0xFFFF` означает +отсутствие геометрии для комбинации LOD/group. + +## Slot and batch + +Type 2 содержит header `0x8C`, затем `Slot68`: + +```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]; +}; +``` + +Type 13 задаёт draw ranges: + +```c +#pragma pack(push, 1) +struct Batch20 { + uint16_t batch_flags; + uint16_t material_index; + uint16_t opaque4; + uint16_t opaque6; + uint16_t index_count; + uint32_t index_start; + uint16_t opaque14; + uint32_t base_vertex; +}; +#pragma pack(pop) +``` + +Index check выполняется как `base_vertex + index < vertex_count` для всего +используемого slice. diff --git a/docs/reference/nres.md b/docs/reference/nres.md new file mode 100644 index 0000000..3b8384f --- /dev/null +++ b/docs/reference/nres.md @@ -0,0 +1,61 @@ +# NRes + +`NRes` -- основной контейнер ресурсов Iron3D. Он используется как внешний +архив и как внутренний контейнер модели `*.msh`. + +```text +[Header: 16 bytes] +[Data region: payload with alignment] +[Directory: entry_count * 64 bytes] +``` + +## Header + +```c +struct NResHeader16 { + char magic[4]; // "NRes" + uint32_t version; // 0x00000100 + int32_t entry_count; // >= 0 + uint32_t total_size; // equals file size +}; +``` + +`directory_offset = total_size - entry_count * 64`. Reader проверяет отсутствие +переполнений, `directory_offset >= 16` и точное окончание каталога на +`total_size`. + +## Entry + +```c +#pragma pack(push, 1) +struct NResEntry64 { + uint32_t type_id; + uint32_t attr1; + uint32_t attr2; + uint32_t size; + uint32_t attr3; + char name[36]; + uint32_t data_offset; + uint32_t sort_index; +}; +#pragma pack(pop) +``` + +Имя содержит bounded C-string до 35 полезных bytes. `sort_index` задаёт +отображение из sorted position в original entry index. В строгом режиме все +`sort_index` образуют перестановку `0..N-1`. + +## Data region + +Payload каждой записи лежит после header и до начала каталога. Игровые архивы +выравнивают следующий payload до 8 bytes нулями, но reader не должен требовать +плотного покрытия data region. + +Различаются: + +- active payload -- диапазон, на который указывает entry; +- gap/padding -- bytes между активными диапазонами; +- unindexed preserved region -- произвольные bytes, не принадлежащие entry. + +Lossless editor сохраняет все три категории. Compact writer может исключить +unindexed regions только при явной операции repack. diff --git a/docs/reference/render-frame.md b/docs/reference/render-frame.md new file mode 100644 index 0000000..6f0975c --- /dev/null +++ b/docs/reference/render-frame.md @@ -0,0 +1,56 @@ +# Render frame + +Кадр является последней стадией цикла, а не самостоятельной функцией renderer-а. +До draw calls уже накоплен input, рассчитан tick, применены отложенные операции, +выбрана камера и обновлён 3D sound listener. + +## Frame skeleton + +```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 передаётся Terrain, +настраиваются viewport/matrices, вызываются renderer boundary slots, +устанавливается `in_render`, выполняется traversal мира, закрывается world/shade +pass, вызывается renderer completion, снимается `in_render`, рассылается +end-of-render. + +## Draw item + +Подготовленный draw item содержит: + +- 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. + +Подготовленный item должен ссылаться на immutable данные кадра. Изменение phase +или texture cache посреди прохода не должно менять уже собранную очередь. + +## Parity risks + +- x87 precision and rounding; +- scalar/SIMD `g_FastProc` differences; +- object, batch and transparent primitive order; +- depth, cull, alpha test and blend transitions; +- mip-skip, palette and Page coordinates; +- material fallback and phase selection; +- RNG sequence for FX and atmosphere; +- device capability fallback; +- simulation time quantization. + +Для отладки нужен deterministic frame capture: camera state, visible object IDs, +draw-item list, pipeline keys, matrices и hashes промежуточных buffers. diff --git a/docs/reference/rsli.md b/docs/reference/rsli.md new file mode 100644 index 0000000..a28aa1d --- /dev/null +++ b/docs/reference/rsli.md @@ -0,0 +1,69 @@ +# RsLi + +`RsLi` -- библиотечный архив Iron3D с каталогом в начале файла и payloads после +него. + +```text +[Header: 32 bytes] +[Entry table: entry_count * 32 bytes] +[Payloads] +[optional trailer] +``` + +## Header fields + +```text ++0x00 char[2] "NL" ++0x02 u8 reserved ++0x03 u8 version = 1 ++0x04 i16 entry_count ++0x0E u16 presorted_flag = 0xABBA ++0x14 u32 xor_seed +``` + +Остальные bytes сохраняются без нормализации. + +## Entry + +```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` связывает sorted +position с исходной записью. + +## Table transform + +Entry table проходит обратимое потоковое XOR-преобразование. Начальное +состояние берётся из младших 16 bits `xor_seed` и продолжается через всю +таблицу, не сбрасываясь на границе записи. + +## Storage methods + +```text +0x000 raw block +0x020 byte transform only +0x040 LZSS +0x060 transform + LZSS +0x080 adaptive Huffman + LZSS +0x0A0 transform + adaptive Huffman + LZSS +0x100 raw Deflate +``` + +После любого пути должно получиться ровно `unpacked_size` bytes. Методы +`0x080` и `0x0A0` подтверждены decoder-кодом, но не живыми payload демоверсии +или обеих частей. + +## Compatibility quirk + +`sprites.lib::INTERF8.TEX` объявляет Deflate range на один byte дальше EOF. +Совместимый reader допускает `packed_size - 1` только для этого именованного +случая. Строгий режим сообщает `deflate_eof_plus_one`. diff --git a/docs/reference/texm.md b/docs/reference/texm.md new file mode 100644 index 0000000..db4321e --- /dev/null +++ b/docs/reference/texm.md @@ -0,0 +1,67 @@ +# Texm + +`Texm` -- основной формат изображений Iron3D. Payload содержит header, +необязательную палитру, mip chain и иногда `Page` chunk. + +```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; +}; +``` + +## Pixel formats + +```text +0 Indexed8 + palette 256 * 4 bytes +565 R5 G6 B5 +556 R5 G5 B6 +4444 A4 R4 G4 B4 +88 L8 A8 +888 RGB8 in four-byte element +8888 A8 R8 G8 B8 +``` + +Короткие каналы расширяются до 8 bits повторением значимых bits. Для 888 +служебный четвёртый byte сохраняется при roundtrip. + +## Layout + +```text +TexmHeader32 +[palette 1024 bytes, only for format 0] +level 0 pixels +level 1 pixels +... +level mip_count-1 pixels +[optional Page chunk] +``` + +Размер mip level вычисляется через `max(1, width >> i)` и +`max(1, height >> i)`. Parser суммирует размеры с проверкой переполнения до +чтения данных. + +## 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`. Rectangles находятся в pixel +space базового mip и масштабируются после mip-skip. diff --git a/docs/reference/tma.md b/docs/reference/tma.md new file mode 100644 index 0000000..30ee495 --- /dev/null +++ b/docs/reference/tma.md @@ -0,0 +1,64 @@ +# TMA + +`data.tma` -- основное описание расстановки и логической конфигурации миссии. +Файл перечисляет paths, clans, objects, свойства, ссылку на ландшафт и extras. + +## String primitive + +```c +struct LpString { + uint32_t byte_length; + uint8_t bytes[byte_length]; +}; +``` + +Reader продвигается ровно на `4 + byte_length`. Завершающий NUL не является +обязательной частью framing. Для человекочитаемого вида используется legacy +ANSI/CP1251 view, но исходные bytes сохраняются. + +## Top level + +```text +u32 format_version +u32 path_count +PathRecord paths[path_count] +u32 clan_section_version +u32 clan_count +ClanRecord clans[clan_count] +u32 object_section_version +u32 object_count +PlacedObject objects[object_count] +LpString land_path +u32 mission_flag +LpString description_raw +u32 extra_section_version +u32 extra_count +ExtraRecord28 extras[extra_count] +``` + +Все 60 TMA Частей 1 и 2 проходят parser до точного EOF. Версии стабильны: +верхний уровень `1`, clan section `6`, object section `10`, property schema +`1`, trailing section `1`. + +## PlacedObject + +```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 +u32 property_count +Property properties[property_count] +``` + +`Property` состоит из четырёх raw `u32` и имени. Typed views разрешены только +для доказанных property names и consumers. diff --git a/docs/specs/ai.md b/docs/specs/ai.md deleted file mode 100644 index 7570cd0..0000000 --- a/docs/specs/ai.md +++ /dev/null @@ -1,35 +0,0 @@ -# AI system - -Страница фиксирует границы подсистемы AI на уровне движка: - -- выбор целей; -- тактические приоритеты; -- координация с `Behavior`, `ArealMap`, `Missions`. - -## 1. Текущая зафиксированная часть - -1. AI работает поверх ареалов/клеток карты, а не напрямую поверх render-геометрии. -2. Результат AI передается в behavior/command-слой как набор целевых состояний и команд. -3. Решения AI зависят от миссионных триггеров и состояния объектов мира. - -## 2. Контракт интеграции - -В 1:1 реализации AI должен быть совместим с: - -1. системой ареалов (`Land.map`); -2. объектными категориями (`BuildDat.lst`); -3. поведением юнитов (`behavior.md`); -4. миссионными условиями (`missions.md`). - -## 3. Статус покрытия и что осталось до 100% - -Закрыто: - -- роль AI в общей архитектуре и точки интеграции с соседними подсистемами. - -Осталось: - -1. Полный формат runtime-AI состояний и таблиц решений. -2. Полные правила выбора цели/маршрута/приоритета огня. -3. Полная спецификация влияния миссионных скриптов на AI. -4. Набор тест-кейсов «AI tick parity» для побайтного/пошагового сравнения с оригиналом. diff --git a/docs/specs/arealmap.md b/docs/specs/arealmap.md deleted file mode 100644 index 3b234c9..0000000 --- a/docs/specs/arealmap.md +++ /dev/null @@ -1,31 +0,0 @@ -# ArealMap - -`ArealMap` — подсистема топологии мира и логических зон. - -Подробный бинарный формат `Land.map` и связь с terrain описаны в: - -- [Terrain + ArealMap](terrain-map-loading.md) - -## 1. Роль в движке - -1. Хранит ареалы, связи между ареалами и клеточный индекс. -2. Используется для навигации, логики объектов и AI-решений. -3. Связывает геометрию карты с миссионной и поведенческой логикой. - -## 2. Минимальный runtime-контракт - -1. Валидный граф ареалов и edge-link связей. -2. Валидная cell-grid индексация (`cellsX/cellsY` + hit lists). -3. Согласованные идентификаторы ареалов для AI/Behavior/Missions. - -## 3. Статус покрытия и что осталось до 100% - -Закрыто: - -- бинарный контракт `Land.map` и pair-загрузка с `Land.msh`. - -Осталось: - -1. Полная доменная семантика `class_id`/`logic_flag` по всем игровым сценариям. -2. Формальная спецификация API-запросов к ArealMap (поиск зон, фильтры, события). -3. Набор parity-тестов поведения навигационных запросов на одинаковых входах. diff --git a/docs/specs/behavior.md b/docs/specs/behavior.md deleted file mode 100644 index 33d403d..0000000 --- a/docs/specs/behavior.md +++ /dev/null @@ -1,28 +0,0 @@ -# Behavior system - -`Behavior` — слой исполнения состояний юнитов между AI-решением и низкоуровневым control-командованием. - -## 1. Роль в кадре - -1. Принимает решения из AI. -2. Переводит их в state machine юнита. -3. Формирует команды движения/атаки/действий в world/control-слой. - -## 2. Внешние зависимости - -1. `ArealMap` (доступность/топология). -2. `Missions` (триггеры и ограничения сценария). -3. `Control` (выполнение команд). - -## 3. Статус покрытия и что осталось до 100% - -Закрыто: - -- архитектурная роль подсистемы и ее место в runtime-пайплайне. - -Осталось: - -1. Полная спецификация finite-state машин по типам юнитов. -2. Полная таблица переходов, таймаутов и приоритетов. -3. Формализация входных/выходных структур поведения для 1:1 эмуляции. -4. Поведенческие parity-тесты на фиксированных replay-сценариях. diff --git a/docs/specs/control.md b/docs/specs/control.md deleted file mode 100644 index eb1e535..0000000 --- a/docs/specs/control.md +++ /dev/null @@ -1,28 +0,0 @@ -# Control system - -`Control` — подсистема входа и маршрутизации команд (пользовательских и системных). - -## 1. Роль - -1. Преобразует ввод устройств в команды движка. -2. Синхронизирует управление камерой, UI и объектами мира. -3. Передает команды в gameplay-подсистемы с учетом активного режима игры. - -## 2. Минимальный контракт совместимости - -1. Детерминированный mapping input -> command. -2. Стабильная обработка очереди команд в пределах кадра. -3. Корректный приоритет UI-фокуса над world-input. - -## 3. Статус покрытия и что осталось до 100% - -Закрыто: - -- место control-слоя в архитектуре и базовый runtime-контур. - -Осталось: - -1. Полная карта input actions и режимов обработки. -2. Формат внутренних очередей команд и их сериализация. -3. Спецификация edge-case поведения (повтор клавиш, захват мыши, hotkey-конфликты). -4. Пошаговые parity-тесты на записанных последовательностях ввода. diff --git a/docs/specs/coverage-audit.md b/docs/specs/coverage-audit.md deleted file mode 100644 index bee27ee..0000000 --- a/docs/specs/coverage-audit.md +++ /dev/null @@ -1,45 +0,0 @@ -# Documentation coverage audit - -Дата аудита: `2026-02-19` -Корпус данных: `testdata/Parkan - Iron Strategy` - -## 1. Проверка форматов архивов - -Результаты: - -- `NRes`: `120` архивов, roundtrip `120/120` (byte-identical) -- `RsLi`: `2` архива, roundtrip `2/2` (byte-identical) -- подтвержден один совместимый quirk: `sprites.lib`, entry `23`, `deflate EOF+1` - -Проверено legacy-валидатором архивов. - -## 2. Проверка рендерных форматов - -Результаты: - -- `MSH`: `435/435` валидны -- `Texm`: `518/518` валидны -- `FXID`: `923/923` валидны -- `Terrain/Map` (`Land.msh` + `Land.map`): `33/33` без ошибок/предупреждений - -Проверено legacy-валидаторами рендерных форматов. - -## 3. Глобальный статус по подсистемам - -| Подсистема | Статус | Что блокирует 100% | -|---|---|---| -| Архивы (`NRes`, `RsLi`) | практически закрыта | формализация редких не-ASCII/служебных edge-case | -| 3D geometry (`MSH core`) | высокая готовность | семантика opaque-полей и канонический writer «с нуля» | -| Animation (`Res8/Res19`) | высокая готовность | полный FP-parity на всех edge-case | -| Material/Wear/Texture | высокая готовность | полная field-level семантика служебных флагов и writer-профиль | -| FXID | высокая готовность | полная field-level семантика payload по каждому opcode | -| Terrain/Areal map formats | высокая готовность | доменная семантика `class_id/logic_flag`, ветка `poly_count>0` | -| Render pipeline | хорошая | полный pixel-parity набор эталонных кадров в CI | -| AI/Behavior/Control/Missions/UI/Sound/Network | начальное покрытие | требуется полная спецификация форматов и runtime-контрактов | - -## 4. План доведения до 100% - -1. Закрыть field-level семантику opaque/служебных полей в 3D/FX/terrain подсистемах. -2. Завершить canonical writer paths для авторинга новых ассетов без copy-through. -3. Зафиксировать и автоматизировать pixel/frame parity-критерии в CI. -4. Расширить подсистемные спецификации (`AI`, `Behavior`, `Missions`, `Control`, `UI`, `Sound`, `Network`) до уровня «полный формат + полный runtime-контракт + parity-тесты». diff --git a/docs/specs/fxid.md b/docs/specs/fxid.md deleted file mode 100644 index e3a583d..0000000 --- a/docs/specs/fxid.md +++ /dev/null @@ -1,202 +0,0 @@ -# FXID - -`FXID` — бинарный формат эффекта в движке Parkan: Iron Strategy. -Эта страница задаёт контракт формата и исполнения на уровне, достаточном для 1:1 порта рендера/симуляции эффектов и для lossless-инструментов. - -Связанные контейнеры: [NRes](nres.md), [RsLi](rsli.md). - -## 1. Контейнер - -- Тип ресурса в `NRes`: `0x44495846` (`FXID`). -- Значения `attr1/attr2/attr3` в типовых игровых данных стабильны, но при редактуре их нужно сохранять как есть. - -## 2. Бинарный формат - -Все значения little-endian. - -### 2.1. Заголовок (60 байт) - -```c -struct FxHeader60 { - uint32_t cmd_count; // 0x00 - uint32_t time_mode; // 0x04 - float duration_sec; // 0x08 - float phase_jitter; // 0x0C - uint32_t flags; // 0x10 - uint32_t settings_id; // 0x14 - float rand_shift_x; // 0x18 - float rand_shift_y; // 0x1C - float rand_shift_z; // 0x20 - float pivot_x; // 0x24 - float pivot_y; // 0x28 - float pivot_z; // 0x2C - float scale_x; // 0x30 - float scale_y; // 0x34 - float scale_z; // 0x38 -}; -``` - -Поток команд начинается строго с `offset = 0x3C`. - -### 2.2. Команда - -Каждая команда: - -1. `uint32 cmd_word` -2. body фиксированного размера, зависящего от `opcode` - -Поля `cmd_word`: - -- `opcode = cmd_word & 0xFF` -- `enabled = (cmd_word >> 8) & 1` -- `bits 9..31` нужно сохранять 1:1 - -Выравнивания между командами нет. - -### 2.3. Размеры команд - -| Opcode | Размер | -|---:|---:| -| 1 | 224 | -| 2 | 148 | -| 3 | 200 | -| 4 | 204 | -| 5 | 112 | -| 6 | 4 | -| 7 | 208 | -| 8 | 248 | -| 9 | 208 | -| 10 | 208 | - -## 3. Смысл заголовка - -- `cmd_count`: число команд в потоке. -- `time_mode`: способ вычисления текущего коэффициента эффекта. -- `duration_sec`: длительность (в рантайме переводится в миллисекунды). -- `phase_jitter`: амплитуда случайного фазового сдвига. -- `flags`: флаги поведения (видимость, альфа-модификаторы, режимы гейтинга). -- `settings_id`: индекс профиля/настроек эффекта. -- `rand_shift_*`: случайный пространственный сдвиг. -- `pivot_*`: локальная опора. -- `scale_*`: базовый масштаб инстанса эффекта. - -## 4. Флаги заголовка - -Практически важные биты: - -- `0x0001`: случайный сдвиг фазы -- `0x0008`: случайный пространственный сдвиг (`rand_shift_*`) -- `0x0010`: ветки видимости/окклюзии -- `0x0020`: треугольный ремап альфы -- `0x0040`: инверсия исходного active-state -- `0x0080`, `0x0100`: фильтрация по времени суток -- `0x0200`: умножение альфы на нормализованное время жизни -- `0x0400`, `0x1000`: дополнительные биты состояния менеджера эффекта -- `0x0800`: дополнительный гейтинг - -Неизвестные биты должны сохраняться без изменений. - -## 5. `time_mode` (0..17) - -База: - -- `tn = (now - start) / (end - start)` -- `prev = предыдущая вычисленная альфа` - -Поддерживаемые семейства режимов: - -- константный режим; -- линейный (`tn`), обратный (`1-tn`), циклический (`fract(tn)`); -- режимы от внешних параметров мира/очереди; -- режимы на основе норм векторов состояния; -- режимы с ограничением вниз/вверх относительно `prev`. - -После вычисления: - -- при `flags & 0x0200` применяется `alpha *= tn`; -- при `flags & 0x0020` применяется triangular remap. - -## 6. Resource-ссылки внутри команд - -Для opcode `2/3/4/5/7/8/9/10` используется ссылка: - -```c -struct ResourceRef64 { - char archive[32]; - char name[32]; -}; -``` - -Контракт: - -- строки ASCII, нуль-терминированные; -- сравнение имён регистронезависимое; -- обычно: - - `opcode 2`: `sounds.lib` + `*.wav` - - остальные: `material.lib` + имя материала/эффекта. - -## 7. Runtime-контракт исполнения - -На создании инстанса: - -1. Заголовок копируется в runtime-состояние. -2. Вычисляется `end_time`. -3. Для каждой команды создаётся runtime-объект по `opcode`. -4. В объект копируется `enabled`. -5. Объект инициализируется контекстом эффекта. - -На каждом кадре: - -1. Вычисляется текущий коэффициент/альфа по `time_mode` и `flags`. -2. Выполняется update каждой команды. -3. Выполняется emit/render часть активных команд. -4. Применяются события Start/Stop/Restart. - -## 8. Строгий парсер (рекомендуемый) - -1. Проверить `len(payload) >= 60`. -2. Прочитать `cmd_count`. -3. Идти от `ptr = 0x3C`. -4. Для каждой команды: - - проверить `ptr + 4 <= len`; - - прочитать `opcode`; - - проверить, что `opcode` поддержан; - - проверить `ptr + size(opcode) <= len`; - - сдвинуть `ptr += size(opcode)`. -5. Проверить `ptr == len(payload)`. - -## 9. Writer и редактор - -Для lossless-совместимости: - -- сохранять все неизвестные поля/биты; -- не менять фиксированные размеры команд; -- не добавлять padding; -- пересчитывать только `cmd_count` и размеры контейнера; -- сохранять порядок команд. - -## 10. Что требуется для 1:1 переноса - -1. Полная поддержка opcode `1..10`. -2. Точный контракт вычисления `time_mode` и `flags`. -3. Точное поведение `ResourceRef64`. -4. Повторяемый RNG и одинаковая политика плавающей точки. - -## 11. Статус валидации - -- Формальные инварианты FXID зафиксированы в спецификациях проекта и проверены legacy-валидаторами. -- На полном retail-корпусе `testdata/Parkan - Iron Strategy` проверено `923/923` FXID payload без ошибок. - -## 12. Статус покрытия и что осталось до 100% - -Закрыто: - -1. Контейнер FXID, fixed-size командный поток, opcode-покрытие `1..10`. -2. Базовый runtime-контур исполнения эффекта. -3. Корпусная валидация формата на retail-данных. - -Осталось: - -1. Полная field-level семантика payload каждого opcode для авторинга новых эффектов «с нуля». -2. Формальная спецификация всех `time_mode` веток на уровне точных числовых формул и edge-case поведения. -3. Полный набор пиксельных parity-тестов FX (оригинал vs новый рендер) на фиксированных сценах. diff --git a/docs/specs/material.md b/docs/specs/material.md deleted file mode 100644 index 1aa3510..0000000 --- a/docs/specs/material.md +++ /dev/null @@ -1,144 +0,0 @@ -# Material (`MAT0`) - -`MAT0` описывает материал и его фазовую анимацию. - -Связанные страницы: - -- [Wear table (`WEAR`)](wear.md) -- [Texture (`Texm`)](texture.md) -- [Render pipeline](render.md) - -## 1. Контейнер - -- Тип ресурса: `0x3054414D` (`MAT0`). -- Обычно хранится в `Material.lib`. -- `attr1` используется как битовое поле runtime-флагов материала. -- `attr2` задаёт версию заголовка payload. - -## 2. Бинарный layout - -```c -struct Mat0Payload { - uint16_t phaseCount; - uint16_t animBlockCount; // должно быть < 20 - - // если attr2 >= 2 - uint8_t metaA8; - uint8_t metaB8; - // если attr2 >= 3 - uint32_t metaC32; - // если attr2 >= 4 - uint32_t metaD32; - - PhaseRecord34 phases[phaseCount]; - AnimBlockRaw anim[animBlockCount]; -}; -``` - -Если `attr2 < 2`, используются runtime-значения по умолчанию: - -- `metaA = 255` -- `metaB = 255` -- `metaC = 1.0f` -- `metaD = 0` - -## 3. Фазы материала - -```c -struct PhaseRecord34 { - uint8_t params[18]; - char textureName[16]; -}; -``` - -В рантайме запись разворачивается в структуру ~76 байт: - -- набор коэффициентов цвета/освещения/прозрачности; -- индекс слота текстуры; -- дополнительные целочисленные поля. - -`textureName`: - -- пустая строка -> фаза без текстуры (`texSlot = -1`); -- непустая строка -> загрузка текстуры по имени. - -## 4. Анимационные блоки - -```c -struct AnimBlockRaw { - uint32_t headerRaw; // mode = low 3 bits, interpMask = остальные - uint16_t keyCount; - KeyRaw keys[keyCount]; -}; - -struct KeyRaw { - uint16_t k0; - uint16_t k1; - uint16_t k2; // opaque, сохранять 1:1 -}; -``` - -`k2` нельзя удалять или нормализовать: это часть бинарного контракта. - -## 5. Выбор текущей фазы - -Материал выбирает фазу по времени и по режиму анимации блока: - -- loop; -- ping-pong; -- one-shot с clamp; -- random-offset. - -При смешивании интерполируется только часть полей, остальные копируются из активной фазы. -Для 1:1 совместимости важно сохранить эту выборочную интерполяцию. - -## 6. Загрузка и fallback - -При запросе материала по имени: - -1. Точный поиск по имени. -2. Если не найдено — fallback на `DEFAULT`. -3. Если `DEFAULT` отсутствует — используется запись с индексом `0`. - -## 7. Атрибуты и флаги - -Практически важные биты `attr1`: - -- бит загрузки текстурной фазы с расширенными флагами; -- флаги аппаратного профиля; -- 4-битный режим (`nibbleMode`); -- дополнительный флаг material-поведения. - -Неизвестные биты должны сохраняться без изменений. - -## 8. Ограничения - -- `animBlockCount < 20` -- `phaseCount` и фактический размер секции фаз должны совпадать -- `textureName` должен быть NUL-terminated и укладываться в 16 байт - -## 9. Правила writer/editor - -1. Сохранять `attr1/attr2/attr3`. -2. Не менять `metaA/B/C/D` без явного запроса. -3. Сохранять opaque-поля анимации (включая `k2`) 1:1. -4. Проверять выход за границы payload при парсинге. - -## 10. Статус валидации - -- Инварианты MAT0 зафиксированы в спецификациях проекта. -- Структурная валидация MAT0 проверена legacy-валидатором на полном retail-наборе. - -## 11. Статус покрытия и что осталось до 100% - -Закрыто: - -1. Бинарный layout `MAT0` и правила чтения фаз/анимационных блоков. -2. Fallback-цепочка материала. -3. Контракт сохранения opaque-полей для lossless editor path. - -Осталось: - -1. Полная семантика всех битов `attr1` и `metaA/B/C/D` для авторинга новых материалов. -2. Полный writer-профиль «канонический MAT0» для генерации ассетов без copy-through. -3. Набор визуальных parity-тестов по material phase animation на реальных моделях. diff --git a/docs/specs/materials-texm.md b/docs/specs/materials-texm.md deleted file mode 100644 index beef3ee..0000000 --- a/docs/specs/materials-texm.md +++ /dev/null @@ -1,18 +0,0 @@ -# Materials, WEAR, Texm - -Старая объединённая страница разбита по объектам. - -- [Material (`MAT0`)](material.md) -- [Wear table (`WEAR`)](wear.md) -- [Texture (`Texm`)](texture.md) -- [Render pipeline](render.md) - -## Статус покрытия и что осталось до 100% - -Закрыто: - -1. Страница корректно декомпозирована на отдельные объектные спецификации. - -Осталось: - -1. Поддерживать единый changelog согласованности между `material.md`, `wear.md`, `texture.md` и `render.md`. diff --git a/docs/specs/missions.md b/docs/specs/missions.md deleted file mode 100644 index f8b2cd4..0000000 --- a/docs/specs/missions.md +++ /dev/null @@ -1,46 +0,0 @@ -# Missions - -Подсистема `Missions` управляет сценарием: - -- стартовыми условиями; -- триггерами; -- победой/поражением; -- синхронизацией с AI/Behavior/World. - -## 1. Что уже зафиксировано - -1. Миссии связаны с картами (`Land.msh`/`Land.map`) и объектными категориями. -2. Скриптовые ресурсы хранятся в архивных контейнерах (`NRes`) и участвуют в runtime-логике. -3. Миссионные события влияют на AI и поведение объектов через общий gameplay-слой. - -## 2. Минимальный runtime-контракт - -1. Детерминированный порядок обработки триггеров в кадре. -2. Единая шкала времени миссии для всех подсистем. -3. Согласованность идентификаторов объектов между mission-data и world-state. - -## 3. Статус покрытия и что осталось до 100% - -Закрыто: - -- связь миссионной подсистемы с форматом ресурсов и runtime-контуром. - -Осталось: - -1. Полная спецификация форматов миссионных скриптов/таблиц. -2. Полный перечень типов триггеров и их параметров. -3. Формальные правила разрешения конфликтов триггеров в одном кадре. -4. Набор replay parity-тестов «миссия от старта до завершения». -## 4. Mission -> Prototype -> Mesh bridge - -Для 3D-объектов миссии обязательна промежуточная стадия `objects.rlb`: - -1. `data.tma` задаёт либо прямой ключ объекта, либо путь к `*.dat`. -2. `*.dat` даёт `model_key` (в retail-наборе через `objects.rlb`). -3. Ключ резолвится в запись прототипа внутри `objects.rlb`. -4. Из прототипа выбирается фактический `*.msh` и архив (например `bases.rlb`, `static.rlb`, `fortif.rlb`). -5. Только после этого запускается стандартная цепочка материалов и текстур. - -Детальный формат и алгоритм вынесены в отдельную страницу: - -- [Object registry (`objects.rlb`)](object-registry.md) diff --git a/docs/specs/msh-animation.md b/docs/specs/msh-animation.md deleted file mode 100644 index 1c0807a..0000000 --- a/docs/specs/msh-animation.md +++ /dev/null @@ -1,126 +0,0 @@ -# MSH animation - -`MSH animation` описывает связку `Res8 + Res19` и runtime-правила сэмплирования/смешивания поз. - -Связанные страницы: - -- [MSH core](msh-core.md) -- [Render pipeline](render.md) - -## 1. Ресурсы анимации - -### 1.1. `Res8` (пул ключей) - -```c -struct AnimKey24 { - float pos_x; - float pos_y; - float pos_z; - float time; - int16_t qx; - int16_t qy; - int16_t qz; - int16_t qw; -}; -``` - -Декодирование quaternion-компонент: `q = s16 / 32767.0`. - -### 1.2. `Res19` (карта кадров) - -```c -uint16_t map_words[]; // size/2 элементов -``` - -`Res19.attr2` хранит глобальную длину таймлайна (число кадров). - -### 1.3. Связь с `Res1` - -Для каждого узла: - -- `anim_map_start` (`hdr2`) — начало блока в `Res19` или `0xFFFF`. -- `fallback_key` (`hdr3`) — индекс fallback-ключа в `Res8`. - -## 2. Сэмплирование узла - -Вход: время `t`, текущий узел. -Выход: `quat(w,x,y,z)` и `pos(x,y,z)`. - -### 2.1. Индекс кадра - -Движок использует x87-совместимое округление для выражения `t - 0.5`. -Для 1:1 повторения нужно сохранить ту же политику плавающей точки. - -### 2.2. Выбор key index - -1. Если кадр вне диапазона `frame_count` -> `fallback_key`. -2. Если `anim_map_start == 0xFFFF` -> `fallback_key`. -3. Иначе берётся `map_words[anim_map_start + frame]`: - - если значение `>= fallback_key`, тоже используется `fallback_key`; - - иначе используется значение из map. - -### 2.3. Интерполяция - -Если выбран fallback, возвращается ровно этот ключ без интерполяции. - -Иначе: - -1. Берутся соседние ключи `k0` и `k1`. -2. Если `t` точно равен `k0.time` или `k1.time`, возвращается соответствующий ключ. -3. Иначе: - - `alpha = (t - k0.time) / (k1.time - k0.time)` - - `pos = lerp(k0.pos, k1.pos, alpha)` - - `quat = slerp_like(k0.quat, k1.quat, alpha)` - -Кватернион в runtime хранится в порядке `[w, x, y, z]`. - -## 3. Смешивание двух сэмплов - -При blending между позами A и B: - -1. Выбираются валидные стороны по `blend` и валидности времени. -2. Если активна одна сторона, берётся она. -3. Если активны обе: - - применяется shortest-path flip для `qB`; - - выполняется quaternion blend; - - позиция смешивается линейно. - -Матрица строится из quaternion, а translation подставляется отдельным шагом. - -## 4. Каноника writer - -Рекомендуемые правила: - -1. Ключи узлов писать подряд в `Res8` в порядке узлов. -2. `fallback_key` узла указывает на последний ключ его трека. -3. Для узлов с map выделять блок длины `frame_count` в `Res19`. -4. Для статических узлов: `anim_map_start = 0xFFFF`, один ключ с `time=0`. -5. `Res8.attr1 = key_count`, `Res8.attr3 = 4`. -6. `Res19.attr1 = map_word_count`, `Res19.attr2 = frame_count`, `Res19.attr3 = 2`. - -## 5. Валидация перед сохранением - -- `Res8.size % 24 == 0` -- `Res19.size % 2 == 0` -- каждый `fallback_key < key_count` -- для узла с map: `anim_map_start + frame_count <= map_word_count` -- внутри трека времена ключей строго возрастают - -## 6. Статус валидации - -- Форматные проверки были покрыты legacy-валидатором. -- Корпусная валидация анимационных инвариантов выполнена на полном retail-наборе. - -## 7. Статус покрытия и что осталось до 100% - -Закрыто: - -1. Контракт `Res8 + Res19` и fallback-логика выбора ключа. -2. Базовая интерполяция поз и blending двух сэмплов. -3. Канонические инварианты writer path для существующих ассетов. - -Осталось: - -1. Полная фиксация численного поведения на всех FP-edge-case (включая платформенные различия округления). -2. Полный writer-профиль для авторинга новых анимаций без опоры на reference copy-through. -3. Набор runtime parity-тестов «frame-by-frame pose equivalence» на длинных анимациях. diff --git a/docs/specs/msh-core.md b/docs/specs/msh-core.md deleted file mode 100644 index db465e7..0000000 --- a/docs/specs/msh-core.md +++ /dev/null @@ -1,192 +0,0 @@ -# MSH core - -`MSH core` описывает геометрию, слоты, батчи и базовые таблицы модели. -Документ покрывает контракт, необходимый для 1:1 воспроизведения рендера и коллизии. - -Связанные страницы: - -- [MSH animation](msh-animation.md) -- [Material](material.md) -- [Texture (Texm)](texture.md) -- [Render pipeline](render.md) -- [NRes](nres.md) -- [RsLi](rsli.md) - -## 1. Общая модель - -MSH-модель хранится как `NRes`-контейнер. -Связь таблиц строится по `type`, а не по порядку записей. - -Базовый путь геометрии: - -1. `Res1` выбирает slot по `(node, lod, group)`. -2. `Res2.slot` задаёт диапазоны треугольников и батчей. -3. `Res13` задаёт диапазон индексов и `baseVertex`. -4. `Res6` даёт `uint16` индексы. -5. `Res3/Res4/Res5` дают вершины, нормали и UV. - -## 2. Карта core-ресурсов - -| Type | Ресурс | Обязательность | Stride / layout | -|---:|---|---|---| -| 1 | Node table | обязательный | обычно 38 байт | -| 2 | Header + slots | обязательный | `0x8C + n*68` | -| 3 | Positions | обязательный | 12 | -| 4 | Packed normals | обычно обязательный | 4 | -| 5 | Packed UV0 | обычно обязательный | 4 | -| 6 | Index buffer | обязательный | 2 | -| 7 | Tri descriptors | для коллизии/пикинга | 16 | -| 8 | Anim key pool | для анимированных | 24 | -| 10 | Node strings | опциональный | variable | -| 13 | Batch table | обязательный | 20 | -| 15 | Доп. stream | опциональный | 8 | -| 16 | Доп. stream | опциональный | 8 | -| 18 | Доп. stream | опциональный | 4 | -| 19 | Anim map | для анимированных | 2 | -| 20 | Доп. таблица | опциональный | variable | - -## 3. Основные структуры - -### 3.1. `Res1` (узлы) - -```c -struct Node38 { - uint16_t hdr0; - uint16_t parent_or_link; - uint16_t anim_map_start; - uint16_t fallback_key; - uint16_t slotIndex[15]; // lod0:g0..g4, lod1:g0..g4, lod2:g0..g4 -}; -``` - -Формула slot-выбора: - -```c -slot = node.slotIndex[lod * 5 + group] -``` - -`0xFFFF` означает отсутствие слота. - -### 3.2. `Res2` (header + slot records) - -```c -struct Slot68 { - uint16_t triStart; - uint16_t triCount; - uint16_t batchStart; - uint16_t batchCount; - float aabbMin[3]; - float aabbMax[3]; - float sphereCenter[3]; - float sphereRadius; - uint32_t opaque[5]; -}; -``` - -`opaque[5]` должны сохраняться 1:1. - -### 3.3. `Res3`, `Res4`, `Res5`, `Res6` - -- `Res3`: `float3` позиции (`stride=12`) -- `Res4`: `int8[4]` packed normal (`stride=4`) -- `Res5`: `int16[2]` UV (`stride=4`) -- `Res6`: `uint16` индексы (`stride=2`) - -Декодирование: - -- normal = `clamp(n / 127.0, -1..1)` -- uv = `packed / 1024.0` - -### 3.4. `Res7` и `Res13` - -```c -struct TriDesc16 { - uint16_t triFlags; - uint16_t link0; - uint16_t link1; - uint16_t link2; - int16_t nx; - int16_t ny; - int16_t nz; - uint16_t selPacked; -}; - -struct Batch20 { - uint16_t batchFlags; - uint16_t materialIndex; - uint16_t opaque4; - uint16_t opaque6; - uint16_t indexCount; - uint32_t indexStart; - uint16_t opaque14; - uint32_t baseVertex; -}; -``` - -`selPacked` хранит 3 селектора по 2 бита; значение `3` трактуется как `0xFFFF`. - -## 4. Runtime-обход модели - -Псевдокод рендера: - -```c -for each node: - slot = resolve_slot(node, lod, group) - if slot == none: continue - - if culled(slot.bounds, node_transform): continue - - for b in slot.batchRange: - batch = batches[b] - bind_material(batch.materialIndex) - - draw_indexed( - baseVertex = batch.baseVertex, - indexStart = batch.indexStart, - indexCount = batch.indexCount - ) -``` - -## 5. Критические инварианты - -Обязательно проверять: - -- `Res2.size >= 0x8C` -- `(Res2.size - 0x8C) % 68 == 0` -- `batchStart + batchCount` не выходит за `Res13` -- `triStart + triCount` не выходит за `Res7` -- `indexStart + indexCount` не выходит за `Res6` -- `baseVertex + max(indexSlice) < vertexCount` -- `slotIndex == 0xFFFF` или `< slotCount` - -## 6. Важные edge-cases - -- Встречается редкий вариант `Res1.attr3 = 24`; для существующих ассетов нужен copy-through. -- Для строгого writer лучше генерировать `Res1` в основном формате `38` байт/узел. -- Неизвестные поля таблиц нельзя нормализовать или обнулять. - -## 7. Правила для writer/editor - -1. Сохранять неизвестные поля и неизвестные `type`-ресурсы. -2. Пересчитывать только явно вычислимые атрибуты (`attr1/attr3` и size-зависимые поля). -3. Не менять порядок/контент opaque-данных без явной цели. -4. Сериализовать little-endian, без внутреннего padding. - -## 8. Статус валидации - -- Инварианты формата проверены legacy-валидатором. -- На полном retail-корпусе `testdata/Parkan - Iron Strategy` проверено `435/435` MSH-моделей без структурных ошибок. - -## 9. Статус покрытия и что осталось до 100% - -Закрыто: - -1. Базовые таблицы geometry path (`Res1/2/3/4/5/6/7/13`). -2. Критичные range-инварианты slot/batch/index. -3. Правила совместимого writer/editor для lossless работы с существующими ассетами. - -Осталось: - -1. Полная семантика части opaque-полей (`Slot68` tail, `Batch20` opaque-поля) для authoring без copy-through. -2. Полная формализация редких веток (`Res1.attr3 != 38`) на расширенном корпусе. -3. End-to-end writer для генерации новых игровых MSH с подтвержденным runtime-паритетом. diff --git a/docs/specs/msh-notes.md b/docs/specs/msh-notes.md deleted file mode 100644 index 5c95eb5..0000000 --- a/docs/specs/msh-notes.md +++ /dev/null @@ -1,118 +0,0 @@ -# 3D implementation notes - -Контрольная страница с практическими правилами реализации 3D-пайплайна и с перечнем незакрытых зон. -Документ intentionally high-level: без ссылок на внутренние функции/адреса. - -Связанные страницы: - -- [MSH core](msh-core.md) -- [MSH animation](msh-animation.md) -- [Material (`MAT0`)](material.md) -- [Texture (`Texm`)](texture.md) -- [FXID](fxid.md) -- [Render pipeline](render.md) - -## 1. Базовые двоичные правила - -1. Все форматы в этой подсистеме little-endian. -2. Внутри NRes данные ресурсов выравниваются по 8 байт. -3. Внутри payload таблиц padding между записями обычно отсутствует: записи идут подряд по stride. - -## 2. Быстрая карта stride'ов - -| Ресурс | Запись | Stride | -|---|---|---:| -| Res1 | Node | 38 | -| Res2 | Slot | 68 (после header `0x8C`) | -| Res3 | Position | 12 | -| Res4 | Normal | 4 | -| Res5 | UV0 | 4 | -| Res6 | Index | 2 | -| Res7 | Tri descriptor | 16 | -| Res8 | Animation key | 24 | -| Res13 | Batch | 20 | -| Res19 | Animation map | 2 | - -## 3. Декодирование ключевых потоков - -## 3.1. Позиции (Res3) - -`float3`, stride `12`. - -## 3.2. Нормали (Res4) - -`int8[4]`, используются первые 3 компоненты: - -```text -n = clamp(s8 / 127.0, -1..1) -``` - -## 3.3. UV (Res5) - -`int16[2]`: - -```text -u = s16 / 1024.0 -v = s16 / 1024.0 -``` - -## 3.4. Animation key (Res8) - -`pos(float3) + time(float) + quat(int16x4)`: - -```text -q = s16 / 32767.0 -``` - -## 4. Практический reader-контракт - -Для runtime-совместимого чтения модели: - -1. Найти нужные ресурсы по `type_id` в NRes. -2. Проверить `size/stride`-инварианты. -3. Проверить диапазоны ссылок: - - slot -> batch/triangles; - - batch -> indices; - - indices -> vertices; - - anim_map -> anim_keys. -4. Неизвестные поля и неизвестные ресурсы сохранять через copy-through. - -## 5. Практический writer-контракт - -1. Пересчитывать только явно вычислимые поля. -2. Не нормализовать opaque-данные без уверенной спецификации. -3. При roundtrip неизмененных данных требовать byte-identical результат. -4. Для новых ассетов фиксировать отдельную политику «генерация vs preserve». - -## 6. Runtime-связка материалов и текстур - -Канонический путь резолва: - -1. Модель -> wear-таблица (`*.wea`). -2. Wear-слот -> material name. -3. Material -> текущая фаза -> `textureName`. -4. `Texm` ищется в `Textures.lib` (или lightmap-библиотеке для lightmap-ветки). - -Fallback: - -- материал: `DEFAULT`, затем индекс `0`; -- текстура/lightmap: fallback-слот движка. - -## 7. Что уже закрыто для 1:1 - -1. Бинарный контракт базовых MSH таблиц. -2. Контракт animation sampling (`Res8 + Res19`). -3. Контракт MAT0/WEAR/Texm на уровне чтения и применения в кадре. -4. Формат FXID-контейнера, командный поток и fixed command sizes. -5. Валидация на retail-корпусе legacy-валидатором (0 ошибок/предупреждений). - -## 8. Статус покрытия и что осталось до 100% - -1. Полная field-level семантика части служебных полей: - - `Batch20` opaque-поля; - - хвостовые служебные поля slot-записей; - - часть флагов узлов/групп. -2. Полный writer-путь для авторинга новых анимированных ассетов (не только roundtrip существующих). -3. Полная формализация семантики FX payload полей по каждому opcode для генерации новых эффектов, а не только для корректного чтения/исполнения. -4. Полный канонический writer `Texm` для всех редких форматов и edge-case комбинаций служебных флагов. -5. Сквозной «импорт внешнего ассета -> игровой пакет» с формальной спецификацией sidecar-метаданных (материал/эффект/анимация). diff --git a/docs/specs/msh.md b/docs/specs/msh.md deleted file mode 100644 index 0581502..0000000 --- a/docs/specs/msh.md +++ /dev/null @@ -1,39 +0,0 @@ -# Форматы 3D-ресурсов движка NGI - -Этот документ теперь является обзором и точкой входа в набор отдельных спецификаций. - -## Структура спецификаций - -1. [MSH core](msh-core.md) — геометрия, узлы, батчи, LOD, slot-матрица. -2. [MSH animation](msh-animation.md) — `Res8`, `Res19`, выбор ключей и интерполяция. -3. [Material (`MAT0`)](material.md) — формат материала и фазовая анимация. -4. [Wear (`WEAR`)](wear.md) — текстовая таблица привязки материалов/lightmap. -5. [Texture (`Texm`)](texture.md) — форматы текстур, mip-chain и `Page`. -6. [FXID](fxid.md) — контейнер эффекта и поток команд. -7. [Render pipeline](render.md) — полный процесс рендера кадра. -8. [Terrain + map loading](terrain-map-loading.md) — ландшафт, шейдинг и привязка к миру. -9. [3D implementation notes](msh-notes.md) — контрольные заметки и открытые вопросы. -10. [Documentation coverage audit](coverage-audit.md) — сводка покрытия и оставшиеся блокеры. - -## Связанные спецификации - -- [NRes](nres.md) -- [RsLi](rsli.md) - -## Принцип декомпозиции - -- Форматы и контейнеры документируются отдельно, чтобы их можно было верифицировать и править независимо. -- Runtime-пайплайн вынесен в отдельный документ, потому что пересекает несколько runtime-подсистем и не является форматом на диске. - -## Статус покрытия и что осталось до 100% - -Закрыто: - -1. Документация декомпозирована по объектам: geometry, animation, material, texture, wear, fx, render, terrain. -2. Форматные инварианты ключевых 3D-ресурсов проверяются автоматическими валидаторами на retail-корпусе. - -Осталось: - -1. Полный сквозной writer-путь для генерации новых игровых ассетов без copy-through зависимостей. -2. Полный паритетный рендер-тест (эталонные кадры оригинала vs новый рендер) на расширенном наборе моделей/материалов/FX. -3. Полное покрытие соседних геймплейных подсистем (`AI`, `Behavior`, `Missions`, `Control`, `UI`, `Sound`, `Network`) до уровня точных форматов и runtime-контрактов. diff --git a/docs/specs/network.md b/docs/specs/network.md deleted file mode 100644 index 9411c34..0000000 --- a/docs/specs/network.md +++ /dev/null @@ -1,28 +0,0 @@ -# Network system - -`Network` — подсистема синхронизации состояния игры между узлами (мультиплеер/обмен состоянием). - -## 1. Роль - -1. Транспортирует игровые события и state-delta. -2. Синхронизирует критичные объекты мира и таймеры. -3. Обеспечивает согласованность simulation между участниками. - -## 2. Минимальный контракт для 1:1 - -1. Детеминированная сериализация сетевых сообщений. -2. Согласованная обработка порядка/потерь/повторов пакетов. -3. Единая политика authority и коррекции расхождений. - -## 3. Статус покрытия и что осталось до 100% - -Закрыто: - -- определено место сетевого слоя в общей архитектуре движка. - -Осталось: - -1. Полная спецификация wire-протокола (header, message types, payload layout). -2. Полный контракт handshake/session lifecycle. -3. Формальные правила resync/rollback/correction. -4. Набор сетевых parity-тестов на контролируемой потере/задержке. diff --git a/docs/specs/nres.md b/docs/specs/nres.md deleted file mode 100644 index 150b38b..0000000 --- a/docs/specs/nres.md +++ /dev/null @@ -1,200 +0,0 @@ -# NRes - -`NRes` — базовый контейнер ресурсов движка Parkan: Iron Strategy. -Страница фиксирует формат на диске и runtime-контракт чтения/поиска/сохранения в высокоуровневом виде, без привязки к внутренним адресам и именам из дизассемблера. - -Связанная страница: - -- [RsLi](rsli.md) - -## 1. Назначение - -`NRes` используется как универсальный архив: - -- 3D-модели (`*.msh`, `*.rlb`); -- текстуры (`Texm`); -- материалы (`MAT0`); -- эффекты (`FXID`); -- миссионные и служебные ресурсы. - -Формат поддерживает: - -- чтение; -- поиск по имени; -- редактирование (add/replace/remove); -- полную пересборку архива. - -## 2. Общий layout файла - -```text -[Header: 16] -[Data region: variable, 8-byte aligned chunks] -[Directory: entry_count * 64, всегда в конце файла] -``` - -Критично: каталог всегда расположен в конце файла. - -## 3. Заголовок (16 байт) - -Все значения little-endian. - -| Offset | Size | Type | Значение | -|---:|---:|---|---| -| 0 | 4 | char[4] | `NRes` | -| 4 | 4 | u32 | `0x00000100` (версия 1.0) | -| 8 | 4 | i32 | `entry_count` (должен быть `>= 0`) | -| 12 | 4 | u32 | `total_size` (должен быть равен фактическому размеру файла) | - -Производные значения: - -- `directory_size = entry_count * 64`; -- `directory_offset = total_size - directory_size`. - -Ограничения: - -- `directory_offset >= 16`; -- `directory_offset + directory_size == total_size`. - -## 4. Запись каталога (64 байта) - -| Offset | Size | Type | Поле | -|---:|---:|---|---| -| 0 | 4 | u32 | `type_id` | -| 4 | 4 | u32 | `attr1` | -| 8 | 4 | u32 | `attr2` | -| 12 | 4 | u32 | `size` (размер payload) | -| 16 | 4 | u32 | `attr3` | -| 20 | 36 | char[36] | `name_raw` (C-строка) | -| 56 | 4 | u32 | `data_offset` | -| 60 | 4 | u32 | `sort_index` | - -### 4.1. Имя ресурса (`name_raw`) - -Контракт: - -- максимум 35 полезных байт + NUL; -- допускается ровно один терминатор внутри 36-байтового поля; -- имя сравнивается регистронезависимо по ASCII-правилу (`A..Z` -> `a..z`). - -Для writer/editor: - -- запрещено писать NUL внутри полезной части имени; -- запрещены имена длиной > 35 байт. - -### 4.2. Диапазон данных (`data_offset`, `size`) - -Для каждой записи: - -- `data_offset >= 16`; -- `data_offset + size <= directory_offset`. - -Практически (канонический writer): каждый payload начинается с 8-байтного выравнивания. - -## 5. Таблица сортировки (`sort_index`) - -`sort_index` задает перестановку «отсортированный список -> исходный индекс записи». - -Пусть: - -- `entries[i]` — i-я запись каталога в исходном порядке; -- `P` — массив индексов `0..entry_count-1`, отсортированный по `entries[idx].name` (ASCII case-insensitive). - -Тогда в канонической записи: - -- `entries[i].sort_index = P[i]`. - -Это именно таблица для бинарного поиска по имени, а не «ранг текущей записи». - -## 6. Поиск по имени - -Алгоритм поиска: - -1. Выполнить бинарный поиск по диапазону `i in [0, entry_count)`. -2. На шаге `i` взять `target = entries[i].sort_index`. -3. Сравнить искомое имя с `entries[target].name` (ASCII case-insensitive). -4. При совпадении вернуть `target`. - -Fail-safe поведение: - -- если `sort_index` некорректен (выход за диапазон), реализация должна перейти на линейный fallback по всем записям; -- fallback использует то же ASCII case-insensitive сравнение. - -## 7. Каноническая пересборка архива - -Канонический writer выполняет: - -1. Пишет заглушку заголовка (16 байт). -2. Пишет payload всех записей в текущем порядке. -3. После каждого payload добавляет 0-padding до кратности 8. -4. Пересчитывает `sort_index` через сортировку имен. -5. Дописывает каталог (`entry_count * 64`). -6. Пересчитывает и записывает `total_size`. - -Итоговый файл должен удовлетворять всем ограничениям из разделов 3–5. - -## 8. Режим `raw` (совместимость инструментов) - -Для служебных инструментов допускается `raw_mode`: - -- любой бинарный файл трактуется как один «сырой» ресурс; -- возвращается одна запись (`name = RAW`, `data_offset = 0`, `size = len(file)`). - -Этот режим не является форматом `NRes` на диске, это только режим открытия. - -## 9. Контрольные инварианты - -Минимальный набор проверок при чтении: - -1. `magic == "NRes"`. -2. `version == 0x100`. -3. `entry_count >= 0`. -4. `header.total_size == file_size`. -5. Каталог находится в конце файла. -6. Для каждой записи диапазон данных не пересекает каталог. -7. Имя корректно C-терминировано и не длиннее 35 байт. - -Минимальный набор проверок при записи: - -1. Все имена <= 35 байт и без внутренних NUL. -2. `sort_index` формирует валидную перестановку `0..N-1`. -3. Все паддинги между payload состоят из нулевых байт. -4. `total_size` равен фактической длине выходного файла. - -## 10. Эмпирическая проверка на retail-корпусе - -Валидация на полном наборе `testdata/Parkan - Iron Strategy`: - -- найдено `120` архивов `NRes`; -- roundtrip `unpack -> repack -> byte-compare`: `120/120` совпали побайтно; -- критических расхождений формата не обнаружено. - -Проверено legacy-валидатором архивов. - -## 11. Статус покрытия и что осталось до 100% - -Закрыто: - -- формат заголовка/каталога; -- правила поиска; -- каноническая пересборка; -- строгие инварианты валидатора; -- побайтовый roundtrip на retail-корпусе. - -Осталось до полного 100% архитектурного покрытия движка: - -1. Формальная семантика `attr1/attr2/attr3` для всех типов ресурсов (частично вынесена в профильные страницы `msh`, `material`, `texture`, `fxid`, `terrain`). -2. Полная спецификация поведения при не-ASCII именах (в реальных игровых архивах используется ASCII-практика; для Unicode-коллации движок не документирован). -3. Полная спецификация платформенных гарантий атомарной записи (формат данных закрыт, но OS-уровневые гарантии замены файла зависят от платформы и файловой системы). -## 12. Специализация `objects.rlb` - -Хотя `objects.rlb` формально является обычным `NRes`, его payload имеет отдельный семантический контракт: - -- запись каталога соответствует одному объектному прототипу; -- payload записи - массив фиксированных ссылок `ObjectRef64` (`archive_name[32] + resource_name[32]`); -- runtime-резолв меша выполняется через эти ссылки, а не через имя entry `*.msh` внутри `objects.rlb`. - -Это означает, что `objects.rlb` должен рассматриваться не как архив мешей, а как реестр привязок между mission/unit-ключами и фактическими ресурсами. - -См. детальную страницу: - -- [Object registry (`objects.rlb`)](object-registry.md) diff --git a/docs/specs/object-registry.md b/docs/specs/object-registry.md deleted file mode 100644 index 0e6e2dd..0000000 --- a/docs/specs/object-registry.md +++ /dev/null @@ -1,145 +0,0 @@ -# Object Registry (`objects.rlb`) - -`objects.rlb` - это не архив с готовыми мешами. -Это реестр игровых прототипов, который связывает логический идентификатор объекта (`r_h_01`, `s_tree_04`, `fr_m_brige`, ...) с набором реальных ресурсов в других архивах. - -Документ описывает формат и runtime-контракт на высоком уровне, без привязки к внутренним именам/адресам из дизассемблера. - -Связанные страницы: - -- [Missions](missions.md) -- [NRes](nres.md) -- [MSH core](msh-core.md) -- [Wear (`WEAR`)](wear.md) -- [Material (`MAT0`)](material.md) -- [Render pipeline](render.md) - -## 1. Роль в пайплайне - -При загрузке миссии движок работает так: - -1. Из `data.tma` получает `resource_name` объекта: - - либо прямой ключ (`s_tree_04`); - - либо путь к `*.dat` (например `UNITS\\UNITS\\HERO\\tut1_p.dat`). -2. Для `*.dat` читает заголовок и получает: - - `archive_name` (в retail-корпусе всегда `objects.rlb`); - - `model_key` (например `R_H_02`). -3. В `objects.rlb` по ключу (`model_key`/`resource_name`) ищет запись прототипа. -4. Из записи прототипа резолвит фактический `*.msh` и архив, где лежит геометрия. -5. Дальше запускается стандартная цепочка: - `MSH -> WEAR -> MAT0 -> Texm`. - -## 2. Контейнер - -`objects.rlb` сам является обычным `NRes`-архивом. - -Практические наблюдения на retail-корпусе: - -- формат заголовка/каталога полностью совпадает с `NRes`; -- payload каждой записи прототипа кратен `64` байтам; -- имя entry в каталоге - это логический ключ объекта (например `r_h_01`, `s_tree_04`). - -## 3. Формат payload записи прототипа - -Payload состоит из массива фиксированных записей: - -```c -struct ObjectRef64 { - char archive_name[32]; // C-строка (CP1251/ASCII) - char resource_name[32]; // C-строка (CP1251/ASCII) -} -``` - -Интерпретация: - -- `archive_name`: архив-источник (`bases.rlb`, `static.rlb`, `fortif.rlb`, `effects.rlb`, ...). -- `resource_name`: имя ресурса в этом архиве (`*.msh`, `*.wea`, `*.cpt`, `*.ctl`, `*.bas`, ...). - -Важно: - -- после первого `NUL` в 32-байтовом поле могут встречаться служебные байты; для runtime-резолва используется только C-строка до первого `NUL`; -- неизвестные хвостовые байты должны сохраняться 1:1 при writer/roundtrip-редактировании. - -## 4. Runtime-резолв геометрии - -Канонический порядок выбора меша: - -1. Найти запись прототипа по ключу в `objects.rlb`. -2. Прочитать список `ObjectRef64`. -3. Если есть ссылка на `*.msh`: - - взять первую валидную ссылку; - - открыть указанный архив; - - загрузить этот `*.msh`. -4. Если `*.msh` нет, но есть `*.bas`: - - взять stem от `*.bas` (`fr_m_brige.bas` -> `fr_m_brige`); - - искать `<stem>.msh` в том же архиве (`fortif.rlb`). -5. Если нет ни `*.msh`, ни `*.bas`, объект трактуется как не-геометрический (пример: солнечный/системный объект) и в 3D-проход не попадает. - -## 5. Типовые примеры - -`r_h_01`: - -- `bases.rlb :: r_h_01.msh` -- `bases.rlb :: r_h_01.wea` -- `bases.rlb :: r_h_01.cpt` -- ... - -`s_tree_04`: - -- `static.rlb :: s_tree_0_04.msh` -- `static.rlb :: s_tree_0_04.wea` -- ... - -`fr_m_brige`: - -- прямого `*.msh` в записи нет; -- есть `fortif.rlb :: fr_m_brige.bas`; -- меш резолвится как `fortif.rlb :: fr_m_brige.msh`. - -`sun_01`: - -- ссылки на `*.sun`/effect-ресурсы; -- 3D-меш отсутствует. - -## 6. Инварианты для reader/writer - -Reader: - -- payload записи прототипа должен быть кратен `64`; -- каждая запись читается как две независимые C-строки фиксированной длины; -- поиск в архивах должен быть case-insensitive по ASCII. - -Writer/editor: - -- сохранять порядок `ObjectRef64` без перестановок; -- сохранять неизвестные служебные байты полей 1:1; -- не нормализовать имена, если это не требуется задачей. - -## 7. Валидация - -Проверено на retail-корпусе `testdata/Parkan - Iron Strategy`: - -- все `590` записей `objects.rlb` имеют payload, кратный `64`; -- `554` записей имеют прямую ссылку на `*.msh`; -- `34` записи используют ветку через `*.bas`; -- `2` записи не содержат геометрии (системные/sun). - -Интеграционные тесты в Rust подтверждают резолв: - -- `r_h_01 -> bases.rlb :: r_h_01.msh` -- `s_tree_04 -> static.rlb :: s_tree_0_04.msh` -- `fr_m_brige -> fortif.rlb :: fr_m_brige.msh` - -## 8. Статус покрытия и что осталось до 100% - -Закрыто: - -1. Формат payload записи прототипа (`ObjectRef64`) и правила чтения. -2. Runtime-алгоритм выбора меша (`*.msh` напрямую и fallback через `*.bas`). -3. Корпусная проверка структуры и интеграционные тесты резолва. - -Осталось: - -1. Полная field-level семантика служебных байтов после `NUL` в `resource_name[32]`. -2. Формальная семантика всех категорий ссылок (`*.ctl`, `*.cpt`, `*.ndp`, `*.sun`) в терминах систем движка (не только render-пути). -3. Writer-спецификация уровня "authoring new prototype from scratch" с гарантией runtime-паритета. diff --git a/docs/specs/render-parity.md b/docs/specs/render-parity.md deleted file mode 100644 index 8955414..0000000 --- a/docs/specs/render-parity.md +++ /dev/null @@ -1,90 +0,0 @@ -# Рендер-паритет (кадровый diff) - -Документ описывает процесс проверки соответствия рендера: -`оригинальный движок -> эталонный кадр -> render-demo -> diff-метрики`. - -## Цель - -- Зафиксировать объективный критерий "паритет достигнут / не достигнут". -- Убрать субъективную визуальную оценку "похоже/не похоже". -- Дать CI-проверку, которая ловит регрессии сразу после коммита. - -## Единица проверки - -Один тест-кейс = один объект (одна модель) + фиксированная конфигурация: - -- архив ресурса; -- имя модели; -- `lod`; -- `group`; -- размер кадра (`width`, `height`); -- угол камеры (`angle`); -- PNG-эталон из оригинального рендера. - -## Инварианты детерминизма - -Для корректного сравнения кадры должны быть сняты в одинаковых условиях: - -- одинаковый FOV и расстояние камеры до объекта; -- одинаковый clear-color/фон; -- одинаковые `lod/group`; -- фиксированный угол (`angle`), без анимации; -- фиксированное разрешение. - -## Метрики сравнения - -Сравнение выполняется по RGB-каналам: - -- `mean_abs`: средняя абсолютная разница канала (0..255); -- `max_abs`: максимальная разница канала; -- `changed_ratio`: доля пикселей, где хотя бы один канал превышает `diff_threshold`. - -Кейс считается пройденным, если: - -- `mean_abs <= max_mean_abs`; -- `changed_ratio <= max_changed_ratio`. - -## Конфигурация кейсов - -Файл: `parity/cases.toml`. - -- секция `[meta]`: глобальные дефолты; -- `[[case]]`: параметры конкретной модели и путь к эталонному PNG. - -Эталонные кадры хранятся в `parity/reference/`. - -## Локальный запуск - -```bash -cargo run -p render-parity -- \ - --manifest parity/cases.toml \ - --output-dir target/render-parity/current -``` - -При расхождении утилита пишет diff-изображение в: - -- `target/render-parity/current/diff/<case>.png` - -## CI-модель - -CI запускает `render-parity` на каждом push/PR: - -1. собирает `parkan-render-demo`; -2. прогоняет кейсы из `cases.toml`; -3. при падении публикует текущие кадры и diff как артефакт. - -Важно: оригинальный движок в CI обычно не запускается. -Эталонные PNG снимаются офлайн и версионируются в репозитории. - -## Статус покрытия и что осталось до 100% - -Закрыто: - -1. Определена метрика сравнения кадров (`mean_abs`, `max_abs`, `changed_ratio`). -2. Описан единый manifest-формат кейсов и CI-процедура. - -Осталось: - -1. Снять и зафиксировать расширенный эталонный набор кадров оригинала (10-20+ ключевых моделей и режимов). -2. Зафиксировать пороговые критерии pass/fail по каждому классу сцен (статик, анимация, FX, lightmap). -3. Добавить автоматическую публикацию diff-артефактов и регрессионных отчетов в CI. diff --git a/docs/specs/render.md b/docs/specs/render.md deleted file mode 100644 index f1d098f..0000000 --- a/docs/specs/render.md +++ /dev/null @@ -1,182 +0,0 @@ -# Render pipeline - -Документ описывает полный процесс рендера кадра в движке Parkan: Iron Strategy, без привязки к внутренним адресам/именам дизассемблера. - -Связанные страницы: - -- [MSH core](msh-core.md) -- [MSH animation](msh-animation.md) -- [Material (`MAT0`)](material.md) -- [Wear table (`WEAR`)](wear.md) -- [Texture (`Texm`)](texture.md) -- [FXID](fxid.md) - -## 1. Инициализация рендера - -На старте движок: - -1. Выбирает видеодрайвер (software или аппаратный). -2. Создаёт render backend. -3. Подключает библиотеки ресурсов: - - `Material.lib` - - `Textures.lib` - - `LightMap.lib` - - `palettes.lib` -4. Инициализирует менеджеры: - - material manager - - texture/lightmap cache - - effect manager -5. Загружает базовые world-ресурсы (включая наборы объектов сцены). - -## 2. Структура кадра - -Кадр выполняется как последовательность: - -1. `Simulation update` -2. `Animation sampling` -3. `Visibility / culling` -4. `Material + texture resolve` -5. `Mesh draw` -6. `FX update + draw` -7. `UI/overlay draw` -8. `Present` - -## 3. Geometry path - -### 3.1. Подготовка инстансов - -Для каждого видимого объекта: - -1. Вычисляется `world transform`. -2. Выбирается `LOD`. -3. Для каждого узла выбирается slot через `Res1`. - -### 3.2. Culling - -Сначала отсекаются узлы/слоты по bounds (`AABB/sphere`) из `Res2`. - -### 3.3. Батчи - -Для каждого прошедшего slot: - -1. Берутся батчи из диапазона `Res13`. -2. По `materialIndex` выбирается активный материал. -3. По фазе материала выбирается текстура/lightmap. -4. Выполняется `DrawIndexedPrimitive`: - - индексный диапазон: `indexStart/indexCount` - - базовая вершина: `baseVertex` - - индексы читаются из `Res6` - - вершины/атрибуты читаются из `Res3/Res4/Res5` (+ optional streams) - -## 4. Animation path - -Для анимированных моделей: - -1. Для узла выбирается ключ через `Res19` и fallback-логику. -2. Декодируются `pos + quat` из `Res8`. -3. При необходимости выполняется blending двух сэмплов. -4. Узловая матрица передаётся в geometry path. - -## 5. Material path - -Material pipeline на кадре: - -1. По material handle выбирается запись `MAT0`. -2. По игровому времени выбирается текущая фаза. -3. Применяются коэффициенты фазы (цвет/альфа/параметры). -4. Резолвятся ссылки на texture/lightmap. -5. Невалидные ссылки обрабатываются fallback-стратегией. - -Практическая цепочка привязки для большинства `*.msh` ассетов из `*.rlb`: - -1. Для модели выбирается одноимённый `WEAR` (`<model_stem>.wea`). -2. Из `WEAR` берётся material-слот (по имени, `legacyId` не участвует в выборе). -3. В `Material.lib` ищется `MAT0` по имени (`DEFAULT`, затем индекс `0` как fallback). -4. Из выбранной material-фазы берётся `textureName`. -5. `Texm` ищется в `Textures.lib` (и/или lightmap-архиве для lightmap-ветки). - -## 6. Texture path - -При резолве текстуры: - -1. Ищется `Texm` entry по имени. -2. Проверяется и декодируется заголовок. -3. При необходимости применяется `mipSkip`. -4. Для indexed-формата подключается палитра. -5. Optional `Page` chunk интерпретируется как atlas-таблица. -6. Объект текстуры кладётся/берётся из cache. - -## 7. FX path - -Эффекты выполняются параллельно mesh-рендеру: - -1. Для активных инстансов FX вычисляется runtime-коэффициент (`time_mode + flags`). -2. Команды FX обновляют внутреннее состояние. -3. Команды emit-этапа формируют примитивы/батчи эффектов. -4. Эффекты рисуются в 3D-кадре с собственным счётчиком батчей. - -## 8. Псевдокод кадра - -```c -void RenderFrame(Scene* scene, Camera* cam, float dt) { - UpdateGame(scene, dt); - - for (Object* obj : scene->objects) { - if (!obj->visible) continue; - - UpdateObjectAnimation(obj, scene->time); - BuildObjectNodeTransforms(obj); - } - - BeginFrame(cam); - - for (Object* obj : scene->objects) { - if (!obj->visible) continue; - RenderObjectMeshes(obj, cam); - } - - UpdateAndRenderFx(scene, dt, cam); - RenderUI(scene); - Present(); -} -``` - -## 9. Критичные условия для 1:1 - -1. Та же политика округления/FP для анимации и FX. -2. Та же логика fallback по материалам и текстурам. -3. Та же очередность стадий кадра. -4. Тот же контракт интерпретации `Res1/Res2/Res13/Res6`. -5. Тот же контракт `FXID` командного потока. - -## 10. Статус валидации - -- Порядок кадра и подключение `Material.lib / Textures.lib / LightMap.lib` подтверждены текущей runtime-валидацией проекта. -- Детальные инварианты форматов зафиксированы в спецификациях проекта и проверены legacy-валидаторами. - -## 11. Статус покрытия и что осталось до 100% - -Закрыто: - -1. Высокоуровневый кадр: simulation -> animation -> culling -> material/texture resolve -> mesh draw -> fx -> ui -> present. -2. Связка MSH/MAT0/WEAR/Texm/FXID в едином runtime-процессе. -3. Форматная валидация входных данных на полном retail-корпусе. - -Осталось: - -1. Полный pixel-parity контур с эталонными кадрами оригинального рендера по набору моделей/сцен. -2. Формализация всех render-state деталей (точные blend/depth/cull/state transitions) для гарантии 1:1 в каждом draw-pass. -3. Полный coverage-пакет по динамическим веткам (FX-heavy кадры, сложные material-режимы, lightmap-комбинации). - -## 12. Object registry bridge (`objects.rlb`) - -Для миссионного/юнитного рендера критично учитывать промежуточный слой прототипов: - -1. `TMA`/`*.dat` обычно дают не прямой `*.msh`, а ключ прототипа. -2. Ключ резолвится через `objects.rlb` (реестр ссылок на реальные архивы ресурсов). -3. Только после этого выполняется стандартный путь: - `MSH -> WEAR -> MAT0 -> Texm`. - -Детальная спецификация этого шага вынесена в отдельную страницу: - -- [Object registry (`objects.rlb`)](object-registry.md) diff --git a/docs/specs/rsli.md b/docs/specs/rsli.md deleted file mode 100644 index 239b3ff..0000000 --- a/docs/specs/rsli.md +++ /dev/null @@ -1,227 +0,0 @@ -# RsLi - -`RsLi` — библиотечный контейнер ресурсов движка Parkan: Iron Strategy с зашифрованной таблицей записей и несколькими методами упаковки данных. - -Страница описывает формат и runtime-контракт в высокоуровневом виде, без ссылок на внутренние адреса/функции дизассемблера. - -Связанная страница: - -- [NRes](nres.md) - -## 1. Общая структура файла - -```text -[Header: 32] -[Entry table: entry_count * 32, XOR-encrypted] -[Packed payloads] -[Optional trailer: "AO" + overlay:u32] -``` - -В отличие от `NRes`, таблица записей у `RsLi` расположена в начале файла. - -## 2. Заголовок (32 байта) - -Все значения little-endian. - -| Offset | Size | Type | Поле | -|---:|---:|---|---| -| 0 | 2 | char[2] | `NL` (магия) | -| 2 | 1 | u8 | зарезервировано, в retail = `0` | -| 3 | 1 | u8 | версия, в retail = `1` | -| 4 | 2 | i16 | `entry_count` (должен быть `>= 0`) | -| 14 | 2 | u16 | `presorted_flag` (`0xABBA` = таблица сортировки уже задана) | -| 20 | 4 | u32 | `xor_seed` | - -Остальные байты заголовка считаются служебными и должны сохраняться без нормализации. - -## 3. Таблица записей (после дешифровки) - -Таблица начинается с `offset = 32`, размер `entry_count * 32`. - -Каждая запись (32 байта): - -| Offset | Size | Type | Поле | -|---:|---:|---|---| -| 0 | 12 | char[12] | `name_raw` (обычно uppercase ASCII, NUL optional) | -| 12 | 4 | bytes | служебный хвост, сохранять как есть | -| 16 | 2 | i16 | `flags` | -| 18 | 2 | i16 | `sort_to_original` | -| 20 | 4 | u32 | `unpacked_size` | -| 24 | 4 | u32 | `data_offset_raw` | -| 28 | 4 | u32 | `packed_size` | - -### 3.1. Метод упаковки - -`method = flags & 0x1E0` - -Поддерживаемые значения: - -| Маска | Метод | -|---:|---| -| `0x000` | без сжатия | -| `0x020` | XOR only | -| `0x040` | LZSS | -| `0x060` | XOR + LZSS | -| `0x080` | LZSS + адаптивный Huffman | -| `0x0A0` | XOR + LZSS + адаптивный Huffman | -| `0x100` | raw Deflate (RFC1951) | - -Другие значения считаются неподдерживаемыми. - -## 4. XOR-дешифрование таблицы и данных - -Для таблицы и XOR-методов payload используется один и тот же потоковый XOR-алгоритм. - -Ключ: - -- `key16 = xor_seed & 0xFFFF` (используются только младшие 16 бит seed). - -Состояние: - -```text -lo = key16 & 0xFF -hi = key16 >> 8 -``` - -Для каждого байта: - -```text -lo = hi XOR ((lo << 1) mod 256) -out = in XOR lo -hi = lo XOR (hi >> 1) -``` - -## 5. `sort_to_original` и поиск по имени - -### 5.1. Режим `presorted_flag == 0xABBA` - -`sort_to_original` обязан быть перестановкой `0..entry_count-1` без дубликатов. - -### 5.2. Режим без presorted-флага - -Слой загрузки строит `sort_to_original` самостоятельно: - -- сортирует индексы по `strcmp`-порядку имен (байтовое сравнение); -- записывает эту перестановку в lookup-таблицу. - -### 5.3. Поиск - -Поиск выполняется бинарным поиском по lookup-таблице: - -1. запрос переводится в uppercase ASCII; -2. на шаге бинарного поиска используется индекс `sort_to_original[mid]`; -3. сравнение имен — bytewise (`strcmp`-логика). - -Fail-safe: - -- при невалидном индексе lookup-таблицы выполняется линейный fallback. - -## 6. AO-трейлер и media overlay - -Опциональный трейлер в конце файла: - -```text -"AO" + overlay:u32 -``` - -Если трейлер присутствует: - -- эффективный offset payload: `effective_offset = data_offset_raw + overlay`. - -Ограничение: - -- `overlay <= file_size`. - -## 7. Декодирование payload по методам - -## 7.1. Без сжатия (`0x000`) - -Берутся первые `unpacked_size` байт из packed-диапазона. - -## 7.2. XOR only (`0x020`) - -XOR-дешифрование первых `unpacked_size` байт. - -## 7.3. LZSS (`0x040`, `0x060`) - -Параметры: - -- ring buffer: `4096` байт; -- начальное заполнение ring: `0x20`; -- стартовый указатель ring: `0xFEE`; -- control-биты читаются LSB-first. - -Правила: - -- `bit=1`: literal byte; -- `bit=0`: ссылка из 2 байт - `offset = low | ((high & 0xF0) << 4)` - `length = (high & 0x0F) + 3`. - -Для `0x060` XOR применяется на лету к packed-потоку до LZSS-декодирования. - -## 7.4. LZSS + адаптивный Huffman (`0x080`, `0x0A0`) - -Параметры: - -- `N=4096`, `F=60`, `THRESHOLD=2`; -- адаптивное дерево Huffman обновляется по мере декодирования. - -Для `0x0A0` XOR применяется на лету к битовому потоку до Huffman/LZSS-декодирования. - -## 7.5. Deflate (`0x100`) - -Используется raw Deflate-поток (RFC1951). - -Важно: - -- zlib-обертка (`RFC1950`) не принимается. - -## 8. Quirk: Deflate EOF+1 - -На retail-корпусе встречается один подтвержденный случай, где: - -- `effective_offset + packed_size == file_size + 1`. - -Совместимое поведение: - -- для метода `0x100` допустить чтение `packed_size - 1` байт (если включен режим совместимости); -- в строгом режиме считать это ошибкой. - -## 9. Контрольные инварианты - -Минимальные проверки: - -1. `magic == "NL"`, `reserved == 0`, `version == 1`. -2. `entry_count >= 0`. -3. `table_end <= file_size`. -4. Если `presorted_flag == 0xABBA`, `sort_to_original` — валидная перестановка. -5. `effective_offset + packed_size` не выходит за EOF (кроме разрешенного deflate EOF+1 quirk). -6. Итоговый распакованный размер равен `unpacked_size`. - -## 10. Эмпирическая проверка на retail-корпусе - -Проверка на полном наборе `testdata/Parkan - Iron Strategy`: - -- обнаружено `2` архива `RsLi`; -- roundtrip `unpack -> repack -> byte-compare`: `2/2` совпали побайтно; -- подтвержден ровно один `deflate EOF+1` случай (`sprites.lib`, entry `23`). - -Проверено legacy-валидатором архивов и тестами `crates/rsli`. - -## 11. Статус покрытия и что осталось до 100% - -Закрыто: - -- формат заголовка/таблицы; -- XOR-алгоритм; -- все используемые методы декодирования; -- AO overlay; -- lookup-поиск и fallback; -- retail-валидация и побайтовый roundtrip. - -Осталось до полного 100% архитектурного покрытия движка: - -1. Полная функциональная семантика битов `flags` вне маски метода (`0x1E0`) для геймплейных подсистем. -2. Канонический writer для авторинга новых архивов со стабильной стратегией выбора методов (`0x080/0x0A0/0x100`) и параметров компрессии. -3. Формализация поведения для не-ASCII имен (на практике архивы используют ASCII-диапазон). diff --git a/docs/specs/runtime-pipeline.md b/docs/specs/runtime-pipeline.md deleted file mode 100644 index fb8af06..0000000 --- a/docs/specs/runtime-pipeline.md +++ /dev/null @@ -1,18 +0,0 @@ -# Runtime pipeline - -Актуальный документ по полному кадру находится здесь: - -- [Render pipeline](render.md) - -Эта страница оставлена как совместимый указатель для старых ссылок. - -## Статус покрытия и что осталось до 100% - -Закрыто: - -1. Актуальный runtime-пайплайн централизован в `render.md`. - -Осталось: - -1. Поддерживать обратную совместимость ссылок при дальнейшей декомпозиции render-документа. - diff --git a/docs/specs/sound.md b/docs/specs/sound.md deleted file mode 100644 index 360f590..0000000 --- a/docs/specs/sound.md +++ /dev/null @@ -1,32 +0,0 @@ -# Sound system - -`Sound` — подсистема аудио: - -- загрузка и кеширование звуковых ресурсов; -- воспроизведение SFX/voice/music; -- пространственное позиционирование и микширование. - -## 1. Архитектурная роль - -1. Получает события от gameplay/FX/mission/UI. -2. Резолвит аудиоресурсы через архивные библиотеки. -3. Управляет каналами, приоритетами и жизненным циклом источников звука. - -## 2. Минимальный runtime-контракт - -1. Стабильный выбор источника и fallback при отсутствии ресурса. -2. Детерминированные правила приоритета при переполнении каналов. -3. Согласованная модель пространственного затухания и панорамирования. - -## 3. Статус покрытия и что осталось до 100% - -Закрыто: - -- место аудио-подсистемы в общем runtime-контуре. - -Осталось: - -1. Полная спецификация форматов аудио-ресурсов и lookup-таблиц. -2. Полный контракт 2D/3D микширования и лимитов каналов. -3. Правила взаимодействия с FXID-командами, которые инициируют звук. -4. Набор audio parity-тестов (тайминг/громкость/панорама). diff --git a/docs/specs/terrain-map-loading.md b/docs/specs/terrain-map-loading.md deleted file mode 100644 index a511799..0000000 --- a/docs/specs/terrain-map-loading.md +++ /dev/null @@ -1,291 +0,0 @@ -# Terrain + ArealMap - -Документ описывает подсистему ландшафта и ареалов мира в движке Parkan: Iron Strategy: - -- `Land.msh` (terrain-геометрия и вспомогательные таблицы); -- `Land.map` (ареалы и навигационные связи); -- `BuildDat.lst` (категории объектных зон). - -Описание дано в высокоуровневом переносимом виде, без ссылок на внутренние адреса и имена из дизассемблера. - -Связанные страницы: - -- [NRes](nres.md) -- [RsLi](rsli.md) -- [MSH core](msh-core.md) -- [Render pipeline](render.md) - -## 1. End-to-End загрузка уровня - -Для каждой карты движок загружает пару файлов: - -- `.../Land.msh` -- `.../Land.map` - -Высокоуровневый порядок: - -1. Открыть `Land.msh` как `NRes`. -2. Прочитать обязательные terrain-chunk'и. -3. Построить runtime-структуры terrain (slots, faces, spatial grid). -4. Открыть `Land.map` как `NRes`. -5. Найти единственный chunk `type=12`. -6. Прочитать ареалы, их связи и cell-grid. -7. Применить инициализацию объектных категорий из `BuildDat.lst`. - -## 2. Формат `Land.msh` - -`Land.msh` — обычный `NRes` архив с фиксированным набором terrain-ресурсов. - -## 2.1. Состав chunk'ов - -Обязательные типы: - -- `1`, `2`, `3`, `4`, `5`, `11`, `18`, `21` - -Опциональные типы: - -- `14` - -Наблюдаемый retail-порядок chunk'ов: - -```text -[1, 2, 3, 4, 5, 18, 14, 11, 21] -``` - -## 2.2. Stride и атрибуты - -| Type | Назначение | Stride | -|---:|---|---:| -| 1 | node/slot матрица | 38 | -| 3 | позиции вершин | 12 | -| 4 | нормали (packed) | 4 | -| 5 | UV (packed) | 4 | -| 11 | cell-ускоритель | 4 | -| 14 | доп. поток | 4 | -| 18 | доп. поток | 4 | -| 21 | terrain face | 28 | - -Общее правило для этих chunk'ов: - -- `attr1 == size / stride` -- `attr3 == stride` - -## 2.3. Type `2`: slot table - -`type=2` содержит: - -- заголовок `0x8C` байт; -- затем таблицу slots по `68` байт. - -Инварианты: - -- `size >= 0x8C` -- `(size - 0x8C) % 68 == 0` -- `attr1 == (size - 0x8C) / 68` -- `attr3 == 68` - -## 2.4. Type `21`: terrain face (28 байт) - -Высокоуровневая структура face: - -- флаги face; -- индексы треугольника (`i0, i1, i2`); -- индексы соседей (`n0, n1, n2`, значение `0xFFFF` = нет соседа); -- служебные поля (материал/класс/edge-поля и др.). - -Критичные проверки: - -- `i0/i1/i2 < vertex_count` (`type=3`); -- `nX == 0xFFFF` или `nX < face_count`. - -## 2.5. Маски face и compact-представления - -В рантайме используются: - -- полная 32-битная маска (`full`); -- компактные представления (`compactMain16`, `compactMaterial6`). - -Подтвержденный remap `full -> compactMain16`: - -| Full bit | Compact bit | -|---:|---:| -| `0x00000001` | `0x0001` | -| `0x00000008` | `0x0002` | -| `0x00000010` | `0x0004` | -| `0x00000020` | `0x0008` | -| `0x00001000` | `0x0010` | -| `0x00004000` | `0x0020` | -| `0x00000002` | `0x0040` | -| `0x00000400` | `0x0080` | -| `0x00000800` | `0x0100` | -| `0x00020000` | `0x0200` | -| `0x00002000` | `0x0400` | -| `0x00000200` | `0x0800` | -| `0x00000004` | `0x1000` | -| `0x00000040` | `0x2000` | -| `0x00200000` | `0x8000` | - -Подтвержденный remap `full -> compactMaterial6`: - -| Full bit | Compact bit | -|---:|---:| -| `0x00000100` | `0x01` | -| `0x00008000` | `0x02` | -| `0x00010000` | `0x04` | -| `0x00040000` | `0x08` | -| `0x00080000` | `0x10` | -| `0x00000080` | `0x20` | - -Для 1:1 реализации нужно поддерживать оба представления и обратное восстановление `compact -> full`. - -## 2.6. Type `11` и cell-ускоритель terrain - -`type=11` служит источником cell-ускорителя для terrain-запросов. - -Практические требования для editor/toolchain: - -- не переупорядочивать содержимое без полного пересчета зависимых таблиц; -- сохранять служебные/неизвестные поля побайтно; -- выполнять валидацию диапазонов face/slot после любых правок. - -## 3. Формат `Land.map` (chunk `type=12`) - -`Land.map` — `NRes`, содержащий ровно один ресурс `type=12`. - -Контракт верхнего уровня: - -- `entry.attr1` = `areal_count`; -- payload включает: - - `areal_count` переменных записей ареалов; - - затем grid-секцию cell-попаданий. - -## 3.1. Запись ареала - -Старт записи: - -```c -float anchor_x; // +0 -float anchor_y; // +4 -float anchor_z; // +8 -float reserved_12; // +12 -float area_metric; // +16 -float normal_x; // +20 -float normal_y; // +24 -float normal_z; // +28 -uint32_t logic_flag; // +32 -uint32_t reserved_36; // +36 -uint32_t class_id; // +40 -uint32_t reserved_44; // +44 -uint32_t vertex_count; // +48 -uint32_t poly_count; // +52 -``` - -Далее: - -1. `float3 vertices[vertex_count]` -2. `EdgeLink8 links[vertex_count + 3 * poly_count]`, где - `EdgeLink8 = { int32 area_ref; int32 edge_ref; }` -3. для каждого полигона block: - - `uint32 n` - - `4 * (3*n + 1)` байт данных полигона - -## 3.2. Семантика edge-link - -Для `links[0 .. vertex_count-1]`: - -- `(-1, -1)` означает «соседа нет»; -- иначе `area_ref` указывает на индекс соседнего ареала, `edge_ref` — на ребро в соседнем ареале. - -## 3.3. Grid-секция после ареалов - -Формат: - -```c -uint32 cellsX; -uint32 cellsY; -for (x=0; x<cellsX; x++) { - for (y=0; y<cellsY; y++) { - uint16 hitCount; - uint16 areaIds[hitCount]; - } -} -``` - -В runtime существует упакованное cell-meta представление: - -- high 10 бит: `hitCount`; -- low 22 бита: `startIndex` (в общем `areaIds` пуле). - -## 3.4. Валидация целостности chunk 12 - -Обязательные проверки: - -- `areal_count > 0`; -- `cellsX > 0 && cellsY > 0`; -- каждый `area_id` из cell-списков `< areal_count`; -- все `area_ref/edge_ref` валидны относительно целевых ареалов; -- полный объем прочитанных байт должен точно совпасть с размером payload. - -## 4. `BuildDat.lst` - -Используются 12 объектных категорий ареалов: - -| Имя | Маска | -|---|---:| -| `Bunker_Small` | `0x80010000` | -| `Bunker_Medium` | `0x80020000` | -| `Bunker_Large` | `0x80040000` | -| `Generator` | `0x80000002` | -| `Mine` | `0x80000004` | -| `Storage` | `0x80000008` | -| `Plant` | `0x80000010` | -| `Hangar` | `0x80000040` | -| `MainTeleport` | `0x80000200` | -| `Institute` | `0x80000400` | -| `Tower_Medium` | `0x80100000` | -| `Tower_Large` | `0x80200000` | - -Файл должен парситься строго секционно; поврежденный формат считается ошибкой. - -## 5. Требования к reader/writer/editor - -1. Сохранять порядок и бинарную форму chunk'ов, если не выполняется осознанная нормализация. -2. Все неизвестные поля хранить и писать побайтно (`preserve-as-is`). -3. После правок пересчитывать только вычислимые поля, не «чистить» opaque-данные. -4. Проверять диапазоны индексов между связанными таблицами (`nodes/slots/faces/vertices/areas/cells`). -5. Для неизмененных ресурсов обеспечивать byte-identical roundtrip. - -## 6. Эмпирическая верификация (retail) - -Валидация на `testdata/Parkan - Iron Strategy`: - -- карт: `33` -- `Land.msh`: `33/33` валидны -- `Land.map`: `33/33` валидны -- `issues_total = 0`, `errors_total = 0`, `warnings_total = 0` - -Подтвержденные наблюдения: - -- `Land.msh` порядок chunk'ов стабилен: `[1,2,3,4,5,18,14,11,21]`; -- `Land.map` всегда содержит один chunk `type=12`; -- `cellsX == cellsY == 128` во всех retail-картах; -- `poly_count == 0` во всем проверенном retail-корпусе; -- `normal` имеет длину ~1.0; -- `reserved_12`, `reserved_36`, `reserved_44` в retail наблюдаются как `0`. - -Проверено legacy-валидатором terrain/map форматов. - -## 7. Статус покрытия и что осталось до 100% - -Закрыто: - -- бинарный контракт `Land.msh` и `Land.map`; -- диапазонные и структурные инварианты; -- remap масок `full/compact`; -- валидация на полном retail-корпусе карт. - -Осталось до полного 100% архитектурного покрытия движка: - -1. Полная доменная семантика `class_id` и `logic_flag` (игровые значения/поведенческие правила). -2. Полная спецификация ветки `poly_count > 0` на живых данных (в retail не встречена). -3. Полная field-level семантика части битов `TerrainFace28.flags` (бинарный контракт и remap закрыты, но не все биты имеют документированные геймплейные имена). diff --git a/docs/specs/texture.md b/docs/specs/texture.md deleted file mode 100644 index 81ef3b3..0000000 --- a/docs/specs/texture.md +++ /dev/null @@ -1,153 +0,0 @@ -# Texture (`Texm`) - -`Texm` — основной формат текстур движка. - -Связанные страницы: - -- [Material (`MAT0`)](material.md) -- [Wear table (`WEAR`)](wear.md) -- [Render pipeline](render.md) - -## 1. Контейнер - -- Тип ресурса: `0x6D786554` (`Texm`). -- Используется в `Textures.lib`, `LightMap.lib` и других `NRes` архивах. - -## 2. Заголовок - -```c -struct TexmHeader32 { - uint32_t magic; // 'Texm' - uint32_t width; - uint32_t height; - uint32_t mipCount; - uint32_t flags4; - uint32_t flags5; - uint32_t unk6; - uint32_t format; -}; -``` - -## 3. Поддерживаемые форматы - -Базовые форматы: - -- `0` (8-bit indexed + palette) -- `565` -- `4444` -- `888` -- `8888` - -Дополнительные ветки загрузки поддерживают также `556` и `88`. - -## 4. Layout payload - -1. `TexmHeader32` (32 байта) -2. palette `1024` байта, если `format == 0` -3. mip-chain пикселей -4. optional `Page` chunk - -Расчёт ядра: - -```c -bytesPerPixel = - (format == 0) ? 1 : - (format == 565 || format == 556 || format == 4444 || format == 88) ? 2 : - 4; - -pixelCount = sum(max(1, width>>i) * max(1, height>>i), i=0..mipCount-1); -sizeCore = 32 + (format==0 ? 1024 : 0) + bytesPerPixel * pixelCount; -``` - -## 4.1. Декодирование в RGBA8 (runtime/инструменты) - -Для CPU-пути (preview, валидация, оффлайн-конвертация) используется декодирование: - -- `0` (`Indexed8`): `index -> palette[index]` (`RGBA` из палитры 256×4). -- `565`: `R5 G6 B5`, `A=255`. -- `556`: `R5 G5 B6`, `A=255`. -- `4444`: `A4 R4 G4 B4` (с расширением 4-битных каналов в 8-битные). -- `88`: `L8 A8` (`R=G=B=L`). -- `888`: `R8 G8 B8` + padding/служебный байт, `A=255`. -- `8888`: `A8 R8 G8 B8`. - -Это декодирование соответствует текущему test/demo pipeline проекта. - -## 5. `Page` chunk - -```c -struct PageChunk { - uint32_t magic; // 'Page' - uint32_t rectCount; - Rect16 rects[rectCount]; -}; - -struct Rect16 { - int16_t x; - int16_t w; - int16_t y; - int16_t h; -}; -``` - -`Page` задаёт atlas-прямоугольники для выборки под-областей текстуры. - -## 6. Mip-skip политика - -Загрузчик может пропускать первые mip-уровни в зависимости от: - -- `flags5`, -- размеров текстуры, -- количества mip. - -После `mipSkip`: - -- уменьшаются `width/height/mipCount`; -- сдвигается начало пиксельных данных; -- `Page`-координаты пересчитываются в соответствии с новым базовым уровнем. - -## 7. Палитры - -Для части текстур движок связывает палитру по суффиксу имени. - -Практический формат: - -- буква `A..Z` + вариант `""` или `0..9` -- всего `26 * 11 = 286` возможных слотов палитр. - -Невалидные суффиксы нужно считать ошибкой входных данных в инструментах. - -## 8. Кэширование - -Движок ведёт отдельные кэши: - -- общий texture cache; -- lightmap cache. - -Для обычных текстур используется отложенный сбор неиспользуемых слотов (по времени нулевого refcount). - -## 9. Правила writer/editor - -1. Не нормализовать `flags4/flags5/unk6`. -2. Сохранять payload без лишних хвостовых байт. -3. Если есть `Page`, его размер должен быть ровно `8 + rectCount * 8`. -4. Проверять `width > 0`, `height > 0`, `mipCount > 0`. - -## 10. Статус валидации - -- Инварианты `Texm` проверены legacy-валидатором. -- На полном retail-корпусе `testdata/Parkan - Iron Strategy` проверено `518/518` текстурных payload (`Texm`) без ошибок. - -## 11. Статус покрытия и что осталось до 100% - -Закрыто: - -1. Заголовок `Texm`, mip-chain layout и `Page` chunk. -2. Базовые decode-пути в RGBA8 для проверок/preview. -3. Корпусная валидация структурных инвариантов. - -Осталось: - -1. Полная формальная спецификация всех редких служебных комбинаций `flags4/flags5/unk6`. -2. Канонический writer для полного набора форматов (`indexed`, `565`, `556`, `4444`, `88`, `888`, `8888`) с проверенным roundtrip-профилем. -3. Pixel-parity тесты «оригинальный рендер vs новый рендер» с учетом mipSkip/atlas-page веток. diff --git a/docs/specs/ui.md b/docs/specs/ui.md deleted file mode 100644 index bb915cb..0000000 --- a/docs/specs/ui.md +++ /dev/null @@ -1,33 +0,0 @@ -# UI system - -`UI` — подсистема интерфейса: - -- экранные панели и HUD; -- меню; -- шрифты; -- minimap и служебные оверлеи. - -## 1. Архитектурная роль - -1. Работает поверх render-пайплайна как отдельный этап кадра. -2. Использует UI-ресурсы из архивных библиотек. -3. Перехватывает пользовательский ввод по правилам фокуса. - -## 2. Минимальный runtime-контракт - -1. Детерминированный порядок draw-проходов UI. -2. Консистентный фокус и приоритет ввода (UI vs world). -3. Стабильная загрузка font/minimap/ui-ресурсов по именам. - -## 3. Статус покрытия и что осталось до 100% - -Закрыто: - -- позиция UI-слоя в общем кадре и его связи с render/input. - -Осталось: - -1. Полная спецификация форматов UI layout и контролов. -2. Полный контракт ресурсов шрифтов и text-rendering поведения. -3. Формат minimap-данных и правила трансформации координат. -4. UI parity-тесты (скриншотные и событийные). diff --git a/docs/specs/wear.md b/docs/specs/wear.md deleted file mode 100644 index e969f9c..0000000 --- a/docs/specs/wear.md +++ /dev/null @@ -1,96 +0,0 @@ -# Wear table (`WEAR`) - -`WEAR` — текстовый ресурс, который связывает слоты wear с именами материалов и lightmap. - -Связанные страницы: - -- [Material (`MAT0`)](material.md) -- [Texture (`Texm`)](texture.md) - -## 1. Контейнер - -- Тип ресурса: `0x52414557` (`WEAR`). -- Обычно хранится как `*.wea` внутри world/mission архивов. - -## 2. Формат текста - -```text -<wearCount:int> -<legacyId:int> <materialName> -... (wearCount строк) - -[пустая строка] -[LIGHTMAPS -<lightmapCount:int> -<legacyId:int> <lightmapName> -... (lightmapCount строк)] -``` - -`legacyId` читается, но логика выбора работает по имени. - -## 3. Совместимость парсинга - -В движке используются два режима чтения (`из файла` и `из буфера`), у которых различается обработка блока `LIGHTMAPS`. - -Практическое правило для полного совпадения: - -- если присутствует блок `LIGHTMAPS`, перед строкой `LIGHTMAPS` должна быть пустая строка-разделитель. - -## 4. Runtime-ограничения - -- Число wear-таблиц в менеджере ограничено: максимум `70`. -- Для `wearCount <= 0` ресурс считается некорректным. -- Для `LIGHTMAPS` блока `lightmapCount <= 0` — также ошибка формата. - -## 5. Поведение резолва - -### 5.1. Материал - -Для каждого wear-слота: - -1. Ищется материал по имени. -2. Если не найден — используется fallback (`DEFAULT`, затем индекс 0). - -### 5.2. Lightmap - -Для каждого lightmap-слота: - -1. Ищется текстура lightmap по имени. -2. Если не найдено — слот получает `-1`. - -## 6. Handle-кодирование - -Движок кодирует ссылку на material-slot как: - -```c -handle = (tableIndex << 16) | wearIndex -``` - -- `tableIndex` — номер wear-таблицы. -- `wearIndex` — индекс строки внутри таблицы. - -## 7. Правила writer/editor - -1. Сохранять порядок строк. -2. Не переставлять и не нормализовать `legacyId`. -3. Для совместимости buffer-парсинга сохранять пустую строку перед `LIGHTMAPS`. -4. Проверять, что число строк соответствует `wearCount`/`lightmapCount`. - -## 8. Статус валидации - -- Поведение `WEAR` согласовано с текущей спецификацией материалов/текстур и runtime-пайплайном. -- Корпусные проверки связки `WEAR -> MAT0 -> Texm` включены в текущий валидаторный контур проекта. - -## 9. Статус покрытия и что осталось до 100% - -Закрыто: - -1. Текстовый формат `WEAR`, включая блок `LIGHTMAPS`. -2. Handle-кодирование material slot и fallback-резолв. -3. Правила совместимого writer/editor path. - -Осталось: - -1. Полная спецификация edge-case форматов строк (кодировки, редкие разделители, возможные legacy-варианты). -2. Формализация всех ограничений менеджера wear-таблиц в runtime (лимиты и политики вытеснения). -3. Интеграционные parity-тесты на полном цикле «модель -> wear -> material -> texture/lightmap». 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) @@ -3,7 +3,7 @@ site_name: FParkan site_url: https://fparkan.popov.link/ site_author: Valentin Popov site_description: >- - Utilities and tools for the game “Parkan: Iron Strategy”. + Техническая книга о восстановлении игрового движка Iron3D из Parkan: Iron Strategy. # Repository repo_name: valentineus/fparkan @@ -16,36 +16,66 @@ copyright: Copyright © 2023 — 2026 Valentin Popov theme: name: material language: ru + features: + - navigation.instant + - navigation.sections + - navigation.indexes + - navigation.top + - toc.follow + - search.highlight + - search.suggest palette: - scheme: slate + - media: "(prefers-color-scheme: light)" + scheme: default + primary: indigo + accent: deep orange + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: indigo + accent: deep orange + +markdown_extensions: + - admonition + - attr_list + - def_list + - md_in_html + - toc: + permalink: true + - pymdownx.details + - pymdownx.highlight + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + +plugins: + - search: + lang: + - ru + - en # Navigation nav: - - Home: index.md - - Specs: - - 3D implementation notes: specs/msh-notes.md - - AI system: specs/ai.md - - ArealMap: specs/arealmap.md - - Behavior system: specs/behavior.md - - Control system: specs/control.md - - FXID: specs/fxid.md - - Material (MAT0): specs/material.md - - Wear (WEAR): specs/wear.md - - Texture (Texm): specs/texture.md - - Materials index: specs/materials-texm.md - - Missions: specs/missions.md - - Object registry (objects.rlb): specs/object-registry.md - - MSH animation: specs/msh-animation.md - - MSH core: specs/msh-core.md - - Network system: specs/network.md - - NRes / RsLi: specs/nres.md - - Render pipeline: specs/render.md - - Render parity: specs/render-parity.md - - Runtime pointer: specs/runtime-pipeline.md - - Sound system: specs/sound.md - - Terrain + map loading: specs/terrain-map-loading.md - - UI system: specs/ui.md - - Форматы 3D‑ресурсов (обзор): specs/msh.md + - Начало: index.md + - Книга: + - I. Путеводитель и методика: tomes/01-guide.md + - II. Запуск, архитектура и игровой цикл: tomes/02-architecture.md + - III. Ресурсная система и форматы: tomes/03-resources.md + - IV. Мир, миссии и игровой runtime: tomes/04-world.md + - V. Геометрия, материалы и рендер: tomes/05-render.md + - VI. Поведение, управление, звук и сеть: tomes/06-behavior.md + - VII. Руководство по полной реализации: tomes/07-implementation.md + - VIII. Справочник и доказательная база: tomes/08-evidence.md + - Справочник: + - NRes: reference/nres.md + - RsLi: reference/rsli.md + - TMA: reference/tma.md + - MSH: reference/msh.md + - WEAR и MAT0: reference/materials.md + - Texm: reference/texm.md + - Render frame: reference/render-frame.md + - Приложения: + - Глоссарий: appendices/glossary.md + - Границы знания: appendices/knowledge-boundaries.md # Additional configuration extra: |
