Files
fparkan/docs/specs/terrain-map-loading.md
Valentin Popov 0d7ae6a017
Some checks failed
Test / Lint (push) Failing after 1m10s
Test / Test (push) Has been skipped
Test / Render parity (push) Has been skipped
Документирование и обновление спецификаций
- Обновлены спецификации `runtime-pipeline`, `sound`, `terrain-map-loading`, `texture`, `ui` и `wear`.
- Добавлены разделы о статусе покрытия и оставшихся задачах для достижения 100% завершенности.
- Внесены уточнения по архитектурным ролям, минимальным контрактам и требованиям к toolchain для каждой подсистемы.
- Уточнены форматы данных и правила взаимодействия между компонентами системы.
2026-02-19 11:07:04 +04:00

10 KiB
Raw Blame History

Terrain + ArealMap

Документ описывает подсистему ландшафта и ареалов мира в движке Parkan: Iron Strategy:

  • Land.msh (terrain-геометрия и вспомогательные таблицы);
  • Land.map (ареалы и навигационные связи);
  • BuildDat.lst (категории объектных зон).

Описание дано в высокоуровневом переносимом виде, без ссылок на внутренние адреса и имена из дизассемблера.

Связанные страницы:

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'ов:

[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.mapNRes, содержащий ровно один ресурс type=12.

Контракт верхнего уровня:

  • entry.attr1 = areal_count;
  • payload включает:
    • areal_count переменных записей ареалов;
    • затем grid-секцию cell-попаданий.

3.1. Запись ареала

Старт записи:

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) байт данных полигона

Для links[0 .. vertex_count-1]:

  • (-1, -1) означает «соседа нет»;
  • иначе area_ref указывает на индекс соседнего ареала, edge_ref — на ребро в соседнем ареале.

3.3. Grid-секция после ареалов

Формат:

uint32 cellsX;
uint32 cellsY;
for (x=0; x<cellsX; x++) {
    for (y=0; y<cellsY; y++) {
        uint16 hitCount;
        uint16 areaIds[hitCount];
    }
}

В runtime существует упакованное cell-meta представление:

  • high 10 бит: hitCount;
  • low 22 бита: startIndex (в общем areaIds пуле).

3.4. Валидация целостности chunk 12

Обязательные проверки:

  • areal_count > 0;
  • cellsX > 0 && cellsY > 0;
  • каждый area_id из cell-списков < areal_count;
  • все area_ref/edge_ref валидны относительно целевых ареалов;
  • полный объем прочитанных байт должен точно совпасть с размером payload.

4. BuildDat.lst

Используются 12 объектных категорий ареалов:

Имя Маска
Bunker_Small 0x80010000
Bunker_Medium 0x80020000
Bunker_Large 0x80040000
Generator 0x80000002
Mine 0x80000004
Storage 0x80000008
Plant 0x80000010
Hangar 0x80000040
MainTeleport 0x80000200
Institute 0x80000400
Tower_Medium 0x80100000
Tower_Large 0x80200000

Файл должен парситься строго секционно; поврежденный формат считается ошибкой.

5. Требования к reader/writer/editor

  1. Сохранять порядок и бинарную форму chunk'ов, если не выполняется осознанная нормализация.
  2. Все неизвестные поля хранить и писать побайтно (preserve-as-is).
  3. После правок пересчитывать только вычислимые поля, не «чистить» opaque-данные.
  4. Проверять диапазоны индексов между связанными таблицами (nodes/slots/faces/vertices/areas/cells).
  5. Для неизмененных ресурсов обеспечивать byte-identical roundtrip.

6. Эмпирическая верификация (retail)

Валидация на testdata/Parkan - Iron Strategy:

  • карт: 33
  • Land.msh: 33/33 валидны
  • Land.map: 33/33 валидны
  • issues_total = 0, errors_total = 0, warnings_total = 0

Подтвержденные наблюдения:

  • Land.msh порядок chunk'ов стабилен: [1,2,3,4,5,18,14,11,21];
  • Land.map всегда содержит один chunk type=12;
  • cellsX == cellsY == 128 во всех retail-картах;
  • poly_count == 0 во всем проверенном retail-корпусе;
  • normal имеет длину ~1.0;
  • reserved_12, reserved_36, reserved_44 в retail наблюдаются как 0.

Инструмент:

  • 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 закрыты, но не все биты имеют документированные геймплейные имена).