aboutsummaryrefslogtreecommitdiff
path: root/docs/tomes
diff options
context:
space:
mode:
Diffstat (limited to 'docs/tomes')
-rw-r--r--docs/tomes/01-guide.md253
-rw-r--r--docs/tomes/03-resources.md15
-rw-r--r--docs/tomes/05-render.md1235
-rw-r--r--docs/tomes/06-behavior.md225
-rw-r--r--docs/tomes/07-implementation.md779
-rw-r--r--docs/tomes/08-evidence.md779
6 files changed, 423 insertions, 2863 deletions
diff --git a/docs/tomes/01-guide.md b/docs/tomes/01-guide.md
index cc4e4c0..e83c9b8 100644
--- a/docs/tomes/01-guide.md
+++ b/docs/tomes/01-guide.md
@@ -1,115 +1,19 @@
-# I. Путеводитель и методика
-
-Первый том задаёт язык и правила всей документации. Он объясняет, как читать
-технические главы, какие термины используются для игрового runtime, как
-разделяются уровни уверенности и какие требования предъявляются к реализации,
-которая должна работать с оригинальными данными без потери информации.
-
-Документация рассчитана на разработчика, который уже умеет читать C/C++,
-байтовые форматы, PE-модули и графические pipeline, но не обязательно знаком с
-Iron3D. Поэтому этот том не описывает один конкретный crate, package или
-физическое деление будущего кода. Он фиксирует контракты: что должно быть
-прочитано, сохранено, рассчитано и показано.
-
-## Назначение книги
-
-Книга ведёт от общей архитектуры Iron3D к точным форматам данных и алгоритмам
-исполнения. Практическая цель -- реализация, способная открыть оригинальный
-каталог *Parkan: Iron Strategy*, загрузить миссию, создать мир, провести
-игровой шаг и сформировать кадр.
-
-Форматы в главах описываются как байтовые контракты. Если указано поле
-`+0x10`, это означает расположение в потоке или структуре данных, а не
-разрешение читать файл прямым `reinterpret_cast`. Для постоянных layouts
-используются offsets, проверки размеров, bounded cursor и явное сохранение
-неизвестных байтов. Для versioned и variable-length записей приоритет имеет
-последовательный parser с контролем границ.
-
-Игровое поведение описывается не только размером структур. Совместимая
-реализация должна учитывать порядок событий, время, fallback-правила,
-идентификаторы объектов, численные ограничения, состояние материалов,
-границы кадра и правила завершения операций.
-
-## Маршруты чтения
-
-**Читатель, новый для игровой разработки**, начинает с базовых понятий этого
-тома, затем переходит к архитектуре, игровому циклу и вводу в рендер. После
-этого имеет смысл читать главы о миссиях, мире и ресурсных форматах.
-
-**Разработчик совместимого движка** читает тома II-VII линейно. Технические
-главы имеют одинаковую логику: назначение подсистемы, данные на диске,
-представление в памяти, алгоритм работы, проверки и требования к новой
-реализации.
-
-**Аналитик оригинальной программы** использует этот том вместе с разделами о
-доказательной базе, ABI, результатах корпусных проверок и границах знания.
-Факты, согласованные выводы и открытые вопросы должны оставаться разделёнными:
-это позволяет расширять реализацию без подмены проверенных контрактов
-удобными догадками.
-
-## Состав документации
-
-1. **Путеводитель и методика** -- язык предметной области, правила чтения и
- процедура проверки.
-2. [**Запуск, архитектура и игровой цикл**](02-architecture.md) -- от
- `iron_3d.exe` до расчёта и вывода кадра.
-3. [**Ресурсная система и форматы**](03-resources.md) -- архивы, кэши, реестры
- и служебные данные.
-4. [**Мир, миссии и игровой runtime**](04-world.md) -- TMA, ландшафт, ареалы и
- создание объектов.
-5. [**Геометрия, материалы и рендер**](05-render.md) -- от вершины модели до
- изображения на экране.
-6. [**Поведение, управление, звук и сеть**](06-behavior.md) -- интерактивные
- подсистемы.
-7. [**Руководство по полной реализации**](07-implementation.md) -- предлагаемая
- архитектура и порядок работ.
-8. [**Справочник и доказательная база**](08-evidence.md) -- ABI,
- конфигурация, статистика и открытые вопросы.
-
-Дополнительные краткие определения собраны в
-[глоссарии](../appendices/glossary.md). Технические области, где контракт ещё
-не закрыт полностью, перечислены в
-[границах знания](../appendices/knowledge-boundaries.md).
-
-## Условные обозначения
-
-`+0x10` означает смещение поля относительно начала структуры или записи.
-`RVA 0x13B60` -- адрес относительно базы PE-модуля. `u16`, `u32`, `i16` и
-`float32` обозначают типы фиксированной ширины. `LE` означает little-endian.
-`payload` -- полезные данные записи после метаданных контейнера. `EOF` -- точное
-завершение файла или ограниченного блока.
-
-Если в тексте указан hash, RVA или ordinal, значение относится к явно
-обозначенному binary profile. Адреса разных сборок не объединяются по имени
-функции. При публикации функции нужны минимум модуль, SHA-256 сборки и RVA.
-
-Размеры структур выражаются в байтах. Счётчики и offsets считаются частью
-формата, даже когда их можно восстановить из длины файла. Padding, reserved
-поля, неизвестные хвосты и gaps не нормализуются без доказанного правила.
-
-## Совместимость
-
-Слово "совместимость" в этой книге имеет несколько уровней.
-
-**Reader** умеет открыть файл, проверить границы, извлечь известные поля и
-сохранить неизвестные bytes так, чтобы данные можно было записать обратно.
-
-**Viewer** умеет показать ресурс: модель, texture, material, эффект или карту.
-Viewer может быть полезен для анализа, но он не доказывает поведение runtime.
-
-**Runtime** умеет создать мир, зарегистрировать объекты, исполнять события,
-обновлять время, применять контроллеры, выбирать видимое состояние и передавать
-его рендеру.
-
-**Полноценный движок** дополнительно воспроизводит порядок операций, численные
-правила, fallback-поведение, resource lifetime, reference ownership, pause,
-manual input, сетевые идентификаторы, boundaries кадра и состояние
-интерактивных подсистем.
-
-Поэтому файл может быть "прочитан правильно", но всё ещё не быть реализованным
-на уровне движка. Например, reader MSH может восстановить вершины и индексы,
-viewer может нарисовать mesh, а runtime обязан ещё сохранить material slots,
-animation state, bounds, LOD, visibility, collision и связи с объектом мира.
+# I. Путеводитель
+
+Игра выглядит как непрерывное движение, но компьютер строит её из отдельных
+шагов. Он читает ввод, обновляет мир, вычисляет положение моделей и рисует
+очередной кадр. Чтобы воспроизвести старую игру, нужно понять как её файлы,
+так и правила этих шагов.
+
+Книга не требует опыта игровой разработки. Ниже объясняются основные понятия,
+а последующие главы показывают их на конкретных данных «Паркана».
+Подробные таблицы адресов и байтов полезны при реализации; при первом чтении
+их можно пропустить.
+
+`+0x10` означает смещение в байтах от начала записи. `RVA` — адрес относительно
+начала загруженной DLL. `u16`, `u32`, `i16`, `float32` — числа фиксированного
+размера. `LE` означает little-endian: младший байт числа записан первым.
+`EOF` — конец файла. Неизвестные поля сохраняются без изменений.
## Движок как программа длительного действия
@@ -250,122 +154,15 @@ Writer пересчитывает только производные значе
записей, сортировочные таблицы и padding, если правило доказано. Unknown fields
и reserved ranges сохраняются побайтно.
-## Иерархия доказательств
-
-Документация использует четыре уровня уверенности.
-
-**Прямое наблюдение** -- поле, значение или последовательность видны в
-инструкции программы, таблице PE, экспорте, строке, обработчике файла или в
-самом ресурсе. Это самый сильный уровень.
-
-**Корпусное подтверждение** -- правило проверено на всех подходящих файлах
-одного или нескольких явно названных наборов: демоверсии, Части 1 и Части 2.
-Например, базовый корпус содержит 435 моделей MSH, 518 textures Texm и 923
-эффекта FXID, прошедших структурные проверки без ошибок; полные части расширяют
-эту матрицу вариантов.
-
-**Согласованный вывод** -- назначение восстановлено по нескольким независимым
-признакам: вызывающим функциям, vtable slots, строкам ошибок, диапазонам
-значений и связям между форматами. Такой вывод пригоден для реализации, но его
-численные детали следует проверять тестами.
-
-**Открытый вопрос** -- данные можно читать и сохранять, однако предметный смысл
-поля или редкой ветки не доказан. Такие bytes нельзя обнулять,
-переупорядочивать или превращать в authoring API.
-
-Уровень уверенности должен быть виден из формулировки. "Поле равно" означает
-проверенный layout или значение. "Вероятно отвечает за" означает согласованный
-вывод. "Неизвестно" означает сохранять без изменения и не строить вокруг этого
-публичный контракт.
-
-## Проверенные материалы
-
-Локальный набор проверки включает демоверсию, полные каталоги Частей 1 и 2,
-исполняемые файлы, 15 DLL каждой сборки и игровые ресурсы. DLL из
-первоначального архива и DLL демоверсии совпали по SHA-256: `15/15`, поэтому
-выводы по этому коду и demo-ресурсам образуют один доказательный профиль.
-
-Исполняемый файл демоверсии `iron_3d.exe` имеет размер 36 864 байта, PE32/x86,
-entry RVA `0x141E`, image base `0x400000` и SHA-256
-`b0a8b0db1c3a8698c4d4604d89c655496bd91ac1f8859a455e8a45838aebfbd6`.
-
-Исполняемые файлы Частей 1 и 2 также имеют размер 36 864 байта и побайтно
-совпадают между собой, но относятся к другому binary profile: entry RVA
-`0x147E`, SHA-256
-`f476af85c034a4b4f34f49d0806e4dff397b5da0ee26d382a7674231144979f7`.
-
-Полные каталоги Частей 1 и 2 суммарно включают 60 TMA, 1 101 unit DAT, 254
-NRes-файла и 14 975 NRes entries. Все контейнеры и TMA прошли bounded parser до
-точного EOF; полный достижимый граф обеих частей разрешился без ошибок.
-
-## Процедура проверки
-
-Проверка строится как воспроизводимая цепочка:
-
-1. Снять PE-метаданные, хэши, импорты, экспорты, ordinals, RTTI и строки.
-2. Построить граф вызовов между модулями и отметить фабрики подсистем.
-3. Разобрать функции запуска, загрузчики файлов, главный цикл и критические
- vtable-вызовы.
-4. Проверить форматы независимыми reader-скриптами с контролем границ и точного
- завершения файла.
-5. Построить цепочку миссия -> объект -> прототип -> модель -> материал ->
- texture.
-6. Сравнить счётчики, диапазоны, ссылки и размеры на всём доступном корпусе.
-
-Ключевой результат сквозной проверки демо-миссий: все 201 объектов шести
-миссий разрешились в 501 запрос прототипов, затем в 501 модель, 501 таблицу
-WEAR, 3 879 слотов материалов и 5 085 ссылок на textures или lightmaps. Ошибок
-в фактически исполняемом пути нет.
-
-## Что не считается доказательством
-
-Удобное имя поля не доказывает его назначение. Совпадение layout с текущей
-реализацией не доказывает поведение оригинального runtime. Успешный viewer не
-доказывает writer. Успешный reader одного файла не доказывает формат всего
-корпуса. Совпадение ABI не доказывает побайтную идентичность всех сборок.
-
-Если локальные данные и предположение расходятся, приоритет имеют исполняемый
-код, реальные ресурсы и взаимные invariants между форматами. Неизвестное поле
-лучше оставить без имени, чем дать ему ложное предметное значение.
-
-## Требования к воспроизводимости
-
-Каждая новая реализация должна иметь strict parser mode, lossless roundtrip
-mode и набор corpus tests. Неизвестные поля сохраняются побайтно. Любое
-присвоенное полю имя должно сопровождаться наблюдаемым поведением или тестом.
-Численные правила -- округление, порядок умножения, RNG и время -- считаются
-частью формата исполнения, даже если файл читается правильно.
-
-Минимальный отчёт проверки должен фиксировать:
-
-1. build profile и hashes модулей;
-2. путь или ключ ресурса;
-3. размер входного файла и hash входных bytes;
-4. версию parser-а или commit реализации;
-5. список включённых quirks;
-6. число прочитанных записей и точку EOF;
-7. ошибки, предупреждения и unknown ranges;
-8. результат roundtrip, если writer участвует в проверке.
-
-Для runtime-проверок дополнительно нужны mission key, configuration, device
-profile, начальное состояние, input/time script и trace значимых callbacks.
-
-## Разделение профилей
-Binary profile описывает исполняемый код: PE-метаданные, exports/imports,
-ordinals, hashes, RVA и layout функций. Corpus profile описывает набор файлов:
-каталог, миссии, ресурсы, размеры, counts, variants и статистику parser-а.
-
-Эти профили нельзя смешивать без явной пометки. Один и тот же формат может
-иметь общий смысл в разных сборках, но отличаться редкими ветками, адресами
-функций или набором встреченных вариантов. Один и тот же address может иметь
-смысл только внутри конкретного module hash.
-
-При расширении документации новое утверждение должно отвечать на три вопроса:
+## Проверка предположений
-1. Где это видно напрямую?
-2. На каком корпусе это проверено?
-3. Что должна сделать реализация, если правило нарушено?
+Формат подтверждается тем, как оригинальная программа читает запись, и тем,
+какие значения встречаются в игровых ресурсах. Изображение из стороннего
+просмотрщика помогает заметить ошибку, но само по себе не доказывает формулу.
+Для восстановленного поведения оставляем небольшой тест с конкретным входом
+и ожидаемым результатом. Для неопределённого поля прямо пишем, что его смысл
+неизвестен.
-Если на один из вопросов нет ответа, утверждение остаётся согласованным выводом
-или открытым вопросом, а не закрытым контрактом.
+RVA зависит от сборки DLL. Сравнивая инструкции, сначала сверяют соответствующий
+файл с [таблицей оригинальных модулей](../reference/original-binaries.md).
diff --git a/docs/tomes/03-resources.md b/docs/tomes/03-resources.md
index fa5377a..29fef77 100644
--- a/docs/tomes/03-resources.md
+++ b/docs/tomes/03-resources.md
@@ -484,21 +484,6 @@ Visual dependency expansion наследует этот индекс на edges
diffuse texture и lightmap. Поэтому ошибка или asset в любой из этих фаз
сохраняет путь до конкретной записи unit DAT, а не только до миссионного object.
-Для воспроизводимой проверки используется `fparkan-cli prototype inspect`.
-Схема JSON `fparkan-prototype-inspect-v2` содержит lossless hex каждого
-32-byte поля unit record и materialized graph edges с `unit_component_index`.
-Например, для GOG AutoDemo:
-
-```powershell
-cargo run -p fparkan-cli -- prototype inspect `
- --root 'C:\GOG Games\Parkan - Iron Strategy' `
- --key 'UNITS\UNITS\AutoDEMO\w_m_wlk2.dat' --format json
-```
-
-Вывод фиксирует 18 component records, их MSH/WEAR/MAT0/Texm dependencies и
-точные parent edges. JSON предназначен для анализа и regression evidence, а
-не для интерпретации `kind`, links или opaque tails как готовой game logic.
-
## Вспомогательные форматы
MSH, материал и текстура отвечают за видимую форму. Полноценный прототип
diff --git a/docs/tomes/05-render.md b/docs/tomes/05-render.md
index e63c3ae..71cad1d 100644
--- a/docs/tomes/05-render.md
+++ b/docs/tomes/05-render.md
@@ -86,10 +86,8 @@ drivers и video modes, проверяет поддержку 3D, перевод
`IDirect3D` 1/2/3/7 и соответствующих `IDirect3DDevice`. Следовательно,
современная реализация заменяет наблюдаемую fixed-function семантику над этой
границей, а не должна эмулировать COM-объекты DirectX. Равный SHA-256 Ngi32 в
-Частях 1 и 2 зафиксирован в
-[evidence](../evidence/original_engine_hashes.md); это поддерживает единый
-Vulkan backend contract, но не отменяет раздельных corpus/capture baselines
-для изменённых assets и gameplay DLL.
+Частях 1 и 2 зафиксирован в [таблице оригинальных модулей](../reference/original-binaries.md);
+это поддерживает единый контракт Vulkan backend.
```text
enumerate adapters and video modes
@@ -361,9 +359,16 @@ local.set_translation(pose.position);
world[n] = world[parent(n)] * local;
```
-Для parity особенно важны x87-compatible округление при выборе frame index и
-порядок операций. Одинаковая формула на SSE может выбрать соседний кадр возле
-границы.
+Статический Vulkan preview берёт начальный кадр узла из явной ссылки прототипа
+на CTLD `.ctl`. Используются только включённые строки, а при повторе node ID
+поздняя строка заменяет раннюю. Без `--static-animation-frame` preview применяет
+эти настройки узлов; узлы без настройки остаются на кадре `0`. Явный
+`--static-animation-frame N` переопределяет их и задаёт `N` всем узлам.
+Результат — одна статическая поза, не обновление анимации, Control или AI.
+MSH sampler выбирает type 19 frame округлением `(time - 0.5)` к ближайшему
+целому с ties-to-even, затем интерполирует выбранный type 8 key и следующий
+key в исходном `time`; это portable-вариант native x87 conversion в default
+rounding mode, без обещания совпадения при любом x87 control word.
Проверки animation data:
@@ -613,11 +618,20 @@ Lightmap не является обычной diffuse texture. WEAR содерж
diffuse texture ломает LOD, atlas coordinates и динамическую модуляцию.
Тени проходят отдельным render pass. Terrain содержит пути для теней зданий и
-роботов, ограничения максимального числа, detail level и smoothing. Доказаны
-shadow manager/pass, настройки detail/smoothing/count и зависимость от
-Terrain/CShade; полная формула projection geometry для каждого caster требует
-dynamic trace. Unknown settings из `shade.cfg` читаются и сохраняются по
-именам, а не заменяются произвольными modern defaults.
+роботов, ограничения максимального числа, detail level и smoothing. Реализованы
+native visibility по шести Vulkan clip-плоскостям с радиусом sphere +100,
+порядок кандидатов и лимит 20 до построения страниц, projected-size LOD,
+directional/point light contribution, native page layout, raster, smoothing и
+receiver projection с edge clipping. Unknown settings из `shade.cfg` читаются и
+сохраняются по именам, а не заменяются произвольными modern defaults. Для native ray query
+surface descriptor в `+0x10` требует наличия всех переданных bits, а descriptor
+в `+0x14` исключает поверхности с любым переданным bit. Общий shadow query
+использует full exclusion mask `0x2000`; actor four-ray path получает compact
+mask `0x8008`, который landscape boundary разворачивает в full mask
+`0x00200020`. Sun path передаёт full `0x20`, кодирует его как compact bit `8`
+и затем получает тот же full bit после разворачивания. Hit record хранит в
+`+0x20` квадрат евклидовой длины луча, поэтому native four-ray fade использует
+`distance² * 1e-4`, а не линейную дальность.
Atmosphere manager создаёт world objects для фоновых и погодных явлений.
Отдельно подтверждены lightning, sun render, flare, `env_lightning`, rain
@@ -626,6 +640,9 @@ background sound и обязательные ссылки на lightning effect.
требует screen position и occlusion test, rain -- области рядом с observer,
sound -- listener. Их нельзя один раз запечь в terrain.
+Sun occlusion начинается от точки `camera + 0.5 * normalize(sun - camera)`;
+этот offset является частью native query, а не произвольным bias renderer-а.
+
RNG для lightning, atmosphere phases и FX должен иметь стабильный порядок.
Даже правильный средний интервал не даёт повторяемый кадр, если random values
запрашиваются в другой последовательности.
@@ -679,11 +696,14 @@ settings ID сохраняются.
```text
opcode = command_word & 0xFF
-enabled = (command_word >> 8) & 1
+native_flag = (command_word >> 8) & 1
```
-Bits 9-31 являются частью данных и сохраняются. Между командами нет
-выравнивания. Размер команды, включая word:
+`native_flag` передаётся созданному native command object как режим/feature
+flag и не отключает команду. Например, реальный `env_lightning` использует
+`native_flag == 0` для opcode 3, opcode 1 и opcode 2, и все три команды
+исполняются. Bits 9-31 являются частью данных и сохраняются. Между командами
+нет выравнивания. Размер команды, включая word:
```text
opcode 1 224 байта
@@ -842,1107 +862,94 @@ draw_indexed(item.batch.base_vertex,
item.batch.index_count);
```
-## Текущий контракт pipeline key
-
-`fparkan-render` теперь переносит вместе с каждым backend-neutral draw
-`LegacyPipelineState` и детерминированный `PipelineKey`. Ключ не хешируется:
-он явно упаковывает blend mode, depth mode, cull mode и флаг alpha-test, поэтому
-одинаковые snapshots дают одинаковый capture и будущий Vulkan cache не зависит
-от версии Rust или процесса. Alpha reference намеренно не входит в key: это
-dynamic material constant, а не структурный вариант graphics pipeline.
-
-Текущий static Vulkan viewer дедуплицирует состояния ranges при каждой сборке
-swapchain resources, создаёт один `vk::Pipeline` на уникальный `PipelineKey` и
-выбирает его непосредственно перед соответствующим `vkCmdDrawIndexed`.
-Кэш корректно уничтожается при recreate/teardown; setup failure откатывает уже
-созданные variants. Baseline оригинального MSH подтверждён с состоянием
-`opaque + depth disabled + cull disabled + alpha-test disabled`.
-
-`SourceAlpha`, front/back culling и depth modes имеют Vulkan mapping в этом
-кэше. При создании swapchain resources renderer выбирает capability-approved
-depth/stencil format, выделяет device-local attachment, очищает его в render
-pass и прикрепляет к каждому framebuffer. `TestWrite` включает depth test и
-write; `TestReadOnly` включает test без write. Alpha test выполняется во
-fragment shader: перед каждым draw renderer передаёт через push constant
-`alpha_test_reference / 255`, а shader отбрасывает fragment с меньшей alpha.
-При выключенном `alpha_test` cutoff принудительно равен нулю; reference остаётся
-dynamic material data и не входит в `PipelineKey`. Также не установлен источник
-значений для Batch20/MAT0: их
-поля нельзя объявлять blend/depth/cull mapping без dynamic capture или
-дополнительного дизассемблирования. Это частично реализованная compatibility
-boundary, а не заявление о готовой parity fixed-function state.
-
-### Capability-gated swapchain source for future pixel capture
-
-Pixel parity требует чтения результата GPU, но swapchain image разрешает это
-только если surface сообщает `VK_IMAGE_USAGE_TRANSFER_SRC_BIT`. Поэтому
-swapchain policy всегда запрашивает обязательный `COLOR_ATTACHMENT`, а
-`TRANSFER_SRC` добавляет лишь при фактической поддержке surface; выбранные raw
-usage bits попадают в deterministic plan и native smoke report. Это не создаёт
-readback buffer, не копирует pixels и не закрывает pixel-capture acceptance.
-
-На Windows GOG native smoke 2026-07-18 (`MTCHECK.MSH` из `system.rlb` и
-`DEFAULT.0` из `Textures.lib`) AMD Radeon Pro WX 3200 Series подтвердил
-`image_usage=17` (`COLOR_ATTACHMENT | TRANSFER_SRC`): 300 frames, 3 resize
-events, 2 swapchain recreations, Vulkan validation warnings/errors `0/0`.
-Следующий шаг должен выполнить явный image-to-buffer copy и сравнить полученные
-bytes с зафиксированным reference capture.
-
-### First synchronized Vulkan pixel-readback artifact
-
-The current artifact contract is one final swapchain image, not a
-concatenation of every swapchain allocation. After the final successful
-graphics submission, the renderer retains that image index; synchronized
-teardown reads only its matching buffer. The raw payload is therefore exactly
-`width * height * 4` bytes for the current format-50 path. Historical
-multi-image byte counts below describe the superseded concatenation behavior
-and are not comparable to this final-image contract.
-
-Stage 3 static viewer теперь выполняет фактический readback для surface с
-`TRANSFER_SRC`: на каждый swapchain image создаётся host-visible coherent
-`TRANSFER_DST` buffer. После render pass command buffer переводит image из
-`PRESENT_SRC_KHR` в `TRANSFER_SRC_OPTIMAL`, выполняет `vkCmdCopyImageToBuffer`
-и возвращает image в `PRESENT_SRC_KHR`. CPU отображает memory только после
-`vkDeviceWaitIdle` during shutdown; это исключает чтение GPU work in flight.
-Smoke JSON фиксирует число записанных copy-команд, final byte count и FNV-1a-64
-hash всех current-swapchain readback buffers, не сохраняя игровые pixels в
-репозитории.
-
-На GOG `MTCHECK.MSH`/`DEFAULT.0` AMD Radeon Pro WX 3200 Series выполнил 300
-copy-команд, final artifact 4,147,200 bytes и hash `2184179010340020629` при
-validation warnings/errors `0/0`. Повторный идентичный запуск дал тот же
-размер и hash. Это доказывает Vulkan copy/readback path только для нашего
-static viewer. Он ещё не захватывает original DirectDraw frame, не задаёт
-fixed original camera и не сравнивает два изображения, поэтому pixel-parity
-acceptance остаётся blocked.
-
-The following paragraph records the superseded multi-image export only as
-historical bootstrap evidence; it is not the current artifact contract.
-
-Smoke также сохранял raw artifact рядом с JSON: `<report-stem>.readback-vkformat-<raw>.raw`.
-Это были concatenated current-swapchain images in Vulkan order, каждый с dimensions
-из JSON и четырьмя bytes per pixel; format намеренно указан в имени файла,
-потому что bytes не перекодируются; JSON содержит actual raw enum. GOG selected `50` and produced a 4,147,200-byte file
-for two 960x540 images. Артефакт остаётся локальным output и не попадает в Git.
-
-Smoke option `--expected-readback <path>` выполняет exact byte comparison после
-readback и завершает run с первым differing byte или length mismatch. Fresh GOG
-run с предыдущим format-50 artifact прошёл этот gate. Это regression contract
-нашего static Vulkan viewer, не comparison с оригинальным renderer.
-Comparator прежде сравнивает `vkformat` identity из имен обоих artifacts и
-отклоняет mismatch до byte scan; raw blobs разных Vulkan formats нельзя
-считать сопоставимыми без явной conversion contract.
-
-`fparkan-game` render-snapshot bridge больше не берёт только первый prepared
-visual mission object. Каждый `MissionAssets::visuals_for_object` entry
-создаёт отдельный backend-neutral draw с own mesh/material IDs; object без
-prepared visual сохраняет один compatibility fallback draw. Это сохраняет
-multi-component prototype graph для будущего real Vulkan renderer, однако
-текущий game app всё ещё использует planning backend и triangle ranges.
-
-### Mission position/scale render bridge
-
-`fparkan-game` now maps preserved TMA position and non-uniform scale into each
-backend-neutral draw matrix instead of diagnostic index placement. Raw
-orientation deliberately remains uninterpreted: the original Euler order and
-matrix convention are not yet proven. This preserves authored values without
-inventing transform math; the app still uses planning backend and triangle
-ranges, so it is not a full renderer claim.
-
-### Mission original-ID render provenance
-
-Each backend-neutral draw now retains the `OriginalObjectId` preserved by the
-TMA loader for its source mission object. This gives capture and diagnostics a
-stable link from a draw back to its original object record, including every
-visual component emitted for that object. The bridge deliberately leaves the
-value absent when no mission draft exists; it does not infer an original ID
-from a runtime slot or draw order.
-
-### Opt-in mission static-Vulkan bridge
-
-`fparkan-game --backend static-vulkan` is an explicit native-window experiment,
-not the default planning path. It visits only the first mission root, selects
-every prepared MSH component of that root, merges their static XY clip-space
-geometry, and renders a requested number of frames through
-`VulkanSmokeRenderer`; teardown rejects validation warnings/errors and reports
-swapchain/readback telemetry. For each used `Batch20.material_index`, it resolves
-the positional prepared WEAR material and uploads mip 0 of that material's first
-MAT0 diffuse texture request. Because `material_index` is local to an MSH/WEAR
-component, the merged preview assigns a unique preview-local selector only after
-this source resolution; it does not falsely treat equal local indexes as equal
-materials.
-
-This is a narrow bootstrap from mission assets to a live Vulkan renderer. It
-does not render every placed object, apply TMA transforms or orientation, select
-later MAT0 phases or animation, bind lightmaps, or establish a game camera. Fresh GOG
-`MISSIONS/Autodemo.00/data.tma` evidence now proves the narrow GPU bridge: one
-presented frame completed in 39.6 seconds with a native 1280×720 two-image
-swapchain, 14 merged mesh components and 14 selector-keyed original diffuse
-descriptors, 7,372,800-byte synchronized readback (FNV-1a
-`16595193636416981301`) and validation
-warnings/errors `0/0`. This is not a
-full-scene or original-renderer pixel-parity claim.
-
-The same bounded command was then run against the licensed installed Part 1
-(`C:\\Program Files (x86)\\Nikita\\IS`) and Part 2
-(`C:\\Program Files (x86)\\Nikita\\IS2`) corpora supplied for testing. Both
-`MISSIONS/Autodemo.00/data.tma` runs completed with 14 mesh components and 14
-original diffuse descriptors. Part 1 took 28.2 seconds and matched the GOG
-readback hash `16595193636416981301`; Part 2 took 93.6 seconds and remained
-validation-clean but produced distinct hash `18268338333658342130`. Part 2's
-synchronous checkpoint was `Graph` while it was still loading, so the longer
-startup is not attributed to Vulkan. This is only a cross-corpus confirmation
-of the first-root static-preview bridge, not a claim that the two games' full
-mission renderers are compatible.
-
-The preview now has an explicit root-prefix contract. `--preview-roots N`
-prepares the first non-zero `N` TMA roots for the opt-in static Vulkan path.
-Without that diagnostic limiter, the static Vulkan path now requests every TMA
-root (the runtime clamps the request to the decoded mission length), so a
-normal native preview no longer silently displays just root zero. Normal mission
-loading remains full and transactional; the prefix is retained for bounded
-investigation rather than as a hidden relaxation of gameplay validation.
-
-### Shared terrain/root diagnostic frame
-
-The bounded native preview now retains the already validated `Land.msh` inside
-`TerrainWorld`; this lets the application consume source geometry through the
-runtime boundary instead of decoding archive formats a second time. It merges
-one terrain component with the first TMA root's 14 reachable MSH components in
-one explicit top-down XY frame. The frame is computed from terrain positions
-and the root models after applying decoded TMA placement: non-uniform `scale`,
-then the recovered `Rz * Ry * Rx` orientation, then `position`. The XY camera
-itself remains diagnostic; applying the established placement transform here
-prevents differently oriented mission objects from being framed or rasterized
-at a false location.
-
-Terrain is assigned an explicit 1x1 white diagnostic texture, rather than a
-guessed terrain material. `Land.msh` slot selection, `material_tag`, masks,
-auxiliary streams, original terrain phases and camera remain unresolved. Thus
-this is an integrated geometry/provenance checkpoint, not a claim of original
-terrain shading or scene camera reconstruction.
-
-Fresh canonical GOG `MISSIONS/Autodemo.00/data.tma` evidence: one native
-1280x720 Vulkan frame completed in 40.6 seconds with 14 root MSH components,
-one terrain component, 15 descriptors, a 7,372,800-byte readback (FNV-1a
-`10739087367165646439`) and validation warnings/errors `0/0`. The distinct hash
-from the earlier first-root-only preview is expected because the submitted
-geometry and descriptor set changed; it is not an original-frame comparison.
-
-### Multiple static-preview roots
-
-The native bridge now combines every mesh-backed visual of each selected root,
-using that root's recovered static TMA placement (`Rz * Ry * Rx` after scale)
-in the shared diagnostic XY frame. It reports the actual selected root count as
-`preview_roots`, rather than implying that all mission objects are rendered.
-The original camera/frustum, dynamic orientation ownership and visibility
-remain unresolved.
-
-Fresh canonical GOG `Autodemo.00` evidence with `--preview-roots 2` completed
-in 59.7 seconds: 25 submitted MSH components, one terrain component and 26
-descriptors on the native 1280x720 two-image swapchain, with validation
-warnings/errors `0/0`. The readback FNV-1a remained
-`10739087367165646439`, equal to the one-root terrain/root run. That equality
-does **not** prove the second root is visible or equivalent: it was recorded as
-an investigation target, not hidden as a parity result.
-
-The reproducible `fparkan-cli mission inspect` probe closes two tempting but
-incorrect explanations for that equality. GOG `Autodemo.00` root 0 is
-`w_m_wlk2.dat` at `(418.10318, 717.433, 3.0409389)` while root 1 is
-`w_s_wlk1.dat` at `(479.12396, 795.95337, 1.6228507)`; therefore the TMA data
-is neither duplicate nor co-located. Separately, the Vulkan static submit loop
-calls `cmd_draw_indexed` for every prepared draw range and its depth comparison
-is `LESS_OR_EQUAL`. A later direct `Land.msh` bounds probe identified the actual
-static-viewer defect: GOG `AutoMAP/Land.msh` spans X/Y
-`0..1190.6976` but Z only `0..94.50981`; the same TMA objects use X/Y for their
-world placement and a small Z height. The old XZ CPU frame discarded the second
-horizontal TMA component and treated height as the screen axis. The renderer now
-uses a shared XY CPU frame and retains Z as geometry height. This proves only
-the source-axis convention needed by the diagnostic bridge; it does not recover
-the original camera, orientation order, culling or projection matrix.
-
-The new `fparkan-cli terrain inspect <Land.msh>` exposes these three-axis source
-bounds without a renderer. The first XY GPU rechecks were intentionally bounded
-to 120 seconds but remained at the existing `Graph` loading checkpoint and were
-terminated without a native frame/report; therefore no new readback hash or GPU
-acceptance result is claimed for the corrected projection yet.
-
-The load trace now splits base root expansion (`Graph`) from MSH/WEAR/MAT0/TEXM
-visual-dependency expansion (`GraphVisuals`) and enters `Assets` immediately
-before actual asset preparation. A controlled 60-second GOG first-root run of
-the XY build stopped at `GraphVisuals`; the process was terminated by the probe
-and produced no frame. The startup bottleneck is therefore narrowed to visual
-graph expansion, rather than the base prototype graph, asset decoding, window
-creation or Vulkan submission. This is a diagnostic boundary, not a claim that
-the visual expansion is semantically optional or may be skipped.
-
-Visual expansion now caches the validation result of each unique diffuse TEXM
-and each unique lightmap TEXM independently. It still emits an edge and request
-count for every original material reference, and it replays the same cached
-success or failure at every reference; only duplicate archive open/read/decode
-work is removed. Under the same controlled 60-second GOG first-root probe, the
-last checkpoint advanced to `Assets`. The process was deliberately terminated
-before a frame, so this proves reduced visual-graph work rather than a Vulkan
-acceptance result.
-
-Asset preparation additionally caches validated MSH models and decoded WEAR
-tables by their complete resource key. This preserves separate prepared visual
-provenance while avoiding repeat format work for repeated components. The same
-60-second first-root GOG probe remained at `Assets`, so that corpus does not
-demonstrate an additional checkpoint advance from this cache; it must not be
-reported as a measured startup improvement.
-
-Visual-graph material resolution now also caches the complete success or failure
-of each WEAR material request. Every material edge and failure remains in the
-graph; the cache only avoids reopening and decoding an already resolved MAT0.
-Under the controlled 60-second GOG first-root probe, the phase sequence advanced
-from `GraphVisuals` to `AssetModelMeshes`, `AssetWearTables`, then
-`AssetTextures` before termination. This is a bounded timing observation, not a
-claim that a frame completed or that every run has identical timing.
-
-After these visual-graph caches, the corrected XY static-Vulkan preview again
-completed on canonical GOG `MISSIONS/Autodemo.00/data.tma` within a controlled
-120-second run. It reported `Complete`, a native 1280x720 two-image swapchain,
-one frame, 14 root MSH components, one terrain component, 15 material
-descriptors, validation warnings/errors `0/0`, and a 7,372,800-byte readback
-FNV-1a `6451493914305554398`. This is the first GPU acceptance evidence for
-the corrected XY diagnostic projection; the hash is not compared with the
-original renderer and does not establish original camera, shading or parity.
-
-The identical bounded XY command also completed against the supplied licensed
-Part 1 installation (`C:\\Program Files (x86)\\Nikita\\IS`). Its report had
-the same 14 root MSH components, terrain component, 15 descriptors, native
-1280x720 two-image swapchain, validation `0/0`, and FNV-1a
-`6451493914305554398`. This confirms the current first-root diagnostic bridge
-across GOG and Part 1 data; it does not claim full mission or Part 2 parity.
-
-The corresponding Part 2 run (`C:\\Program Files (x86)\\Nikita\\IS2`) did
-not reach asset preparation or a Vulkan frame within a controlled 180-second
-window: its final persisted checkpoint was `GraphVisuals`, and the exact child
-process was terminated. This gives no Part 2 GPU acceptance or pixel result and
-does not attribute the delay to Vulkan; its visual-dependency corpus remains a
-separate profiling and compatibility target.
-
-`GraphVisuals` is now split observationally into `GraphVisualWears`,
-`GraphVisualMaterials` and `GraphVisualTextures`; it preserves the existing
-graph traversal, edge order and validation semantics. Repeating the controlled
-180-second Part 2 probe once ended at `GraphVisualTextures`. That particular
-execution had passed WEAR and MAT0 expansion before timeout, but a checkpoint
-is only the last phase reached in one run: it is not proof of a single global
-bottleneck or of Vulkan involvement.
-
-Visual expansion now reports each dependency-class entrance, its first request
-and every 64th later request; runtime progress persists MAT0/TEXM counts at
-those boundaries. This is diagnostic-only and does not alter traversal, edges
-or validation. A
-later controlled 180-second Part 2 probe ended at `GraphVisualMaterials`,
-before the first TEXM progress marker. The differing checkpoints rule out the
-previous overly narrow claim that TEXM is the sole remaining target. Profiling
-must compare WEAR, MAT0, TEXM, graph allocation and archive I/O quantitatively.
-
-Visual expansion now snapshots each prototype's base-graph `Prototype→MSH`
-anchor before it appends any WEAR/MAT0/TEXM nodes and edges. The subsequent
-traversal reads this immutable vector rather than repeatedly linearly searching
-the growing graph; graph content, node/edge IDs and provenance remain
-unchanged. The same controlled 180-second Part 2 probe still ended at
-`GraphVisualMaterials`, so this is a complexity/correctness improvement rather
-than measured evidence of a startup checkpoint advance.
-
-The MAT0 counter now records `GraphVisualMaterialRequests(N)` with the same
-64-request throttle. A fresh controlled Part 2 probe ended at
-`GraphVisualMaterialRequests(71)`: at least 71 MAT0 requests had begun before
-termination. It does not preserve the simultaneous TEXM count, so it neither
-identifies a slow individual material nor attributes time to MAT0 parsing; the
-next profiler revision needs one cumulative snapshot of all classes.
-
-Visual MAT0/TEXM request markers now also carry the graph node and edge counts
-materialized immediately before that request resolves. A rebuilt bounded Part 2
-probe recorded MAT0 request 3 at 52 nodes / 51 edges and request 4 at 55 nodes
-/ 54 edges. These are observational snapshots for graph-growth profiling only:
-they do not change traversal order, node or edge identities, provenance,
-validation, cache semantics, or rendering.
-
-The same throttled snapshots now retain the number of distinct validation-cache
-keys for WEAR, MAT0, and TEXM before the request. In a fresh 50-second Part 2
-probe, MAT0 request 3 recorded cache counts 2 / 3 / 3 and request 4 recorded
-3 / 4 / 4, while the graph moved from 52 / 51 to 55 / 54 nodes / edges. The
-counts distinguish cache growth from graph growth without adding per-request
-file writes or asserting which parser, archive I/O operation, or allocation
-caused the elapsed interval.
-
-`fparkan-game --load-progress` now initializes its file with `Starting` and
-appends each later mission-load event instead of replacing the prior line. A
-bounded probe can therefore retain both MAT0 and TEXM request milestones even
-when a later event is last. The behavior is diagnostic persistence only: it
-does not alter graph traversal, resource validation, or rendering.
-
-Each trace row now keeps the phase name first and appends a monotonic
-`elapsed_ms=<N>` field from one process-local `Instant`; successful completion
-is appended rather than overwriting the trace. A short rebuilt Part 2 probe
-confirmed real timestamps from `Map` at 5 ms through `GraphVisualTextures` at
-2,389 ms, then later MAT0 request milestones at 28,841 and 35,486 ms. The
-probe was deliberately terminated and provides no frame/GPU result. These
-timestamps measure whole elapsed intervals, including archive I/O, allocation,
-validation, cache effects and progress-file writes; they are not per-parser
-benchmarks.
-
-One bounded Part 2 trace reached `GraphVisualTextureRequests(64)` at 134,793
-ms but did not reach `Assets`: its MAT0 markers were 3/4 at 28,823/35,473 ms,
-13/16 at 88,260/88,334 ms, 25 at 108,137 ms, 38 at 121,369 ms, and 47 at
-128,160 ms. The process was stopped immediately after the 134.8-second marker.
-This makes visual-graph expansion, rather than asset preparation or Vulkan,
-the measured unfinished interval for this run. It still does not allocate that
-time among MAT0 decode, graph allocation, archive I/O, OS cache state, or the
-interleaved TEXM requests.
-
-Visual expansion now also caches WEAR validation by the complete derived WEAR
-archive/name key and replays both successes and failures. It still creates each
-per-prototype WEAR edge, increments the same request counters, and reports the
-same failure provenance. A unit test verifies an archive-qualified cached
-failure is replayed without consulting the repository. In a matched bounded
-Part 2 probe the first TEXM-64 marker occurred at 135,520 ms, versus 134,793
-ms in the preceding sample, and neither run reached `Assets`; therefore this
-corpus does not evidence a startup advance from WEAR caching. The path is kept
-as a correctness-preserving duplicate-resolve optimization, not a causal
-performance claim.
-
-A rebuilt executable then ran a controlled 180-second Part 2 `Autodemo.00`
-probe with the append-only trace. Before exact-child termination it recorded
-`GraphVisualTextureRequests(64)`, `GraphVisualMaterialRequests(100)`, and every
-asset-preparation checkpoint through `AssetTextures`. It produced no native
-window, frame, readback, or Vulkan acceptance report because it was terminated
-while preparation was still active. This is one warm-state timing sample, not
-evidence that the trace change improved loading or that either MAT0/TEXM alone
-caused the earlier timeout; it does show the prior last-event-only trace hid
-concurrent request classes and later preparation progress.
-
-Mission loading now raises the decoded-payload cache entry budget from 64 to
-256 while retaining its 64 MiB byte budget. This avoids premature entry-count
-eviction during resource-rich loads without making memory unbounded. The same
-180-second Part 2 probe nevertheless remained at `GraphVisualTextures`; this
-change is a safe cache-capacity improvement, not measured evidence of a Part 2
-startup advance.
-
-`Assets` now has four ordered diagnostic sub-checkpoints: `AssetModelMeshes`
-(MSH), `AssetWearTables` (WEAR), `AssetMaterials` (MAT0) and `AssetTextures`
-(diffuse TEXM and lightmaps). The callback is observational: it neither changes
-the preparation order nor deduplicates requests. A fresh controlled 60-second
-canonical GOG `Autodemo.00` first-root probe reached `AssetWearTables` but not
-`AssetMaterials`; its created process was terminated and no native frame was
-reported. At least one MSH therefore finished before the timeout, but this does
-not attribute the remaining interval to WEAR decoding rather than later
-MSH/WEAR iteration or archive I/O.
-
-The same source-axis proof now applies to `TerrainWorld`: its `Land.msh` surface
-height query and `Land.map` areal/grid lookup use XY ground coordinates, return
-source Z height, and leave raycasts as full 3D intersections. The Part 1 and
-Part 2 licensed `Land.map` gate successfully locates sampled polygon vertices
-under that contract. This closes the former XZ mismatch in terrain queries; it
-does not establish object orientation, physics/gravity, original culling or the
-camera matrix.
-
-### Camera ownership boundary from the GOG renderer
-
-The GOG `World3D.dll` export `LoadCamera` at RVA `0x1FB06` is only an import
-thunk: it jumps through IAT `0x10020148` to `Terrain.dll!LoadCamera`. The
-Terrain export is at RVA `0x4EBE0` and is a `__stdcall` entry with `ret 0x10`,
-so it receives four machine-word arguments. It allocates a `0x1A4`-byte object,
-forwards those four words to its constructor, and returns a pointer at object
-offset `+0x134`. The constructor is at RVA `0x4EC60`: it passes arguments 3 and
-4 to the base initialization at `this + 4`, stores argument 3 at `this + 0x138`,
-and passes arguments 1, 2 and 4 plus `this` to its helper at RVA `0x4CEF0`.
-Argument 1 is a NUL-terminated mode string at this call boundary: only the
-embedded literals `REFLECTION` and `REFLECTION_SHIFTED` select a non-zero value
-stored at `this + 0x1A0`. This proves that `LoadCamera` supports reflection
-variants, but not the semantic types of its remaining arguments or the meaning
-of the resulting value. `Terrain.dll!stdGetCurrentCamera2` returns a global
-camera pointer, and `stdSetCurrentCamera2` updates it through the object's
-interface methods.
-
-This establishes that original camera creation/selection is Terrain-owned and
-not a field that can safely be inferred from a TMA transform. It does **not**
-yet establish world-to-view matrix layout, FOV, near/far values, projection
-handedness, or initial mission camera selection. The Vulkan path must therefore
-continue to label its XY projection as diagnostic until a dynamic capture or
-further Terrain disassembly proves these contracts.
-
-On 2026-07-18 the canonical windowed GOG process was confirmed running with
-one targetable window titled `Parkan. Железная Стратегия`. Two read-only
-Windows.Graphics.Capture attempts failed before returning a frame with
-`SetIsBorderRequired failed: Интерфейс не поддерживается (0x80004002)`. No game
-input was sent and this contributes no visual, camera, timing, or memory
-evidence; a different capture/debugger path is required for dynamic analysis.
-
-The public `World3D.dll!stdSetCurrentCamera` is a one-pointer `__stdcall`
-wrapper: it delegates to the Terrain `stdSetCurrentCamera2` IAT thunk and then
-writes the same pointer to a World3D mirror global. An `iron3d.dll` call site
-at RVA `0x4FB95` loads the argument from an active game-state chain
-`[esi + 0xBC90] + 0x94` before calling that wrapper. This proves that the
-selected camera is an object reference supplied by game state, not a scalar
-mission-coordinate field. The caller's enclosing type and the meaning of
-offsets remain unnamed until a class layout is recovered.
-
-Terrain strings also identify `CBufferingCamera`, `ICamera::SetTransformMatrix`
-and `ICamera::GetTransformMatrix`, plus frustum/clip methods. These names prove
-that a transform-matrix interface exists, but give neither method slots nor a
-matrix convention; they must not be treated as a usable renderer ABI yet.
-
-### Buffering-camera projection path from static Terrain analysis
-
-A further headless-IDA review of the same GOG `Terrain.dll` identifies a
-`CBufferingCamera`-related implementation path without requiring a live game
-process. The procedure at RVA `0x4D740` copies exactly 64 bytes (16 dwords)
-into its receiver at offset `+0x10`. The frame-preparation procedure at RVA
-`0x4D9C0` queries a viewport rectangle through a virtual method at `+0x3C`, derives its
-width, height, centre and an aspect ratio from that rectangle, and obtains
-projection data through the camera interface. Projection type `0` uses a
-receiver float at offset `+0x234` as an angle passed to `tan(angle / 2)`;
-projection type `2` obtains a five-float block through a virtual method at
-`+0x70`. Any other non-zero type raises `Not supported projection type`.
-
-This is sufficient to prove that original viewport and projection state are
-live camera inputs, rather than a fixed diagnostic projection. It does **not**
-yet prove matrix row/column convention, angle units, near/far field mapping,
-handedness, or the initial mission camera object. In particular, the exported
-string-labelled `ICamera::SetTransformMatrix` (RVA `0x4F830`) and
-`ICamera::GetTransformMatrix` (RVA `0x4F850`) are both obsolete-call stubs, so
-they are not a usable method ABI.
-
-Live read-only evidence now confirms the relocation-aware global contract. In
-one elevated GOG run, `Terrain.dll` loaded at `0x02510000`; its global at
-`base + 0x7355C` held non-null `0x0B37DF08`, whose first dword was
-`0x025765B4` — exactly the relocated `off_100665B4` vtable from the
-`LoadCamera` construction path. This proves the global is a live camera object
-with that outer vtable. It also corrects an overreach: raw reads at global
-offsets `+0x10` and `+0x234` did not yield a finite 4×4 matrix and a projection
-angle in this sample. The static procedures' receiver and the exported global
-pointer are therefore not yet proven to have the same adjustment/layout; those
-offsets must remain unassigned until the selector/interface relationship is
-recovered.
-
-The unattended demo also proves that this global is dynamic. Across 25 seconds
-of read-only sampling it held three distinct heap pointers; all began with the
-same relocated outer vtables `0x025765B4` and `0x02576558`. Two selected
-objects exposed synchronized translation triples in the paired raw blocks at
-offsets `+0x2C/+0x3C/+0x4C` and `+0x6C/+0x7C/+0x8C`, with world-like values
-such as `(491.562, 761.551, 7.361)`. A third object used the same vtable but
-reported normalized-looking values near `(0.098, 0.018, 0.856)` and did not
-match the paired block. The observed pointer transitions are consistent with
-the unattended camera changes, but the sample carries no mode label and does
-not prove first- versus third-person identity. It does prove that the static
-direct-xref initializer is not the sole runtime writer of the global; indirect
-or unanalyzed write paths remain to be recovered.
-
-The shared outer vtable now recovers the exact transform adjustment for the
-world-like samples. Its slot `+0x54` asks the subobject at `outer + 4` for
-selector `0` through that subobject's slot `+0x20`, then copies three dwords
-from returned offsets `+0x0C`, `+0x1C`, and `+0x2C`. In the live world-like
-object, the selector field at `outer + 0x10` was `0xFFFFFFFF`; the recovered
-selector implementation therefore returns `outer + 0x20`. The copied triple is
-exactly `outer + 0x2C/+0x3C/+0x4C`, proving its association with the active
-affine transform rather than merely a sampled correlation. The paired block
-starts at `outer + 0x60` under selector `2`. Another outer slot at `+0x70`
-obtains that selector-2 transform and applies `atan2` to its axis values, so it
-is a proven orientation-angle path. Field names, angle order and matrix
-handedness remain unassigned, but a backend-neutral camera pose may now retain
-the raw affine transform and the exact translation triple without guessing them.
-
-The render contract now represents that boundary directly. `RawCameraTransform`
-preserves each selector result as sixteen original `u32` words, so diagnostic
-data is not rounded or silently assigned a row/column convention. Its only
-typed accessor reinterprets the proven X/Y/Z words `3/7/11` as IEEE-754
-floats. `RawCameraPose` retains selector `0` and selector `2` together, and
-`CameraSnapshot::raw_pose` is optional. The present Vulkan renderer continues
-to use its existing view/projection matrices: raw pose capture is intentionally
-not converted into a view matrix until the original transform/projection
-convention has evidence.
-
-The next live sample strengthens the representation contract but still does
-not name the transform's camera-space direction. For six no-input samples of
-one auto-demo camera object, selectors `0` and `2` were byte-identical,
-finite affine blocks with bottom row `(0, 0, 0, 1)`. Their upper 3x3 basis
-was normalized and their last-column translations changed from approximately
-`(441.038, 687.481, 10.754)` to `(433.545, 652.293, 10.673)`.
-
-This storage/order is not guessed: live `NGI32.dll!g_FastProc[23]` dispatches
-to RVA `0x1D9A0`, whose SSE implementation writes each output row as the sum
-of four scalar-weighted rows. Thus a contiguous 16-float block is multiplied
-as a conventional row-major 4x4 matrix. The render contract now exposes a
-mathematical `try_inverse_affine_row_major` helper for such finite,
-non-singular `[R | t; 0 0 0 1]` blocks. It returns no result for a singular or
-non-affine block and, crucially, does **not** identify that inverse as a view
-matrix. Whether a selector result is camera-to-world or already a renderer
-transform remains an evidence boundary.
-
-The active camera now also has a proven live projection-interface boundary.
-`LoadCamera` stores its selector-18 camera interface at outer offset `+0x19C`;
-in the elevated GOG AutoDemo sample it was a `CBufferingCamera` object with
-relocated vtable `0x02576344`. Its rectangle at `+0x10` was exactly
-`(left=0, top=0, right=1024, bottom=768)`, its projection-type field at
-`+0x230` was `0`, and its FOV field at `+0x234` was `1.04` radians.
-`CBufferingCamera` passes that FOV to `tan(fov / 2)` on its type-0 projection
-path. This proves the live angle input and viewport for this sample;
-it does not yet assign the two remaining projection values, near/far planes,
-depth range, clip-space Y direction, or a Vulkan projection matrix.
-
-The unresolved values are now located rather than guessed. `CBufferingCamera`
-uses its primary-render interface at component offset `+0x220` to select a
-render context, then asks that context for a five-float projection block. The
-sample's primary vtable was relocated `0x02574DF0`; its selector at `+0x0C`
-is only a table lookup keyed by two still-unnamed inputs. No virtual call was
-made by the probe. Therefore the block's values cannot yet be assigned as
-near/far or reused by the Vulkan adapter.
-
-The runtime render model now retains this evidence as `RawCameraProjection` on
-`CameraSnapshot`: the viewport rectangle, projection selector, exact FOV bits,
-and all five context words travel through the backend-neutral boundary without
-changing the current Vulkan projection. Its float accessors deliberately leave
-the context fields unnamed; preserving a raw ABI value is safer than encoding a
-near/far or clip-space convention before it is demonstrated.
-
-The next downstream boundary is now also identified. In the canonical static
-`CBufferingCamera` vtable (`0x10066344`), slot `+0x4C` is Terrain RVA
-`0x4D9C0`; it packages the camera transform, rectangle-derived viewport data,
-FOV-derived tangent, and primary-render context values for another renderer
-component. It does not itself assemble the final projection matrix. The GOG
-`Ngi32.dll` export named `vrtSetCameraParam` resolves to a no-op compatibility
-stub, while the live process loads the Direct3D7 runtime (`D3DIM700.DLL`). The
-next recovery target is therefore the Direct3D transform consumer in
-`iron3d.dll`, not a guessed Vulkan perspective formula. The observed `0.5`,
-`700`, `0.1`, and `0.99` values remain unlabelled until that consumer proves
-their roles.
-
-### Ngi32 Direct3D7 camera matrices recovered
-
-The downstream renderer is now concrete. `iron3d.dll` obtains an opaque
-render interface through `Ngi32!niGet3DRender`; Ngi32 RVA `0x5640` returns the
-global renderer object at RVA `0x3A460`. Its constructor at RVA `0x5E10` gives
-the `0x8CC`-byte object vtable `0x100315E0`. During device setup, Ngi32 RVA
-`0x8E70` calls the Direct3D7 device vtable at `+0x2C` (`SetTransform`) with
-state `3` for the matrix made by RVA `0x7030` and state `2` for the matrix made
-by RVA `0x9450`: projection then view.
-
-RVA `0x7030` exactly builds the row-major D3D7 projection from renderer FOV
-`f`, near `n`, far `z` and viewport width/height `w`/`h`:
-```text
-[ cos(f/2), 0, 0, 0 ]
-[ 0, w/h * cos(f/2), 0, 0 ]
-[ 0, 0, sin(f/2)*z/(z-n), sin(f/2) ]
-[ 0, 0, -sin(f/2)*n*z/(z-n), 0 ]
-```
+## Камера и размещение в оригинале
-The sine in the depth scale and homogeneous-W term is intentional: after the
-D3D perspective divide the diagonal has the expected cotangent scale and the
-depth range remains finite. RVA `0x9450` applies a
-specific axis permutation/sign change and translated dot products to selector
-0 before the view `SetTransform`; it is not merely the generic affine inverse.
-
-An elevated read-only AutoDemo probe sampled a 1024×768 renderer with
-near=`0.5`, far=`700`, FOV=`1.3` radians and showed its view-source pointer
-byte-identical to the active Terrain outer camera's selector-0 block. This
-proves ownership and the D3D7 boundary, while keeping the earlier
-`CBufferingCamera` FOV=`1.04` as a distinct upstream interface value.
-
-The live GOG layout is now independently repeatable without invoking any game
-method: `Terrain.dll + 0x7355c` contains the current outer camera-object
-pointer, whose vtable relocates to `Terrain.dll` RVA `0x665b4`. Selector 0
-returns the affine block beginning at outer `+0x20`; its sixteen row-major
-words have translations at indices 3, 7 and 11. The public `LoadCamera`
-interface at outer `+0x134` is a different view and must not be substituted
-for that block. Captures for `RawCameraTransform` therefore contain the sixteen
-selector-0 words from outer `+0x20`. A passive AutoDemo sample produced a
-finite affine camera matrix and passed through the offline Vulkan adapter as
-source-world geometry, rather than relying on a guessed identity view.
-
-The Vulkan static path now accepts this recovered camera as
-`VulkanStaticCamera`. It composes the row-major D3D7 result as
-`view * projection`, uploads its 64 bytes through a vertex-stage push constant,
-and changes static vertices from XY to XYZ. GLSL reads the same bytes as its
-default column-major `mat4`; therefore `matrix * vec4(position, 1)` is the
-transpose-equivalent of the original D3D7 row-vector multiplication. Both APIs
-use the 0..1 depth range, and this renderer uses a positive viewport height, so
-this narrow bridge needs no extra depth or Y correction. The fragment alpha
-cutoff moved to byte offset 64 in its own push-constant range. The default
-identity camera deliberately preserves the old XY diagnostic preview; feeding
-live terrain camera/projection values into mission rendering remains later
-runtime wiring, not a claim of scene parity.
-
-The geometry side of that handoff is also explicit. The static mesh adapter now
-has source-world projections for `Land.msh` and MSH: terrain positions retain
-all three decoded coordinates; model positions retain Z and apply the recovered
-static mission placement. The diagnostic XY counterpart uses that same placement
-before normalizing X/Y, so it no longer silently omits authored object rotation.
-These APIs preserve triangle order, batch ranges and packed UV decoding. The
-current game preview deliberately stays on its XY diagnostic projection until a
-runtime supplies a real legacy camera; selecting source-world coordinates with
-identity camera would be a misleading empty/off-screen viewer rather than a
-compatibility result.
-
-Static analysis of GOG `iron3d.dll` recovers the placement primitive at RVA
-`0x36610`: it evaluates a row-major affine `Rz(z) * Ry(y) * Rx(x)` and writes
-translation in the final column. The backend-neutral
-`LegacyIron3dEulerTransform` preserves that finite portable formula with golden
-yaw and scaled-point tests. A read-only elevated AutoDemo scan closes the
-previous TMA binding gap for static placement: it finds the exact raw mission
-records (position, `(0, 0, z)`, scale) and live object blocks at the same
-world positions. For example, `z = 1.6262363` produces the observed pair
-`-sin(z) = -0.99846`, `cos(z) = -0.05541`; matching pairs occur for the other
-observed objects. The opt-in captured-camera path therefore applies
-`R * (scale * local_position) + translation` before Vulkan upload. It remains
-a static-placement contract: dynamic/animated transforms, physics ownership,
-terrain material selection, lighting and gameplay parity are still separate
-evidence tasks.
-
-`VulkanSmokeRenderer::set_camera` now accepts a new finite camera between frame
-submissions and uses it for the next command buffer without rebuilding the
-swapchain, buffers, descriptors or pipelines. `VulkanStaticCamera` also accepts
-generic finite row-major view/projection pairs, so the final game camera
-controller need not be coupled to the D3D7 recovery type. The native Windows
-smoke executes that update before each of 300 validation-clean frames. This is
-the renderer-side cadence contract only; it does not yet supply a gameplay
-camera controller or claim camera movement parity.
-
-For controlled differential work, `fparkan-game --backend static-vulkan` now
-accepts `--legacy-camera-capture <path>`. The JSON capture stores only the
-selector-0 words and the already recovered Ngi32 viewport/near/far/FOV inputs;
-the application reconstructs the D3D7 camera offline, selects source-world
-geometry, and never attaches to or controls the original process. A live
-read-only AutoDemo confirmation corrected an ABI ambiguity: the Terrain global
-is the base of the `0x1A4` camera object (vtables at `+0` and `+4`), while
-`LoadCamera`'s public return is the separate interface view at `base + 0x134`
-with its own vtable. This capture format deliberately preserves the former
-base-object interpretation and does not invent camera ownership.
-
-A three-frame GOG AutoDemo run with one such read-only capture selected the
-`legacy-d3d7-capture` path, rendered all eight mission roots / 66 MSH
-components plus terrain, reported 5,226 vertices inside the recovered clip
-volume, produced a 7,372,800-byte readback with FNV-1a
-`1737487240747766901`, and ended with zero Vulkan validation warnings and
-errors. This validates the offline handoff for one captured instant; it does
-not claim live camera tracking, material parity, or an original-frame pixel
-comparison.
-
-`tools/capture-original-camera.ps1` makes that handoff repeatable for a live
-GOG process. It finds the actual `Terrain.dll` base with Toolhelp module
-enumeration, opens the chosen PID with only
-`PROCESS_QUERY_INFORMATION | PROCESS_VM_READ`, verifies the expected outer
-vtable, and reads exactly 64 selector-0 bytes at outer `+0x20`. AutoDemo can
-briefly select a normalized/reflection-like camera object whose finite
-translation is near the origin. The tool therefore re-reads the global on
-each attempt and accepts a sample only when the proven translation length
-exceeds its explicit `-MinimumWorldTranslation` threshold (default `100`);
-the JSON retains the accepted outer pointer and translation for audit. It emits the
-`fparkan-legacy-camera-v1` JSON accepted by `fparkan-game`; it does not write
-to the process, send window messages, inject code, suspend threads, or invoke
-an original method. PowerShell execution policy may require the explicit
-one-shot bypass below; that changes neither the script nor the game.
-
-```powershell
-$capture = powershell -NoProfile -ExecutionPolicy Bypass -File `
- .\tools\capture-original-camera.ps1 -ProcessId 5632
-$capture | Set-Content -NoNewline target\fparkan\live-camera.json
-cargo run -q -p fparkan-game -- --root 'C:\GOG Games\Parkan - Iron Strategy' `
- --mission 'MISSIONS\Autodemo.00\data.tma' --backend static-vulkan --frames 3 `
- --legacy-camera-capture target\fparkan\live-camera.json `
- --readback-out target\fparkan\autodemo-live-camera.raw
-```
+Миссия хранит положение, три угла и масштаб объекта. Функция `iron3d.dll`
+RVA `0x36610` строит `Rz(z) * Ry(y) * Rx(x)` с переносом в последнем столбце.
+Точка модели преобразуется как `R * (scale * point) + position`.
+Повороты и переносы узлов модели применяются до размещения объекта в миссии.
-On the unattended live AutoDemo the accepted world-space capture completed the
-same all-root static bridge with 10,152 clip-visible vertices, 71 descriptors,
-a 7,372,800-byte format-50 readback, and zero Vulkan validation
-warnings/errors. A passive desktop-surface capture of that original window also
-proved that the selected instant contains the textured terrain, atmospheric
-sky, HUD and weapon FX; `PrintWindow` itself returns a black Direct3D buffer,
-so it must not be treated as an original-frame oracle. The paired images expose
-the next real renderer gap: sharing the raw pose and Ngi32 projection inputs is
-not yet pixel parity, because the static bridge still lacks the full runtime
-camera-selection timing, terrain composition, culling, lighting, UI and FX.
-
-For visual regression work, `fparkan-game --backend static-vulkan` also accepts
-`--readback-out <path>`. It writes the final synchronized Vulkan swapchain
-image only after the renderer has completed its normal teardown evidence; the
-JSON report records its raw Vulkan format, byte count, hash, and requested
-path. At 1280x720, format `50` (`VK_FORMAT_B8G8R8A8_UNORM`) produces exactly
-3,686,400 bytes. The artifact is a Vulkan-side comparison input, not an
-asserted original-frame image.
-
-The first bounded launch showed that repeated fingerprinting, rather than
-Vulkan initialization, was the load-path bottleneck: each new MAT0/TEXM request
-re-opened an already decoded archive and re-hashed its entire source file.
-`ResourceRepository::open_archive_unchanged` now makes the *explicit* bounded
-loading-transaction contract available. The normal `open_archive` remains the
-strict public path and still invalidates stale entry handles after an external
-archive replacement; only mission asset preparation uses the transactional
-variant because it consumes an immutable snapshot of the selected game root.
-
-With that scoped cache, canonical GOG
-`MISSIONS/Autodemo.00/data.tma --backend static-vulkan --preview-roots 1`
-completed preparation in 6.308 seconds (the visual graph completed in 6.012
-seconds) and produced a 1280×720 native Vulkan frame with the captured D3D7
-camera, 15 material descriptors, and zero validation warnings or errors. This
-is a real original-asset rendering milestone, but not pixel parity or gameplay
-camera parity: it intentionally renders the bounded first preview root and
-uses a one-time offline camera capture.
-
-Expanding that same preview to all eight AutoDemo mission objects exposed a
-different, renderer-local limit: the merged static scene crossed 65,535
-vertices before any Vulkan command was recorded. `VulkanStaticMesh` now uses a
-32-bit GPU index buffer (`VK_INDEX_TYPE_UINT32`), while source MSH/TerrainFace28
-indices remain decoded in their original 16-bit representations and are widened
-only at the static-render bridge. A regression test crosses the former boundary.
-The full captured-camera AutoDemo preview completed at 13.369 seconds and
-rendered three 1280×720 native frames: 8 mission objects, 66 mesh components,
-67 material descriptors, and zero Vulkan validation warnings or errors. A later
-orientation-enabled run rendered the same full scene for three 1280×720 frames
-with `validation_warnings=0`, `validation_errors=0` and readback hash
-`6406107678925513509`. It is still a static source-world preview: terrain
-material selection, lighting, animation, physics and gameplay behavior have
-not yet been claimed as parity.
-
-A fresh no-input launch of the canonical `iron_3d.exe` did create a responsive
-window titled `Parkan. Железная Стратегия`. A read-only probe then requested
-`PROCESS_QUERY_INFORMATION | PROCESS_VM_READ` and attempted to read the known
-Terrain global at image address `0x1007355C`; `OpenProcess` returned
-`ERROR_ACCESS_DENIED` (5), so no process memory or camera value was read. The
-window was left running. A same- or higher-integrity 32-bit debugger is still
-required for live camera evidence.
-
-`Land.msh` использует отдельный geometry-only bridge: validated `TerrainFace28`
-сохраняет source triangle order, а его positions и packed UV0 попадают в тот же
-static vertex/index upload path. Для текущего диагностического viewer XY bounds
-нормализуются в clip-space, packed UV0 декодируется как signed `int16 / 1024`.
-Один static draw range намеренно не трактует `material_tag`, surface mask, slots
-или auxiliary streams как material/pipeline state: их связь с original terrain
-renderer пока не доказана. GOG `DATA/MAPS/11/Land.msh` дал 6 458 vertices и
-24 672 indices в 300-frame native Vulkan run без validation warning/error.
-
-Выбор depth/stencil attachment отделён от renderer lifetime:
-`select_depth_stencil_attachment_format` применяет тот же фиксированный порядок
-форматов, что и capability gate, к фактически поддерживаемому списку GPU.
-Это исключает ситуацию, когда admission принимает один совместимый формат, а
-allocation позднее выбирает другой; resource уничтожается после framebuffer и
-render pass при recreate/teardown.
-
-После последнего world pass renderer закрывает сцену и выводит back buffer.
-World3D снимает `in_render`, восстанавливает временный viewport state и вызывает
-`on_end_render` у active objects. Только после этого допустимо освобождать
-temporary vertex buffers или заменять render representation. UI/shell
-обслуживается верхним уровнем после возврата из world-render path; для
-диагностики полезно уметь сохранять world-only command list и финальный
-framebuffer отдельно.
-
-Terrain material discovery is now reproducible instead of inferred from the
-mesh shape. The original AutoDemo map places two textual sidecar WEAR tables,
-`Land1.wea` and `Land2.wea`, beside `Land.msh`; `Terrain.dll` contains the
-corresponding `1.wea` and `2.wea` loading suffixes. The new terrain inspector
-reports all packed face tags. For `DATA/MAPS/AutoMAP/Land.msh`, its 3,174 faces
-use `0x0102` (592), `0x0203` (386), `0xff01` (918), `0xff02` (970), and
-`0xff03` (308). The low byte is always a valid positional selector 1..3 in
-`Land2.wea`; the high byte is a `Land1.wea` selector or sentinel `0xff`.
-This is evidence for a two-layer terrain-material contract, but not yet for
-its blend equation, so the Vulkan bridge intentionally remains texture-agnostic
-until the base/overlay composition is recovered.
-
-Static recovery of `Terrain.dll!CLandscape` now fixes the loader contract behind
-that observation. The constructor opens `Land.msh`, creates one material manager
-from `Land1.wea`, then invokes that manager's second-table load with `Land2.wea`.
-It requires NRes type 18 with the original diagnostic “microtexture mapping
-chunk” and retains it as four-byte entries; type 14 is optional. Therefore the
-two WEAR selectors must not be collapsed into an invented one-layer material,
-and the type-18 `aux18` stream is now documented as microtexture mapping data.
-`TerrainFace28::material_layers` exposes the evidenced high-byte `Land1`
-selector (with `0xff` absent sentinel) and low-byte `Land2` selector. The
-manager's actual blend/pass operation remains unrecovered. `World3D.dll`
-supplies the manager ABI: its load slot appends `Land1` as table 0 and `Land2`
-as table 1, while its material-phase lookup receives the exact 32-bit selector
-`(table_index << 16) | material_index`. `TerrainMaterialSelection` preserves
-that key for the renderer without assuming how the two phases are blended.
-
-The same `World3D!GetMaterialPhase` recovery establishes the next boundary. It
-validates the table/row selector, chooses a MAT0 phase (or interpolates two
-phases for an animated material), returns the selected material record, and
-hands its caller a 76-byte expanded phase-state block. It does not submit a
-draw or compose the two terrain tables. The later consumer of that record and
-the type-18 microtexture mapping remains the required evidence before adding
-an overlay, blend, or microtexture shader.
-
-The terrain render traversal is now located at `Terrain.dll` RVA `0x11340`.
-After visibility/cell work, it derives a 68-byte source-material record and
-dispatches `Terrain!GetShade()` slot `+16` with four arguments: zero, the
-record's word at `+2`, a pointer selected from a four-byte table by its word at
-`+0`, and zero. `GetShade` is a Terrain singleton, not `niGet3DRender`; the
-preceding slot `+60` call sets its visibility mode. Slot `+16` consumes the
-second word as the count of adjacent `u16` pairs and the first as the pair-table
-index, then groups the pairs by material lookup before constructing draw work.
-`TerrainSlotTable::render_dispatch` exposes exactly those two proven disk
-fields, while `LandMeshDocument::slot_material_pairs` decodes the selected
-type-11 entries as `{ material_lookup: u16, flags: u16 }`. Flag `0x0010` is a
-proven batch boundary in `GetShade`. Crucially, this lookup does **not** select
-either map-local Land table: the `GetShade` constructor loads its own one-table
-manager from `system.rlb` resource `Shade.wea`. That WEAR table has one row,
-`LIGHT1`, whereas AutoDemo's 3,174 pairs use keys `0..3173`; therefore the
-16-bit value is an opaque manager lookup/cache key, **not** a table-row
-selector. `TerrainMaterialPair::shade_lookup_key` records this separate key
-without conflating it with `Land1.wea`/`Land2.wea`. The AutoDemo inspector now
-reports 128 slots and 392 `0x0010` batch boundaries. The meanings of remaining
-flags, this cache mapping, and the final blend operation remain unassigned.
-
-The cache boundary is now mechanically separated from the WEAR loader. In the
-`GetShade` object, the `Shade.wea` manager is stored at byte offset `3092`, but
-the dispatcher calls a distinct object at byte offset `3244`. Its virtual slot
-`+16` receives every type-11 key repeatedly with mode values `512`, `16`,
-`20497`, and `20752`. The cache object itself has an eight-byte entry table at
-`+316` and its exclusive key count at `+320`: the first dword of an entry is a
-stored-record pointer and its byte at `+4` selects a 212-byte profile bank. A
-key outside that count, or an entry pointer below `0x1000`, fails the lookup.
-
-The static constructor now identifies this object as the embedded `CLandscape`
-subobject whose vtable is `Terrain.dll` RVA `0x643D0`. It installs that vtable
-at parent offset `+316`, allocates the entry table at parent `+632` (subobject
-`+316`) as `0x1f40` bytes / 1,000 eight-byte entries, and initializes its count
-to zero. The profile-bank region starts at subobject `+332`: every one of the
-100 banks has a 116-byte header followed by a 96-byte initialized stream, hence
-the recovered 212-byte stride. The byte selected from each table entry is
-therefore a bank index into a constructor-owned, fixed 100-bank region. This
-proves the cache's allocation layout. Its population path is also now located.
-`Terrain.dll` RVA `0x10280` resets the count, recreates the base entries from
-the 28-byte stream at parent `+756`, and assigns bank zero. RVA `0x12e20`
-copies one `0xd4`-byte profile header into bank `profile_count`, increments that
-count, and remaps supplied keys to 28-byte records through the entry table. A
-mapped record is stored as `profile_record_base + 28 * record_index` with the
-new bank byte; a mapping whose second index is `-1` stores only the profile
-ordinal, below `0x1000`, and is deliberately rejected by the lookup routine.
-This explains the runtime sentinel without interpreting it as a missing WEAR
-row. The input map is not a direct `Shade.wea` decode: the sole call site is
-the terrain loader at `Terrain.dll` RVA `0x0a3f0`, after it has generated and
-stitched 28-byte terrain records. That loader first appends pairs copied from
-an intermediate primitive-key list with second value `-1`, then appends one
-`(-1, generated_record_index)` pair for every generated record. Before calling
-the cache builder it runs a three-edge matching pass over those records, using
-their quantized positions and adjacency selectors. The cache profile therefore
-describes loader-produced geometry and its generated correspondence map, not a
-named on-disk `Shade.wea` section. The source of the intermediate primitive-key
-list and the exact semantics of the 28-byte record's non-position fields remain
-unassigned; this is sufficient to rule out an invented static WEAR parser, but
-not to reproduce the profile generator yet.
-
-On success the method returns the shared result view at cache offset `+24`, not
-the stored-record pointer and not a WEAR row. Thus the dispatcher's observed
-flags at result-view `+60`, secondary selector `+24`, optional byte `+88`,
-referenced state block `+104`, three signed shorts `+108`, and another selector
-`+112` are materialized lookup state, not yet proven on-disk record offsets.
-Mode `0x0200` (`512`) only validates the key in the recovered path; mode
-`0x5011` includes the recovered selector-table transfer and mode `0x5110`
-includes the recovered bank-byte transfer. The meanings of their remaining
-bits, record population, and the result fields remain unrecovered, so the Vulkan
-path must not map any of them to invented pipeline state.
-
-A read-only elevated AutoDemo probe confirms that this is a lifecycle boundary,
-not a permanently allocated table: the live `GetShade` singleton is initialized
-and its `Shade.wea` manager pointer is non-null, while its byte-offset `3244`
-cache pointer is normally null. Passive 100-ms sampling of the unattended
-AutoDemo caught it briefly at `0x035C64B4`; its relocated vtable is
-`Terrain.dll` RVA `0x643D0` and slot `+16` is RVA `0x10910`, matching the
-recovered lookup routine. Therefore a future runtime implementation must treat
-cache availability as contextual and cannot dereference or prebuild the cache
-solely from constructor state.
-
-`tools/capture-terrain-shade-cache.ps1` now makes the same evidence repeatable
-without debugger attachment. It discovers `Terrain.dll` with Toolhelp, opens
-only `PROCESS_QUERY_INFORMATION | PROCESS_VM_READ`, searches readable
-32-bit-process regions for the already proven cache vtable, and excludes the
-Terrain image itself because relocations there also contain that pointer value.
-It reports only addresses and count metadata; it does not alter the process or
-save game resources. On the unattended GOG AutoDemo process 5632 on 2026-07-18
-it found the live cache at `0x035C64B4`, with entry table `0x17284590` and
-exclusive count `3462`. Two adjacent passive captures retained the cache
-address/table/count but returned result views `0x0274BD89` and `0x008A60DC`.
-That changing result view is direct evidence that it is transient lookup
-state, not a persistent cache identity or a WEAR row. The observation narrows
-the next terrain task to profiling the cache's generated entries and their
-consumer; it still does not establish a Land1 blend equation.
-
-The probe now also reads the bounded eight-byte entry table and only hashes
-the fixed 212-byte profile banks it observes; raw process bytes never enter
-the repository. The same AutoDemo cache had 3,426 materialized record pointers
-among 3,462 entries and referenced banks `0..4`. Two consecutive captures
-kept that count, bank set, and all five FNV-1a-64 bank hashes stable:
-`9426539192111566946`, `2915543853261903601`, `13156371703608727216`,
-`11280405573172155968`, and `18234288329145104612`. Their result views changed
-from `0x008A565C` to `0x008A55A8`. Thus the generated cache/profile state is
-stable over these render samples while the shared result view is not. This
-still identifies neither the record-field semantics nor the final terrain
-blend/pass operation.
-
-```powershell
-powershell -NoProfile -ExecutionPolicy Bypass -File `
- tools\capture-terrain-shade-cache.ps1 -ProcessId <iron_3d-pid>
-```
+Горизонтальная плоскость карты — XY, высота — Z. У GOG AutoDemo диапазон XY
+ландшафта составляет `0..1190.6976`. Высоты, сохранённые в `Land.msh`, переводятся
+в единицы мира делением на 32. Это отдельное преобразование ландшафта;
+применять его к координатам размещённых объектов нельзя.
-The static Vulkan bridge now carries each contiguous face run as a separate
-draw range keyed by the original packed `material_tag`; it neither reorders
-triangles nor collapses the high byte. For the first usable visual layer, the
-low byte resolves positionally through the adjacent standalone `Land2.wea`,
-then the existing `MAT0 -> Textures.lib -> TEXM` chain supplies the RGBA8
-descriptor. `Land1.wea` remains preserved as the high byte/overlay channel and
-is deliberately not drawn until its blend equation is recovered. A canonical
-three-frame AutoDemo run now uses 71 descriptors (66 MSH plus five terrain
-tags), produces readback hash `17034026600547661445`, and stays at zero Vulkan
-validation warnings/errors. This is original terrain texture upload, not
-terrain visual parity.
-
-```powershell
-cargo run -q -p fparkan-cli -- terrain inspect `
- 'C:\GOG Games\Parkan - Iron Strategy\DATA\MAPS\AutoMAP\Land.msh' --format json
-```
+`Ngi32!niGet3DRender` (RVA `0x5640`) возвращает графический интерфейс.
+Вызов Direct3D7 `SetTransform` устанавливает projection из RVA `0x7030`
+и view из RVA `0x9450`. Для FOV `f`, ближней плоскости `n`, дальней `z`
+и размеров viewport `w`, `h` projection в порядке строк имеет вид:
-## Проверки паритета
-
-Главные риски совпадения кадра:
-
-- x87 extended precision и правила округления;
-- различия scalar/SIMD slots `g_FastProc`;
-- порядок objects, batches и transparent primitives;
-- depth write/test, cull, alpha test и blend transitions;
-- mip-skip, palette и `Page` coordinates;
-- material fallback и выбор phase;
-- последовательность RNG для FX и atmosphere;
-- capability fallback конкретного устройства;
-- quantization времени и дополнительный simulation step;
-- eager/lazy resource resolve и cache side effects.
-
-Минимальный deterministic frame capture должен включать camera state, viewport,
-visible object IDs, выбранные LOD/group/slot, draw-item list, material и texture
-handles, pipeline keys, matrices, render phase, sort key, причины culling и
-hashes промежуточных buffers. Без такой трассировки нельзя уверенно отделить
-ошибку формата MSH от ошибки state machine renderer-а или сортировки.
-
-Связанные справочные страницы с таблицами форматов: [MSH](../reference/msh.md),
-[materials](../reference/materials.md), [Texm](../reference/texm.md) и
-[render frame](../reference/render-frame.md).
-
-### Live AutoDemo: empty frame устранён, но parity ещё не достигнут
-
-Read-only `PrintWindow` capture работающего GOG AutoDemo даёт полноценный
-оригинальный кадр (terrain, варбот и близкая geometry), поэтому теперь есть
-безвводный source для будущего visual comparison. Текущий полный static-Vulkan
-preview раньше состоял только из clear color. Validation `0/0` тогда доказывал
-только GPU lifecycle, но не совпадение с original renderer.
-
-Новая CPU-диагностика применяет к тем же source vertices ту же row-major D3D7
-matrix, которую GLSL получает как column-major push constant. Для свежего
-captured Ngi32 camera (`viewport 1024x768`, near `5`, far `700`, FOV `1.3`)
-она насчитала `clip_visible_vertices=0`. Последующая сверка с
-`Ngi32!sub_10009450` и `sub_10007030` нашла два literal translation defects:
-Y translation view matrix должна быть `-(m23*m22 + m13*m12 + m03*m02)`, а
-projection использует `width/height` и множитель `sin(fov/2)` в обоих depth
-coefficients. После исправления того же capture диагностировал
-`clip_visible_vertices=938`, а `PrintWindow` native Vulkan window показал
-реальную rasterized terrain/model geometry. Это исключает прежний empty-frame
-барьер; face culling и texture selection не были его причиной.
-
-Получившийся кадр всё ещё явно не похож на оригинал: geometry сосредоточена у
-границы и material/terrain representation грубая. Значит, следующие задачи —
-camera timing/selection, source model-node transforms, terrain material phases,
-lighting и visibility, а не объявление pixel parity.
-
-MSH static bridge теперь также перестал отправлять каждый `Batch20` файла.
-Для стандартного `Node38` он выбирает `slot_index[LOD0, group0]`, затем
-соответствующий `Slot68.batch_start..batch_count`; alternate LOD/groups больше
-не подмешиваются в initial static pose. Regression test фиксирует selection,
-а fresh AutoDemo run меняет readback hash при тех же 938 clip-visible vertices
-и остаётся validation-clean. Новый capture по-прежнему показывает сильное
-смещение частей, поэтому одной selection недостаточно: нужны local node poses,
-parent hierarchy и animation-key/fallback semantics. Модели без layout Node38
-сохраняют прежний all-batches diagnostic fallback.
-
-Чтобы следующий шаг не перечитывал raw NRes streams в renderer, validated
-`ModelAsset` теперь переносит `ModelAnimation` с decoded type-8 keys, type-19
-map и declared frame count. `node38_fallback_pose` возвращает exact fallback
-key pose. Licensed gates Part 1/Part 2 подтвердили 435/511 models, 157/200
-animated models, 3,469/5,233 node samples и утверждённые captures.
-
-Следующий узкий шаг применяет этот уже декодированный `fallback_key` в
-legacy-camera static preview. Для каждого выбранного `Node38` bridge поворачивает
-вершины normalized quaternion, прибавляет local translation pose и только затем
-применяет доказанный TMA `Rz * Ry * Rx`, scale и mission translation. Вершины
-намеренно разворачиваются per node/draw range, чтобы один source vertex мог
-получить разные poses. `parent_or_link_raw == 0xFFFF` теперь доказан как root,
-а любое меньшее child значение — parent index: inspected `R_B_01.msh` содержит
-34-node parent-before-child tree, а Part 1/Part 2 animation gate подтвердил
-этот контракт для всех standard nodes. Parent rotation корректно поворачивает
-child translation до суммирования. Frame map всё ещё не семплируется, поэтому
-это static fallback hierarchy, а не full animation parity.
-На той же GOG `MISSIONS/Autodemo.00/data.tma` и offline Ngi32 camera capture
-full preview из 8 objects / 66 mesh components / 67 descriptors завершился за
-один native Vulkan run с `clip_visible_vertices=3415`, `validation_warnings=0`,
-`validation_errors=0` и readback hash `1275533143935640133`. Рост с 2584
-видимых вершин подтверждает, что composed parent poses реально вошли в geometry
-path; он не является оценкой сходства с original frame.
-
-Параллельно bridge для legacy-camera path теперь переводит только высоту
-`Land.msh` в world units с масштабом `1/32`: raw AutoDemo heights
-`95.29412..288.23532` становятся `2.978..9.007`, что согласуется с TMA Z и
-live camera height. Это доказанное coordinate conversion, но оно само по себе
-не сделало вершины видимыми и не является заявлением о pixel parity.
-
-### Explicit offline Node38 animation-frame preview
-
-The static Vulkan path now accepts `--static-animation-frame <u16>`. For each
-standard `Node38` model it resolves the already-decoded type-19 map at that
-logical frame, selects its mapped type-8 key when it is valid before the
-node's fallback key, samples the selected key and its immediate successor with
-the portable reference sampler, then composes the resulting parent hierarchy.
-Nodes with no usable map, or a requested frame outside the declared map,
-retain their exact fallback pose. An invalid/incomplete hierarchy deliberately
-falls back to the existing unposed static geometry path instead of guessing a
-runtime state.
-
-This option is a reproducible asset-viewer probe, not a gameplay clock: it
-does not identify the original animation controller, x87 rounding profile,
-clip selection, LOD/group controller, blending or FX timing. The JSON report
-contains `animation_frame` (`null` for the established fallback preview) so
-readbacks from the two modes cannot be mistaken for each other. On 2026-07-18,
-the licensed GOG `MISSIONS/Autodemo.00/data.tma` run with all eight roots,
-`--static-animation-frame 1`, two frames and format `50` completed with 66
-mesh components, 75,543 clip-visible vertices, a 1280×720 / 3,686,400-byte
-readback hash `4638497144561211935`, and validation warnings/errors `0/0`.
-
-### Windows PowerShell camera-capture hand-off
-
-`tools/capture-original-camera.ps1` is an observer: it opens the original
-process only with `PROCESS_QUERY_INFORMATION | PROCESS_VM_READ`, samples the
-active Terrain camera and emits `fparkan-legacy-camera-v1` JSON. Windows
-PowerShell 5.1 commonly redirects or pipes that text as UTF-16LE with a BOM.
-The Vulkan viewer now accepts BOM-marked UTF-16LE and UTF-16BE in addition to
-UTF-8 (including an UTF-8 BOM), so a normal PowerShell capture can be used
-directly as `--legacy-camera-capture` without a lossy manual re-encoding step.
-Malformed byte lengths and invalid Unicode remain explicit load errors.
-
-Fresh read-only evidence from the running licensed GOG AutoDemo sampled outer
-camera `0x0B368FD8` in `Terrain.dll` at base `0x02510000`; selector-0
-translation was `(548.020752, 559.672852, 3.00516248)`. Passing the captured
-UTF-16LE file to the native Vulkan viewer rendered all eight mission roots
-with `camera_mode="legacy-d3d7-capture"`, 66 mesh components, 6,787
-clip-visible vertices, 71 material descriptors, a 1280x720 / 3,686,400-byte
-format-50 readback hash `7904810245714997753`, and validation warnings/errors
-`0/0`. This proves the live-camera hand-off and projection path; it does not
-establish frame-by-frame camera-selection parity, terrain shading or pixel
-parity with the original renderer.
-
-### Explicit MAT0 phase preview
-
-`fparkan-game --backend static-vulkan --static-material-phase <u16>` now
-selects the exact decoded MAT0 texture for that phase of each MSH material.
-The override is deliberately per material: when the requested phase is absent
-or intentionally untextured, that material retains its existing first prepared
-diffuse request, rather than fabricating a texture or aborting the whole
-mission. Terrain remains on its separately proven Land2 base path. The JSON
-report records `material_phase` so the artifact's selector is explicit. This
-is an asset-viewer control, not a claim about the original material clock,
-phase mode, interpolation, random offset, lighting or Land1 composition.
-
-On 2026-07-18 the licensed GOG AutoDemo run with all eight roots, phase `1`,
-two frames and format `50` completed validation-clean with 71 descriptors and
-a 1280×720 / 3,686,400-byte readback hash `3152437928708207395`.
+```text
+[ cos(f/2), 0, 0, 0 ]
+[ 0, w/h*cos(f/2), 0, 0 ]
+[ 0, 0, sin(f/2)*z/(z-n), sin(f/2) ]
+[ 0, 0, -sin(f/2)*n*z/(z-n), 0 ]
+```
+
+После деления на однородную координату получается обычная перспективная
+проекция с глубиной `0..1`. Матрица view дополнительно меняет оси и знаки;
+она не равна одному лишь обратному преобразованию камеры.
+В проверенном GOG контексте Ngi32 использовались `n=0.5`, `z=700`, `f=1.3`.
+
+У GOG `Terrain.dll + 0x7355c` хранит указатель на активную внешнюю камеру.
+Её selector 0 возвращает 16 слов начиная с `+0x20`, перенос находится в словах
+3, 7, 11. Интерфейс `LoadCamera` расположен отдельно, по `+0x134` объекта.
+Эти два указателя нельзя подменять друг другом. Формулы камеры и размещения
+реализованы и проверяются в `fparkan-render`.
+
+## Материалы ландшафта
+
+Рядом с `Land.msh` лежат `Land1.wea` и `Land2.wea`. Старший байт
+`TerrainFace28.material_tag` выбирает материал первой таблицы, младший —
+второй. `0xff` в старшем байте означает отсутствие первого слоя.
+В AutoDemo встречаются теги `0102`, `0203`, `ff01`, `ff02`, `ff03`.
+Менеджер World3D получает селектор `(table_index << 16) | material_index`
+и возвращает выбранную фазу MAT0 размером 76 байт.
+
+Type 18 в `Land.msh` оригинал называет microtexture mapping chunk;
+он содержит четырёхбайтовые записи. Type 11 хранит пары `u16`, выбранные
+таблицей terrain slots. Их первое поле — ключ shade cache, а не номер строки
+WEAR. Флаг `0x0010` отделяет группы при обходе. `GetShade` использует собственный
+материал `LIGHT1` из `system.rlb::Shade.wea`.
+
+Shade cache в GOG `Terrain.dll` имеет vtable RVA `0x643D0`. Его таблица по
+`+316` состоит из восьмибайтовых записей: указатель и номер банка. Число записей
+находится по `+320`; банки начинаются с `+332`, их шаг — 212 байт. Lookup
+RVA `0x10910` возвращает временное представление по `+24`, а не саму запись.
+Построение в RVA `0x10280` и `0x12e20` использует созданную загрузчиком геометрию.
+В рабочем Vulkan-пути `TerrainMaterialLayers` разрешает для каждого terrain slot
+базовую, detail, overlay и overlay-detail фазы из WEAR/MAT0; их UV, alpha и
+lightmap state передаются в `VulkanStaticMaterial`, а shader последовательно
+сэмплирует эти четыре стадии. Animation clock обновляет выбранные фазы и
+коэффициенты без пересборки terrain mesh, сохраняя native type-14 overlay mask.
+
+## Реализация Vulkan
+
+`LegacyPipelineState` хранит blend, depth, cull и alpha-test. Из этих значений
+получается ключ Vulkan pipeline. Alpha reference передаётся отдельно через
+push constant; он не требует нового pipeline.
+
+Рабочий путь `fparkan-game` загружает terrain и все выбранные mission roots
+(по умолчанию весь список объектов), разрешая локальные material slots через
+WEAR и MAT0 до объединения геометрии. Индексы GPU имеют тип `u32`: вся карта
+может содержать больше 65 535 вершин, даже если каждый исходный mesh использует
+`u16`. Terrain передаёт в shader базовый, detail и overlay stages, lightmap и
+animation phase; object batches сохраняют их legacy blend/depth/alpha state.
+
+После загрузки world path создаёт интерактивную free-flight камеру (`WASD`,
+`E/Q`, `Shift`, `Ctrl`, RMB relative-look), обновляет listener и на каждом
+кадре собирает environment frame. Mission-local `sky.ske`/`sky.wea` выбирают
+атмосферное расписание и sky materials, `effects.rlb/env_lightning` даёт
+lightning visual; weather particles, flares, sun/moon, point lights,
+projected shadows и audio events обновляются вместе с камерой и временем.
+`--atmosphere-seconds` задаёт старт времени, `--frames 0` оставляет цикл
+бесконечным, а `--preview-roots` служит только диагностическим ограничителем.
+`--legacy-camera-capture` выбирает воспроизводимую камеру без free-flight
+управления.
+
+Для сохранения кадра surface должен поддерживать `TRANSFER_SRC`. Renderer
+копирует последний отправленный swapchain image в host-visible buffer и
+читает его после завершения GPU. Размер raw-файла — `width * height * 4`.
+Порядок каналов определяется выбранным Vulkan format; его необходимо учитывать
+при преобразовании в обычное изображение.
diff --git a/docs/tomes/06-behavior.md b/docs/tomes/06-behavior.md
index cebb100..a3fa711 100644
--- a/docs/tomes/06-behavior.md
+++ b/docs/tomes/06-behavior.md
@@ -143,7 +143,7 @@ references каждого record, жёстко ограничивает counts/a
файла, но не таблица семантик: названия opcode/words появятся только после
handler contracts и runtime traces.
-Связь первого header word с dispatch теперь доказана статически: `ai.dll`
+Связь первого header word с dispatch доказана статически: `ai.dll`
создаёт 73 handler pointers в известном порядке и копирует table без
перестановки. По всем 58 GOG packages первый word — индекс `0..72` либо
`0xffff_ffff` sentinel; `fparkan-script` отражает это как typed
@@ -201,16 +201,6 @@ Ghidra 12.1.2 decompile GOG `ai.dll` фиксирует отдельный evalu
typed condition/evaluation layer, но **не** формат `.scr`, размеры инструкций
или связь чисел tag с языковыми операторами.
-Выгрузка воспроизводится без изменения PE:
-
-```powershell
-& 'C:\Tools\ghidra_12.1.2_PUBLIC\support\analyzeHeadless.bat' `
- C:\temp\fparkan-ghidra ai -import 'C:\GOG Games\Parkan - Iron Strategy\ai.dll' `
- -processor x86:LE:32:default `
- -scriptPath C:\Develop\fparkan\tools\ghidra `
- -postScript ExportAiExpressionDispatcher.java -deleteProject
-```
-
### TRF и preload-данные
TRF-файлы проходят структурный разбор. `auto.trf`, `data.trf` и tutorial
@@ -292,9 +282,7 @@ Headless Ghidra 12.1.2 decompile GOG binary подтверждает ABI форÐ
а mode передаётся последним; decompiler не восстанавливает предметные имена
остальных слов. `InitializeSettings` получает `CreateGameSettings()` из
World3D и делает virtual call slot `+0x24` с literal `0x15` и строкой по RVA
-`0x42478`. Reproducible extractor находится в
-`tools/ghidra/ExportControlFunctions.java`; он декомпилирует только эти exports
-в локальном Ghidra project и не изменяет оригинальную DLL.
+`0x42478`.
Именно update methods этих private objects, а не пять exports, остаются
следующим объектом динамической трассировки. Поэтому reference movement в
@@ -330,8 +318,7 @@ interface сразу получает пять virtual calls, связывающ
`+0x158`, `+0x160`, `+0x164`, `+0x168` и `+0x18c`; collision object затем
связывается с `+0x170`. Это достаточное основание хранить будущий Control
component как ordered raw-string/resource provenance, но не для присвоения
-этим строкам смысловых имён до трассировки private update methods. Extractor:
-`tools/ghidra/ExportAniMeshControlCaller.java`.
+этим строкам смысловых имён до трассировки private update methods.
Runtime сохраняет ordered raw Unit DAT records рядом с каждым mission object
draft. Это создаёт проверяемую границу передачи данных от loader-а к будущему
@@ -393,22 +380,36 @@ Collision manager не должен хранить прямую незащищё
### CTLD и physical resources
Реестр прототипов ссылается на `*.ctl`, `*.cpt` и связанные control resources.
-В Части 1 структурно проверен 531 CTLD payload без ошибок. Размеры и пять
-внутренних счётчиков образуют множество вариантов: наиболее частый размер
-392 байта с pattern `(0,0,0,1,0)`, но встречаются блоки от примерно 212 до
-1868 байт и более сложные комбинации.
-
-CTLD является составным count-driven форматом, а не фиксированной struct.
-Parser должен:
-
-- прочитать prefix и все счётчики с проверкой переполнения;
-- вычислить границы секций по их counts;
-- сохранять неизвестные records в typed raw containers;
-- требовать точного завершения payload;
-- не использовать размер одного популярного варианта как универсальный layout.
-
-Полная предметная семантика всех секций ещё не доказана, но существующие файлы
-можно безопасно читать, индексировать и сохранять.
+В заголовке CTLD идут пять `u32`; обозначим первые три counts как `S`, `M` и
+`T`. Native layout задаёт начало control-row table формулой
+`128 + S * (156 + 16 * M) + 4 * S * S`; за ним следуют `T` records по 36 байт.
+В record известны node ID (`i32`, `+0`), два endpoint frame (`f32`, `+4`, `+8`),
+начальный blend (`f32`, `+0x0c`) и raw flags (`u32`, `+0x20`). Остальные поля
+нельзя выводить из этой позовой привязки.
+
+Пример `fr_l_plant.ctl` имеет counts `[14, 0, 5, 11, 13]`: формула даёт
+`row_start = 3096 (0xC18)`, а пять control rows заканчиваются на `+0xCCC`.
+GOG `Control.dll` function RVA `0x9950` читает blend из `+0x0c` и flags из
+`+0x20`. Его branch RVA `0x99C4..0x9A66` применяет flags: при `flags & 0x1`
+blend один раз переносится через границу — из значения выше `1` вычитается
+`1`, к значению ниже `0` прибавляется `1`; без этого флага blend ограничивается
+`[0, 1]`. Затем `flags & 0x2` инвертирует значение (`1 - blend`). Константы
+`Control.dll+0x3B188 = 1.0` и `+0x3B18C = 0.0` подтверждают границы этих
+сравнений.
+
+Control в call site RVA `0x99B5` передаёт в AniMesh через slot `+0x30` пару A
+`[-1, -1]` и пару B из endpoint полей строки. В call site `0x9A7B` slot `+0x28`
+сохраняет нормализованный blend по `+0x114` и weight `1` по `+0x118`. AniMesh
+update RVA `0x8BF2..0x8C76` вычисляет
+`frame = (1 - blend) * endpoint_a + blend * endpoint_b`; update RVA `0x12560`
+выбирает B-state из-за weight `1`. В static preview строки с `flags & 0x4 != 0`
+пропускаются до проверки float полей, поскольку endpoint поля в них могут быть
+sentinel `-1`. Повторные node ID сохраняют исходный порядок, поэтому последняя
+включённая строка задаёт кадр узла. Привязки сохраняются отдельно для каждого
+экземпляра компонента, даже когда `PreparedVisual` разделяется через cache.
+Это даёт начальную позу preview, но не запускает игровой Control, AI или полный
+runtime animation path. Остальные
+секции CTLD shape records и contact solver не входят в эту интерпретацию.
### Terrain queries и movement handoff
@@ -447,33 +448,13 @@ Licensed test на GOG `Autodemo.00` запускает этот путь для
доказывает связывание current runtime data, но не доказывает семантику
оригинального движения.
-Путь доступен и через самостоятельный composition root:
-
-```powershell
-fparkan-headless --root "C:\GOG Games\Parkan - Iron Strategy" `
- --mission MISSIONS/Autodemo.00/data.tma `
- --move-object 0 419.10318 717.433 0.25 --ticks 1
-```
-
-`--move-object` принимает original object ID, target X/Y и maximum step;
-требует `--root` и `--mission`, допускается один раз за запуск и отвергает
-non-finite/неположительный шаг ещё при разборе аргументов. Приложение печатает
-`reached`, затем normal headless tick/hash. Проверка на GOG 18 июля 2026 года
-загрузила 8 objects, 343 areals и 3 174 terrain surfaces без graph failures;
-команда для объекта `0` вернула `reached=false`, что подтверждает именно
-ограниченный шаг, а не телепортацию к цели.
-
-### Различия Control в Части 2
-
`Control.dll` пересобрана при неизменных размере, imports и пяти именах/ordinals
exports; RVA всех пяти exports изменились. Форматы и cross-module boundary
сохранились, но точное physical/collision behavior нельзя считать побайтно тем
же.
-CTLD-корпус расширен с 531 до 623 payload. Новых framing errors не найдено;
-большинство общих CTLD изменено вместе с переработанными моделями. Это
-подтверждает count-driven parser, но не закрывает предметную семантику shape
-records и contact solver.
+Покадровая привязка выше описывает начальную позу компонента. Она не задаёт
+семантику CTLD shape records и не восстанавливает contact solver.
Differential test обеих частей должен воспроизводить движение без препятствий,
slope following, pair collision, timing collision event и удаление объекта в
@@ -587,6 +568,142 @@ Ngi32 создаёт низкоуровневый DirectSound backend. `services
`ISoundServer`. Game, Terrain и FX работают уже через эти интерфейсы:
воспроизводят 2D/3D sources, меняют volume и связывают listener с camera.
+### Погода, осадки и звуковые события
+
+Погода для игрока — это одновременно движущиеся точки на экране и звуковой
+фон. Расписание `sky.ske` говорит, в какой момент действует дождь, снег или
+молния, а `sky.wea` назначает имена материалов. Система окружения каждый
+кадр превращает это состояние в два списка: мировые частицы для renderer-а и
+звуковые переходы для audio backend-а.
+
+```text
+sky.ske + sky.wea
+ -> состояние погоды
+ -> EnvironmentFrame
+ -> Particle / Lightning (renderer)
+ -> StartLoop, SetLoopVolume, StopLoop, OneShot (sound)
+```
+
+Такое разделение нужно для понятной границы ответственности. Система погоды
+решает, **что** произошло и где находится источник. Renderer решает, как
+нарисовать прозрачный квадрат или молнию. Audio backend разрешает архив и
+имя, создаёт источник звука и сравнивает его с текущим listener.
+
+У осадков есть объём перед наблюдателем. Эталонные границы имеют глубину
+`2..50`, половины углов `0.65` и `0.4875` радиана. Для текущей камеры:
+
+```text
+half_y = tan(vertical_fov / 2) * 50
+half_x = half_y * aspect_ratio
+```
+
+Размер осадков получает тот же camera query, что и native `Terrain`: ширина
+viewport делится на горизонтальный FOV в радианах.
+
+```text
+horizontal_fov = 2 * atan(tan(vertical_fov / 2) * aspect_ratio)
+precipitation_scalar = viewport_width / horizontal_fov
+rain_size = precipitation_scalar * 0.0065
+snow_size = precipitation_scalar * 0.0195
+```
+
+Число точек получает масштабирование по отношению текущего объёма к
+эталонному и округляется к ближайшему чётному целому:
+
+```text
+N = round_even(clamp(current_volume / reference_volume, 0, 1)
+ * density * intensity * 1000)
+```
+
+В рабочем эмиттере `density` равна единице. Точка сначала появляется в
+локальных координатах этого объёма, затем получает мировую позицию. Дождь
+движется с мировым вектором `[0.5, 0, -60]`, снег — `[0.5, 0, -4]`.
+Поворот камеры меняет видимую область и проекцию, но не вращает эти векторы.
+Когда точка пересекает грань, она переводится в локальные координаты,
+циклически переносится на противоположную грань и возвращается в мир.
+
+Поэтому источник звука и положение частицы должны храниться в мировых
+координатах. У дождя хвостом экранной полосы становится предыдущая мировая
+позиция; у снега остаётся квадрат в текущей позиции. Scalar вычисляется из
+projection и реального viewport каждого кадра, поэтому отдельная настройка
+размера не нужна.
+
+Дождевой loop следует жизненному циклу состояния:
+
+| Событие | Действие audio backend |
+| --- | --- |
+| `StartLoop` | открыть объявленный sample и начать пространственный loop |
+| `SetLoopVolume` | сохранить loop и применить новую интенсивность как громкость |
+| `StopLoop` | остановить текущий loop |
+| `OneShot` | создать отдельный источник и воспроизвести sample молнии один раз |
+
+Интенсивность между ключами `sky.ske` интерполируется, поэтому `SetLoopVolume`
+может приходить на каждом кадре. Имя из расписания сохраняется. Если указано
+только `atm_rain1.wav`, архив остаётся пустым и audio owner использует
+библиотеку миссии; запись `archive/name` задаёт архив явно. Ресурс загружается
+лениво и кэшируется после проверки, чтобы не читать все возможные погодные
+звуки при запуске миссии.
+
+Молния использует отдельный таймер. Для интенсивности `I` и случайного `U` из
+15-битного диапазона задержка имеет вид
+
+```text
+delay_ms = round_even((1 - min(I, 0.95)) * 60000 * U)
+```
+
+После срока выбираются мировые X и Y из `LightningBounds`, а Z копируется из
+границ. Затем объект ждёт 6000 миллисекунд перед новой попыткой. Визуальный
+контракт передаёт renderer-у material и numeric body opcode 3; native
+descriptor `[40, 40, 600]` начинается на `sampled_z + 300`. При нулевом
+локальном смещении его концы находятся на `sampled_z` и `sampled_z + 600`.
+Opcode 1 в это же время отдельно обновляет point light; он не задаёт размеры
+или UV quad. Звуковой `OneShot` использует ту же позицию, а его opcode 2
+параметры `min_distance=100`, `max_distance=1500`, `frequency_ratio=1`
+проходят в spatial source. Renderer и audio backend применяют numeric FX
+поля и lifetime из заголовка эффекта, а CPU не подменяет их собственной
+шириной или fade-кривой.
+
+Подробные поля `EnvironmentFrame`, правила wrap и границы CPU-модуля собраны
+в [справочнике эффектов окружения](../reference/environment-effects.md).
+
+### Ambient variations и переход день/ночь
+
+`ambient_music_loop` запускает `THEME` сразу после открытия миссии. Вариации
+не выбираются последовательным счётчиком: audio owner получает `dt_seconds`
+как приращение времени кадра и после строгого условия `elapsed > delay`
+выбирает один sample.
+Первый положительный tick поэтому запускает первую вариацию, а задержка между
+следующими попытками равна `10 + rand() % 10` секунд.
+
+В `ambient_music_variation` поддерживаются три независимых пула:
+
+| Пул | Ключи | Когда выбирается |
+| --- | --- | --- |
+| default | `DEFAULT_VARIATION1..n` | когда отсутствуют оба пула `DAY` и `NIGHT` |
+| day | `DAY_VARIATION1..n` | длина базового RGB активного небесного объекта больше `1.1` |
+| night | `NIGHT_VARIATION1..n` | длина базового RGB активного небесного объекта не больше `1.1` |
+
+Базовый RGB передаётся до camera-dependent glare и берётся у первого активного
+небесного объекта. Если существует хотя бы один day/night пул, выбранный
+пустой пул остаётся пустым: он не заменяется default или противоположным пулом.
+`LIBRARY` у `ambient_music_variation` может отличаться от библиотеки theme;
+если поле отсутствует, используется библиотека loop.
+
+Индекс выбирается двумя 16-битными состояниями Iron3D:
+
+```text
+a = (a << 1) xor b
+b = (b >> 1) xor a
+index = b % pool_length
+```
+
+При длине пула больше одного предыдущий индекс отбрасывается одной или более
+повторными выборками. Последний индекс сохраняется при переходе между day и
+night; для пустого пула sample не создаётся, но следующий таймер продолжает
+работать. При пустом пуле native сбрасывает индекс в `-1`. Пауза окна сохраняет
+логическое состояние таймера и индекса, поэтому
+возобновление не перескакивает на случайную вариацию.
+
Публичные функции Ngi32:
```text
diff --git a/docs/tomes/07-implementation.md b/docs/tomes/07-implementation.md
index c48fea2..9270bfa 100644
--- a/docs/tomes/07-implementation.md
+++ b/docs/tomes/07-implementation.md
@@ -1,711 +1,68 @@
-# VII. Руководство по полной реализации
-
-Этот том описывает инженерный путь к совместимому движку FParkan. Он опирается
-на доказанные форматы и runtime-контракты, но не требует повторять физическое
-деление оригинала на пятнадцать DLL. Повторить нужно наблюдаемое поведение:
-форматы, имена, fallback, object IDs, порядок событий, численную политику,
-границы кадра, сохранения и воспроизводимость прохождения.
-
-Предложенные ниже modules, handles, snapshots, queues и scheduler phases являются
-целевой архитектурой новой реализации, а не восстановленным внутренним layout
-оригинального Iron3D. Главная практическая цель: запускаться из неизменённого
-оригинального каталога игры, проходить corpus gates для демоверсии, Части 1 и
-Части 2, а затем измеримо двигаться от archive compatibility к полной игровой
-совместимости.
-
-## Целевая архитектура
-
-Практичная форма новой реализации -- модульный монолит с узкими интерфейсами и
-отдельными platform adapters. Внутренние границы должны соответствовать ролям
-Iron3D, а не обязательно его DLL. Это упрощает перенос на современные платформы
-и оставляет возможность поддерживать разные compatibility profiles для разных
-сборок данных.
-
-```text
-application запуск, окно, конфигурация, shutdown
-platform filesystem, clocks, input, threads, dynamic libraries
-resources NRes, RsLi, paths, archives, cache and diagnostics
-assets MSH, WEAR, MAT0, Texm, FXID and auxiliary formats
-mission TMA, unit DAT, prototype graph, scenario data
-world ObjectId, queue, lifecycle, time, messages, mirrors
-terrain Land.msh, Land.map, surface and spatial queries
-navigation areals, graph search, corridors
-behavior unit state machines, target and path requests
-physics control systems, collision proxies and contacts
-animation pose sampling, hierarchy and blending
-audio sample cache, sources, listener and buses
-render immutable frame contracts and modern backend
-network game message schema plus transport adapters
-tools validators, extractors, viewers, captures and editors
-```
-
-Каждый модуль зависит от нижележащих интерфейсов, а не от concrete managers.
-Behavior видит `INavigation` и `IPhysicsCommandSink`, но не включает headers
-renderer-а. Render получает immutable snapshot, а не mutable world. Network
-receive не меняет мир напрямую: validated messages попадают в очередь следующей
-calculation boundary.
-
-### Центральные идентичности
-
-Resource identity хранит и исходное написание, и нормализованный ASCII-key для
-поиска:
-
-```c
-struct ResourceKey {
- NormalizedRelativePath archive;
- FixedAsciiName name;
- uint32_t type_id;
-};
-```
-
-Normalization сохраняет исходную строку для diagnostics и roundtrip, а отдельный
-ASCII-casefold key используется только для lookup. Эта граница важна для
-архивов [NRes](../reference/nres.md), таблиц [RsLi](../reference/rsli.md),
-prototype references и fallback-путей материалов.
-
-Object identity разделяет внутреннюю защиту от dangling references и исходную
-сетевую/script-семантику:
-
-```c
-struct ObjectHandle { uint32_t generation; uint32_t slot; };
-struct OriginalObjectId { uint32_t raw; };
-```
-
-`ObjectHandle` нужен для безопасного внутреннего владения, deferred deletion и
-weak references. `OriginalObjectId` сохраняет наблюдаемую семантику исходной
-игры: scripts, mirrors, network messages и savegame references должны видеть
-логический ID, а не адрес объекта или номер slot в новом allocator-е.
-
-Frame snapshot отделяет simulation от render. Simulation пишет mutable state;
-renderer читает опубликованное состояние или строго ограниченную фазу
-`in_render`. Deferred deletion применяется между фазами, а не во время traversal.
-Командный контур renderer-а должен сверяться с [описанием кадра](../reference/render-frame.md)
-до pixel comparison.
-
-### Владение ресурсами
-
-Ресурс проходит несколько уровней:
-
-```text
-ArchiveHandle -> EntryView -> DecodedBlob -> ParsedAsset -> RuntimeResource
-```
-
-`EntryView` ссылается на metadata архива, `DecodedBlob` владеет подготовленными
-bytes, `ParsedAsset` является CPU-представлением, `RuntimeResource` может
-дополнительно владеть GPU/audio objects. Eviction верхнего уровня не закрывает
-архив, если он ещё нужен другому entry. Ссылки идут вниз только через явные
-handles.
-
-Для shared objects допустимы reference counting или generation handles.
-Intrusive refcount нужен только в ABI-shim; внутренний современный код
-предпочтительно держит понятное владение и weak handles. Архивы, decoded blobs,
-CPU assets и GPU resources имеют отдельные бюджеты и отдельные diagnostics.
-
-### Backend adapters
-
-Render, audio, input и network получают отдельные adapters. Compatibility state
-живёт вне Vulkan, D3D11 или Metal backend; DirectPlay compatibility живёт
-отдельно от modern transport. Так можно заменить платформу, не меняя форматы,
-игровую семантику и regression corpus.
-
-Backend adapter не должен быть местом, где исправляются данные. Если
-[MSH](../reference/msh.md), [MAT0](../reference/materials.md) или
-[Texm](../reference/texm.md) требуют fallback, это фиксируется в asset/runtime
-слое и попадает в trace. Backend получает уже выбранные resources, states и
-draw items.
-
-### Scheduler phases
-
-```text
-collect_platform_events
-build_input_snapshot
-advance_game_clock
-calculate_world_queue
-apply_deferred_operations
-update_navigation_physics_animation_fx
-publish_render_snapshot
-render_world
-render_ui
-end_frame_callbacks
-maintenance_and_eviction
-```
-
-Фазы имеют стабильный порядок и запрещённые операции. Registry mutation
-запрещена во время world traversal, GPU upload не изменяет simulation state, а
-maintenance не влияет на gameplay. Script timers, material animation и FX
-lifetime относятся к game time, если обратное не доказано.
-
-Сначала реализуется однопоточный эталон. Параллелизм добавляется только внутри
-фаз с детерминированным merge: decoding независимых assets, culling chunks или
-подготовка immutable draw items. Это снижает риск скрытых race conditions и
-расхождений replay.
-
-### Структурированные ошибки
-
-Каждая ошибка должна содержать фазу, путь, archive entry, object/prototype key,
-offset и цепочку причины.
-
-```text
-MissionLoadError
- mission: Campaign.00/Mission.02
- object: 17
- resource_name: UNITS/.../unit.dat
- component: e_tur_...
- prototype: objects.rlb::e_tur_...
- cause: model archive missing
-```
-
-Логическое отсутствие необязательного lightmap, отсутствующий entry в архиве,
-неизвестное opaque поле, выход ссылки за диапазон и повреждённый offset имеют
-разный severity и разные способы исправления. Ошибка данных должна быть
-actionable chain, а не строка вида `failed to load resource`.
-
-## Порядок работ
-
-Движок строится от данных к поведению и от детерминированных CPU-компонентов к
-аппаратным. Каждый этап заканчивается исполняемым инструментом и тестовым
-критерием. Нельзя начинать полноценный gameplay, пока ресурсный граф и
-model/material path не дают воспроизводимый результат.
-
-### Этап 0. Corpus harness
-
-- индексировать оригинальный каталог и вычислить hashes;
-- реализовать bounded binary cursor и structured diagnostics;
-- создать CLI для массового запуска parser-ов;
-- сохранять JSON-отчёт с counts, variants, warnings и failures;
-- зафиксировать демоверсию, Часть 1 и Часть 2 как независимые baselines.
-
-Готовность: повторный запуск на каждом неизменённом каталоге даёт идентичный
-отчёт. Любой parser умеет завершиться контролируемой ошибкой с offset и
-контекстом, а не crash или allocation по непроверенному count.
-
-### Этап 1. Архивы и пути
-
-- реализовать strict/lossless [NRes](../reference/nres.md) reader/writer;
-- реализовать [RsLi](../reference/rsli.md) mapping, table transform, lookup,
- LZSS и Deflate;
-- добавить адаптивный decoder для методов `0x080` и `0x0A0`;
-- воспроизвести overlay и известные compatibility quirks;
-- реализовать archive-handle cache и ASCII name policy.
-
-Готовность: неизменённые архивы проходят byte-identical roundtrip; поиск всех
-имён совпадает с каталогом; malformed corpus отклоняется без выхода за память.
-NRes с ненулевым unindexed region обязательно остаётся regression case.
-
-### Этап 2. Граф ресурсов
-
-- разобрать `objects.rlb` и unit DAT;
-- построить resolver прямой MSH, рекурсивного parent prototype через
- `objects.rlb` и отдельного BASE payload;
-- реализовать dependency graph с reachability от миссии;
-- добавить parsers CTPT, NDPR и остальных служебных форматов в lossless-режиме;
-- создать инспектор прототипа, показывающий все связанные ресурсы.
-
-Готовность: 201 demo-объект раскрывается в 501 прототип. Затем все миссии
-Частей 1 и 2 дают 4 701 и 5 845 prototype requests без failures. Недостижимые
-отсутствующие ресурсы отмечаются отдельно от критических ошибок в reachable
-graph.
-
-### Этап 3. Статический asset viewer
-
-- реализовать [MSH](../reference/msh.md) core streams, slots и batches;
-- декодировать Texm во все подтверждённые pixel formats;
-- разобрать WEAR и [MAT0](../reference/materials.md) с точными fallback;
-- построить современный renderer compatibility layer;
-- добавить wireframe, normals, bounds, LOD/group и material debug views.
-
-Готовность: открываются 435/511 моделей, 518/631 textures и 905/1 127 materials
-Частей 1/2; batch/index bounds не нарушаются; viewer показывает корректно
-текстурированную статическую модель из исходного архива. Красивый viewer всё ещё
-означает только asset compatibility, а не готовую игру.
-
-Текущее состояние репозитория нужно формулировать строже. `apps/fparkan-viewer`
-сейчас является inspection CLI и synthetic command producer, а не live Vulkan
-asset viewer. Реальный Vulkan в репозитории сегодня доказан только через
-Stage 0 smoke triangle path; Stage 3 GPU vertical slice для оригинального
-`MSH` + `Texm` + `WEAR/MAT0` + terrain остаётся блокером. Для различения
-smoke, planning и live GPU путей используйте [таблицу правды renderer paths](../rendering/renderer_truth_table.md).
-
-### Этап 4. Анимация и эффекты
-
-- реализовать MSH type 8/type 19 sampling и hierarchy;
-- добавить x87-compatible reference path для чувствительных формул;
-- реализовать material phase animation;
-- разобрать FXID header/commands и runtime instances;
-- сначала поддержать все opcodes, встречающиеся в корпусе, сохраняя raw body;
-- добавить deterministic RNG stream и effect capture.
-
-Готовность: frame-by-frame poses совпадают с golden reference своей части; все
-923/1 065 FXID создаются без parser errors; перезапуск одинакового effect seed
-даёт идентичный список emitted primitives.
-
-Текущее состояние репозитория опять же уже, чем целевой этап. В коде есть
-portable reference sampler и детерминированный FX reference stub, но нет
-runtime-captured parity для lifecycle/opcode semantics и нет Stage 4 rendered
-acceptance поверх live Vulkan asset renderer. Поэтому rendered Stage 4 следует
-считать заблокированным входным gate Stage 3, а parallel Stage 4 work вести
-через captures, schemas и backend-neutral snapshots.
-
-### Этап 5. Карта и мир
-
-- реализовать `Land.msh` и corrected `TerrainFace28` layout;
-- построить terrain rendering и CPU surface queries;
-- реализовать `Land.map`, cell grid и graph links;
-- визуализировать areals и найденные маршруты;
-- разобрать [TMA](../reference/tma.md) и выполнять staged mission loading;
-- создать World3D queue, ObjectId и deferred deletion.
-
-Готовность: 65 карт и 60 TMA Частей 1 и 2 загружаются до EOF; все areal links
-валидны; objects появляются в правильных transforms; мир выдерживает расчётные
-шаги без рендера.
-
-### Этап 6. Gameplay controllers
-
-- подключить input snapshot и camera controller;
-- реализовать navigation corridor, Behavior state machine и Wizard boundary;
-- создать physical controller и collision manager;
-- загрузить control resources в lossless typed model;
-- внедрить game time, pause, event queue и end-of-frame callbacks;
-- подключить AI layer и symbol/event layer сценариев.
-
-Готовность: юнит получает цель, строит маршрут, движется по terrain, реагирует
-на collision и исполняет базовые миссионные события в детерминированном replay.
-На этом этапе вводится differential branch для изменённых `AniMesh`, `Control` и
-`Effect`; неизменённые DLL используют общий reference path.
-
-### Этап 7. Полный кадр, звук и UI
-
-- реализовать render phases, sorting, lighting, shadows и atmosphere;
-- подключить 3D listener, sample cache, FX sounds и mission audio;
-- воспроизвести shell/UI loading и post-world pass;
-- добавить frame capture до UI и после UI;
-- зафиксировать capability fallback profiles.
-
-Готовность: миссия визуально и звуково проходима; каждый draw и sound event
-имеет trace; одинаковый replay создаёт одинаковые command lists. На этом этапе
-вводится differential branch для `iron3d` и `services`.
-
-### Этап 8. Сеть, сохранения и динамическая совместимость
-
-- реализовать modern transport над versioned game-message schema;
-- отдельно исследовать DirectPlay wire и `netZipData` для native compatibility;
-- добавить mirrors, ownership transfer и disconnect cleanup;
-- восстановить save/campaign state и dispatcher;
-- выполнить динамические captures оригинала для render states, script VM и
- physics edge cases.
-
-Готовность: одиночная кампания запускается из оригинального каталога,
-сохраняется и продолжается; multiplayer replay согласован между peers; full
-corpus не создаёт новых parser variants без явной регистрации.
-
-## Тестовый контур
-
-Совместимость нельзя подтвердить одним screenshot. Нужны тесты на уровне bytes,
-структур, ссылок, simulation state, команд renderer-а и конечного изображения.
-Каждый слой локализует свой класс ошибки.
-
-```text
-unit tests
- -> parser/property tests
- -> corpus validation
- -> cross-resource integration
- -> deterministic simulation replay
- -> render/audio command captures
- -> pixel and gameplay parity
-```
-
-Failure верхнего уровня всегда должен позволять спуститься к меньшему тесту и
-понять причину.
-
-### Unit, property и fuzz tests
-
-Для каждого binary primitive проверяются little-endian чтение, bounded strings,
-checked arithmetic и cursor boundaries. Для структур -- минимальный размер,
-максимальные counts, пустые arrays, нулевые варианты и редкие branches.
-
-Property tests генерируют случайные корректные NRes/RsLi/WEAR records,
-выполняют encode -> decode и сравнивают семантику. Fuzz tests изменяют длины,
-offsets, counts и termination bytes и требуют контролируемой ошибки без crash и
-чрезмерного выделения памяти.
-
-Критические алгоритмы имеют отдельные vectors: ASCII casefold, NRes permutation
-search, RsLi byte transform, LZSS backreferences, quaternion shortest path,
-matrix composition и terrain mask remap.
-
-### Corpus validation
-
-Каждый файл оригинального каталога проходит parser своего семейства. Отчёт
-содержит hash, variant, counts, warnings, errors и точный offset сбоя. Baseline
-демоверсии:
-
-```text
-MSH 435
-MAT0 905
-Texm 518
-FXID 923
-WEAR 457
-Land.msh 6
-Land.map 6
-TMA 6
-unit DAT 425
-errors 0
-```
-
-Изменение parser-а принимается только если baseline остаётся стабильной либо
-новый variant зарегистрирован с образцом и объяснением. Warnings должны быть
-именованными: «неизвестное opaque поле» не равно «выход ссылки за диапазон».
-
-### Cross-resource integration
-
-Интеграционный тест начинается с миссии и проходит весь dependency graph:
-object -> prototype -> MSH -> WEAR -> MAT0 -> Texm/lightmap/FXID. Он не
-ограничивается тем, что файлы существуют: material slot должен указывать на
-допустимый MAT0, phase -- на допустимую texture, model batch -- на существующий
-WEAR index.
-
-Demo mission total: 201 objects -> 501 prototypes -> 501 object MSH/WEAR.
-Чистый object graph даёт 3 873 material slots и 5 049 texture requests; после
-включения environment WEAR итог равен 3 879 material slots, 5 067 textures и
-18 lightmaps, failures 0. Такой тест ловит ошибки casefold, suffix, fallback и
-путей, которые отдельный parser не замечает.
-
-Для каждого отсутствующего узла отчёт хранит полный parent chain, чтобы
-различать broken global archive и реально достижимый mission failure.
-
-### Deterministic simulation replay
-
-#### Mission transform state in the world contract
-
-`fparkan-world` now carries a `TransformState` for every live object: the
-three TMA position words, three orientation words and three scale words are
-preserved as exact IEEE-754 bit patterns. This stores source identity before a
-movement or physics controller interprets axes, units or Euler order.
-`WorldSnapshot` publishes transforms in stable object-handle order and the
-canonical SHA-256 state hash includes every transform word.
-
-Mission loading assigns this state after construction and before registration.
-A headless licensed GOG AutoDemo run on 2026-07-18 loaded eight objects, 343
-areals and 3,174 terrain surfaces with zero graph failures, then completed two
-deterministic ticks. This is the state foundation for a future route/movement
-controller; it does not claim recovered velocity, collision or original
-behavior-controller semantics.
-
-The ordinary planning renderer now consumes this snapshot state before falling
-back to a mission draft. Its current transform bridge applies the preserved
-position and non-uniform scale only; raw orientation remains uninterpreted in
-this backend-neutral path. A GOG AutoDemo planning run on 2026-07-18 completed
-two ticks with eight objects, 66 draws and state hash
-`a54855a4f47ffa380911228f295dd49a9a7b88d6ff271a23db48ba318b1fbbb4`.
-
-Записывается начальная миссия, seed, input events, network messages и значения
-внешних часов. На контрольных ticks сохраняется canonical state hash:
-
-```text
-sorted ObjectId list
-transforms and velocities
-critical properties and owners
-AI/behavior state IDs
-active effect state
-game clock and RNG states
-```
-
-Pointer addresses, allocator order и GPU handles в hash не входят. Два запуска с
-одинаковым log должны давать одинаковый state hash на каждом checkpoint. Первое
-расхождение гораздо информативнее финального разного результата миссии.
-
-### Render command parity
-
-До pixel comparison сравнивается command list:
-
-```text
-camera matrices and viewport
-visible ObjectIds
-render phase and stable order
-model/node/slot/batch IDs
-material phase and texture handles
-legacy pipeline states
-index ranges and transforms
-```
-
-Если command lists совпадают, но pixels различаются, проблема находится в
-shader/backend, sampling или численной точности. Если command lists уже
-различаются, pixel diff лишь скрывает более раннюю ошибку.
-
-Golden captures следует хранить отдельно для статической модели, анимации,
-terrain, transparent FX, shadows, lightmap и atmosphere.
-
-### Pixel, audio и network tests
-
-Pixel tests используют фиксированное разрешение, camera, device profile, seed и
-timeline. Сравниваются exact pixels для CPU/reference path и tolerance metrics
-для GPU path, но tolerance не должна скрывать переставленные прозрачные
-primitives.
-
-Audio tests сравнивают список sound events, sample IDs, positions, loop flags и
-gains; waveform зависит от mixer/device и является вторичным уровнем. Network
-tests воспроизводят captured message sequences, проверяют mirrors, ownership и
-disconnect. Для native DirectPlay compatibility дополнительно нужен packet-level
-corpus.
-
-## Regression baselines
-
-Corpus validation формирует три независимых отчёта: демоверсия, Часть 1 и
-Часть 2. Каждый сохраняет manifest файлов, hashes executable/DLL, variants,
-warnings, global archive health и mission reachability.
-
-Ключевые corpus gates:
-
-```text
-NRes: 120 файлов / 6 804 entries и 134 / 8 171 для Частей 1/2
-TMA: 29 миссий / 864 objects / 28 extras и 31 / 885 / 41
-MSH: 435 и 511 моделей
-MAT0: 905 и 1 127 материалов
-Texm: 518 и 631 текстура
-FXID: 923 и 1 065 эффектов
-full reachability: 4 701 и 5 845 prototype requests, failures 0
-```
-
-Расширенные mission-reachability totals:
-
-```text
-Часть 1: 29 TMA, 864 objects, 4 701 prototypes,
- 36 954 materials, 48 806 textures, 139 lightmaps, failures 0
-Часть 2: 31 TMA, 885 objects, 5 845 prototypes,
- 50 888 materials, 68 603 textures, 214 lightmaps, failures 0
-```
-
-Обязательные regression cases:
-
-- NRes с ненулевым unindexed region;
-- prototype inheritance через `objects.rlb`;
-- unit DAT `description[32]` без NUL;
-- TMA epilogue и `extra_count` 0--4;
-- empty SWAV entry;
-- stale save-slot metadata без payload;
-- build-scoped RVA lookup.
-
-Byte-identical asset comparison выполняется только внутри одного корпуса. Между
-Частями 1 и 2 сравниваются semantic invariants и decoded representation,
-поскольку многие assets пересобраны.
-
-## Точность, скорость и повторяемость
-
-Совместимый движок должен быть корректным, повторяемым и достаточно быстрым.
-Эти свойства нельзя получать одним и тем же приёмом. Сначала создаётся простой
-эталонный путь, затем он измеряется и оптимизируется без изменения результата.
-
-Главные источники расхождений: x87 extended precision, преобразование float в
-integer, порядок операций, старые SIMD implementations, нестабильная сортировка,
-RNG и использование разных часов.
-
-### x87 и округление
-
-Оригинальный x86-код мог хранить промежуточные значения в 80-битных регистрах
-x87, а в память записывать 32-битный float. Современный compiler чаще использует
-SSE с округлением после каждой операции. Различие заметно на границах animation
-frame, culling plane и collision threshold.
-
-Для критических формул нужен reference mode:
-
-- фиксированный порядок операций без reassociation;
-- запрещённый fast-math;
-- явные преобразования и проверенный режим округления;
-- тесты возле half-integer и epsilon boundaries;
-- при необходимости extended intermediate через `long double` на проверенной
- платформе.
-
-Не требуется эмулировать x87 во всём движке. Нужно локализовать функции, где
-малое отличие меняет дискретное решение, и держать для них scalar reference path.
-
-### RNG как часть состояния
-
-FX, atmosphere и, вероятно, AI используют случайные значения. Один глобальный
-RNG легко расходится, если новая реализация запрашивает дополнительное число для
-визуальной оптимизации. Для трассировки полезны именованные streams:
-
-```text
-world/gameplay RNG
-AI/script RNG
-FX instance RNG
-atmosphere RNG
-non-deterministic cosmetic RNG
-```
-
-Для native parity может потребоваться один общий алгоритм и точная sequence. До
-подтверждения capture каждый stream хранит seed и счётчик вызовов в trace.
-Cosmetic stream не входит в simulation hash.
-
-### Стабильный порядок
-
-Коллекции не должны зависеть от адресов, unordered containers или порядка
-завершения worker threads. Для объектов, collision pairs, opaque/transparent
-draws и network messages задаются явные stable keys:
-
-- objects -- queue insertion sequence или OriginalObjectId;
-- collision pairs -- упорядоченная пара IDs;
-- opaque draws -- phase, pipeline key, material, stable insertion ID;
-- transparent draws -- layer, quantized distance, stable insertion ID;
-- network messages -- sequence и sender.
-
-Даже когда математический результат коммутативен, side effects, cache accesses и
-RNG делают порядок наблюдаемым.
-
-### Часы и fixed-step
-
-Monotonic platform clock хранится отдельно от game clock. Pause и time scaling
-применяются к game clock. Simulation работает с фиксированным или точно
-воспроизводимым шагом, а render может интерполировать presentation state, не
-изменяя authoritative world.
-
-Maintenance timers кэшей используют реальные часы или отдельную подтверждённую
-шкалу; их срабатывание не должно менять gameplay. При перегрузке лучше выполнить
-ограниченное число simulation steps и явно зафиксировать dropped presentation
-frames, чем передать огромный `dt` в AI/physics.
-
-### Оптимизация без потери эталона
-
-1. Сохранить scalar reference implementation.
-2. Добавить profiler counters на decoding, culling, sorting, animation, upload
- и draw.
-3. Оптимизировать только измеренный bottleneck.
-4. Сравнить SIMD/parallel результат с reference на полном corpus.
-5. Оставить runtime switch для отключения оптимизации при диагностике.
-
-`g_FastProc` удобно моделировать как таблицу function objects: все slots сначала
-указывают на scalar path, затем безопасные slots заменяются SIMD-вариантами
-после self-test на старте.
-
-### Кэш и память
-
-Архивы, decoded blobs, CPU assets и GPU resources имеют отдельные budgets.
-Eviction разрешена только для объектов с нулевым external refcount и после
-безопасной frame fence. Original delayed cleanup порядка десятков секунд можно
-воспроизвести policy-параметрами, не сканируя все entries каждый кадр.
-
-Основные показатели: число открытых архивов, decoded bytes, resident
-textures/lightmaps, models, active FX, draw items и deferred-delete size. Любой
-неограниченно растущий счётчик является regression. Производительность считается
-достаточной только после корректности: стабильные 60 FPS с неверным LOD или
-пропущенными эффектами не являются успехом.
-
-## Release gates
-
-Версия не выпускается, если:
-
-- появился новый corpus error;
-- изменился byte roundtrip неизменённых ресурсов;
-- dependency graph получил failure в достижимом пути;
-- deterministic replay расходится;
-- command capture изменился без ожидаемого changelog;
-- parser допускает allocation по непроверенному count;
-- новая оптимизация не имеет scalar reference comparison.
-
-Каждое исправление регистрирует минимальный regression asset или synthetic
-vector. Если новый behavior намеренно отличается от предыдущего, изменение
-должно иметь compatibility profile, corpus sample и объяснение, почему старый
-baseline был неполным или неверным.
-
-## Уровни совместимости
-
-Слово «совместимый» используется только с уровнем:
-
-1. **Archive-compatible** -- открывает и сохраняет контейнеры.
-2. **Asset-compatible** -- декодирует модели, материалы, текстуры и эффекты.
-3. **Mission-compatible** -- загружает карту и создаёт все объекты.
-4. **Runtime-compatible** -- исполняет время, события, поведение и физику.
-5. **Presentation-compatible** -- воспроизводит рендер и звук.
-6. **Game-compatible** -- позволяет пройти миссии, сохраняться и продолжать.
-7. **Native-interoperable** -- взаимодействует с оригинальной сетью и внешним
- ABI.
-
-Viewer с красивой моделью находится только на втором уровне.
-
-### Обязательные критерии запуска и данных
-
-- приложение запускается из неизменённого оригинального каталога;
-- относительные пути, регистр и legacy encodings разрешаются по исходным
- правилам;
-- все требуемые NRes/RsLi открываются без предварительной конвертации;
-- parsers проверяют границы и не используют неопределённые bytes как указатели;
-- неизвестные поля сохраняются lossless;
-- все mission-reachable prototype, model, material, texture, lightmap и effect
- references разрешаются;
-- отсутствие необязательного ресурса следует документированному fallback, а не
- случайному default.
-
-### Обязательные критерии мира
-
-- TMA разбирается до точного EOF;
-- `Land.msh` и `Land.map` создают корректную поверхность и areal graph;
-- ObjectId, owner и mirror semantics устойчивы;
-- queue traversal и deferred deletion безопасны;
-- pause, game time и simulation steps повторяемы;
-- AI/Behavior/Wizard/Control взаимодействуют через заданные границы;
-- collision и navigation не подменяют друг друга;
-- script events используют logical IDs и переживают удаление объектов;
-- deterministic replay совпадает на контрольных ticks.
-
-### Обязательные критерии presentation
-
-- static и animated MSH используют правильные slots, batches и transforms;
-- WEAR/MAT0/Texm fallback и phase timing совпадают;
-- mip-skip, palettes, Page atlases и lightmaps работают;
-- render phases, depth/cull/blend state и transparent order подтверждены
- captures;
-- FXID commands и RNG дают устойчивый результат;
-- camera и 3D sound listener синхронизированы;
-- atmosphere, тени, солнце и flares не являются декоративными заглушками;
-- UI и world rendering имеют правильную границу;
-- golden command captures стабильны, pixel parity измеряется на фиксированных
- сценах.
-
-### Обязательные критерии полной игры
-
-- все доступные миссии стартуют, завершаются и корректно сообщают
- success/failure;
-- campaign dispatcher сохраняет прогресс;
-- savegame восстанавливает world, script, AI, RNG и clocks, а не только
- placement;
-- input remapping, pause, camera modes, sound и настройки работают из UI;
-- длительный прогон не накапливает objects, resources или audio sources;
-- ошибки данных показывают actionable chain;
-- производительность приемлема без отключения подсистем;
-- демоверсия, Часть 1 и Часть 2 проходят один и тот же тестовый контур с
- раздельными manifests и эталонами.
-
-### Native interoperability
-
-Самый строгий уровень дополнительно требует совпадения x86 ABI экспортов, vtable
-slots и calling conventions для подключаемых оригинальных модулей, а также
-DirectPlay wire/framing и compression. Этот уровень независим от возможности
-играть в новом standalone runtime.
-
-Проект может честно заявлять game compatibility без native DLL/network
-interoperability, но это должно быть явно указано. Аналогично pixel-perfect режим
-может быть отдельным compatibility profile поверх функционально корректного
-renderer-а.
-
-### Совместимость нескольких наборов данных
-
-Критерий полной совместимости применяется отдельно к демоверсии, Части 1 и
-Части 2. Прохождение одного набора не позволяет заявлять поддержку остальных.
-
-Обязательное различие:
-
-- **format compatibility** -- один parser принимает все три набора;
-- **content compatibility** -- конкретная миссия разрешает весь reachable graph;
-- **behavior compatibility** -- runtime совпадает с соответствующей сборкой
- изменённых DLL;
-- **cross-version support** -- один новый движок выбирает корректные данные и
- defaults по fingerprint установки.
-
-Content fingerprint включает hashes executable/DLL и manifest ключевых архивов.
-Он не используется для запрета модификаций, но выбирает compatibility profile и
-делает отклонение диагностируемым.
-
-## Definition of done
-
-Полное документирование и реализация считаются завершёнными только когда каждый
-критерий связан с главой спецификации, executable test и хотя бы одним
-corpus/golden case. Утверждение без проверяемого критерия остаётся
-исследовательской заметкой, а не контрактом.
+# VII. Работа над движком
+
+FParkan развивается небольшими законченными изменениями. Для нового поведения
+сначала нужен пример из оригинала и Rust-тест, затем реализация и объяснение
+в соответствующей главе. Если меняется видимый результат, проверка заканчивается
+запуском приложения на настоящих игровых ресурсах.
+
+## Где находится код
+
+Библиотеки в `crates/` читают архивы и форматы, связывают ресурсы миссии,
+вычисляют геометрию и хранят мир. Они не создают окно и не обращаются к GPU.
+Окно находится в адаптере winit, графические ресурсы — в адаптере Vulkan.
+`fparkan-game` связывает их в приложение.
+
+У файла на диске и объекта в игре разные сроки жизни. Одна модель может быть
+прочитана однажды и использоваться многими объектами, у каждого из которых
+свои положение и время анимации. Материалы и текстуры тоже разделяются между
+экземплярами. Владение этими данными видно из Rust-структур; отдельный сервис
+или интерфейс добавляется, когда появляется реальная потребность.
+
+## Один тест на правило
+
+Для бинарного формата полезен короткий массив байтов: тест проверяет значения
+полей и отказ при обрыве записи или неправильной ссылке. Для преобразования
+координат удобны точки и повороты, результат которых легко посчитать вручную.
+Тесты находятся рядом с соответствующим алгоритмом и запускаются через
+`cargo test --workspace`.
+
+Оригинальные ресурсы остаются в установленной игре. Локальные тесты с ними
+помечены `#[ignore]`, чтобы обычная проверка работала без коммерческих файлов.
+Сообщение об ошибке должно сразу назвать ресурс и причину: например, какой
+материал сослался на отсутствующую текстуру. Поэтому диагностика остаётся
+частью самого запуска и не зависит от отдельного отчёта.
+
+## Числа и порядок вычислений
+
+Порядок умножения матриц имеет значение: поворот объекта вокруг своей оси и
+поворот его положения вокруг начала мира дают разные результаты. Единицы высоты
+ландшафта могут отличаться от единиц размещённых объектов. Эти преобразования
+выполняются в одном месте и проверяются конкретными точками.
+
+Оригинальный x86-код использует x87. Промежуточное значение может иметь большую
+точность, чем сохранённый `float32`. Особенно внимательно проверяются переходы
+между кадрами анимации, округление и границы треугольников. Эмулировать x87 во
+всём движке не требуется: существенные расхождения разбираются в конкретной
+формуле.
+
+Случайные числа и порядок обхода тоже влияют на поведение. Перестановка вызовов
+генератора меняет время молнии или траекторию частицы даже при прежнем seed.
+Для повторяемой проверки сохраняют начальное состояние и одинаковую
+последовательность шагов. Дополнительные потоки и альтернативные алгоритмы
+имеют смысл после измерения узкого места.
+
+## Проверка изображения
+
+Успешное создание Vulkan pipeline доказывает только корректность обращения
+к API. Положение моделей, масштаб карты, текстуры, прозрачность и свет
+проверяются в самом окне. Validation layer помогает находить ошибки владения
+GPU-ресурсами, синхронизации и пересоздания swapchain.
+
+Кадр для сравнения читается после завершения работы GPU. При изменении размера
+окна старые framebuffer и изображения освобождаются лишь после окончания их
+использования. При сворачивании окно может иметь нулевой размер; тогда новые
+кадры не отправляются. Обычное закрытие завершает программу успешно.
+
+Оптимизация начинается с измерения: время загрузки, число повторных декодирований,
+объём текстур и время кадра. Повторное использование уже загруженного ресурса
+обычно полезнее дополнительного слоя управления кэшем.
diff --git a/docs/tomes/08-evidence.md b/docs/tomes/08-evidence.md
index 98f6f13..35bee99 100644
--- a/docs/tomes/08-evidence.md
+++ b/docs/tomes/08-evidence.md
@@ -1,33 +1,9 @@
-# VIII. Справочник и доказательная база
+# VIII. Устройство оригинальной программы
-Восьмой том фиксирует, на чём держится книга: ABI, exports/imports, файловая
-поверхность, статистика корпусов, открытые вопросы, критерии доказанности и
-словарь терминов. Это самостоятельная справочная глава: она не заменяет
-профильные статьи о форматах, но задаёт общий контракт, по которому проверяются
-реализация, parser-ы, compatibility layer и будущие динамические эксперименты.
-
-## Как читать доказательства
-
-Доказательством считается наблюдение, которое можно повторить на конкретном
-файле, сборке или трассе. Вывод может объединять несколько наблюдений, но он
-должен сохранять происхождение данных: демоверсия, полная Часть 1 и полная
-Часть 2 не смешиваются в один безымянный corpus.
-
-Для каждого утверждения полезно различать четыре уровня:
-
-- `layout-confirmed`: известны offset, size, count, bounds и правила безопасного
- чтения;
-- `corpus-verified`: branch или вариант реально встречается в доступных игровых
- данных;
-- `code-confirmed`: branch виден в бинарном коде, но отсутствует в доступном
- corpus;
-- `behavior-confirmed`: поведение подтверждено исполнением оригинальной
- программы, трассой API/vtable или controlled differential test.
-
-Если поле не имеет доказанного предметного смысла, документация хранит его как
-opaque field. Это не мешает lossless read/write, но запрещает строить writer,
-который очищает, переименовывает или пересчитывает такое поле на основании
-правдоподобной догадки.
+Здесь собраны детали, полезные при чтении оригинальных DLL: экспортируемые
+функции, соглашения вызова, адреса и конфигурационные файлы. Адрес относится
+к конкретной сборке; сравнить её можно по
+[хэшам модулей](../reference/original-binaries.md).
## ABI и границы модулей
@@ -215,12 +191,10 @@ RVA используются только для сопоставления и Ñ
implementation не должна встраивать их как постоянные игровые идентификаторы.
Таблица внутренних RVA хранится по SHA-256 конкретного модуля.
-Операционные evidence-артефакты, которые должны оставаться синхронизированными
-с кодом и acceptance, вынесены в отдельные страницы:
-
-- [Hashes и import/export summary оригинального движка](../evidence/original_engine_hashes.md)
-- [Stage 4 capture schema](../evidence/stage4_capture_schema.md)
-- [Renderer truth table](../rendering/renderer_truth_table.md)
+Сводка hashes и import/export оригинального движка вынесена в отдельную
+[страницу](../reference/original-binaries.md). Текущее состояние
+реализации и границы live Vulkan path описаны в [справочнике render frame](../reference/render-frame.md)
+и в исходном коде адаптера.
Подтверждённые hashes неизменённых DLL:
@@ -229,11 +203,10 @@ World3D.dll 17e4a3089b2583a8cf2356c9db0390b1aba138356a09130d79b4e7e4791da61e
Ngi32.dll bab9840d94f4e4e74ffc26677724fa896cf4823845504d09a9e025f80016edf5
```
-Повторный headless IDA/Hex-Rays review GOG `World3D.dll` с этим hash уточнил
-RVA export-ов: `stdCalculateGame=0x139A0`, `stdRenderGame=0x13BD0`,
+Для GOG `World3D.dll` с этим hash RVA export-ов такие: `stdCalculateGame=0x139A0`,
+`stdRenderGame=0x13BD0`,
`sendEndOfRender=0x13D90`, `stdSetCurrentCamera=0x13E60` и
-`stdGetCurrentCamera=0x13E80`. Предыдущая таблица была сдвинута и не должна
-использоваться для hooks или differential capture.
+`stdGetCurrentCamera=0x13E80`.
`stdRenderGame(camera)` сначала вызывает экспорт Terrain
`stdSetCurrentCamera2(camera)`, затем сохраняет текущий camera pointer в
@@ -254,7 +227,7 @@ Terrain object. Следовательно, selector `18`, slot `+12` и global
должны оставаться именованными evidence boundary до dynamic capture; считать
`stdGetCurrentCamera2` getter-ом переданной camera было бы ошибкой.
-Повторный headless-IDA review той же GOG базы уточняет рабочий static contract
+Статический анализ той же GOG базы уточняет рабочий контракт
`CBufferingCamera`. Метод Terrain RVA `0x4D740` копирует ровно 64 байта
(16 dword) в component offset `+0x10`. Frame-preparation метод RVA `0x4D9C0`
получает viewport rectangle через virtual slot `+0x3C`, выводит width, height,
@@ -268,45 +241,20 @@ near/far mapping, handedness или initial camera selection. Важно, что
`ICamera::GetTransformMatrix` (RVA `0x4F850`) ведут только в obsolete-call
stubs и не дают usable ABI.
-Live elevated read-only probe впервые подтвердил relocation-aware runtime связь:
-`Terrain.dll` был загружен по `0x02510000`, global `base + 0x7355C` содержал
-non-null `0x0B37DF08`, а первый dword этого объекта был `0x025765B4` — ровно
-relocated `off_100665B4` из `LoadCamera` construction path. Это доказывает
-live camera object с outer vtable, но одновременно исправляет прежнее слишком
-сильное сопоставление offsets: raw read `global + 0x10` не дал finite 4x4 matrix,
-а `+0x234` был zero в данном sample. Receiver static procedures `0x4D740`/
-`0x4D9C0` и exported global ещё не доказаны как один layout без interface
-adjustment; их offsets остаются unassigned до recovery selector relationship.
-
-Локальная IDA-база уточняет адреса этой связи: `stdGetCurrentCamera2` — это
-шестибайтный getter по RVA `0x4FD80`, который возвращает `dword` по RVA
-`0x7355C`. Единственный найденный **direct static** initializer этого global — функция Terrain
-по RVA `0x4D4D0`: она запрашивает selector `8` у своего `this` и сохраняет
-полученный interface pointer. Это доказывает адрес хранения и путь заполнения,
-но не разрешает трактовать pointer как конкретный layout камеры либо читать его
-как runtime evidence без доступа к процессу на том же уровне привилегий.
-
-Elevated live sampling теперь доказывает, что direct static xref не исчерпывает
-runtime writers: за 25 секунд autoplay global переключился между тремя heap
-pointer, все с relocated outer vtables `0x025765B4`/`0x02576558`. У двух объектов
-paired blocks `+0x2C/+0x3C/+0x4C` и `+0x6C/+0x7C/+0x8C` синхронно несли
-world-like translation, например `(491.562, 761.551, 7.361)`; третий давал
-normalized-looking `(0.098, 0.018, 0.856)` и не совпадал с paired block.
-Наблюдение согласуется с автоматическими camera switches, но не маркирует mode;
-оно запрещает называть `0x4D4D0` единственным runtime writer и требует recovery
-indirect/unanalyzed write path.
-
-Outer vtable `off_100665B4` теперь даёт exact transform adjustment для
-world-like sample. Slot `+0x54` вызывает у subobject `outer + 4` slot `+0x20`
-с selector `0`, затем копирует returned `+0x0C/+0x1C/+0x2C`. В live объекте
-selector field `outer + 0x10` был `0xFFFFFFFF`; реализация selector `0` при
-этом возвращает `outer + 0x20`. Значит observed triple
-`outer + 0x2C/+0x3C/+0x4C` — доказанная translation часть active affine
-transform, а не корреляция. Selector `2` возвращает paired block `outer + 0x60`;
-outer slot `+0x70` применяет `atan2` к его axis values, что доказывает
-orientation-angle path. Названия полей, angle order и handedness пока не
-установлены, но raw affine transform и translation можно сохранять как
-backend-neutral camera pose без догадок.
+Глобальная camera boundary имеет более точное статическое описание.
+`stdGetCurrentCamera2` — короткий getter Terrain по RVA `0x4FD80`, который
+читает указатель из global RVA `0x7355C`; initializer по RVA `0x4D4D0`
+запрашивает selector `8` у своего `this` и сохраняет полученный interface
+pointer. Запрос selector `18` у landscape object проходит через RVA `0x106D0`
+и `0x107E0`, возвращает subobject по `base + 0x138`.
+
+Внешняя camera vtable по RVA `0x665B4` имеет slot `+0x54`, который вызывает у
+subobject `outer + 0x4` slot `+0x20` с selector `0` и копирует возвращённые
+компоненты `+0x0C`, `+0x1C` и `+0x2C`. При значении selector field
+`outer + 0x10 = -1` selector `0` возвращает `outer + 0x20`; selector `2`
+возвращает `outer + 0x60`, а slot `+0x70` выводит углы через `atan2`.
+Это фиксирует пути чтения положения и ориентации, но не назначает имена полям,
+порядок углов или handedness.
### Vtable и interface negotiation
@@ -315,6 +263,11 @@ backend-neutral camera pose без догадок.
world traversal; camera и viewport получаются через selector-based interface
calls; shared objects используют ранний slot как AddRef-подобную операцию.
+Запрос selector `18` у landscape object проходит через RVA `0x106D0` и
+`0x107E0`, возвращает subobject по `base + 0x138`, а его vtable slot `+0x18`
+ведёт через RVA `0x14230` к обработчику `0x127D0`. Это geometry-interface
+boundary, отдельная от хранения активной camera.
+
Правила реконструкции:
1. Зафиксировать byte offset slot и число аргументов.
@@ -326,45 +279,6 @@ calls; shared objects используют ранний slot как AddRef-поÐ
Нельзя добавлять virtual destructor в начало reconstructed interface: это
сдвинет все slots.
-### ABI-матрица Частей 1 и 2
-
-Во всех пятнадцати DLL совпадают export names, ordinals и import sets. Общее
-число exports остаётся 313. Обе полные части содержат 1 134 imported function
-slots; значение 1 126 относится к демоверсии и хранится отдельно.
-
-Побайтно идентичны девять DLL:
-
-```text
-ai.dll
-Behavior.dll
-Joystick.dll
-MisLoad.dll
-Net.dll
-Ngi32.dll
-Terrain.dll
-Wizard.dll
-World3D.dll
-```
-
-Пересобраны `AniMesh.dll`, `ArealMap.dll`, `Control.dll`, `Effect.dll`,
-`iron3d.dll`, `services.dll`.
-
-Изменение export RVA:
-
-```text
-AniMesh 2 / 2
-Control 5 / 5
-iron3d 8 / 8
-services 6 / 6
-ArealMap 0 / 9
-Effect 0 / 2
-```
-
-Нулевое изменение export RVA не доказывает идентичность тела функции:
-`ArealMap.dll` и `Effect.dll` имеют изменённый `.text` при прежних адресах
-exports. Compatibility headers фиксируют внешний ABI один раз, но внутренняя
-таблица адресов, тестов и semantic deltas выбирается по build fingerprint.
-
## Файловая поверхность
### Каталог как внешний API
@@ -546,628 +460,11 @@ provenance: parser хранит не только effective value, но и прÐ
identifier и path normalization должны оставаться разными слоями: локализация
текста не меняет ASCII-casefold policy имён entries.
-## Результаты проверки корпусов
-
-### Demo baseline
-
-Демоверсия содержит `iron_3d.exe`, те же 15 DLL и сокращённый набор
-миссий/ресурсов. Все 15 DLL совпали с первоначально исследованными файлами по
-SHA-256. Поэтому executable, бинарный код DLL и demo-assets относятся к одной
-совместимой технологической сборке.
-
-```text
-modules: 16, из них DLL: 15
-DLL exports: 313
-DLL imports: 1126
-DLL identity: 15/15
-```
-
-`iron_3d.exe`: 36 864 байта, PE32/x86, image base `0x400000`, entry RVA
-`0x141E`, timestamp 28 июня 2001 года, SHA-256
-`b0a8b0db1c3a8698c4d4604d89c655496bd91ac1f8859a455e8a45838aebfbd6`.
-
-### Миссии и сквозные ссылки
-
-Шесть TMA разобраны до точного EOF: суммарно 20 paths, 15 clans, 201 placed
-objects и 1 extra record. 48 объектов ссылаются на unit DAT, 153 -- на прямые
-prototype keys. Unit-файлы раскрыли 348 компонентов.
-
-Сквозной результат:
-
-```text
-501 prototype requests 501 resolved
-501 MSH requests 501 resolved
-501 WEAR requests 501 resolved
-3879 material slots 3879 resolved
-5067 texture requests 5067 resolved
-18 lightmap requests 18 resolved
-failures 0
-```
-
-Это самое сильное интеграционное подтверждение текущего корпуса: имена,
-архивы, ASCII casefold и fallback согласуются между реальными форматами.
-
-### Реестр и unit DAT
-
-`objects.rlb` содержит 590 prototype entries:
-
-```text
-554 имеют прямую MSH-ссылку
-549 прямых MSH разрешаются в demo-каталоге
-34 раскрываются через родительский prototype и локальный BASE
-7 не дают доступной геометрии
-41 ссылка общего реестра указывает на отсутствующий demo-content
-```
-
-Негеометрические или неразрешённые глобальные entries:
-
-```text
-sun_01
-sun_02
-ws_al_01
-ws_al_02
-ws_fl_01
-ws_hm_01
-ws_hm_02
-```
-
-Они не входят в фактически требуемую цепочку проверенных миссий.
-
-Проверено 425 unit DAT, 5 219 records, errors 0. Все records имеют kind 1 и
-archive `objects.rlb`; в 5 205 name fields есть ненулевые хвостовые байты после
-string terminator. Такой tail является данными, а не мусором, если цель --
-lossless roundtrip.
-
-### Модели
-
-Проверено 435 MSH без errors/warnings; 157 анимированных. Диапазоны: 1-38
-nodes, 1-112 slots, 12-9 686 vertices, 1-439 batches.
-
-```text
-414 моделей: types [1,2,3,4,5,15,13,6,7,8,19,9,10,17]
-21 модель: [1,2,3,4,5,18,15,13,6,7,8,19,9,10,17,20]
-```
-
-Type 17 непуст у 29 моделей; type 20 встречается у 21. Редкий variant type 1
-найден в `system.rlb::MTCHECK.MSH`.
-
-Повторная проверка terrain исправила layout face: vertex indices находятся с
-`+0x08`, neighbor indices с `+0x0E`. Эта локальная проверка имеет приоритет над
-ранними черновыми описаниями.
-
-### Материалы и текстуры
-
-Проверено 457 WEAR, 905 MAT0 и 518 Texm без ошибок. У всех MAT0 `attr2 = 6`.
-531 материал содержит одну phase; максимальное число phases -- 29. У 860
-материалов один animation block, у 43 -- два, у 2 -- восемь.
-
-Распределение Texm по форматам:
-
-```text
-indexed 15
-565 155
-4444 59
-888 52
-8888 237
-```
-
-Форматы 556 и 88 присутствуют в loader-е, но не встречаются в demo-assets.
-65 текстур содержат `Page`; размеры лежат от `8x8` до `256x256`. Все 385
-уникальных texture references из MAT0 разрешаются.
-
-### Эффекты
-
-Проверено 923 FXID без ошибок. Наиболее часты команды 3, 7, 1 и 2. Команда 6 в
-данных демоверсии не встречается. Наблюдаются режимы времени 0, 1, 2, 4, 5,
-14, 15, 16 и 17.
-
-### Карты
-
-Шесть `Land.msh` и шесть `Land.map` проходят проверку без ошибок. Всего 3 811
-ареалов; grid всегда `128x128`, максимальное число candidates в ячейке -- 10,
-`poly_count` во всех записях равен нулю.
-
-```text
-AutoMAP 3051 vertices, 3174 faces, 343 areas
-PROL 11125 vertices, 9234 faces, 731 area
-Tut_1 8827 vertices, 8290 faces, 378 areas
-Tut_2 9456 vertices, 8996 faces, 900 areas
-Tut_3 9833 vertices, 8560 faces, 722 areas
-Tut_4 9022 vertices, 8612 faces, 737 areas
-```
-
-Максимальное отклонение длины areal normal от единицы около `1.05e-7`.
-
-### Вспомогательные форматы
-
-```text
-CTPT 284 resources, 3599 points, errors 0
-NDPR 494 resources, 1915 records, errors 0
-BASE 30 resources, errors 0
-EXPL 144 resources, versions 1/2/3, errors 0
-reference arrays 585 resources, 2956 records, errors 0
-SUND 2 resources, 12 keys, errors 0
-CTLD 531 payloads, errors 0
-TRF 5 files, errors 0
-preload 38 entries
-ANI 8 resources
-SKE 6 resources
-```
-
-CTPT names подтверждают attachment semantics: `TurretCenter`, `TurretDirect`,
-`CameraCenter`, `TargetDirect`, `Root`, `Sfx`, `Width`, `Height`, `Dir` и
-другие.
-
-### Как читать статистику
-
-Нулевое число parser errors подтверждает layout и диапазонные инварианты на
-имеющихся variants, но не автоматически раскрывает предметный смысл каждого
-opaque field. Отсутствие opcode или poly branch в corpus означает, что эту
-ветку нельзя считать corpus-verified.
-
-Особенно важно различать весь архив и достижимый runtime path. В `objects.rlb`
-есть ссылки на вырезанный demo-content, однако шесть миссий не требуют их.
-Поэтому quality gate имеет два отчёта: global archive health и mission
-reachability.
-
-### Полные каталоги Частей 1 и 2
-
-Статистика демоверсии остаётся неизменной. Полные Части 1 и 2 образуют два
-самостоятельных профиля с отдельными manifests, hashes и golden data.
-
-Часть 1:
-
-```text
-files 1 017, bytes 197 056 957
-NRes 120 / 6 804 entries
-TMA 29 / 864 objects / 28 extras
-unit DAT 425 / 5 219 records
-objects.rlb 590 prototypes
-MSH 435, MAT0 905, Texm 518, FXID 923
-Land maps 33 / 34 662 areals
-reachable prototypes 4 701
-materials 36 954, textures 48 806, lightmaps 139
-reachability failures 0
-```
-
-Часть 2:
-
-```text
-files 1 302, bytes 358 004 931
-NRes 134 / 8 171 entries
-TMA 31 / 885 objects / 41 extras
-unit DAT 676 / 8 145 records
-objects.rlb 683 prototypes
-MSH 511, MAT0 1 127, Texm 631, FXID 1 065
-Land maps 32 / 18 984 areals
-reachable prototypes 5 845
-materials 50 888, textures 68 603, lightmaps 214
-reachability failures 0
-```
-
-Bootstrap Частей 1 и 2 идентичен. Девять DLL идентичны, шесть пересобраны при
-сохранённом ABI. Активные NRes entries сравниваются так: 3 733 идентичны, 2 503
-имеют изменённый payload, 1 934 добавлены в Части 2, 567 удалены. Это
-показывает стабильность форматов при существенной переработке content,
-особенно MSH, CTLD и FXID.
-
-## Границы знания
-
-### Закрытые или практически закрытые области
-
-- Startup bootstrap и восемь exports `iron3d.dll`.
-- Карта 15 DLL, exports/imports и основные interface boundaries.
-- NRes layout, поиск и writer rules.
-- RsLi header, table transform, lookup, mapping и используемые decode paths.
-- TMA всех 60 проверенных миссий, unit DAT и `objects.rlb` resolution.
-- MSH core/animation range contracts.
-- WEAR, MAT0, Texm и FXID framing.
-- `Land.msh`/`Land.map` и areal grid.
-- World3D calculation/render order и deferred deletion.
-- Сквозная mission-to-texture цепочка.
-
-Полная проверка доступных каталогов усилила NRes active ranges, recursive
-prototype inheritance через `objects.rlb`, bounded non-NUL unit descriptions,
-полный TMA epilogue, extra records и Clan mode 0, MSH/MAT0/Texm/FXID variant
-matrix Частей 1 и 2, 65 `Land.msh`/`Land.map`, полный reachable graph 60
-миссий, stability matrix пятнадцати DLL, empty SWAV и stale save-slot metadata.
-
-### Render-state и pixel parity
-
-Доказан порядок frame boundaries, world traversal, material resolve и крупных
-проходов. Не доказаны символами точные названия renderer vtable slots
-`+0x28/+0x30/+0x34`, полный набор state transitions CShade и окончательный
-взаимный порядок некоторых transparent/FX/shadow subpasses.
-
-Pixel parity требует эталонных кадров оригинала с фиксированными camera,
-timing, seed, разрешением и capability profile. Вместе с изображением
-необходимо сохранять command/state trace; иначе pixel difference не позволяет
-отличить ошибку формата от ошибки backend-а.
-
-Минимальный capture должен фиксировать resolution, bit depth, selected driver,
-device capabilities, camera matrices, mission, game time, seed, input log,
-scene boundaries, transforms, render states, texture-stage states, texture
-binds, viewport, clear, draw calls и `Blt/Flip`. Сначала сравниваются command
-lists; pixel diff имеет смысл только после совпадения geometry/state sequence.
-
-### FXID field-level semantics
-
-Размеры команд, resource references, lifecycle, flags families и используемые
-time modes известны. Не закрыто значение каждого поля body opcodes 1-10,
-отсутствующий во всех проверенных каталогах opcode 6 и точные формулы редких
-time modes.
-
-Закрывающий эксперимент: создать инструмент, который изменяет по одному полю
-копии эффекта, воспроизводить его в контролируемой сцене и логировать runtime
-command object, emitted primitives и sound events. Одновременно reads в
-`Effect.dll` сопоставляются с offsets body.
-
-### Script VM
-
-Сценарные packages, symbol names, event sections, variable declarations и
-version check доступны. Полная instruction grammar `.scr`, semantics всех
-opcodes и serialization состояния VM ещё не восстановлены.
-
-План реконструкции:
-
-1. Найти loader `.scr`, version check, границы bytecode, таблицы
- strings/symbols/events.
-2. Найти dispatcher loop по повторяющемуся чтению opcode и indirect branch или
- jump table.
-3. Для каждого handler определить instruction size, operands, чтения/записи VM
- state, stack effect, branch target и world side effects.
-4. Hook-нуть dispatcher и писать запись `package,event,ip,opcode,raw
- operands,state before,state after,next ip`.
-5. Построить disassembler и CFG; branch target обязан попадать на
- подтверждённую границу инструкции.
-6. Закрывать opcode после статического handler contract, одного динамического
- trace и одного regression script.
-
-После opcode table отдельно восстанавливаются serialization IP, call/event
-frames, variables, timers и RNG.
-
-### Physical/control formats
-
-CTLD и связанные resources структурно читаются, count patterns и variants
-известны. Не названы все секции, shape types, coefficients и точный contact
-solver. То же относится к редким MSH types 17/20 и части CTPT/NDPR flags.
-
-Закрывающий эксперимент: трассировать `LoadControlSystem`,
-`LoadPhysicalModel` и создание collision objects на нескольких прототипах;
-записать offsets, созданные shape instances и реакции на контролируемое
-движение. Изменение одного resource field должно связываться с одним
-наблюдаемым параметром.
-
-### Сеть
-
-DirectPlay lifecycle и имена игровых сообщений известны. Точные framing,
-payload schema, reliability flags и алгоритм `netZipData` пока не подтверждены
-записью сетевого обмена. Поэтому совместимость с оригинальным сетевым клиентом
-ещё не доказана.
-
-Для закрытия нужны два оригинальных клиента в изолированной среде и логирование
-`netZipData`, `netUnZipData`, DirectPlay Send/Receive и World3D message
-enqueue/dequeue. Native interoperability подтверждается только успешным
-обменом original client <-> compatibility implementation в обе стороны.
-
-### Редкие или отсутствующие corpus-ветки
-
-- `Land.map poly_count > 0`: layout читается из loader-а, но ни одна из 65
- проверенных карт не содержит живой записи.
-- RsLi adaptive methods `0x080`/`0x0A0`: decoder path известен, однако
- демоверсия и обе полные части их не используют.
-- Texm formats 556 и 88: loader поддерживает их, но ни один проверенный Texm не
- использует эти значения.
-- FX opcode 6: размер известен, однако живой command отсутствует во всём
- доступном corpus.
-- Некоторые material flags и MSH auxiliary streams встречаются слишком редко
- для полного authoring contract.
-
-Такие ветки реализуются строго по бинарному коду и synthetic tests, а статус
-corpus-verified получают только после появления реального файла.
-
-### Сохранения и campaign state
-
-`saveslots.cfg` и `missions/dispatcher.ini` найдены, но полный бинарный
-savegame payload, serialization World3D/AI/script/RNG и правила миграции версии
-не восстановлены. Без этого нельзя честно заявлять полную campaign
-compatibility.
-
-Минимальный набор сохранений для каждой части:
-
-```text
-S0 сразу после старта миссии
-S1 тот же state без simulation step
-S2 изменена только позиция одного объекта
-S3 изменено только здоровье/свойство
-S4 активен один Behavior order/path
-S5 активен один FX и timer
-S6 изменена одна script variable
-S7 изменён research/economy state
-S8 перед/после mission completion
-S9 pause и non-default game time
-```
-
-Без самих binary save payload возможно описать обязательный state и найти код
-сериализации, но невозможно доказать disk layout и roundtrip.
-
-### Shell, HUD, шрифты и локализация
-
-Граница shell подтверждена экспортами `createShell`/`getIShell`, `IGUIServer`,
-верхнеуровневым UI-pass и файлами `ui/*.cfg`, `DATA/TextRes.cfg`,
-`gamefont.rlb` и `sprites.lib`. RsLi framing двух библиотек закрыт, но widget
-tree, layout rules, font glyph metrics, sprite command semantics,
-focus/navigation и полный HUD state machine пока не восстановлены до
-field-level спецификации.
-
-До закрытия новая реализация может построить функционально эквивалентный UI
-поверх известных ресурсов, но не заявлять native layout/behavior parity.
-
-### Исследования, экономика и игровые свойства
-
-Экспорты `LoadResearch`, `CalcFullResearchCost`, TRF/preload resources и TMA
-properties доказывают отдельный слой исследований, стоимости, добычи и
-производственных параметров. Сквозные имена (`MaximumOre`, `CurrentOre`,
-`FreeResearchTime`, `FreeConstructionTime` и другие) доступны, однако формулы
-стоимости, dependency graph технологий, inventory/economy transitions и точная
-типизация всех 16-byte property values не закрыты.
-
-Закрывающий эксперимент: сопоставить `LoadResearch`/`CalcFullResearchCost` с
-ресурсами и UI, снять изменения state на контролируемых покупках/исследованиях
-и построить typed schema свойств по consumers, не по одному имени.
-
-### Условия динамического этапа
-
-Полное закрытие оставшихся вопросов технически возможно, но не только по
-статическим архивам. Нужна среда, способная запускать оригинальный 32-битный
-код, и набор эталонных наблюдений:
-
-1. Изолированная 32-битная Windows VM или отдельная машина с исходными
- DirectDraw/Direct3D/DirectSound/DirectPlay interfaces.
-2. Два неизменённых игровых каталога и manifest SHA-256 для executable, DLL,
- конфигураций и ключевых архивов.
-3. Отладчик с hardware/software breakpoints, просмотром x87/SSE state и
- сохранением memory dumps.
-4. API/vtable hooking для Win32 file I/O, DirectDraw/Direct3D, DirectSound и
- DirectPlay; hooks должны писать binary trace, не изменяя порядок вызовов.
-5. Управляемые clocks, input log и RNG seed либо trace всех вызовов источника
- случайности.
-6. Автоматический launcher, который восстанавливает snapshot VM, запускает один
- test case, собирает логи и завершает процесс без ручного вмешательства.
-
-Для каждого capture сохраняются profile сборки, hash модулей,
-mission/resource key, конфигурация, device profile, начальное состояние,
-input/time script и версии инструментов.
-
-### Критерий закрытия открытого вопроса
-
-Для каждого открытого вопроса должны существовать:
-
-- build fingerprint и адреса наблюдаемых функций;
-- raw trace и автоматический parser trace-а;
-- минимальный воспроизводимый input/resource/save/message;
-- формальный контракт или явно ограниченная гипотеза;
-- differential test для Частей 1 и 2, если модуль изменён;
-- обновление тематической статьи;
-- regression case, запускаемый без ручного анализа.
-
-До выполнения этих условий статический контракт пригоден для реализации, но
-утверждение о полном поведенческом или native-паритете не публикуется.
-
-## Глоссарий
-
-### Бинарные файлы и reverse engineering
-
-**PE (Portable Executable)** -- формат исполняемых файлов Windows: EXE и DLL.
-Он содержит заголовки, секции, таблицы импортов и экспортов, relocations и
-адрес точки входа.
-
-**Image base** -- предпочтительный адрес начала загруженного PE-образа.
-**VA** -- виртуальный адрес в процессе. **RVA** -- адрес относительно image
-base. Адрес функции в памяти обычно равен `image_base + RVA`.
-
-**Import** -- внешняя функция или переменная, которую модуль получает из другой
-DLL. **Export** -- символ, предоставляемый другим модулям. Имя, ordinal и
-calling convention вместе образуют часть бинарного контракта.
-
-**ABI** -- соглашение о двоичном взаимодействии: размещение аргументов, возврат
-значений, очистка stack, layout структур, порядок virtual methods и правила
-владения.
-
-**Calling convention** -- часть ABI, определяющая передачу аргументов и очистку
-stack. Для исследованного 32-битного кода важны `__cdecl`, `__stdcall` и
-`__thiscall`.
-
-**Vtable** -- массив указателей на virtual methods C++-объекта. Запись
-`vtable +0x34` означает вызов указателя по байтовому смещению `0x34` от начала
-таблицы.
-
-**Static analysis** исследует файл без его исполнения: disassembly, strings,
-imports, call graph и data flow. **Dynamic analysis** наблюдает работающую
-программу: breakpoints, traces, API hooks, memory state и packet/frame captures.
-
-**Evidence** -- наблюдение, которое можно повторить. **Inference** -- вывод,
-объединяющий несколько наблюдений. **Hypothesis** -- рабочее предположение, ещё
-не подтверждённое достаточным экспериментом.
-
-### Форматы данных и ресурсы
-
-**Archive** -- контейнер, объединяющий множество ресурсов. **Entry** -- запись
-его каталога. **Payload** -- полезные bytes конкретной записи.
-
-**Magic** -- короткая сигнатура формата, например `NRes` или `Texm`.
-**Version** -- номер варианта layout. Проверка одной magic без проверки version
-и размеров недостаточна.
-
-**Offset** -- положение данных относительно начала файла или структуры.
-**Size** -- число занимаемых bytes. **Stride** -- размер одного элемента
-массива. **Alignment** -- требование начинать данные на address или offset,
-кратном заданному числу.
-
-**Little-endian** -- порядок, в котором младший byte многобайтного числа
-расположен первым. Все основные числовые поля исследованных форматов Iron3D
-используют этот порядок.
-
-**Fixed-size string** -- поле заранее известной длины. Полезная строка
-заканчивается первым NUL, но оставшиеся bytes поля могут содержать служебный
-хвост и должны сохраняться.
-
-**Opaque field** -- поле с доказанными offset и размером, но не установленным
-предметным смыслом. Его безопасно читать и копировать, но нельзя очищать или
-переосмысливать без эксперимента.
-
-**Invariant** -- условие, которое обязано выполняться: диапазон находится
-внутри payload, индекс указывает на существующий элемент, число записей
-соответствует размеру секции.
-
-**Strict reader** отклоняет любое нарушение контракта. **Compatibility reader**
-дополнительно воспроизводит только известные особенности оригинала, например
-именованный fallback. Compatibility mode не означает игнорирование произвольной
-порчи.
-
-**Roundtrip** -- последовательность decode -> encode. **Byte-identical
-roundtrip** создаёт файл, полностью совпадающий с исходным. **Lossless editor**
-может изменить известное поле, сохранив все остальные bytes и порядок записей.
-
-**Fallback** -- явно предписанный запасной путь, например материал `DEFAULT`,
-затем entry 0. **Heuristic** -- догадка по похожим данным; она не должна
-незаметно заменять доказанный fallback.
-
-### Игровой runtime
-
-**Engine** -- программная среда, которая загружает данные, ведёт время,
-исполняет мир и формирует изображение/звук. **Game** -- конкретные правила,
-миссии и содержимое, работающие поверх engine services.
-
-**World** -- долгоживущее состояние миссии: objects, terrain, время, кланы и
-managers. **Scene** -- представление части мира для конкретной обработки, чаще
-всего текущей камеры.
-
-**Game object** -- сущность с идентичностью, transform, properties и lifecycle.
-**Component/controller** -- специализированная часть поведения: animation,
-physics, AI или rendering representation.
-
-**Simulation** отвечает за изменение мира. **Tick** -- один расчётный шаг
-simulation. **Frame** -- одно подготовленное изображение. Число ticks и frames
-за единицу времени не обязано совпадать.
-
-**Game loop** -- повторяющийся порядок ввода, расчёта, рендера и обслуживания.
-**Scheduler phase** -- явно ограниченный участок loop, где разрешены
-определённые операции.
-**Event/message** -- типизированное сообщение между objects или subsystems.
-**Queue traversal** -- стабильный обход зарегистрированных объектов.
-**Deferred deletion** -- перенос фактического удаления до безопасной границы
-после traversal.
+## Дальнейшее чтение
-**Determinism** -- одинаковый результат при одинаковом initial state, input,
-времени и порядке событий. **Replay** -- повторное исполнение записанной
-последовательности входов/сообщений для проверки determinism.
-
-**Authority** -- subsystem или network peer, которому разрешено окончательно
-менять состояние объекта. **Mirror object** -- локальное представление объекта,
-authority которого находится у другого player.
-
-### Геометрия, анимация и рендеринг
-
-**Mesh** -- набор vertex/index streams и draw-групп, описывающий форму.
-**Node** -- элемент hierarchy модели со своим local transform. **Slot** в MSH
--- выбранная геометрическая группа для комбинации node, LOD и group; он также
-хранит bounds и диапазоны batches.
-
-**Batch** -- непрерывный индексный диапазон с одним material slot и общим
-render state. **Transform** переводит данные между local, world, view и clip
-spaces. Порядок умножения matrices является частью контракта.
-
-**Quaternion** -- четырёхкомпонентное представление вращения. **Keyframe** --
-pose в определённое время. **Sampling** выбирает pose для времени, а
-**blending** смешивает animation states.
-
-**Bounds** -- упрощённый объём для быстрых тестов. **AABB** -- пара
-minimum/maximum по осям. **Bounding sphere** -- center и radius.
-
-**Renderer** -- subsystem, преобразующая подготовленную сцену в изображение.
-**Backend** -- реализация renderer поверх конкретного API или устройства.
-
-**Draw call** -- команда нарисовать диапазон primitives с текущими resources и
-states. **Material** -- правила отображения поверхности: texture, коэффициенты,
-прозрачность и режимы pipeline. **Material phase** -- одно временное состояние
-анимированного материала.
-
-**Texture** -- двумерный массив texels. **UV coordinates** -- координаты
-выборки. **Mip chain** -- последовательность уменьшенных уровней texture.
-**Lightmap** -- texture с заранее рассчитанным вкладом освещения.
-
-**Fixed-function pipeline** -- старый графический pipeline, где приложение
-выбирает predefined transform, lighting, texture-stage и blend states вместо
-пользовательских shaders.
-
-**Depth buffer** хранит глубину уже принятой поверхности. **Alpha test**
-полностью принимает или отвергает fragment. **Blending** смешивает новый цвет с
-framebuffer.
-
-**Back buffer** -- скрытый framebuffer. **Present/flip** делает завершённый
-кадр видимым. **Pixel parity** -- совпадение конечного изображения при
-фиксированных условиях.
-
-### Навигация, физика, звук и сеть
-
-**Areal** -- логическая область карты с границей, class/flags и связями с
-соседями. **Areal graph** -- граф, вершинами которого служат области, а рёбрами
--- допустимые переходы. **Cell grid** -- пространственный индекс для candidate
-areas или objects.
-
-**Pathfinding** -- поиск маршрута по графу. **A\*** использует стоимость уже
-пройденного пути и оценку расстояния до цели. Навигационная проходимость,
-отсутствие collision и видимость -- разные свойства.
-
-**Collision proxy** -- упрощённое представление объекта для столкновений.
-**Broad phase** быстро находит потенциальные пары; **narrow phase** выполняет
-точную проверку и вычисляет contact.
-
-**Sample** -- декодированные звуковые данные. **Source** -- экземпляр
-воспроизведения с position, gain, loop state и временем. **Listener** -- позиция
-и ориентация слушателя для 3D spatialization.
-
-**Transport** -- механизм доставки bytes между peers. **Protocol** -- framing,
-message types, порядок и правила подтверждения. **Serialization** --
-преобразование typed state в byte sequence.
-
-**Reliable delivery** гарантирует доставку/порядок в пределах выбранной модели;
-**unreliable delivery** допускает потери ради задержки. **Wire compatibility**
--- способность обмениваться данными с оригинальным клиентом, а не только
-воспроизводить ту же игровую семантику в новом протоколе.
-
-## Связанные локальные справки
-
-- [NRes](../reference/nres.md)
-- [RsLi](../reference/rsli.md)
-- [TMA](../reference/tma.md)
-- [MSH](../reference/msh.md)
-- [Texm](../reference/texm.md)
-- [Materials](../reference/materials.md)
-- [Render frame](../reference/render-frame.md)
-- [Границы знания](../appendices/knowledge-boundaries.md)
-- [Глоссарий](../appendices/glossary.md)
-
-## Дополнительное чтение
-
-Эти материалы помогают понять PE, ABI, сжатие, graphics pipeline, game loop и
-навигацию. Они не являются доказательством поведения Iron3D: детали движка
-принимаются только после проверки его бинарного кода и игровых ресурсов.
-
-- [Microsoft PE/COFF specification](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format)
-- [Microsoft x86 calling conventions](https://learn.microsoft.com/en-us/cpp/build/x86-calling-conventions)
-- [Intel Software Developer Manuals](https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html)
-- [Ghidra documentation](https://ghidra-sre.org/)
-- [RFC 1951: DEFLATE](https://www.rfc-editor.org/rfc/rfc1951)
-- [zlib manual](https://zlib.net/manual.html)
-- [Kaitai Struct user guide](https://doc.kaitai.io/user_guide.html)
-- [Microsoft Direct3D documentation](https://learn.microsoft.com/en-us/windows/win32/direct3d)
-- [Vulkan specification](https://registry.khronos.org/vulkan/specs/1.4-extensions/html/vkspec.html)
-- [Real-Time Rendering resources](https://www.realtimerendering.com/)
-- [LearnOpenGL](https://learnopengl.com/)
-- [Scratchapixel](https://www.scratchapixel.com/)
-- [Game Programming Patterns](https://gameprogrammingpatterns.com/)
-- [Fix Your Timestep](https://gafferongames.com/post/fix_your_timestep/)
-- [Red Blob Games: A*](https://www.redblobgames.com/pathfinding/a-star/introduction.html)
+[Глоссарий](../appendices/glossary.md),
+[открытые вопросы](../appendices/knowledge-boundaries.md),
+[сценарная VM](../appendices/script-vm.md),
+[сохранения и кампания](../appendices/saves-campaign.md),
+[интерфейс игры](../appendices/ui-shell.md).