diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/appendices/knowledge-boundaries.md | 39 | ||||
| -rw-r--r-- | docs/appendices/script-vm.md | 20 | ||||
| -rw-r--r-- | docs/baseline/current-project-audit.md | 83 | ||||
| -rw-r--r-- | docs/baseline/notion-reconciliation.md | 88 | ||||
| -rw-r--r-- | docs/baseline/vulkan-revision-plan.md | 158 | ||||
| -rw-r--r-- | docs/evidence/original_engine_hashes.md | 65 | ||||
| -rw-r--r-- | docs/evidence/stage4_capture_schema.md | 107 | ||||
| -rw-r--r-- | docs/index.md | 95 | ||||
| -rw-r--r-- | docs/reference/msh.md | 6 | ||||
| -rw-r--r-- | docs/reference/original-binaries.md | 33 | ||||
| -rw-r--r-- | docs/rendering/renderer_truth_table.md | 51 | ||||
| -rw-r--r-- | docs/tomes/01-guide.md | 253 | ||||
| -rw-r--r-- | docs/tomes/03-resources.md | 15 | ||||
| -rw-r--r-- | docs/tomes/05-render.md | 1177 | ||||
| -rw-r--r-- | docs/tomes/06-behavior.md | 18 | ||||
| -rw-r--r-- | docs/tomes/07-implementation.md | 778 | ||||
| -rw-r--r-- | docs/tomes/08-evidence.md | 682 |
17 files changed, 250 insertions, 3418 deletions
diff --git a/docs/appendices/knowledge-boundaries.md b/docs/appendices/knowledge-boundaries.md index 6f9553e..661069a 100644 --- a/docs/appendices/knowledge-boundaries.md +++ b/docs/appendices/knowledge-boundaries.md @@ -98,42 +98,3 @@ typed schema свойств по consumers, а не по одному имени Такие ветки реализуются по бинарному коду и synthetic tests, а статус corpus-verified получают только после реального файла или runtime trace. - -## Dynamic-stage requirements - -Оставшиеся вопросы нельзя закрыть только статическими архивами. Нужна -изолированная 32-bit Windows-среда, неизменённые игровые каталоги, manifest -SHA-256, debugger, API/vtable hooks, controlled clocks/input и автоматический -launcher, который восстанавливает snapshot, запускает один test case, собирает -логи и завершает процесс без ручного вмешательства. - -Для каждого capture сохраняются build profile, module hashes, mission/resource -key, configuration, device profile, initial state, input/time script и версии -инструментов. - -## Local evidence requests - -Поддерживаемая desktop-платформа — только Windows/Vulkan. Локальный smoke -`fparkan-vulkan-smoke` уже подтверждает настоящий Win32 surface/swapchain, -300 кадров, controlled resize и отсутствие validation warnings/errors. -Это закрывает фундамент Stage 0, но не доказывает визуальный паритет игры. - -Следующие доказательства всё ещё нужны именно для Iron3D-compatible рендера: - -- capability-gated capture из оригинального GOG процесса с camera/matrix, - draw/state и frame-boundary provenance; -- фиксированные Windows Vulkan captures статической модели, lightmapped модели - и terrain после совпадения backend-neutral command capture; -- затем controlled captures анимации, FX, прозрачности, теней и атмосферы. - -Linux/macOS, GLES2, RG40XX и удалённые portability runners не являются -acceptance-гейтами этого проекта. Они не должны появляться в списке блокеров -или подменять Windows evidence. - -## Closure criteria - -Вопрос считается закрытым только при наличии build fingerprint, raw trace, -parser trace-а, минимального воспроизводимого input/resource/save/message, -формального контракта или явно ограниченной гипотезы, differential test для -изменённых DLL, обновления тематической главы и regression case, запускаемого -без ручного анализа. diff --git a/docs/appendices/script-vm.md b/docs/appendices/script-vm.md index afc63e5..1888f35 100644 --- a/docs/appendices/script-vm.md +++ b/docs/appendices/script-vm.md @@ -52,18 +52,11 @@ number, size, operands, control flow, effects, errors и минимальный GOG `ai.dll` доказывает этот framing двумя consumer-ами: loader по `0x10001000` открывает `<bundle>.scr`, `varset.var`, `<bundle>.fml`, затем собирает ровно 73 pointers handlers; `0x10011b20` читает описанную count-driven -структуру. Команда +структуру. В GOG `c1m2p.scr` reader находит 73 handler slots, +9 events, 17 nested records и 20 references, заканчивая на EOF. +Это подтверждает разбор пакета, но не смысл всех opcodes. -```powershell -cargo run -p fparkan-cli -- script inspect ` - 'C:\GOG Games\Parkan - Iron Strategy\MISSIONS\SCRIPTS\c1m2p.scr' --format json -``` - -на исходном пакете возвращает `opcode_handler_count=73`, 9 events, 17 nested -records, 20 references и 0 trailing bytes. Это corpus evidence для reader-а, -но не разрешение на исполнение неизвестных 73 opcodes. - -Теперь установлен selector: loader `0x10001000` создаёт 73 pointers в +Selector устроен следующим образом: loader `0x10001000` создаёт 73 pointers в фиксированном порядке, а `0x10011e70` копирует их без перестановки в runtime array. Во всех 58 GOG `.scr` первый header word каждого nested record равен `0..72` либо `0xffff_ffff`: соответственно 2095 handler selectors и 3992 @@ -157,9 +150,8 @@ slot2 одновременно в `+0x24/+0x28`, slot3 в `+0x2c` и slot6 в `+ На границе mission runtime выбранный TMA clan `first_resource` теперь материализуется как отдельный `MissionScriptBundle`: loader нормализует `<base>.scr`, декодирует его тем же bounded reader-ом и публикует immutable -package вместе с clan provenance. Headless report выводит число таких packages -и их named events. Это именно wiring входных данных, не VM execution: Init и -остальные events пока не dispatch-ятся, а ошибка чтения сохраняет +package вместе с clan provenance. Это именно wiring входных данных, не VM +execution: Init и остальные events пока не dispatch-ятся, а ошибка чтения сохраняет transactional rollback mission loader-а. TMA properties остаются four raw `u32` words плюс имя, пока consumer/schema не diff --git a/docs/baseline/current-project-audit.md b/docs/baseline/current-project-audit.md deleted file mode 100644 index 0dcde93..0000000 --- a/docs/baseline/current-project-audit.md +++ /dev/null @@ -1,83 +0,0 @@ -# Current Project Audit - -Baseline command: - -```text -cargo xtask ci -``` - -Result on 2026-07-18: - -- canonical pipeline passes locally with Rust 1.97.1: formatting, policy, - shader provenance, workspace tests, `clippy`, docs and `cargo deny` are - executed through `cargo xtask ci`; -- `rust-toolchain.toml` pins Rust 1.97.1 and installs only the - `x86_64-pc-windows-msvc` target; Linux and macOS target installation is not - part of the default developer setup; -- internal path dependencies are version-pinned and the Windows-only winit - graph enables only `rwh_06`, excluding Unix window-system dependencies; -- `cargo deny` runs without advisory exceptions in the supported Windows graph. - -Native Vulkan evidence: - -- Windows x86_64 local smoke passed again on 2026-07-18 with Rust 1.97.1, the - system Vulkan loader 1.4.350, and an AMD Radeon Pro WX 3200 Series; -- the smoke created a Win32 surface and swapchain, presented 300 frames, - exercised a controlled resize (three observed resize events and two - swapchain recreations), and shut down with zero validation warnings and - errors; -- Windows is the sole supported runtime target for this project; Linux and - macOS smoke gates are explicitly out of scope. - -Scope labels: - -- Stage 0 codebase gates: locally evidenced. -- Stage 0 Windows native runtime: locally evidenced. -- Linux/macOS runtime and cross-platform hosted CI: out of scope. - -## Current architecture contract - -The standalone engine keeps binary formats, the resource graph, simulation, -animation math and backend-neutral render commands independent from the GPU -adapter. Windows presentation uses `winit`, `raw-window-handle`, `ash-window` -and `ash`; only the Vulkan/FFI adapters may contain narrowly documented -`unsafe` code. Raw Vulkan handles do not cross that boundary. - -The baseline is Vulkan 1.1 with surface, Win32-surface and swapchain support, -binary semaphores/fences and a classic render-pass path. Device capability is -queried at runtime; dynamic rendering, descriptor indexing, synchronization2, -timeline semaphores and extended dynamic state are optional capability-gated -enhancements, not requirements. The canonical initial texture upload is RGBA8 -UNORM. Headless builds remain independent of `winit`, Vulkan and a window -system. - -## Current stage model - -The canonical Vulkan-revision plan has six dependency-ordered stages numbered -0--5. Keeping its original numbering matters: Stage 4 is an evidence-gated -animation/FX runtime, while Stage 5 is the mission/world vertical slice that -depends on it. They must not be reported as one completed stage. - -0. reproducible Windows/Vulkan foundation; -1. paths, VFS and lossless archives; -2. prototype graph and prepared CPU assets; -3. static Vulkan model/terrain viewer; -4. animation and FX runtime, with reference-only semantics until runtime - captures close the x87 and effect-lifecycle evidence gaps; -5. transactional map, mission and world vertical slice, rendered from the - same immutable snapshot through Vulkan. - -This is a local, Windows-only adoption of the Notion page "План реализации -stage 0--5: Vulkan revision" (reviewed on 2026-07-18). Its former -Linux/macOS portability and hosted-CI goals are intentionally not imported: -they conflict with the current supported-platform boundary above. The -portable architectural rules that do apply -- backend-neutral commands, -runtime capability queries, narrow Vulkan/FFI `unsafe`, offline shader -validation, and command capture before pixel comparison -- are retained in -this audit and the rendering tome. - -Contract tests and failure tests precede implementation. Synthetic checks never -read licensed roots; licensed corpus checks use absolute paths from the local -manifest. Backend-neutral command capture precedes pixel comparison, and GPU -addresses, allocator addresses and driver timing are excluded from deterministic -state hashes. diff --git a/docs/baseline/notion-reconciliation.md b/docs/baseline/notion-reconciliation.md deleted file mode 100644 index 8cf2e5d..0000000 --- a/docs/baseline/notion-reconciliation.md +++ /dev/null @@ -1,88 +0,0 @@ -# Сверка локальной книги с FParkan в Notion - -Проверка выполнена 18 июля 2026 года по странице `FParkan`, её восьми томам, -плану Vulkan revision и приложениям A--D. Цель — не копировать структуру -Notion, а убедиться, что каждый доказательный контракт доступен в `docs/` и -остается применимым к Windows/Vulkan scope. - -| Notion | Локальное место | -| --- | --- | -| Статьи 1--3 | `tomes/01-guide.md` | -| Статьи 4--8 | `tomes/02-architecture.md` | -| Статьи 9--13 | `tomes/03-resources.md` и `reference/` | -| Статьи 14--18 | `tomes/04-world.md` и `reference/tma.md` | -| Статьи 19--27 | `tomes/05-render.md`, `reference/` и `rendering/` | -| Статьи 28--32 | `tomes/06-behavior.md` | -| Статьи 33--37 | `tomes/07-implementation.md` и `baseline/vulkan-revision-plan.md` | -| Статьи 38--42 | `tomes/08-evidence.md`, `appendices/glossary.md`, `evidence/` | -| Приложение A | этот audit, `baseline/current-project-audit.md` и тематические тома | -| Приложение B | `appendices/ui-shell.md` | -| Приложение C | `appendices/saves-campaign.md` | -| Приложение D | `appendices/script-vm.md` | - -В ходе сверки добавлены отсутствовавшие локальные контракты UI/Shell, -сохранений/campaign и Script VM. Повторы не переносились: форматы, ABI и -corpus statistics уже находятся рядом с соответствующими readers/consumers. - -Не перенесены только противоречащие утвержденному scope цели: Linux, macOS, -MoltenVK, GLES/RG40XX и hosted CI. Они не считаются пробелами. Текущие -доказательства Windows/Vulkan и все последующие уточнения ведутся только в -локальных файлах. - -## Повторная содержательная сверка (18 июля 2026) - -Повторная сверка проверяет не количество дочерних страниц в Notion, а -утверждения, которые они добавляют к реализации. Источником были корневая -страница `FParkan`, восемь оглавлений с 42 статьями, а также две специальные -страницы: `План реализации stage 0–5: Vulkan revision` (редакция 18 июля) и -`Ревью перехода на Vulkan: решение, доказательства и ограничения`. - -| Контракт из Notion | Локальное подтверждение | Результат | -| --- | --- | --- | -| Статьи 1–3: терминология, уровень доказательств, методика | `tomes/01-guide.md` | Полностью покрыто. | -| Статьи 4–8: bootstrap, DLL, frame loop, World3D | `tomes/02-architecture.md` | Полностью покрыто. | -| Статьи 9–13: VFS, NRes, RsLi, registry, unit и auxiliary formats | `tomes/03-resources.md` и `reference/` | Полностью покрыто. | -| Статьи 14–18: TMA, mission loader, Land, ArealMap и world construction | `tomes/04-world.md`, `reference/tma.md`, `reference/msh.md` | Полностью покрыто. | -| Статьи 19–27: Ngi32, MSH, animation, MAT0/WEAR/Texm, terrain и кадр | `tomes/05-render.md`, `reference/`, `rendering/` | Покрыто; локально дополнено более свежими evidence по D3D7 camera, Node38 и Terrain/GetShade. | -| Статьи 28–32: AI, control, camera, audio, network | `tomes/06-behavior.md`, `appendices/script-vm.md` | Полностью покрыто. | -| Статьи 33–37 и Vulkan revision: ports, stages, deterministic gates, Vulkan profile | `tomes/07-implementation.md`, `baseline/vulkan-revision-plan.md` | Полностью покрыто в Windows-only редакции. | -| Статьи 38–42 и приложения A–D: ABI, corpus, knowledge boundaries, glossary, shell, saves, VM | `tomes/08-evidence.md`, `appendices/`, `evidence/`, этот audit | Полностью покрыто. | - -Пропущенных применимых технических контрактов в этом срезе не найдено. В -частности, локальный Vulkan plan уже содержит независимость решений Vulkan и -`winit`, Vulkan 1.1 baseline, `ash`-изоляцию, SPIR-V manifest/hash, capability -gates, canonical RGBA8 upload и первичность backend-neutral command capture. -Не переносились только исторические cross-platform acceptance требования из -Notion: они прямо отменены текущим Windows-only scope, а не потеряны при -синхронизации. - -Следовательно, новые факты следует добавлять непосредственно в тематический -локальный документ; повторный перенос дерева или дублирование страниц Notion -не требуется. - -## Проверяемые источники и правило разрешения расхождений - -Содержательная сверка опирается не только на оглавления. Были прочитаны -корневая страница `FParkan`, оглавления томов I--VIII и актуальные специальные -страницы [Vulkan revision](https://app.notion.com/p/387e79f2db3981778f94cdf34db5f93f), -[Vulkan review](https://app.notion.com/p/388e79f2db39810eb649edbe90bca529), -а также исторические статьи 33--34. Это позволяет отличить контракт от -исторического статуса работы. - -- В локальной книге сохранены применимые контракты Vulkan revision: Windows - как единственная acceptance-платформа, Vulkan 1.1 baseline, изоляция - `ash`/raw handles в adapter-е, capability gates, offline SPIR-V и первичность - backend-neutral command capture. -- Не переносится прежний статус-аудит Notion (например, утверждения о - synthetic-only renderer или незакрытом Windows smoke): он описывал состояние - до последующих локальных captures и потому не является спецификацией. -- Не переносятся Linux, macOS/MoltenVK и portability-enumeration требования. - Это сознательно исключённая область, а не пробел документации. -- Если страница Notion и свежий локальный evidence расходятся, локальный - evidence с командой воспроизведения, артефактом и датой имеет приоритет; - спорный факт отмечается как граница знания, пока не будет перепроверен. - -Таким образом, на момент сверки не обнаружено пропущенных применимых -технических контрактов: содержательные добавления из Notion уже разнесены по -тематическим локальным документам, а новые результаты разработки должны -добавляться только локально. diff --git a/docs/baseline/vulkan-revision-plan.md b/docs/baseline/vulkan-revision-plan.md deleted file mode 100644 index 868f408..0000000 --- a/docs/baseline/vulkan-revision-plan.md +++ /dev/null @@ -1,158 +0,0 @@ -# План реализации: Vulkan revision (Windows) - -Это локальная, действующая редакция плана `stage 0--5`. Она была получена -вдумчивой сверкой с одноимённой страницей книги FParkan в Notion 18 июля -2026 года. В Notion оставлены исторические формулировки и кроссплатформенные -цели; этот документ сохраняет все применимые технические требования и -приводит их к текущему контракту: самостоятельный движок для Windows, -оригинальные файлы игры и Vulkan. - -## Неизменяемые решения - -- Vulkan — единственный GPU API. Он заменяет прежнюю DirectDraw/Direct3D - реализацию на уровне наблюдаемой семантики кадра, а не через эмуляцию COM - объектов или буквальный перевод старых вызовов. -- `winit` — отдельный adapter окна, ввода и event loop. Vulkan не является - заменой SDL2: это независимые слои. В текущем проекте SDL2 не используется. -- `ash`, `ash-window` и `raw-window-handle` остаются внутри Windows - platform/Vulkan adapters. Backend-neutral crates не экспортируют raw Vulkan - handles и сохраняют `#![forbid(unsafe_code)]`; каждый `unsafe` в adapter-е - имеет локальный safety contract, правило владения и regression test. -- Baseline: Vulkan 1.1, surface + Win32 surface + swapchain, classic render - pass, binary semaphores и fences. Format/queue/present/image-count/sampler - capabilities запрашиваются у конкретного устройства. Dynamic rendering, - descriptor indexing, synchronization2, timeline semaphores и extended - dynamic state допускаются только через capability gate. -- Исходные MSH, WEAR, MAT0 и Texm остаются CPU-форматами. Стартовый upload - путь — канонический RGBA8 UNORM; packed/native GPU formats допустимы только - после доказательства эквивалентности. Shader variants собираются offline в - SPIR-V и проверяются validator-ом, manifest-ом и hash. -- Первичный эталон — backend-neutral command capture. Сравнение пикселей - начинается лишь после совпадения draw order, pipeline key, resource IDs, - descriptor bindings, ranges и transforms. GPU handles, allocator addresses - и driver timing не входят в deterministic state hash. - -Windows — единственная runtime-платформа acceptance. Требования Notion к -Linux, macOS/MoltenVK, portability enumeration и hosted CI намеренно не -переносятся: они противоречат утверждённой области проекта, а не являются -пропуском документации. Headless сборка по-прежнему не зависит от окна, -Vulkan loader или `winit`. - -## Stage 0 — воспроизводимая Windows/Vulkan основа - -**Цель:** минимальный реальный Vulkan vertical slice без игровых assets и -локальные повторяемые gates. - -- Зафиксировать stable Rust/MSRV, `Cargo.lock` и `--locked`; расширять - `cargo xtask ci` форматированием, tests, clippy, документацией, policy для - licenses/advisories/sources и проверкой разрешённого `unsafe` allowlist. -- Synthetic gate не читает лицензированные каталоги и не может молча пропускать - тест. Licensed corpus запускается отдельно по абсолютным путям local - manifest. Hosted CI/CD в этот scope не входит. -- Поддерживать typed parsing конфигурации xtask и `cargo_metadata`, а не - ручную интерпретацию TOML; исключать устаревшие adapter names и Python - runtime components из policy. -- Поддерживать `fparkan-platform-winit` (lifecycle, resize/DPI, input, - suspend/resume, raw handles) и `fparkan-render-vulkan` (instance, - validation, device scoring, queues, swapchain, resize/out-of-date/suboptimal - handling, deterministic capability report). -- Acceptance: Windows smoke создаёт настоящее окно/swapchain, показывает не - менее 300 кадров с resize и завершается без validation errors; negative - cases проверяют loader/device/present-queue/surface-format failures. - -## Stage 1 — пути, VFS и архивы - -**Цель:** безопасный lossless resource substrate без GPU coupling. - -- Для каждого пути различать raw legacy bytes, normalized path, ASCII lookup - key и host path; strict и compatible policy не смешивать. -- Применить symlink-safe traversal и casefold-collision policy ко всем VFS. -- В каждый parser/decompressor внедрить общие `DecodeLimits` и - `AllocationBudget`; malformed offsets, counts и decompression bombs должны - завершаться bounded errors. -- Довести NRes и RsLi до lossless reader/editor/writer: сохранять unknown и - non-zero regions, stable directory order, все наблюдённые decode methods, - explicit compatibility profile и output limits. -- Resource repository обязан иметь generation handles, decoded-byte budget, - deterministic eviction, lock-free decompression section и структурированные - ошибки с archive/entry/path/offset/phase/cause chain. -- Acceptance: synthetic no-edit и edit roundtrip, stale handles, traversal, - symlink/casefold и byte-identical corpus reports; Part 1/Part 2 не дают - необъяснённых parser failures. - -## Stage 2 — prototype graph и CPU assets - -**Цель:** полный mission-reachable graph и typed prepared assets до GPU. - -- Разрешить `objects.rlb`, unit DAT, inheritance, BASE/resource variants и - все компоненты unit, сохраняя hierarchy, provenance и multi-component - composition. -- Каждый edge хранит typed provenance: mission object, component, prototype, - model, wear, material, texture, lightmap или effect. Циклы, depth limit, - optional fallback и corrupt reachable dependency имеют разные outcomes. -- `fparkan-assets` — единственный слой CPU preparation; apps и runtime не - парсят assets ad hoc. Assets immutable, имеют stable IDs, а graph failures - содержат полную parent chain. -- Acceptance: graph order/IDs стабильны; все mission-reachable requests - обеих частей завершаются с failures 0 и передают runtime только prepared - assets. - -## Stage 3 — статический Vulkan viewer - -**Цель:** доказуемый статический MSH/terrain render из оригинальных assets. - -- Закрыть validation streams/slots/batches/indices, Texm decode/mips/palettes/ - Page rectangles и WEAR/MAT0 fallback с раздельными texture/lightmap identity. -- Backend-neutral `LegacyPipelineState` и canonical `PipelineKey` выбираются - до GPU. Vulkan adapter владеет staging/device buffers, image transitions, - samplers/descriptors, pipeline cache, depth, diffuse/lightmap bindings, - alpha/depth/cull/blend mapping и lifecycle per-frame resources. -- Viewer/debug modes включают model, texture, material, wireframe, normals, - bounds, LOD/group и terrain; upload cache ограничен GPU budget. -- Acceptance: CPU golden vectors, descriptor/pipeline-key/row-stride tests, - command captures до GPU и fixed-camera captures модели, lightmapped модели - и terrain; Windows validation smoke остаётся clean. - -## Stage 4 — animation и FX runtime - -**Цель:** заменить reference stubs доказанным deterministic runtime. - -- Реализовать type 8/type 19 node sampling, fallback keys, hierarchy и - material timeline по подтверждённым modes/masks. Portable math не выдают за - x87-compatible: второй путь появляется только после captured vectors. -- FXID отделяет lifecycle/time/RNG gates от backend: неподтверждённые fields - сохраняются raw и не исполняются как догадки; emit формирует - backend-neutral primitive/audio commands. -- Pose/effect snapshots immutable per frame; Part 1/Part 2 profiles различают - только там, где это подтверждено differential captures. -- Acceptance: frame-by-frame poses имеют approved references, FXID corpus не - имеет parser errors, один seed даёт одинаковые commands. До этого semantic - статус строго `reference-only`, а не `runtime-compatible`. - -## Stage 5 — карта, миссия и мир - -**Цель:** транзакционно загрузить миссию, выполнить headless steps и показать -тот же immutable world snapshot через Vulkan. - -- Закрыть Land.msh/TerrainFace28, Land.map, grid/graph validation и runtime - spatial acceleration для surface/raycast/visibility queries. -- Loader выполняет `Context -> Map -> TMA -> Graph -> Assets -> Construct -> - Register`, откатывая любую ошибку. Он сохраняет raw transforms, properties, - original IDs и provenance всех mission components. -- World queue, generation handles, deferred deletion, deterministic clock и - snapshot contract проверяются replay/hash tests; terrain/navigation и render - читают один опубликованный snapshot, не mutable world. -- Acceptance: headless mission replay стабилен, transaction rollback не - оставляет частичного мира, а Windows Vulkan frame использует ту же snapshot - и имеет связанный command/pixel artifact. - -## Сверка с Notion - -Восемь томов локальной книги покрывают 42 основные статьи Notion по тем же -разделам I--VIII; приложения сведены в `appendices/` и том VIII. Специальное -Vulkan-ревью дополнительно внесло в локальные материалы следующие точные -факты: исходный Ngi32 dynamically resolves DirectDraw/Direct3D, современная -граница замены находится выше Vulkan, а совпадающий SHA-256 Ngi32 в Частях 1 и -2 позволяет использовать один backend contract. Детали доказательства и -текущие native captures находятся в `tomes/05-render.md`, -`evidence/original_engine_hashes.md` и `rendering/renderer_truth_table.md`. diff --git a/docs/evidence/original_engine_hashes.md b/docs/evidence/original_engine_hashes.md deleted file mode 100644 index fab8dc7..0000000 --- a/docs/evidence/original_engine_hashes.md +++ /dev/null @@ -1,65 +0,0 @@ -# Original Engine Hashes - -Страница фиксирует минимальный статический baseline, на который должны -ссылаться capture fixtures и Stage 4 evidence. - -## Scope - -- Источник: локальная статическая сверка Part 1 (`IS`) и Part 2 (`IS2`). -- Метод: SHA-256, export/import tables, `objdump -p`, `strings`. -- Эта страница не заменяет динамические traces: она задаёт only-if-match - binary baseline для дальнейших runtime captures. - -## Stable binaries across Part 1 / Part 2 - -| Binary | SHA-256 | Size | Notes | -| --- | --- | ---: | --- | -| `Ngi32.dll` | `bab9840d94f4e4e74ffc26677724fa896cf4823845504d09a9e025f80016edf5` | 253952 | Shared low-level render/resource/audio boundary | -| `World3D.dll` | `17e4a3089b2583a8cf2356c9db0390b1aba138356a09130d79b4e7e4791da61e` | 208896 | Shared gameplay/world/render lifecycle baseline | -| `Terrain.dll` | `6d3e68f0e15b297f6c184af3113baf1f31e19c3326c18f0150dec659242ed667` | 708608 | Shared terrain/shade/world baseline | -| `iron_3d.exe` / `iron_3d_p2.exe` | `f476af85c034a4b4f34f49d0806e4dff397b5da0ee26d382a7674231144979f7` | 36864 | Shared launcher binary | - -## GOG research baseline - -The canonical disassembly source is the windowed GOG installation, not the -Part 1/Part 2 test installations. Its `Terrain.dll` is a distinct revision: - -| Binary | SHA-256 | Size | Notes | -| --- | --- | ---: | --- | -| `Terrain.dll` | `af87d1b2e728a0be73c52be3b44cc196ab46da7799f25a15d40f8c9b0b425ead` | 499712 | GOG camera receiver evidence; do not reuse Part 1/2 RVAs without a matching hash | - -## Divergent binaries across Part 1 / Part 2 - -Эти модули нельзя автоматически считать behavior-compatible между частями: - -- `AniMesh.dll` -- `Effect.dll` -- `iron3d.dll` -- `services.dll` -- `Control.dll` -- `ArealMap.dll` - -## Practical use - -1. Frame-order traces для `World3D.dll`, `Terrain.dll` и `Ngi32.dll` можно - привязывать к shared profile, пока hash совпадает. -2. Animation and FX captures обязаны храниться раздельно для Part 1 и Part 2, - потому что `AniMesh.dll` и `Effect.dll` отличаются. -3. Любой runtime fixture должен записывать минимум: - - `game_part` - - `module_name` - - `module_sha256` - - `mission` - - `frame_or_tick` - - `schema_version` - -## Export / import focus for Stage 4 - -- `World3D.dll`: `stdCalculateGame`, `stdRenderGame`, `sendEndOfRender` -- `Terrain.dll`: `GetShade`, `GetWorld`, `stdSetCurrentCamera2` -- `AniMesh.dll`: `LoadAgent`, `LoadAniMesh` -- `Effect.dll`: `CreateFxManager`, `InitializeSettings` -- `Ngi32.dll`: `niGet3DRender`, `n3dPrimitive`, `n3dEndScene`, `rsLoadTexture`, `rsLoadMultiTexture` - -Если будущий capture fixture не указывает, к какому hash он относится, такой -fixture нельзя считать acceptance evidence. diff --git a/docs/evidence/stage4_capture_schema.md b/docs/evidence/stage4_capture_schema.md deleted file mode 100644 index 18efd91..0000000 --- a/docs/evidence/stage4_capture_schema.md +++ /dev/null @@ -1,107 +0,0 @@ -# Stage 4 Capture Schema - -Stage 4 нельзя закрывать набором ad-hoc логов. Нужна схема, по которой -animation, FX и rendered frame captures сравниваются между Part 1, Part 2 и -современной реализацией. - -## Goals - -- сделать captures пригодными для автоматического diff; -- не хранить host-specific пути, временные каталоги и нестабильные handles; -- связывать frame traces, command captures и pixel artifacts общим identity. - -## Common envelope - -```json -{ - "schema_version": "fparkan-stage4-capture-v1", - "capture_kind": "frame-trace | animation-pose | fx-lifecycle | render-frame", - "game_part": "part1 | part2", - "mission": "MISSIONS/.../data.tma", - "frame_id": 123, - "tick": 123, - "module_hashes": { - "World3D.dll": "sha256...", - "Terrain.dll": "sha256...", - "AniMesh.dll": "sha256...", - "Effect.dll": "sha256..." - }, - "tool_version": "codex/manual/fixture version", - "notes": [] -} -``` - -## Capture kinds - -### `frame-trace` - -Используется для порядка фаз и внешних вызовов. - -Required fields: - -- `events`: ordered list of `{ phase, symbol, sequence, object_id?, fx_id?, camera_id? }` -- `queue_counters`: deferred operations, visible objects, emitted FX, UI callbacks -- `rng_state`: optional, if recoverable - -### `animation-pose` - -Используется для x87 / portable sampler parity. - -Required fields: - -- `clip_id` -- `node_index` -- `sample_time` -- `numeric_profile` -- `translation` -- `rotation_quat` -- `scale` -- `matrix_hash` - -### `fx-lifecycle` - -Используется для create/update/emit/stop parity. - -Required fields: - -- `fx_id` -- `instance_id` -- `time` -- `opcode_events` -- `rng_calls` -- `resource_refs` -- `emissions` - -### `render-frame` - -Связывает backend-neutral snapshot с live Vulkan output. - -Required fields: - -- `camera` -- `visible_object_ids` -- `draws` -- `pipeline_keys` -- `resource_ids` -- `validation` -- `pixel_artifact` - -## Stability rules - -1. Не записывать абсолютные host paths. -2. Не записывать raw pointer addresses как identity fields. -3. `frame_id` должен совпадать между trace, command capture и pixel artifact. -4. GPU-specific transient handles допустимы только внутри diagnostics fields и - не участвуют в canonical equality. -5. Любой capture without `module_hashes` считается informational, а не - acceptance-grade. - -## Acceptance mapping - -- `S4-TRACE-*` rows читают `frame-trace` -- `S4-ANIM-*` rows читают `animation-pose` -- `S4-FX-*` rows читают `fx-lifecycle` -- `S4-VK-*` и `S4-PIXEL-*` rows читают `render-frame` - -Эта схема intentionally минимальна. Новые поля можно добавлять, но нельзя -ломать перечисленные identity and parity anchors без смены `schema_version`. diff --git a/docs/index.md b/docs/index.md index ea17a63..25069a0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,72 +1,27 @@ # FParkan -FParkan -- самостоятельная техническая книга о восстановлении игрового движка -Iron3D из *Parkan: Iron Strategy*. Она ведёт от запуска оригинальной программы -и карты DLL к форматам ресурсов, загрузке миссии, геометрии, материалам, -рендеру, поведению, звуку, сети и плану чистой совместимой реализации. - -Сайт оформлен как онлайн-книга: тома читаются последовательно, а справочник -используется как быстрый доступ к форматам, проверочным правилам и границам -доказанного знания. - -## Источник истины и синхронизация - -Каталог `docs/` — единственный рабочий источник истины для FParkan. 18 июля -2026 года его содержательно сопоставили с книгой FParkan в Notion; результат, -маршрутизация статей и сознательно исключённые цели зафиксированы в -[сверке](baseline/notion-reconciliation.md). Актуальные локальные материалы -содержат и последующие результаты разработки. Notion больше не -является местом внесения изменений: новые факты, исправления и решения -фиксируются только здесь, в томе, справочнике или evidence-документе, которому -они принадлежат. - -При расхождении приоритет имеют более новое локальное доказательство и текущий -scope проекта: самостоятельный runtime ориентирован исключительно на Windows и -Vulkan. Исторические упоминания других ОС в старых внешних заметках не -расширяют поддерживаемую платформу. - -Действующий dependency-ordered план `stage 0--5` находится в -[Vulkan revision для Windows](baseline/vulkan-revision-plan.md). Это -редакторская локальная версия: она включает недостающие контрактные требования -из Notion, но не переносит снятые с проекта Linux/macOS и hosted-CI цели. - -## Как читать - -Если вы впервые разбираете игровой движок, начните с тома I и II. Там вводится -лексика, доказательная политика, модульная архитектура и жизненный цикл кадра. - -Если нужна реализация совместимого движка, читайте тома III--VII линейно: -ресурсы, миссии, мир, рендер, интерактивные подсистемы и порядок работ. - -Если вы проверяете выводы, переходите к тому VIII и приложениям. Там собраны -уровни уверенности, corpus gates, открытые вопросы и критерии закрытия. - -## Восемь томов - -1. **Путеводитель и методика** -- назначение книги, маршруты чтения, язык - предметной области и правила проверки. -2. **Запуск, архитектура и игровой цикл** -- `iron_3d.exe`, пятнадцать DLL, - сервисы, World3D, очередь объектов и границы кадра. -3. **Ресурсная система и форматы** -- NRes, RsLi, кэши, имена, `objects.rlb`, - unit DAT и сквозное разрешение ресурсов. -4. **Мир, миссии и runtime** -- TMA, ландшафт, ареалы, маршруты, создание мира - и свойства размещённых объектов. -5. **Геометрия, материалы и рендер** -- MSH, анимация, WEAR, MAT0, Texm, FXID, - свет, атмосфера и полный render frame. -6. **Поведение, управление, звук и сеть** -- AI, Behavior, Wizard, Control, - ввод, камера, звук и DirectPlay-слой. -7. **Руководство по полной реализации** -- целевая архитектура, этапы работ, - тестовый контур, точность, скорость и критерий совместимости. -8. **Справочник и доказательная база** -- ABI, конфигурация, статистика - корпусов, границы знания и глоссарий. - -## Политика доказательств - -Специфические утверждения об Iron3D принимаются только после локальной проверки -на исполняемых файлах, DLL, демоверсии, полных каталогах Частей 1 и 2 или на -взаимных инвариантах реальных ресурсов. Внешние описания и текущий код FParkan -могут подсказывать вопросы, но не заменяют проверку. - -Неизвестные поля не получают правдоподобных имён. Пока смысл не закрыт, -документация фиксирует raw layout, границы, безопасное чтение и lossless -сохранение. +Эта книга рассказывает, как устроен движок *Паркан: Железная стратегия* и как +FParkan воспроизводит его работу на Rust и Vulkan. Начнём с простого вопроса: +что должно произойти между выбором миссии и появлением первого кадра? +Ответ связывает архивы, модели, поверхность карты, камеру, свет и поведение +объектов в одну программу. + +Главы читаются последовательно. Для знакомства с игровой разработкой +подойдут первые две; подробные структуры файлов можно искать в справочнике. + +1. [Путеводитель](tomes/01-guide.md): основные понятия и чтение бинарных структур. +2. [Архитектура и игровой цикл](tomes/02-architecture.md): запуск, мир и кадр. +3. [Ресурсы](tomes/03-resources.md): архивы, имена и связи файлов. +4. [Миссии и ландшафт](tomes/04-world.md): размещение объектов и поверхность. +5. [Геометрия и рендер](tomes/05-render.md): путь от вершины до пикселя. +6. [Поведение, управление и звук](tomes/06-behavior.md): интерактивный мир. +7. [Работа над движком](tomes/07-implementation.md): код, тесты и проверка результата. +8. [Устройство оригинальной программы](tomes/08-evidence.md): DLL, адреса и конфигурация. + +В [глоссарии](appendices/glossary.md) собраны термины, а в +[открытых вопросах](appendices/knowledge-boundaries.md) — ещё не восстановленное +поведение. Описание алгоритма оригинала и возможности текущего приложения +различаются явно. Предположение остаётся предположением, пока его не +подтверждают игровые файлы или код оригинала. + +Команды запуска и проверки приведены в [README](../README.md). diff --git a/docs/reference/msh.md b/docs/reference/msh.md index 774e388..2ef276c 100644 --- a/docs/reference/msh.md +++ b/docs/reference/msh.md @@ -47,7 +47,7 @@ Validated `ModelAsset` также сохраняет decoded type 8 keys и type `ModelAnimation`. `node38_fallback_pose` возвращает pose по `fallback_key`, то есть доказанный static input. `parent_or_link == 0xFFFF` означает root; иначе это parent index, обязательно меньший индекса child. Этот контракт -подтверждён на licensed animation gates обеих частей и защищён fallback-ом: +подтверждён на тестах анимации с оригинальными ресурсами обеих частей и защищён fallback-ом: модель с нарушенным порядком не получает придуманную hierarchy. В legacy-camera static preview стандартный узел уже получает свой fallback pose @@ -58,10 +58,6 @@ pose применяется `Rz * Ry * Rx`, scale и mission translation. Гео быть нарисован разными node poses. Это static fallback hierarchy, а не полная animation parity: dynamic type-19 frame-map sampling остаётся отдельной задачей. -Для воспроизводимого исследования `fparkan-cli model inspect --root <game> ---archive <archive> --resource <model.msh>` выводит Node38 metadata, включая -parent index, fallback key и наличие LOD0/group0 geometry. - ## Slot and batch Type 2 содержит header `0x8C`, затем `Slot68`: diff --git a/docs/reference/original-binaries.md b/docs/reference/original-binaries.md new file mode 100644 index 0000000..05c0141 --- /dev/null +++ b/docs/reference/original-binaries.md @@ -0,0 +1,33 @@ +# Оригинальные модули + +RVA в книге относится к определённой сборке DLL. SHA-256 позволяет +проверить, что сравнивается тот же код. + +## Совпадающие модули частей 1 и 2 + +| Binary | SHA-256 | Size | Notes | +| --- | --- | ---: | --- | +| `Ngi32.dll` | `bab9840d94f4e4e74ffc26677724fa896cf4823845504d09a9e025f80016edf5` | 253952 | Shared low-level render/resource/audio boundary | +| `World3D.dll` | `17e4a3089b2583a8cf2356c9db0390b1aba138356a09130d79b4e7e4791da61e` | 208896 | Shared gameplay/world/render lifecycle baseline | +| `Terrain.dll` | `6d3e68f0e15b297f6c184af3113baf1f31e19c3326c18f0150dec659242ed667` | 708608 | Shared terrain/shade/world baseline | +| `iron_3d.exe` / `iron_3d_p2.exe` | `f476af85c034a4b4f34f49d0806e4dff397b5da0ee26d382a7674231144979f7` | 36864 | Shared launcher binary | + +## Сборка GOG + +The canonical disassembly source is the windowed GOG installation, not the +Part 1/Part 2 test installations. Its `Terrain.dll` is a distinct revision: + +| Binary | SHA-256 | Size | Notes | +| --- | --- | ---: | --- | +| `Terrain.dll` | `af87d1b2e728a0be73c52be3b44cc196ab46da7799f25a15d40f8c9b0b425ead` | 499712 | GOG camera receiver evidence; do not reuse Part 1/2 RVAs without a matching hash | + +## Различающиеся модули + +Эти модули нельзя автоматически считать behavior-compatible между частями: + +- `AniMesh.dll` +- `Effect.dll` +- `iron3d.dll` +- `services.dll` +- `Control.dll` +- `ArealMap.dll` diff --git a/docs/rendering/renderer_truth_table.md b/docs/rendering/renderer_truth_table.md deleted file mode 100644 index 2687bd4..0000000 --- a/docs/rendering/renderer_truth_table.md +++ /dev/null @@ -1,51 +0,0 @@ -# Renderer Truth Table - -Эта страница нужна для одной вещи: не позволять путям smoke, planning и capture -выглядеть как «почти готовый renderer». Каждый путь доказывает разный класс -свойств, и acceptance не должен смешивать их. - -## Краткая матрица - -| Path | Native window / swapchain | Draws pixels | Uses original assets | Acceptance class | Что доказывает | Чего не доказывает | -| --- | --- | --- | --- | --- | --- | --- | -| `fparkan-vulkan-smoke` / `VulkanSmokeRenderer` | Yes | Yes | Static MSH plus sampled TEXM | `covered-gpu` for Stage 0 smoke and explicit MSH/TEXM/descriptor bridge IDs | Loader, instance, surface, swapchain, submit/present, validation-clean triangle path; original MSH indexed draw; TEXM RGBA8 staging upload; per-batch WEAR/MAT0 diffuse descriptors and fragment sampling | MAT0 phase animation, lightmaps, legacy blend/depth/cull states, terrain, camera/node transforms, gameplay rendering | -| `VulkanPlanningBackend` | No | No | Optional CPU-side IDs only | `covered-planning` | Deterministic command validation, canonical capture, frame submission planning | Любой live GPU draw, pixel parity, validation-clean asset frame | -| `RecordingBackend` | No | No | Optional CPU-side IDs only | `covered-planning` | Stable command capture for backend-neutral tests | Native window, Vulkan, GPU resource lifetime, pixels | -| `NullBackend` | No | No | Optional CPU-side IDs only | Usually `covered` for validation-only rows | Command stream framing and bounds validation | Capture stability, GPU execution, pixels | -| `VulkanAssetRenderer` | Yes | Yes | Yes | `covered-gpu` | Static original asset rendering: MSH/Texm/WEAR/MAT0/terrain through Vulkan | Animation/FX parity unless explicitly wired | -| `fparkan-game --backend static-vulkan` | Yes, GOG `Autodemo.00` | Yes, full static AutoDemo preview | Eight mission objects, 66 MSH components, original diffuse TEXM and Land2 terrain base layer | `covered-gpu` for the bounded static-scene bridge | Diagnostic XY framing with recovered translation/scale/Z Euler placement, fallback node hierarchy, optional captured legacy camera, 32-bit GPU indices, diffuse descriptors, terrain base draws and synchronized teardown telemetry | Material phases, Land1 blend/microtexture/lightmaps, animated keys, FX, camera control and gameplay parity | -| Future rendered `fparkan-game` mode | Yes | Yes | Yes | `covered-gpu` plus original-evidence IDs | Mission-driven render snapshot execution and pixel capture | Original-runtime parity for animation/FX/x87 without dedicated captures | - -## Rules - -1. IDs со смыслом `VK`, `GPU`, `DRAW`, `PIXEL`, `VALIDATION` или `RENDERED` - на Stage 3+ не могут закрываться через `NullBackend`, `RecordingBackend` - или `VulkanPlanningBackend`. -2. `covered-planning` означает command planning/capture evidence. Этот статус - никогда не считается доказательством draw пикселей. -3. `covered-stub` зарезервирован для явно помеченных `STUB` acceptance rows и - не считается compatibility closure для FX lifecycle. -4. `covered-gpu` требует live native handles, реальный draw path и связанный - renderer artifact: report, capture или approved pixel. - -## Current repository status - -- Реальный Vulkan в репозитории имеет smoke triangle path и узкий static asset bridge. `VulkanStaticDrawRange` сохраняет исходный `Batch20.material_index`; когда smoke запускается с `--wear-root`, `--wear-archive`, `--wear-name` без override, он дедуплицирует selectors, проходит каждый через `WEAR → MAT0 → Textures.lib`, создаёт по одному image/descriptor set и бинит set непосредственно перед соответствующим indexed draw. Direct TEXM и `--material-index` — намеренно однотекстурные compatibility modes. Fresh GOG `fortif.rlb::FR_L_MTP.msh` подтверждает 237 batch draws, но его selectors все `0`, поэтому live report содержит один binding `MTP_01.0`; unit contract подтверждает точное сопоставление двух разных selectors с разными descriptor sets. Это не доказывает material phase animation, lightmaps, alpha/depth/cull state, terrain, camera/node transforms или pixel approval. -- Lightmap остаётся отдельным, не реализованным contract: оригинальный `World3D.dll` экспортирует самостоятельный `SetLightMapLib` наряду с `SetTexturesLib` и `SetMaterialLib`; WEAR содержит независимый блок `LIGHTMAPS`. Текущая документация не подтверждает связь этих slots с `Batch20.material_index` или их UV/channel semantics, поэтому viewer не подменяет lightmap diffuse texture и не добавляет недоказанное binding. -- `apps/fparkan-game` по умолчанию выдаёт `render-planning` JSON report поверх - synthetic window descriptor и `VulkanPlanningBackend`. Opt-in `--backend static-vulkan` - создаёт native `winit` window. Для GOG `MISSIONS/Autodemo.00/data.tma` он уже - рендерит весь статический набор из восьми mission objects и 66 MSH components с - diagnostic XY camera (or an optional captured legacy camera), доказанным `Rz * Ry * Rx` placement transform, - fallback node hierarchy и Land2 base terrain. Последний validation-clean - текущий трёхкадровый запуск после применения placement transform в XY path - имел 71 material descriptor, `clip_visible_vertices=75543` и readback hash - `5444013368935681345`. Это `covered-gpu` для ограниченного - static-scene bridge, но не pixel parity и не доказательство material phase, - Land1 blend, microtexture, lightmap, dynamic animation, FX, live camera - selection или gameplay rendering. -- `apps/fparkan-viewer` сейчас inspection-only CLI и не открывает live Vulkan - asset viewer. -- Следующий реальный milestone для rendered acceptance: `VulkanAssetRenderer` - с upload/draw/capture path для хотя бы одной оригинальной модели и одного - terrain slice. 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..586485b 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 @@ -842,1107 +840,80 @@ 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` использует созданную загрузчиком геометрию. +Поэтому повторить terrain shader только разбором WEAR нельзя. Точная композиция +слоёв и микротекстур остаётся открытым вопросом. + +## Реализация Vulkan + +`LegacyPipelineState` хранит blend, depth, cull и alpha-test. Из этих значений +получается ключ Vulkan pipeline. Alpha reference передаётся отдельно через +push constant; он не требует нового pipeline. + +Статический путь `fparkan-game` загружает ландшафт и MSH-компоненты выбранных +объектов. Локальные material slots разрешаются через WEAR и MAT0 перед +объединением геометрии. Индексы GPU имеют тип `u32`: вся карта может содержать +больше 65 535 вершин, даже если каждый исходный mesh использует `u16`. +Текущий путь берёт базовую текстуру Land2; свет, составные слои и атмосфера +пока не участвуют в этом статическом изображении. + +Для сохранения кадра 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..ebb3a03 100644 --- a/docs/tomes/06-behavior.md +++ b/docs/tomes/06-behavior.md @@ -447,24 +447,6 @@ 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 нельзя считать побайтно тем diff --git a/docs/tomes/07-implementation.md b/docs/tomes/07-implementation.md index c48fea2..7701962 100644 --- a/docs/tomes/07-implementation.md +++ b/docs/tomes/07-implementation.md @@ -1,711 +1,67 @@ -# 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..21e3b8d 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, @@ -546,628 +519,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). |
