diff options
| author | Valentin Popov <valentin@popov.link> | 2026-09-06 04:22:12 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-09-06 04:22:12 +0300 |
| commit | aa51f3574d3d4d4e0fa59653eedd718fceb5d1bd (patch) | |
| tree | 9a43f6a3f3c86767e4e2b7316051562d96789f06 /docs | |
| parent | da03e77f53bb33b2fe203ac45578289953a1b524 (diff) | |
| download | fparkan-aa51f3574d3d4d4e0fa59653eedd718fceb5d1bd.tar.xz fparkan-aa51f3574d3d4d4e0fa59653eedd718fceb5d1bd.zip | |
chore: simplify project to engine docs and tests
Remove planning and acceptance scaffolding while retaining the native Vulkan mission preview, format readers, runtime algorithms, and ordinary Rust tests. Keep the book aligned with the runnable project and validate checked-in shaders without generated tool metadata.
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). |
