aboutsummaryrefslogtreecommitdiff
path: root/docs/specs
diff options
context:
space:
mode:
Diffstat (limited to 'docs/specs')
-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
25 files changed, 0 insertions, 2547 deletions
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».