Files
fparkan/docs/tomes/01-guide.md
T
Valentin Popov aa51f3574d chore: simplify project to engine docs and tests
Remove planning and acceptance scaffolding while retaining the native Vulkan mission preview, format readers, runtime algorithms, and ordinary Rust tests. Keep the book aligned with the runnable project and validate checked-in shaders without generated tool metadata.
2026-09-06 05:22:12 +04:00

14 KiB
Raw Blame History

I. Путеводитель

Игра выглядит как непрерывное движение, но компьютер строит её из отдельных шагов. Он читает ввод, обновляет мир, вычисляет положение моделей и рисует очередной кадр. Чтобы воспроизвести старую игру, нужно понять как её файлы, так и правила этих шагов.

Книга не требует опыта игровой разработки. Ниже объясняются основные понятия, а последующие главы показывают их на конкретных данных «Паркана». Подробные таблицы адресов и байтов полезны при реализации; при первом чтении их можно пропустить.

+0x10 означает смещение в байтах от начала записи. RVA — адрес относительно начала загруженной DLL. u16, u32, i16, float32 — числа фиксированного размера. LE означает little-endian: младший байт числа записан первым. EOF — конец файла. Неизвестные поля сохраняются без изменений.

Движок как программа длительного действия

Обычная прикладная программа получает запрос, вычисляет результат и заканчивает работу. Игра живёт в цикле: прочитать ввод, обновить состояние мира, сформировать звук и изображение, показать кадр и повторить. Движок -- набор подсистем и соглашений, которые делают этот цикл устойчивым.

Simulation отвечает на вопрос "что произошло в мире": куда переместился объект, кого он видит, сколько у него здоровья, сработал ли эффект, изменился ли маршрут или приказ. Rendering отвечает на другой вопрос: "как текущее состояние показать". В корректной архитектуре рендер не решает игровые правила, а читает подготовленное состояние.

Tick -- один шаг расчёта. Frame -- одно изображение. Они могут выполняться с разной частотой: игра способна рассчитать несколько шагов между двумя показами или временно не рисовать, не останавливая логику. Поэтому время, накопление input, порядок callbacks и момент удаления объектов считаются частью контракта.

Мир, сцена и объект

Мир -- долгоживущее состояние миссии: ландшафт, объекты, время, погода, принадлежность к кланам и глобальные сервисы. Сцена -- представление той части мира, которую можно обработать для текущей камеры. Игровой объект -- сущность с идентификатором, положением, набором свойств и поведением.

В Iron3D объектами управляет World3D. Объекты регистрируются в общей очереди, получают события, участвуют в расчёте и могут быть удалены отложенно, чтобы не разрушить обход коллекции посреди шага. Это важнее, чем конкретный контейнер в новой реализации: совместимость определяется моментом наблюдаемого добавления, обновления и удаления.

Мир не равен renderer scene graph. Один объект может иметь runtime state, controller, сетевой mirror, визуальную модель, collision bounds и script state. Часть этих данных нужна для gameplay, часть -- для вывода, часть -- для сохранения и воспроизведения.

Ресурс, модель и материал

Ресурс -- именованный блок данных, который можно найти и загрузить. Архивы NRes и RsLi содержат таблицы таких блоков. Имя, индекс, размер, offset, compression method и fallback-правило являются частью контракта загрузки.

Модель описывает форму объекта. Она состоит из вершин, индексов, узлов, групп треугольников, слотов материалов и auxiliary streams. Vertex хранит положение и обычно дополнительные атрибуты: нормаль для освещения и UV-координату для выборки texture. Triangle -- три вершины, образующие примитив. Index buffer хранит номера вершин и позволяет переиспользовать их между треугольниками. Batch -- непрерывный диапазон индексов, который рисуется одним материалом и одним набором состояний.

Материал описывает способ отображения поверхности: texture references, цвет, прозрачность, режимы смешивания и анимацию параметров. Texture -- изображение в памяти графической системы. Mip-уровни -- уменьшенные копии изображения для дальних объектов. Lightmap -- дополнительная texture с заранее рассчитанным освещением.

Runtime должен связывать эти уровни по цепочке: миссия выбирает объект, объект ссылается на prototype, prototype приводит к модели, модель -- к WEAR, материалам, textures и lightmaps. Ошибка на любом участке этой цепочки может не проявиться в parser-е, но проявится в игровом кадре.

Пространственные понятия

Transform переводит точку из локальных координат модели в координаты мира, камеры и экрана. Иерархия узлов позволяет одному элементу наследовать движение другого. LOD выбирает менее подробную геометрию вдали. Culling отбрасывает то, что не видно. Bounds -- упрощённая оболочка объекта, обычно сфера или AABB, используемая для быстрых тестов.

Collision отвечает на геометрические пересечения. Navigation ищет допустимый маршрут. В Iron3D эти задачи разделены: Control обслуживает физическую модель и столкновения, а ArealMap хранит пространственные области и связи между ними.

Важно не смешивать визуальные и игровые упрощения. Render bounds могут быть достаточны для отсечения, но не обязаны совпадать с collision shape. Навигация может использовать areal graph, который не является ни mesh-ем модели, ни геометрией ландшафта в renderer-е.

Графический конвейер

Процессор выбирает видимые объекты, готовит матрицы, материалы и списки примитивов. Графический backend передаёт вершины, индексы, textures и state драйверу. Видеокарта преобразует вершины в координаты экрана, разбивает треугольники на фрагменты, проверяет глубину, смешивает цвет и записывает результат в буфер кадра. После завершения буфер становится видимым пользователю.

Для совместимости важны не только данные draw call. Контракт включает frame boundaries, viewport, camera state, порядок world traversal, material resolve, shadow/transparent/FX subpasses, завершение renderer-а, восстановление state и callbacks после рендера. Если часть имён vtable slots ещё не доказана, новая реализация должна фиксировать крупный порядок операций и оставлять детализацию проверяемой.

Практический словарь реализации

Handle -- компактная ссылка на управляемый объект. Cache -- сохранённый результат загрузки или декодирования. Reference count -- число владельцев ресурса. Fallback -- предписанный запасной вариант при отсутствии данных. Invariant -- условие, которое всегда должно быть истинным для корректного файла или runtime-состояния. Determinism -- повторяемость результата при одинаковых входных данных и порядке событий.

Strict mode -- режим parser-а, который принимает только корректный файл: верные magic, версии, размеры, ranges, индексы и точный EOF. Lossless mode -- режим чтения/записи, который сохраняет неизвестные поля, padding, gaps и raw payload без нормализации. Quirk -- именованное отклонение, разрешённое только после проверки на реальных данных или исполняемом коде.

Эти слова используются как технические термины. Если глава называет значение fallback-ом, invariant-ом или quirk-ом, это должно иметь проверяемое последствие в reader-е, writer-е или runtime.

Как читать C/C++-схемы структур

Структуры в главах описывают байтовый layout, а не переносимый C++ object model. Если поля на диске идут без padding, reader должен читать их по offsets либо использовать явно проверенный packed layout. Прямое отображение native struct допустимо только при доказанном размере, выравнивании и endian-правиле.

sizeof обязательно проверяется static_assert или эквивалентным compile-time test. Это особенно важно для records, где 32-битное поле начинается после нечётного числа 16-битных или 8-битных полей: стандартное выравнивание современного compiler-а может вставить скрытые bytes и изменить offsets.

Для variable-length форматов предпочтителен bounded cursor:

  1. Прочитать header и проверить минимальный размер.
  2. Проверить, что offsets и sizes лежат внутри текущего блока.
  3. Прочитать таблицы до объявленного count, не до "пока получается".
  4. Проверить ссылки между таблицами.
  5. Дойти до точного EOF или сохранить явно разрешённый trailing payload.

Writer пересчитывает только производные значения: размеры, offsets, число записей, сортировочные таблицы и padding, если правило доказано. Unknown fields и reserved ranges сохраняются побайтно.

Проверка предположений

Формат подтверждается тем, как оригинальная программа читает запись, и тем, какие значения встречаются в игровых ресурсах. Изображение из стороннего просмотрщика помогает заметить ошибку, но само по себе не доказывает формулу. Для восстановленного поведения оставляем небольшой тест с конкретным входом и ожидаемым результатом. Для неопределённого поля прямо пишем, что его смысл неизвестен.

RVA зависит от сборки DLL. Сравнивая инструкции, сначала сверяют соответствующий файл с таблицей оригинальных модулей.