From a8faba8aad441e87f8a8b94c148c01f9c075d621 Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Sun, 11 Oct 2026 13:12:50 +0400 Subject: feat: complete map viewer scene and static CTL pose preview Complete the interactive mission viewer with environment rendering, audio events, dynamic shadows, free-flight camera controls, and per-component CTL pose sampling. --- docs/tomes/05-render.md | 74 ++++++++++---- docs/tomes/06-behavior.md | 207 +++++++++++++++++++++++++++++++++------- docs/tomes/07-implementation.md | 5 +- docs/tomes/08-evidence.md | 97 ++++--------------- 4 files changed, 248 insertions(+), 135 deletions(-) (limited to 'docs/tomes') diff --git a/docs/tomes/05-render.md b/docs/tomes/05-render.md index 586485b..71cad1d 100644 --- a/docs/tomes/05-render.md +++ b/docs/tomes/05-render.md @@ -359,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: @@ -611,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 @@ -624,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 запрашиваются в другой последовательности. @@ -677,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 байта @@ -896,8 +918,11 @@ Shade cache в GOG `Terrain.dll` имеет vtable RVA `0x643D0`. Его таб находится по `+320`; банки начинаются с `+332`, их шаг — 212 байт. Lookup RVA `0x10910` возвращает временное представление по `+24`, а не саму запись. Построение в RVA `0x10280` и `0x12e20` использует созданную загрузчиком геометрию. -Поэтому повторить terrain shader только разбором WEAR нельзя. Точная композиция -слоёв и микротекстур остаётся открытым вопросом. +В рабочем 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 @@ -905,12 +930,23 @@ RVA `0x10910` возвращает временное представление получается ключ Vulkan pipeline. Alpha reference передаётся отдельно через push constant; он не требует нового pipeline. -Статический путь `fparkan-game` загружает ландшафт и MSH-компоненты выбранных -объектов. Локальные material slots разрешаются через WEAR и MAT0 перед -объединением геометрии. Индексы GPU имеют тип `u32`: вся карта может содержать -больше 65 535 вершин, даже если каждый исходный mesh использует `u16`. -Текущий путь берёт базовую текстуру Land2; свет, составные слои и атмосфера -пока не участвуют в этом статическом изображении. +Рабочий путь `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 и diff --git a/docs/tomes/06-behavior.md b/docs/tomes/06-behavior.md index ebb3a03..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 @@ -452,10 +453,8 @@ exports; RVA всех пяти exports изменились. Форматы и c сохранились, но точное 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 и удаление объекта в @@ -569,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 7701962..9270bfa 100644 --- a/docs/tomes/07-implementation.md +++ b/docs/tomes/07-implementation.md @@ -28,8 +28,9 @@ FParkan развивается небольшими законченными и Оригинальные ресурсы остаются в установленной игре. Локальные тесты с ними помечены `#[ignore]`, чтобы обычная проверка работала без коммерческих файлов. -Сообщение об ошибке должно назвать ресурс и причину: например, какой материал -сослался на отсутствующую текстуру. Отдельный отчёт для каждого запуска не нужен. +Сообщение об ошибке должно сразу назвать ресурс и причину: например, какой +материал сослался на отсутствующую текстуру. Поэтому диагностика остаётся +частью самого запуска и не зависит от отдельного отчёта. ## Числа и порядок вычислений diff --git a/docs/tomes/08-evidence.md b/docs/tomes/08-evidence.md index 21e3b8d..35bee99 100644 --- a/docs/tomes/08-evidence.md +++ b/docs/tomes/08-evidence.md @@ -241,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 @@ -288,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 и число аргументов. @@ -299,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 -- cgit v1.2.3