# 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`. Инструмент: - `tools/terrain_map_doc_validator.py` ## 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 закрыты, но не все биты имеют документированные геймплейные имена).