2026-02-19 11:07:04 +04:00
# Terrain + ArealMap
2026-02-11 21:12:05 +00:00
2026-02-19 11:07:04 +04:00
Документ описывает подсистему ландшафта и ареалов мира в движке Parkan: Iron Strategy:
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
- `Land.msh` (terrain-геометрия и вспомогательные таблицы);
- `Land.map` (ареалы и навигационные связи);
- `BuildDat.lst` (категории объектных зон).
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Описание дано в высокоуровневом переносимом виде, без ссылок на внутренние адреса и имена из дизассемблера.
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Связанные страницы:
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
- [NRes ](nres.md )
- [RsLi ](rsli.md )
2026-02-12 10:17:41 +00:00
- [MSH core ](msh-core.md )
2026-02-19 11:07:04 +04:00
- [Render pipeline ](render.md )
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
## 1. End-to-End загрузка уровня
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Для каждой карты движок загружает пару файлов:
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
- `.../Land.msh`
- `.../Land.map`
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Высокоуровневый порядок:
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
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` .
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
## 2. Формат `Land.msh`
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
`Land.msh` — обычный `NRes` архив с фиксированным набором terrain-ресурсов.
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
## 2.1. Состав chunk'ов
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Обязательные типы:
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
- `1` , `2` , `3` , `4` , `5` , `11` , `18` , `21`
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Опциональные типы:
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
- `14`
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Наблюдаемый retail-порядок chunk'ов:
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
```text
[1, 2, 3, 4, 5, 18, 14, 11, 21]
```
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
## 2.2. Stride и атрибуты
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
| 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 |
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Общее правило для этих chunk'ов:
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
- `attr1 == size / stride`
- `attr3 == stride`
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
## 2.3. Type `2`: slot table
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
`type=2` содержит:
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
- заголовок `0x8C` байт;
- затем таблицу slots по `68` байт.
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Инварианты:
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
- `size >= 0x8C`
- `(size - 0x8C) % 68 == 0`
- `attr1 == (size - 0x8C) / 68`
- `attr3 == 68`
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
## 2.4. Type `21`: terrain face (28 байт)
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Высокоуровневая структура face:
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
- флаги face;
- индексы треугольника (`i0, i1, i2` );
- индексы соседей (`n0, n1, n2` , значение `0xFFFF` = нет соседа);
- служебные поля (материал/класс/edge-поля и др.).
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Критичные проверки:
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
- `i0/i1/i2 < vertex_count` (`type=3` );
- `nX == 0xFFFF` или `nX < face_count` .
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
## 2.5. Маски face и compact-представления
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
В рантайме используются:
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
- полная 32-битная маска (`full` );
- компактные представления (`compactMain16` , `compactMaterial6` ).
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Подтвержденный remap `full -> compactMain16` :
2026-02-12 10:17:41 +00:00
| 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` |
2026-02-19 11:07:04 +04:00
Подтвержденный remap `full -> compactMaterial6` :
2026-02-12 10:17:41 +00:00
| Full bit | Compact bit |
|---:|---:|
| `0x00000100` | `0x01` |
| `0x00008000` | `0x02` |
| `0x00010000` | `0x04` |
| `0x00040000` | `0x08` |
| `0x00080000` | `0x10` |
| `0x00000080` | `0x20` |
2026-02-19 11:07:04 +04:00
Для 1:1 реализации нужно поддерживать о б а представления и обратное восстановление `compact -> full` .
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
## 2.6. Type `11` и cell-ускоритель terrain
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
`type=11` служит источником cell-ускорителя для terrain-запросов.
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Практические требования для editor/toolchain:
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
- не переупорядочивать содержимое без полного пересчета зависимых таблиц;
- сохранять служебные/неизвестные поля побайтно;
- выполнять валидацию диапазонов face/slot после любых правок.
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
## 3. Формат `Land.map` (chunk `type=12`)
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
`Land.map` — `NRes` , содержащий ровно один р е с у р с `type=12` .
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Контракт верхнего уровня:
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
- `entry.attr1` = `areal_count` ;
- payload включает:
- `areal_count` переменных записей ареалов;
- затем grid-секцию cell-попаданий.
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
## 3.1. Запись ареала
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Старт записи:
2026-02-12 10:17:41 +00:00
```c
2026-02-19 11:07:04 +04:00
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
2026-02-12 10:17:41 +00:00
```
2026-02-19 11:07:04 +04:00
Далее:
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
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)` байт данных полигона
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
## 3.2. Семантика edge-link
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Для `links[0 .. vertex_count-1]` :
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
- `(-1, -1)` означает «соседа нет»;
- иначе `area_ref` указывает на индекс соседнего ареала, `edge_ref` — на р е б р о в соседнем ареале.
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
## 3.3. Grid-секция после ареалов
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Формат:
2026-02-12 10:17:41 +00:00
```c
2026-02-19 11:07:04 +04:00
uint32 cellsX;
uint32 cellsY;
for (x=0; x<cellsX; x++) {
for (y=0; y<cellsY; y++) {
uint16 hitCount;
uint16 areaIds[hitCount];
2026-02-12 10:17:41 +00:00
}
}
```
2026-02-19 11:07:04 +04:00
В runtime существует упакованное cell-meta представление:
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
- high 10 бит: `hitCount` ;
- low 22 бита: `startIndex` (в общем `areaIds` пуле).
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
## 3.4. Валидация целостности chunk 12
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Обязательные проверки:
2026-02-11 21:12:05 +00:00
2026-02-19 11:07:04 +04:00
- `areal_count > 0` ;
- `cellsX > 0 && cellsY > 0` ;
- каждый `area_id` из cell-списков `< areal_count` ;
- все `area_ref/edge_ref` валидны относительно целевых ареалов;
- полный объем прочитанных байт должен точно совпасть с размером payload.
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
## 4. `BuildDat.lst`
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Используются 12 объектных категорий ареалов:
2026-02-12 10:17:41 +00:00
| Имя | Маска |
|---|---:|
| `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` |
2026-02-19 11:07:04 +04:00
Файл должен парситься строго секционно; поврежденный формат считается ошибкой.
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
## 5. Требования к reader/writer/editor
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
1. Сохранять порядок и бинарную форму chunk'ов, если не выполняется осознанная нормализация.
2. В с е неизвестные поля хранить и писать побайтно (`preserve-as-is` ).
3. После правок пересчитывать только вычислимые поля, не «чистить» opaque-данные.
4. Проверять диапазоны индексов между связанными таблицами (`nodes/slots/faces/vertices/areas/cells` ).
5. Для неизмененных ресурсов обеспечивать byte-identical roundtrip.
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
## 6. Эмпирическая верификация (retail)
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Валидация на `testdata/Parkan - Iron Strategy` :
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
- карт: `33`
- `Land.msh` : `33/33` валидны
- `Land.map` : `33/33` валидны
- `issues_total = 0` , `errors_total = 0` , `warnings_total = 0`
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Подтвержденные наблюдения:
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
- `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` .
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
Инструмент:
2026-02-12 10:17:41 +00:00
2026-02-19 11:07:04 +04:00
- `tools/terrain_map_doc_validator.py`
2026-02-11 21:12:05 +00:00
2026-02-19 11:07:04 +04:00
## 7. Статус покрытия и что осталось до 100%
2026-02-11 21:12:05 +00:00
2026-02-19 11:07:04 +04:00
Закрыто:
2026-02-11 21:12:05 +00:00
2026-02-19 11:07:04 +04:00
- бинарный контракт `Land.msh` и `Land.map` ;
- диапазонные и структурные инварианты;
- remap масок `full/compact` ;
- валидация на полном retail-корпусе карт.
2026-02-11 21:12:05 +00:00
2026-02-19 11:07:04 +04:00
Осталось до полного 100% архитектурного покрытия движка:
2026-02-11 21:12:05 +00:00
2026-02-19 11:07:04 +04:00
1. Полная доменная семантика `class_id` и `logic_flag` (игровые значения/поведенческие правила).
2. Полная спецификация ветки `poly_count > 0` на живых данных (в retail не встречена).
3. Полная field-level семантика части битов `TerrainFace28.flags` (бинарный контракт и remap закрыты, но не все биты имеют документированные геймплейные имена).