649 lines
31 KiB
Markdown
649 lines
31 KiB
Markdown
# IV. Мир, миссии и игровой runtime
|
||
|
||
Миссия в Iron3D не является готовым снимком мира. Она задаёт исходные данные:
|
||
маршруты, кланы, размещённые объекты, свойства, ссылку на ландшафт и
|
||
дополнительные записи. Runtime строит из этого карту, пространственные
|
||
структуры, очередь `World3D`, визуальные представления, controllers и связи с
|
||
ресурсной системой.
|
||
|
||
Для совместимой реализации важно не смешивать три слоя:
|
||
|
||
1. **Disk data** -- `data.tma`, `Land.msh`, `Land.map`, `BuildDat.lst` и
|
||
связанные resource archives.
|
||
2. **Prepared data** -- разобранные paths, clans, terrain streams, areal graph,
|
||
prototype graph, material и texture handles.
|
||
3. **Runtime objects** -- World3D instances, domain controllers, spatial
|
||
registration, AI/scripts, timers и расчётный tick.
|
||
|
||
Граница между этими слоями нужна для диагностики и отката. Ошибка в достижимой
|
||
цепочке размещённого объекта должна остановить создание миссии до публикации
|
||
объекта в очереди событий. Недостижимая запись общего архива может быть
|
||
inventory warning и не обязана блокировать текущую карту.
|
||
|
||
## `data.tma`: данные миссии
|
||
|
||
`data.tma` -- основное описание расстановки и логической конфигурации миссии.
|
||
Он не содержит всю геометрию, материалы или AI-код. Файл перечисляет paths,
|
||
clans, objects, свойства и ссылки на внешние прототипы. Подробный справочный
|
||
контракт формата вынесен в [TMA](../reference/tma.md), но глава использует его
|
||
как часть сквозного runtime pipeline.
|
||
|
||
TMA читается строго последовательно bounded cursor-ом. Записи имеют переменную
|
||
длину, поэтому offsets следующих секций получаются только после разбора
|
||
предыдущих. Секции нельзя искать по сигнатурам: порядок управляется счётчиками,
|
||
длинами и mode-dependent ветками.
|
||
|
||
Главный критерий корректности -- `cursor.offset == file_size` после последней
|
||
записи. Неописанный хвост, переполнение при вычислении размеров, отрицательный
|
||
или чрезмерный count и выход за bounds являются ошибками parser-а, а не
|
||
материалом для эвристического восстановления.
|
||
|
||
### Верхний уровень
|
||
|
||
Все переменные строки в проверенных TMA используют length-prefixed primitive:
|
||
|
||
```c
|
||
struct LpString {
|
||
uint32_t byte_length;
|
||
uint8_t bytes[byte_length];
|
||
};
|
||
```
|
||
|
||
Завершающий NUL не является обязательной частью framing. Reader продвигается
|
||
ровно на `4 + byte_length`. Текст можно декодировать как legacy ANSI/CP1251 для
|
||
человекочитаемого представления, но исходные bytes сохраняются для lossless
|
||
режима.
|
||
|
||
Подтверждённый верхний уровень:
|
||
|
||
```text
|
||
u32 format_version // 1
|
||
u32 path_count
|
||
PathRecord paths[path_count]
|
||
u32 clan_section_version // 6
|
||
u32 clan_count
|
||
ClanRecord clans[clan_count]
|
||
u32 object_section_version // 10
|
||
u32 object_count
|
||
PlacedObject objects[object_count]
|
||
LpString land_path
|
||
u32 mission_flag
|
||
LpString description_raw
|
||
u32 extra_section_version // 1
|
||
u32 extra_count
|
||
ExtraRecord28 extras[extra_count]
|
||
```
|
||
|
||
Имена `clan_section_version`, `object_section_version` и
|
||
`extra_section_version` описывают устойчивое положение полей в контракте. Они
|
||
не доказывают исходные имена C++-структур. Strict mode проверяет известные
|
||
значения, compatible mode сохраняет raw value и сообщает диагностический
|
||
контекст.
|
||
|
||
### Paths
|
||
|
||
```c
|
||
struct PathRecord {
|
||
int32_t path_id;
|
||
uint32_t point_count;
|
||
float points[point_count][3];
|
||
};
|
||
```
|
||
|
||
Paths идут сразу после `path_count` без имён и padding. `path_id` не обязан
|
||
совпадать с физической позицией записи: script/gameplay reference должен
|
||
использовать сохранённый ID, а не индекс массива.
|
||
|
||
Перед выделением массива проверяются `point_count`, умножение `point_count *
|
||
12` и наличие всего диапазона в файле. Координаты хранятся как little-endian
|
||
`float32` triples в общей системе координат мира.
|
||
|
||
### Clans
|
||
|
||
Clan section задаёт участников миссии, их ресурсные связи, позиционные anchors
|
||
и таблицы отношений. Общая prefix-часть:
|
||
|
||
```text
|
||
LpString name
|
||
i32 raw_id
|
||
f32 anchor_x
|
||
f32 anchor_y
|
||
u32 mode
|
||
mode-dependent body
|
||
relation table
|
||
```
|
||
|
||
Для обычных modes `1..3` тело содержит две пары:
|
||
|
||
```text
|
||
LpString resource_path
|
||
i32 resource_tag
|
||
LpString resource_path
|
||
i32 resource_tag
|
||
```
|
||
|
||
После них идёт relation table:
|
||
|
||
```text
|
||
u32 relation_count
|
||
repeat relation_count:
|
||
LpString other_clan_name
|
||
i32 relation_value
|
||
```
|
||
|
||
Первая ресурсная строка обычно указывает на script/formula base, вторая -- на
|
||
TRF или пустой ресурс. Tags различаются между кланами и должны сохраняться как
|
||
raw-поля, пока их потребительская семантика не закрыта.
|
||
|
||
Mode `0` имеет отдельный count-driven layout:
|
||
|
||
```text
|
||
LpString first_resource
|
||
u32 spatial_group_count
|
||
repeat spatial_group_count:
|
||
u32 record_count
|
||
repeat record_count:
|
||
float raw_spatial[5]
|
||
LpString second_resource
|
||
i32 second_tag
|
||
u32 relation_count
|
||
relations...
|
||
```
|
||
|
||
Внутренний `record_count` в известных живых образцах равен `1`, но parser читает
|
||
объявленное значение. Нельзя разбирать mode `0` как обычные две resource
|
||
references: это сдвигает cursor и ломает последующую relation table.
|
||
|
||
### PlacedObject и свойства
|
||
|
||
Ключевое поле размещённого объекта -- `resource_name`. Оно имеет два рабочих
|
||
варианта:
|
||
|
||
1. прямой логический ключ прототипа, который ищется в `objects.rlb`;
|
||
2. путь к unit DAT, из которого получается список компонентных ключей.
|
||
|
||
Доказанное framing объектной записи:
|
||
|
||
```text
|
||
u32 raw_kind
|
||
u32 class_or_flags
|
||
LpString resource_name
|
||
u32 raw_after_resource
|
||
u32 identity_or_clan_raw
|
||
f32 position[3]
|
||
f32 orientation[3]
|
||
f32 scale[3]
|
||
LpString instance_name
|
||
u32 raw_after_name
|
||
i32 link0
|
||
i32 link1
|
||
u32 property_schema_version // 1
|
||
u32 property_count
|
||
Property properties[property_count]
|
||
```
|
||
|
||
`orientation[3]` названа по наблюдаемому использованию как transform-поле, но
|
||
точный Euler order должен подтверждаться pose/render parity. `scale` в
|
||
большинстве записей равен `(1,1,1)`. `instance_name` может быть пустым у
|
||
unit-ссылки или содержать stem размещённого прототипа.
|
||
|
||
Свойства хранятся как ordered property bag:
|
||
|
||
```text
|
||
Property:
|
||
u32 raw_value[4]
|
||
LpString name
|
||
```
|
||
|
||
Порядок, повторяемость имени и raw 16-byte value важнее удобного словаря.
|
||
Разные consumers интерпретируют четыре слова как integer, float, default или
|
||
range data в зависимости от имени свойства. Typed view допустим только для
|
||
доказанных property names; базовый parser обязан сохранить исходный порядок.
|
||
|
||
В раннем проверенном корпусе на каждом из 201 размещённого объекта встречаются
|
||
`Invulnerability` и `Life state`. Для 48 unit-ссылок дополнительно наблюдаются
|
||
`LogicalID`, `ClanID`, `Type`, `MaxSpeedPercent`, `MaximumOre`, `CurrentOre`,
|
||
`ChargeRadius`, `FreeBotNum`, `FreeTechnoNum`, `FreeConstructionTime` и
|
||
`FreeResearchTime`. Имя `NOT USED` встречается массово и сохраняется как
|
||
обычное поле, несмотря на исторический смысл названия.
|
||
|
||
### Epilogue и extras
|
||
|
||
После объектов идут путь к ландшафту, флаг миссии, raw-описание и trailing
|
||
section. `description_raw` не всегда является чистым текстом: внутри
|
||
объявленной длины встречаются служебные bytes и остатки путей. Поэтому decoded
|
||
view является вспомогательным, а не каноническим представлением.
|
||
|
||
```c
|
||
struct ExtraRecord28 {
|
||
float position[3];
|
||
uint32_t raw[4];
|
||
};
|
||
```
|
||
|
||
Последние четыре слова `ExtraRecord28` пока не нормализуются. Reader хранит их
|
||
как raw data и не позволяет extra record поглотить начало следующей секции или
|
||
файловый хвост.
|
||
|
||
Покрытие полных каталогов:
|
||
|
||
```text
|
||
Часть 1: 29 TMA, 34 paths, 101 clans, 864 objects, 28 extra records
|
||
Часть 2: 31 TMA, 61 paths, 91 clans, 885 objects, 41 extra records
|
||
```
|
||
|
||
Версии стабильны: верхний уровень `1`, clan section `6`, object section `10`,
|
||
property schema `1`, trailing section `1`. У всех размещённых объектов
|
||
`class_or_flags == 0x80000002`.
|
||
|
||
## Сквозная загрузка миссии
|
||
|
||
`data.tma` описывает размещение, но видимый runtime-объект появляется только
|
||
после прохождения dependency graph. Простая загрузка файлов с похожим stem
|
||
работает на отдельных объектах, но ломается на составных unit DAT, изменённых
|
||
именах моделей и наследовании прототипов через `objects.rlb`.
|
||
|
||
Сквозная цепочка:
|
||
|
||
```text
|
||
TMA object
|
||
-> direct prototype key или unit DAT
|
||
-> component key
|
||
-> objects.rlb entry
|
||
-> MSH и WEAR
|
||
-> material slots
|
||
-> MAT0 phases
|
||
-> Texm и lightmap
|
||
-> prepared World3D instance
|
||
```
|
||
|
||
Контейнеры и графические форматы описаны отдельно в [NRes](../reference/nres.md),
|
||
[MSH](../reference/msh.md), [WEAR и MAT0](../reference/materials.md) и
|
||
[Texm](../reference/texm.md). В этой главе они рассматриваются как ребра
|
||
создания мира.
|
||
|
||
### Фазы loader-а
|
||
|
||
1. **Mission context.** Выбрать каталог миссии, прочитать конфигурацию и
|
||
определить карту.
|
||
2. **World foundation.** Загрузить `Land.msh`, `Land.map`, `BuildDat.lst` и
|
||
создать spatial managers.
|
||
3. **Mission description.** Разобрать TMA, paths и clans, но пока не публиковать
|
||
объекты.
|
||
4. **Prototype resolution.** Для каждой размещённой сущности раскрыть прямой
|
||
ключ или unit DAT и построить component list.
|
||
5. **Resource preparation.** Открыть требуемые RLB/LIB, проверить MSH, WEAR,
|
||
MAT0, textures, lightmaps и effects.
|
||
6. **Instance construction.** Создать World3D objects и domain controllers,
|
||
заполнить transform, ownership и properties.
|
||
7. **Registration.** Только после успешной настройки добавить instances в
|
||
queue и spatial structures.
|
||
8. **Scenario start.** Подключить AI/scripts, активировать timers и разрешить
|
||
первый calculation tick.
|
||
|
||
Разделение construction и registration предотвращает появление наполовину
|
||
созданного объекта в очереди событий. Если ошибка возникает до регистрации,
|
||
pending objects освобождаются без рассылки gameplay-событий. После регистрации
|
||
откат выполняется через обычный lifecycle очереди.
|
||
|
||
### Статистика dependency graph
|
||
|
||
Для ранних шести миссий 201 размещённый объект даёт 48 ссылок на unit-файлы и
|
||
153 прямых ключа. Unit-файлы раскрываются в 348 компонентов. Всего получается
|
||
501 запрос прототипа; для каждого достижимого запроса найдены запись реестра,
|
||
MSH и WEAR.
|
||
|
||
Полный dependency graph частей 1 и 2:
|
||
|
||
```text
|
||
Часть 1
|
||
864 placed objects
|
||
463 unit references -> 4 300 components
|
||
4 701 prototype/MSH/WEAR requests
|
||
36 954 material slots
|
||
48 806 texture requests + 139 lightmaps
|
||
failures 0
|
||
|
||
Часть 2
|
||
885 placed objects
|
||
561 unit references -> 5 521 components
|
||
5 845 prototype/MSH/WEAR requests
|
||
50 888 material slots
|
||
68 603 texture requests + 214 lightmaps
|
||
failures 0
|
||
```
|
||
|
||
`failures 0` означает, что для каждой достижимой ветви найдены prototype,
|
||
effective MSH/WEAR, MAT0, Texm и lightmap. Это не означает, что во всём
|
||
глобальном каталоге нет недостижимых или служебных записей.
|
||
|
||
Метрики нужно помечать областью. Чистая object chain шести ранних миссий даёт
|
||
3 873 material slots и 5 049 texture requests. Mission total включает по одной
|
||
environment WEAR-таблице на миссию и становится 3 879 material slots и 5 067
|
||
texture references.
|
||
|
||
### Диагностика ошибок
|
||
|
||
Ошибка привязывается к конкретному ребру графа:
|
||
|
||
- миссия ссылается на отсутствующий unit-файл;
|
||
- unit DAT раскрывается в component key, которого нет в реестре;
|
||
- prototype найден, но его MSH отсутствует в ожидаемом archive;
|
||
- WEAR указывает на неизвестный MAT0;
|
||
- MAT0 phase ссылается на отсутствующий Texm или lightmap;
|
||
- prepared object не прошёл валидацию transform/properties.
|
||
|
||
Сообщение вида `resource not found` недостаточно для восстановления каталога.
|
||
Диагностика должна содержать исходный placed object, раскрытый ключ, archive,
|
||
entry и тип связи.
|
||
|
||
## `Land.msh`: ландшафт как специализированная модель
|
||
|
||
`Land.msh` является [NRes](../reference/nres.md)-архивом, но его содержимое
|
||
отличается от обычной объектной MSH. Он хранит геометрию поверхности, таблицы
|
||
участков и ускорители пространственных запросов. Видимые buffers являются лишь
|
||
частью данных: CPU-подсистемам остаются нужны adjacency, surface classes и
|
||
cell accelerator streams.
|
||
|
||
Во всех проверенных картах порядок типов одинаков:
|
||
|
||
```text
|
||
1, 2, 3, 4, 5, 18, 14, 11, 21
|
||
```
|
||
|
||
Типы `1`, `3`, `4` и `5` совместимы по базовому представлению с узлами,
|
||
позициями, нормалями и UV обычной модели. Типы `11` и `21` специфичны для
|
||
terrain; `14` и `18` являются дополнительными потоками.
|
||
|
||
### Streams и размеры элементов
|
||
|
||
```text
|
||
type 1 38 байт node/slot mapping
|
||
type 3 12 байт float3 positions
|
||
type 4 4 байта packed normals
|
||
type 5 4 байта packed UV
|
||
type 11 4 байта cell accelerator data
|
||
type 14 4 байта auxiliary stream
|
||
type 18 4 байта auxiliary stream
|
||
type 21 28 байт terrain face
|
||
```
|
||
|
||
Для этих streams `attr1` соответствует числу элементов, а `attr3` -- stride.
|
||
Тип `2` начинается заголовком размером `0x8C`, после которого идут slot records
|
||
по 68 байт. Число slots вычисляется как `(size - 0x8C) / 68`; reader проверяет
|
||
делимость, bounds и отсутствие хвоста.
|
||
|
||
### `TerrainFace28`
|
||
|
||
Запись type `21` связывает triangles, соседей и surface metadata:
|
||
|
||
```text
|
||
+0x00 .. +0x07 flags и служебные поля
|
||
+0x08 u16 vertex0
|
||
+0x0A u16 vertex1
|
||
+0x0C u16 vertex2
|
||
+0x0E u16 neighbor0
|
||
+0x10 u16 neighbor1
|
||
+0x12 u16 neighbor2
|
||
+0x14 .. +0x1B material/class/edge fields
|
||
```
|
||
|
||
Каждый vertex index обязан быть меньше числа позиций type `3`. Neighbor равен
|
||
`0xFFFF` либо указывает на другой элемент type `21`. Последние восемь bytes
|
||
сохраняются без нормализации до полного закрытия предметной семантики.
|
||
|
||
### Маски поверхности
|
||
|
||
Runtime использует полную 32-битную маску face и два compact-представления.
|
||
Основное 16-битное поле собирается из отдельных битов полной маски; второе
|
||
шестибитное поле хранит material classes. Это не усечение младших битов.
|
||
|
||
Для совместимого writer-а нужны явные функции `full_to_compact()` и
|
||
`compact_to_full()`. Неизвестные биты полной маски сохраняются отдельно, иначе
|
||
обратное преобразование потеряет информацию.
|
||
|
||
Основное соответствие:
|
||
|
||
```text
|
||
full 00000001 -> compact 0001
|
||
full 00000008 -> compact 0002
|
||
full 00000010 -> compact 0004
|
||
full 00000020 -> compact 0008
|
||
full 00001000 -> compact 0010
|
||
full 00004000 -> compact 0020
|
||
full 00000002 -> compact 0040
|
||
full 00000400 -> compact 0080
|
||
full 00000800 -> compact 0100
|
||
full 00020000 -> compact 0200
|
||
full 00002000 -> compact 0400
|
||
full 00000200 -> compact 0800
|
||
full 00000004 -> compact 1000
|
||
full 00000040 -> compact 2000
|
||
full 00200000 -> compact 8000
|
||
```
|
||
|
||
Для шестибитного material-поля используются full-биты `0x100`, `0x8000`,
|
||
`0x10000`, `0x40000`, `0x80000` и `0x80`; они переходят соответственно в
|
||
compact-биты `1`, `2`, `4`, `8`, `0x10`, `0x20`.
|
||
|
||
### Проверенное покрытие
|
||
|
||
```text
|
||
AutoMAP 3 051 вершина, 3 174 faces
|
||
PROL 11 125 вершин, 9 234 faces
|
||
Tut_1 8 827 вершин, 8 290 faces
|
||
Tut_2 9 456 вершин, 8 996 faces
|
||
Tut_3 9 833 вершины, 8 560 faces
|
||
Tut_4 9 022 вершины, 8 612 faces
|
||
```
|
||
|
||
Расширенное покрытие:
|
||
|
||
```text
|
||
Часть 1: 33 карты, 299 450 vertices, 275 882 faces
|
||
Часть 2: 32 карты, 188 024 vertices, 184 454 faces
|
||
```
|
||
|
||
Во всех 65 картах порядок типов равен `[1,2,3,4,5,18,14,11,21]`. Strides,
|
||
count-driven размеры, vertex indices, neighbor indices и payload bounds
|
||
валидны. Различия карт являются различиями данных, а не новым вариантом
|
||
loader-а.
|
||
|
||
## `Land.map` и ArealMap
|
||
|
||
`Land.map` хранит логическое разбиение пространства на связанные области. Это
|
||
NRes-архив с одной записью type `12`. Payload содержит переменное число
|
||
ареалов, links и grid быстрого поиска.
|
||
|
||
Ареал -- участок мира с геометрической границей и метаданными. Граф соседств
|
||
позволяет искать маршрут между крупными областями вместо обхода каждой
|
||
terrain-вершины. Grid отвечает на быстрый вопрос: какие области потенциально
|
||
находятся рядом с координатой.
|
||
|
||
### Prefix ареала
|
||
|
||
```c
|
||
struct ArealPrefix56 {
|
||
float anchor_x;
|
||
float anchor_y;
|
||
float anchor_z;
|
||
float reserved_12;
|
||
float area_metric;
|
||
float normal_x;
|
||
float normal_y;
|
||
float normal_z;
|
||
uint32_t logic_flag;
|
||
uint32_t reserved_36;
|
||
uint32_t class_id;
|
||
uint32_t reserved_44;
|
||
uint32_t vertex_count;
|
||
uint32_t poly_count;
|
||
};
|
||
```
|
||
|
||
После prefix идут `float3 vertices[vertex_count]`. Нормаль в проверенных
|
||
записях имеет длину, практически равную единице. Поля `reserved_12`,
|
||
`reserved_36` и `reserved_44` в живом корпусе равны нулю, но writer сохраняет
|
||
их без нормализации.
|
||
|
||
### Links и polygon blocks
|
||
|
||
За вершинами хранится массив:
|
||
|
||
```c
|
||
struct EdgeLink8 {
|
||
int32_t area_ref;
|
||
int32_t edge_ref;
|
||
};
|
||
```
|
||
|
||
Пара `(-1, -1)` означает отсутствие соседа. Иначе `area_ref` указывает на
|
||
другую область, а `edge_ref` -- на соответствующее ребро. Число пар равно
|
||
`vertex_count + 3 * poly_count`.
|
||
|
||
После links для каждого polygon читается `u32 n`, затем block размером
|
||
`4 * (3*n + 1)` bytes. Во всех 65 проверенных картах `poly_count == 0`.
|
||
Framing ветки восстановлен по loader path, но предметное поведение polygon
|
||
blocks не получает статус corpus-verified.
|
||
|
||
### Grid быстрого поиска
|
||
|
||
После всех ареалов записаны `cellsX` и `cellsY`. Далее для каждой ячейки идут
|
||
`u16 hitCount` и `hitCount` номеров областей. Runtime уплотняет это в одно
|
||
32-битное значение: старшие 10 бит содержат число попаданий, младшие 22 --
|
||
начальный индекс в общем пуле.
|
||
|
||
Grid не является точной геометрической проверкой. Он возвращает короткий список
|
||
candidates, после чего выполняется проверка принадлежности области. При
|
||
загрузке каждый area ID обязан быть меньше общего числа ареалов.
|
||
|
||
Покрытие:
|
||
|
||
```text
|
||
Ранние шесть карт: 3 811 areals, grid 128 x 128
|
||
Часть 1: 33 карты, 34 662 areals, 197 698 areal vertices
|
||
Часть 2: 32 карты, 18 984 areals, 114 968 areal vertices
|
||
```
|
||
|
||
Во всех картах grid равен `128 x 128`. Максимальное число candidates в ячейке
|
||
-- 20 для Части 1 и 14 для Части 2. Все area/edge references находятся в
|
||
диапазоне, normals имеют единичную длину в пределах float32-погрешности, parser
|
||
заканчивается точно на конце payload.
|
||
|
||
## Пространственные задачи runtime
|
||
|
||
Движок решает три похожих, но независимых вопроса:
|
||
|
||
- **видимость** -- нужно ли рисовать объект для текущей камеры;
|
||
- **столкновение** -- пересекается ли движение с поверхностью или другим телом;
|
||
- **навигация** -- через какие области допустимо провести маршрут.
|
||
|
||
Terrain, Control и ArealMap используют общие координаты мира, но разные
|
||
структуры данных. Нельзя заменять навигационный граф видимыми triangles или
|
||
вычислять collision только по границе areal. Render frame описан отдельно в
|
||
[Render frame](../reference/render-frame.md); здесь важна подготовка world data,
|
||
которую renderer получает уже после загрузки миссии.
|
||
|
||
### Поиск области
|
||
|
||
Координата переводится в ячейку grid из `Land.map`. Ячейка даёт список
|
||
candidate areas, затем выполняется точная геометрическая проверка. Такой запрос
|
||
не перебирает все области карты и не зависит от количества terrain faces.
|
||
|
||
Если координата попадает в несколько candidates, выбор должен учитывать
|
||
геометрию boundary и class/logic flags, а не только первый ID из grid cell.
|
||
Если область не найдена, caller получает явный miss и решает, допустим ли
|
||
fallback к ближайшей области.
|
||
|
||
### Маршрут
|
||
|
||
После определения начальной и целевой областей маршрут строится по графу
|
||
соседств. Результат высокого уровня -- последовательность areal IDs. Из неё
|
||
формируется локальный corridor, внутри которого movement controller выбирает
|
||
конкретное движение по поверхности.
|
||
|
||
Такое разделение оставляет навигацию устойчивой к деталям terrain mesh:
|
||
изменение density triangles не должно менять high-level route, пока areal graph
|
||
и links остаются теми же.
|
||
|
||
### Категории зон объектов
|
||
|
||
`BuildDat.lst` связывает 12 имён категорий с 32-битными масками:
|
||
|
||
```text
|
||
Bunker_Small 80010000
|
||
Bunker_Medium 80020000
|
||
Bunker_Large 80040000
|
||
Generator 80000002
|
||
Mine 80000004
|
||
Storage 80000008
|
||
Plant 80000010
|
||
Hangar 80000040
|
||
MainTeleport 80000200
|
||
Institute 80000400
|
||
Tower_Medium 80100000
|
||
Tower_Large 80200000
|
||
```
|
||
|
||
Файл читается секционно. Неизвестное имя, дублирование или нарушенная структура
|
||
не должны тихо превращаться в нулевую маску. Нулевая маска является
|
||
диагностируемым состоянием, а не универсальным default.
|
||
|
||
## Создание мира
|
||
|
||
Инициализация карты должна быть staged pipeline, а не набором независимых
|
||
autoload-ов:
|
||
|
||
1. открыть `Land.msh` и построить geometry/spatial данные terrain;
|
||
2. открыть `Land.map` и создать areals, links и cell grid;
|
||
3. загрузить категории `BuildDat.lst`;
|
||
4. создать world managers для поверхности, областей, света и атмосферы;
|
||
5. разобрать TMA, paths и clans;
|
||
6. раскрыть object resources через unit DAT и `objects.rlb`;
|
||
7. подготовить MSH, WEAR, MAT0, Texm, lightmap и FXID dependencies;
|
||
8. создать World3D objects и domain controllers в pending state;
|
||
9. проверить cross references между components, controllers и spatial data;
|
||
10. зарегистрировать visual, physical и behavior components;
|
||
11. подключить AI/scripts и разрешить первый calculation tick.
|
||
|
||
Минимальный псевдокод объектной части:
|
||
|
||
```c
|
||
for (const PlacedObject& placed : mission.objects) {
|
||
vector<string> keys = expand_resource_name(placed.resource_name);
|
||
|
||
for (const string& key : keys) {
|
||
Prototype p = registry.resolve(key);
|
||
PreparedVisual v = prepare_visual(p);
|
||
Object* o = construct_component(p, v, placed.properties);
|
||
|
||
o->set_world_transform(placed.transform);
|
||
pending_registration.push_back(o);
|
||
}
|
||
}
|
||
|
||
validate_cross_references(pending_registration);
|
||
register_all(pending_registration);
|
||
```
|
||
|
||
`prepare_visual` использует явные ссылки прототипа и правила fallback ресурсной
|
||
системы. Она не должна угадывать модель по имени placed object, если prototype
|
||
уже задаёт другой effective MSH/WEAR.
|
||
|
||
## Инварианты реализации
|
||
|
||
- Reader всех count-driven структур проверяет overflow до выделения памяти.
|
||
- Parser TMA, `Land.msh` и `Land.map` завершает работу точно на конце своего
|
||
payload.
|
||
- Неизвестные поля, reserved bytes, raw strings и property values сохраняются
|
||
lossless.
|
||
- Object properties остаются ordered property bag; сортировка имён запрещена.
|
||
- Clan relations и area links проверяются на диапазон, но физический порядок
|
||
записей сохраняется.
|
||
- Terrain vertex indices, face neighbors и areal references валидируются до
|
||
публикации spatial managers.
|
||
- Достижимый missing resource останавливает mission load до регистрации
|
||
объектов; недостижимая запись общего каталога остаётся диагностикой.
|
||
- Calculation tick включается только после успешной сборки terrain, areal graph,
|
||
managers, object queue и scenario bindings.
|