aboutsummaryrefslogtreecommitdiff
path: root/docs/tomes/02-architecture.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/tomes/02-architecture.md')
-rw-r--r--docs/tomes/02-architecture.md472
1 files changed, 472 insertions, 0 deletions
diff --git a/docs/tomes/02-architecture.md b/docs/tomes/02-architecture.md
new file mode 100644
index 0000000..b44bd51
--- /dev/null
+++ b/docs/tomes/02-architecture.md
@@ -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.