From 78fc5f1debf1395d5df0bab7cc0dde54351205cb Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Mon, 22 Jun 2026 01:58:51 +0400 Subject: docs: rewrite MkDocs documentation --- docs/specs/ai.md | 35 ----- docs/specs/arealmap.md | 31 ---- docs/specs/behavior.md | 28 ---- docs/specs/control.md | 28 ---- docs/specs/coverage-audit.md | 45 ------ docs/specs/fxid.md | 202 -------------------------- docs/specs/material.md | 144 ------------------- docs/specs/materials-texm.md | 18 --- docs/specs/missions.md | 46 ------ docs/specs/msh-animation.md | 126 ----------------- docs/specs/msh-core.md | 192 ------------------------- docs/specs/msh-notes.md | 118 ---------------- docs/specs/msh.md | 39 ----- docs/specs/network.md | 28 ---- docs/specs/nres.md | 200 -------------------------- docs/specs/object-registry.md | 145 ------------------- docs/specs/render-parity.md | 90 ------------ docs/specs/render.md | 182 ------------------------ docs/specs/rsli.md | 227 ----------------------------- docs/specs/runtime-pipeline.md | 18 --- docs/specs/sound.md | 32 ----- docs/specs/terrain-map-loading.md | 291 -------------------------------------- docs/specs/texture.md | 153 -------------------- docs/specs/ui.md | 33 ----- docs/specs/wear.md | 96 ------------- 25 files changed, 2547 deletions(-) delete mode 100644 docs/specs/ai.md delete mode 100644 docs/specs/arealmap.md delete mode 100644 docs/specs/behavior.md delete mode 100644 docs/specs/control.md delete mode 100644 docs/specs/coverage-audit.md delete mode 100644 docs/specs/fxid.md delete mode 100644 docs/specs/material.md delete mode 100644 docs/specs/materials-texm.md delete mode 100644 docs/specs/missions.md delete mode 100644 docs/specs/msh-animation.md delete mode 100644 docs/specs/msh-core.md delete mode 100644 docs/specs/msh-notes.md delete mode 100644 docs/specs/msh.md delete mode 100644 docs/specs/network.md delete mode 100644 docs/specs/nres.md delete mode 100644 docs/specs/object-registry.md delete mode 100644 docs/specs/render-parity.md delete mode 100644 docs/specs/render.md delete mode 100644 docs/specs/rsli.md delete mode 100644 docs/specs/runtime-pipeline.md delete mode 100644 docs/specs/sound.md delete mode 100644 docs/specs/terrain-map-loading.md delete mode 100644 docs/specs/texture.md delete mode 100644 docs/specs/ui.md delete mode 100644 docs/specs/wear.md (limited to 'docs/specs') 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`); - - искать `.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/.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` (`.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 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 строк) - -[пустая строка] -[LIGHTMAPS - - -... (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». -- cgit v1.2.3