aboutsummaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2026-06-22 00:58:51 +0300
committerValentin Popov <valentin@popov.link>2026-06-22 00:58:51 +0300
commit78fc5f1debf1395d5df0bab7cc0dde54351205cb (patch)
treeef8f7c72a183723fcbea0b2d1fefd7c28ca7bc18 /docs
parent50c2cf4686b53ebd2b76318223096660e92305a4 (diff)
downloadfparkan-78fc5f1debf1395d5df0bab7cc0dde54351205cb.tar.xz
fparkan-78fc5f1debf1395d5df0bab7cc0dde54351205cb.zip
docs: rewrite MkDocs documentation
Diffstat (limited to 'docs')
-rw-r--r--docs/appendices/glossary.md200
-rw-r--r--docs/appendices/knowledge-boundaries.md120
-rw-r--r--docs/index.md58
-rw-r--r--docs/reference/materials.md69
-rw-r--r--docs/reference/msh.md82
-rw-r--r--docs/reference/nres.md61
-rw-r--r--docs/reference/render-frame.md56
-rw-r--r--docs/reference/rsli.md69
-rw-r--r--docs/reference/texm.md67
-rw-r--r--docs/reference/tma.md64
-rw-r--r--docs/specs/ai.md35
-rw-r--r--docs/specs/arealmap.md31
-rw-r--r--docs/specs/behavior.md28
-rw-r--r--docs/specs/control.md28
-rw-r--r--docs/specs/coverage-audit.md45
-rw-r--r--docs/specs/fxid.md202
-rw-r--r--docs/specs/material.md144
-rw-r--r--docs/specs/materials-texm.md18
-rw-r--r--docs/specs/missions.md46
-rw-r--r--docs/specs/msh-animation.md126
-rw-r--r--docs/specs/msh-core.md192
-rw-r--r--docs/specs/msh-notes.md118
-rw-r--r--docs/specs/msh.md39
-rw-r--r--docs/specs/network.md28
-rw-r--r--docs/specs/nres.md200
-rw-r--r--docs/specs/object-registry.md145
-rw-r--r--docs/specs/render-parity.md90
-rw-r--r--docs/specs/render.md182
-rw-r--r--docs/specs/rsli.md227
-rw-r--r--docs/specs/runtime-pipeline.md18
-rw-r--r--docs/specs/sound.md32
-rw-r--r--docs/specs/terrain-map-loading.md291
-rw-r--r--docs/specs/texture.md153
-rw-r--r--docs/specs/ui.md33
-rw-r--r--docs/specs/wear.md96
-rw-r--r--docs/tomes/01-guide.md371
-rw-r--r--docs/tomes/02-architecture.md472
-rw-r--r--docs/tomes/03-resources.md561
-rw-r--r--docs/tomes/04-world.md648
-rw-r--r--docs/tomes/05-render.md863
-rw-r--r--docs/tomes/06-behavior.md769
-rw-r--r--docs/tomes/07-implementation.md674
-rw-r--r--docs/tomes/08-evidence.md1087
43 files changed, 6279 insertions, 2559 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)