docs: rewrite MkDocs documentation
Docs Deploy / Build and Deploy MkDocs (push) Successful in 34s
Test / Lint (push) Failing after 1m7s
Test / Test (push) Has been skipped
Test / Render parity (push) Has been skipped

This commit is contained in:
2026-06-22 01:58:51 +04:00
parent 50c2cf4686
commit 78fc5f1deb
44 changed files with 6336 additions and 2586 deletions
+472
View File
@@ -0,0 +1,472 @@
# II. Запуск, архитектура и игровой цикл
Этот том описывает путь от запуска `iron_3d.exe` до устойчивого кадра:
загрузку `iron3d.dll`, создание shell/game objects, поднятие платформенных
сервисов, запуск World3D, расчёт simulation step, безопасное удаление объектов,
рендер и завершение программы.
Главная особенность Iron3D -- это не один монолитный engine object, а связка
небольшого Win32 bootstrap и набора DLL, которые обмениваются фабриками,
singleton-интерфейсами и C++ vtable. Совместимая реализация может изменить
физическое деление на библиотеки, но не может произвольно менять порядок
инициализации, object identity, правила владения, fallback ресурсов и порядок
событий.
```text
iron_3d.exe
-> iron3d.dll
-> services.dll
-> World3D.dll
-> Terrain.dll
-> Ngi32.dll
-> AniMesh.dll / ArealMap.dll / Effect.dll
-> ai.dll / Behavior.dll / Wizard.dll
-> Control.dll / MisLoad.dll / Net.dll / Joystick.dll
```
## Карта модулей
Во внешней архитектуре обнаружено пятнадцать DLL. Экспортов сравнительно мало:
они обычно создают объект, возвращают singleton или дают доступ к уже поднятой
подсистеме. Основная работа выполняется через C++-интерфейсы, поэтому порядок
виртуальных слотов является частью ABI, особенно для compatibility shim эпохи
MSVC6.
```text
iron_3d.exe
|
v
iron3d.dll -- композиция игры, shell и главный цикл
|
+-- services.dll -- доступ к display, GUI, ресурсам, звуку, таймеру и сети
+-- World3D.dll -- объекты, очередь, время, камера и кадр
+-- Terrain.dll -- ландшафт, свет, атмосфера и визуальный слой мира
+-- ai.dll / Behavior.dll / Wizard.dll
+-- Control.dll / Effect.dll / MisLoad.dll
+-- Net.dll / Joystick.dll
+-- Ngi32.dll -- ресурсы, графика, звук, математика и CPU dispatch
```
Циклы импортов между DLL ожидаемы. Terrain создаёт визуальные объекты и
обращается к World3D, а World3D получает world-interface из Terrain. Это не
значит, что обе библиотеки совместно владеют всем состоянием. Реальные границы
задаются интерфейсами, refcount, очередью объектов и порядком shutdown.
Практичная новая структура может быть внутренним набором модулей `platform`,
`resources`, `world`, `mission`, `terrain`, `render`, `animation`, `effects`,
`behavior`, `physics`, `audio` и `network`. Важно сохранить не DLL-границы, а
контракты: имена ресурсов, порядок поиска, fallback-ветки, object ID, момент
создания mirror objects, численное поведение и последовательность событий.
## Роли модулей
`iron3d.dll` создаёт shell и game objects, читает `iron_3d.ini`, поднимает
display, sound, CD-audio, network и настройки World3D, загружает миссионные и
UI-конфигурации, содержит message pump и вызывает расчёт/рендер игры.
`services.dll` работает как service locator. Через него запрашиваются display,
GUI, network manager, resource manager, sound server и timer. Этот слой отделяет
высокоуровневую игру от деталей создания устройств.
`World3D.dll` -- центральный runtime: очередь объектов, идентификаторы,
события, отложенное удаление, game time, pause, manual input, камера,
material/texture/lightmap managers, сетевые mirrors, расчёт и 3D-проход.
`Terrain.dll` отвечает не только за землю. В его область входят ландшафт,
здания, визуальный слой мира, камера, shade/state layer, primitive buffers,
сортировочные слои, источники света, тени, microtextures, атмосфера, дождь,
молнии, солнце и flares.
`Ngi32.dll` содержит низкоуровневые сервисы: DirectDraw/Direct3D-era renderer,
DirectSound, readers `NRes`/`RsLi`, память, часы, математику, пересечения,
определение CPU и таблицу быстрых процедур `g_FastProc`.
Предметные DLL закрывают отдельные области. `AniMesh.dll` загружает модели и
агентов. `ArealMap.dll` строит spatial graph и маршруты. `Behavior.dll`
реализует поведение юнитов. `ai.dll` содержит стратегический AI и миссионные
сценарии. `Wizard.dll` корректирует локальное движение. `Control.dll`
обслуживает физическую модель и столкновения. `Effect.dll` создаёт runtime-FX.
`MisLoad.dll` читает миссионные данные. `Net.dll` инкапсулирует DirectPlay.
`Joystick.dll` работает через DirectInput.
## Поток данных
Миссия не создаёт готовый кадр напрямую. Данные проходят через несколько
уровней: описание объекта, прототипы, ресурсы, runtime-object, контроллеры,
simulation state, render items и только затем платформенный renderer.
```text
mission data
-> object identity and properties
-> prototype registry
-> model/material/texture/effect resources
-> World3D object + domain controllers
-> simulation state
-> visible render items
-> Ngi32 render interface
-> DirectX-era device
```
Этот поток объясняет, почему нельзя объединять физический архив, metadata entry,
декодированный payload и готовый runtime-кэш. У каждого уровня свой срок жизни,
собственный refcount и собственные ошибки. Детали ресурсного конвейера описаны
в [Томе III](03-resources.md), а сборка мира из миссии -- в [Томе IV](04-world.md).
## Bootstrap
`iron_3d.exe` -- небольшой PE32/x86 bootstrap размером 36 864 байта. Основная
игровая логика находится в `iron3d.dll`. Исполняемый файл создаёт Win32-процесс,
подготавливает окружение, загружает библиотеку и получает восемь публичных
точек входа:
```text
createShell deleteShell
createGame deleteGame
createSubsystems deleteSubsystems
getIGame getIShell
```
Эти функции образуют внешнюю границу игры. `createShell` создаёт оболочку
интерфейса и меню, `createGame` -- объект игровой логики, `createSubsystems` --
аппаратные и runtime-сервисы. Getter-функции возвращают уже созданные объекты.
Запуск удобно читать как конечный автомат:
```text
PROCESS_CREATED
-> LIBRARY_READY
-> ENTRYPOINTS_READY
-> SHELL_CREATED
-> GAME_CREATED
-> SUBSYSTEMS_READY
-> MAIN_LOOP
-> SUBSYSTEMS_CLOSED
-> GAME_DELETED
-> SHELL_DELETED
```
Каждый переход имеет обратное действие. Если display, sound или другой
обязательный сервис не создан, главный цикл не начинается, но уже созданные
объекты освобождаются в обратном порядке. Новая оболочка запуска должна
работать из каталога оригинальной установки, сохранять смысл относительных
путей, создавать окно до графической подсистемы и закрывать частично поднятые
сервисы без предположения, что init дошёл до конца.
Bootstrap обеих полных частей побайтно одинаков, хотя файл второй части может
иметь другое имя:
```text
size 36 864
entry RVA 0x147E
SHA-256 f476af85c034a4b4f34f49d0806e4dff397b5da0ee26d382a7674231144979f7
```
Следовательно, различия полных частей начинаются после передачи управления DLL
и игровым данным. Адреса executable демоверсии относятся к другой binary
profile и не должны переноситься на полные версии без проверки hash.
## Инициализация подсистем
Iron3D разделяет создание высокоуровневых объектов и создание подсистем.
`createShell` конструирует оболочку пользовательского интерфейса, `createGame`
создаёт объект игры, а `createSubsystems` связывает их с display, sound,
network и World3D.
Высокоуровневая последовательность выглядит так:
```text
прочитать iron_3d.ini
-> получить display service
-> создать окно и графическое устройство
-> проверить доступность 3D-драйвера
-> выбрать CURRENT_D3DCARD
-> получить sound service и настроить громкость
-> создать network instance и передать application GUID
-> создать World3D game settings
```
Ошибка отсутствующего 3D-устройства обрабатывается отдельно от ошибок ресурсов:
это разные стадии запуска. Конфигурация влияет не только на разрешение. В
runtime попадают графическая карта, громкость эффектов, CD-audio, режим
CD-sound, сетевое приложение и World3D settings. Application GUID сетевой
подсистемы:
```text
{3C1D1F01-A870-11D1-8400-000021B14415}
```
Один и тот же GUID передаётся сетевому объекту и service layer. Если он
разойдётся, экземпляры игры станут логически разными приложениями, даже при
исправном транспорте.
## `stdInitGame`
После платформенных сервисов World3D создаёт внутренний runtime:
1. Создаёт глобальную очередь объектов.
2. Сохраняет window handle и режим игры.
3. При нужном режиме ограничивает курсор областью окна.
4. Получает или создаёт 3D sound object.
5. Загружает реестр адресов компонентов из `Comp.ini`.
6. Получает или создаёт 3D renderer.
7. Читает профиль возможностей renderer.
8. Загружает component type 6.
9. Для multiplayer создаёт NetWatcher.
10. Получает world-interface из Terrain.
11. Устанавливает исходные параметры света и тумана.
Порядок важен. World objects не должны появляться до queue, ресурсы рендера --
до renderer, сетевые mirror objects -- до NetWatcher. В новой реализации у
каждого этапа должен быть явный признак успешного создания, чтобы shutdown мог
безопасно разобрать неполный init.
## Завершение
Shutdown идёт в обратном направлении: прекращаются игровые расчёты и сетевые
наблюдатели, разбираются отложенные операции, освобождаются world objects и
менеджеры, затем renderer и sound, затем game settings и platform services.
Ограничение курсора снимается, глобальные ссылки очищаются.
Полезный протокол завершения:
1. Запретить новые события и новые объекты.
2. Дождаться выхода из calculation/render traversal.
3. Разобрать очередь deferred operations.
4. Отсоединить объекты от очереди, контроллеров и менеджеров.
5. Освободить managers и singletons после их consumers.
6. Закрыть устройства и платформенные сервисы.
Такой порядок защищает от dangling-ссылок между World3D, Terrain, renderer,
sound и сетевым слоем.
## Главный цикл
Главный цикл -- не одна функция `update_and_render`, а расписание, связывающее
Win32 messages, input, игровые события, таймеры, сеть и renderer. Системная
очередь сообщает об активации окна, вводе, изменении состояния процесса и
выходе. Очередь World3D рассчитывает игровые объекты. У этих очередей разные
правила времени и владения, поэтому их нельзя смешивать в один контейнер.
Подтверждённые точки вызова в одном из профилей:
```text
stdCalculateGame RVA 0x5FA94, 0x604C1, 0x6086B
ClearManualEventsList RVA 0x6052F
stdRenderGame RVA 0x60B2F
UpdateManualEventsList в обработчике сообщений около RVA 0xA3759
```
Смысловой skeleton:
```c
while (running) {
stdCalculateGame();
clear_keyboard_snapshot();
update_shell_and_mode();
ClearManualEventsList();
process_window_messages();
update_timers_ui_gameplay_network();
if (mode_requires_extra_step) stdCalculateGame();
if (render_enabled) {
stdSetCurrentCamera(camera);
stdRenderGame(camera);
} else {
sleep_briefly();
}
update_post_render_state();
}
```
Ввод из window messages накапливается между расчётными шагами. Если читать
клавиатуру только внутри рендера, события будут теряться при пропущенных кадрах
или отключённом выводе.
## `stdCalculateGame`
Calculation pass сначала очищает или подготавливает список manual events,
увеличивает внутренний depth/counter и опрашивает input device. Если устройство
временно потеряно, выполняется повторное получение доступа и чтение повторяется.
Затем при незамороженной игре выставляется признак `in_calculation` и вызывается
основной traversal очереди объектов.
```text
prepare input/events
-> enter calculation
-> dispatch queue events
-> objects update behavior and transforms
-> leave calculation
-> apply deferred operations
-> occasional cache maintenance
```
После traversal разбирается deferred-delete list. Объект может запросить
собственное удаление во время события, но память освобождается только после
завершения обхода. Периодически также очищаются давно неиспользуемые ресурсы и
объекты по порогам часов порядка 20 и 60 секунд.
Совместимый runtime должен иметь явный traversal depth или флаг
`in_calculation`. Нельзя полагаться на то, что контейнер выдержит удаление
текущего элемента из обработчика события.
## Жизненный цикл кадра
Рендер читает состояние, подготовленное расчётом. Кадр начинается до renderer-а:
message pump уже накопил ввод, World3D уже обновил объекты, отложенные операции
и анимации, после чего выбирается камера и обновляется listener звука.
```text
system messages and input
-> simulation calculation
-> deferred object operations
-> animation and transforms
-> camera and sound listener
-> visibility and render queues
-> materials and draw passes
-> renderer completion
-> end-of-render callbacks and UI
```
В `World3D::stdRenderGame` виден крупный каркас: установка camera/viewport,
renderer frame boundaries, traversal мира, завершение world/shade path,
renderer completion, снятие `in_render`, восстановление viewport и рассылка
end-of-render callbacks. Эти callbacks позволяют объектам безопасно обновить
временные ресурсы после того, как draw-команды больше их не используют.
Один calculation step не обязан соответствовать одному изображению. Главный
цикл допускает дополнительный вызов `stdCalculateGame` и режим, в котором
расчёт продолжается без вывода кадра. Поэтому нужно хранить отдельно:
1. монотонные платформенные часы;
2. игровое время с pause и масштабированием;
3. длительность текущего calculation step;
4. локальное время анимации и FX;
5. реальные часы обслуживания кэшей.
Игровую логику нельзя выводить из render delta: изменение частоты кадров тогда
изменит движение, камеру и сценарные таймеры. Подробности render item и рисков
кадровой совместимости вынесены в справочник [Render frame](../reference/render-frame.md).
## World3D
World3D связывает игровые объекты, события, время, ввод, камеру, сетевые
отражения и визуальное представление. Он не содержит всю предметную логику:
движение делегируется Behavior/Wizard, физика -- Control, мир -- Terrain. Его
задача -- общая идентичность, порядок вызовов и безопасный жизненный цикл.
`CreateQueue` создаёт singleton-объект размером 20 байт, а `GetQueue`
возвращает его. Очередь служит центральным маршрутизатором событий и операций
над объектами.
Публичный слой предоставляет отдельные функции для локальных и сетевых
объектов:
```text
CreateObject
AddObjectToGame
AddNewObjectToGame
CreateMirrorObject
AddMirrorObjectToGame
AddNewMirrorToGame
```
Разделение "создать" и "добавить в игру" означает два этапа: сначала выделить и
настроить instance, затем зарегистрировать его в общей системе. Это позволяет
loader-у заполнить свойства до появления объекта в расчётной очереди.
## Идентичность объектов
Object ID кодирует не только порядковый номер. Проверки диапазонов показывают
разбиение на номер игрока, класс и индекс. Mirror object представляет объект,
владельцем которого является другой участник. Локальный runtime хранит его
видимое состояние, но источник авторитетных изменений находится удалённо.
```c
struct ObjectId {
uint32_t raw;
uint16_t owner_player;
uint16_t class_and_index;
};
```
Точное битовое разбиение нужно брать из сетевых функций. На уровне API уже
сейчас полезно разделить логические свойства: `is_local`, `is_mirror`, `owner`,
`class` и `index`.
Минимальный runtime-object должен хранить identifier, type, owner, transform,
active state, ordered property bag, ссылки на controllers, участие в расчёте и
рендере, сетевой статус и флаг отложенного удаления. Специализированные DLL
могут быть представлены компонентами, но порядок их вызовов задаёт World3D.
## Отложенное удаление
`DeleteGameObject` проверяет, идёт ли calculation pass. Если обход активен и
удаление не принудительное, объект помещается в deferred list. `KillGameObject`
отправляет запрос через очередь, а не освобождает память напрямую.
```c
void request_delete(Object* o) {
if (world.in_calculation) {
world.deferred_delete.push_back(o);
o->pending_delete = true;
} else {
world.detach_and_release(o);
}
}
```
Это защищает итераторы, связи и текущий стек вызовов. Любая новая подсистема,
способная удалить объект из обработчика события, обязана пользоваться тем же
механизмом.
Регистрация в очереди и владение памятью -- разные понятия. Удаление из мира не
всегда означает немедленное освобождение instance: часть объектов и managers
использует intrusive reference count, а renderer, sound и resource managers
могут возвращать уже существующий singleton с увеличенным счётчиком. Поэтому
global manager закрывается после всех объектов, которые на него ссылаются.
## Детерминизм
Даже при одинаковых формулах результат зависит от порядка. Стабильный runtime
сохраняет последовательность queue traversal, момент формирования input
snapshot, порядок сетевых сообщений, обработку deferred operations и порядок
обращений к RNG. Оптимизация и многопоточность допустимы только при
детерминированном объединении результатов.
Для переносимой реализации полезно разделить scheduler phases и immutable
render snapshot. Это архитектурная рекомендация для новой реализации, а не
утверждение о точном layout исходных C++ classes.
## Стабильность между сборками
Внешняя архитектура полных Частей 1 и 2 сохраняет те же пятнадцать DLL, 313
exports, имена, ordinals и import sets. Побайтно идентичны:
```text
ai.dll, Behavior.dll, Joystick.dll, MisLoad.dll, Net.dll,
Ngi32.dll, Terrain.dll, Wizard.dll, World3D.dll
```
Пересобраны:
```text
AniMesh.dll, ArealMap.dll, Control.dll, Effect.dll,
iron3d.dll, services.dll
```
Это разделяет переносимость выводов. World3D lifecycle, Terrain, NRes/RsLi
readers, mission loader, AI/Behavior/Wizard, DirectPlay wrapper и joystick
adapter подтверждаются одной машинной реализацией. Model/agent runtime,
collision, effects, shell/composition и service layer требуют отдельного
сравнения поведения Частей 1 и 2.
Для `World3D.dll` Частей 1 и 2 применим общий hash:
```text
World3D.dll SHA-256
17e4a3089b2583a8cf2356c9db0390b1aba138356a09130d79b4e7e4791da61e
```
RVA внутри `iron3d.dll` нельзя считать общими без проверки конкретного файла:
эта DLL пересобрана между частями, а демоверсия имеет отдельный binary profile.
Смысловая последовательность цикла переносится как контракт scheduler-а, но
адреса остаются build-specific.