# 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.