Fix terrain height and native frame capture
Docs Deploy / Build and Deploy MkDocs (push) Successful in 40s

Use raw world-space terrain heights throughout mesh, camera-floor, shadow, and sun-ray paths while preserving the projection far-plane conversion. Complete the verified selected-camera readback flow with frozen phase sampling, capture tooling, and documentation.
This commit is contained in:
2026-10-11 17:37:48 +04:00
parent 7b7324a413
commit 100f656b08
9 changed files with 5409 additions and 196 deletions
+34 -1
View File
@@ -48,7 +48,17 @@ the exported `stdRenderGame` is RVA `0x13BD0`. It first calls
`Terrain::stdSetCurrentCamera2(camera)`, stores the camera pointer only for
the frame, and clears it before return. `sendEndOfRender` is a separate export
at RVA `0x13D90`. This is frame-order evidence only: the camera ABI, matrices
and viewport values still require dynamic capture or further decompilation.
and viewport values are not recovered by this call-order analysis. The
implemented preview path below accepts selector-0 input produced through the
[native frame capture workflow](../../tools/native-frame-capture/README.md).
The capture workflow also records an observed mission path and, when
its thread/generation and clock checks succeed, an `atmosphere_seconds` sample.
For a fixed-camera preview, the renderer uses that optional absolute schedule
phase when no `--atmosphere-seconds` override is supplied and keeps schedule
sampling at that exact phase across readback frames. An explicit CLI value takes
precedence. The phase sample is not full simulation time: weather, RNG, and
other mission state remain unknown, so this evidence does not establish
full-frame parity.
Receiver-side GOG Terrain evidence refines this contract. `stdSetCurrentCamera2`
(RVA `0x4FD40` in Terrain SHA-256
@@ -67,6 +77,29 @@ mode string: only `REFLECTION` and `REFLECTION_SHIFTED` produce a non-zero mode
value at `this + 0x1A0`. This is factory ABI evidence, not a recovered view or
projection matrix contract.
## Legacy camera preview path
`fparkan-game --legacy-camera-capture <json>` consumes the selector-0 transform,
the native D3D7 viewport RECT `[left, top, right, bottom]`, near/far planes and
field of view. It derives the D3D7 view and clip transform from those inputs,
carries the resulting `clip_from_world` matrix into the preview camera, and
keeps the view-matrix camera axes directly, so the captured basis, including
roll, is not reduced to yaw/pitch. The RECT becomes a physical drawable extent
of `right - left` by `bottom - top`. The preview window is fixed to that extent;
startup and non-zero resize events reject a physical-size mismatch instead of
stretching the captured view. Compare the result with the corresponding
`[left, top, right, bottom]` crop of the original PNG. `FreeFlightCamera` is not
created in this mode, but redraws still update environment and projected
shadows from the captured camera. Before environment updates can emit sound
events, the audio listener receives the captured position, forward and up axes;
initial world draw sorting uses the same captured position. A phase capture may
include an observed mission path and a verified `atmosphere_seconds` value; the
latter is used only when no explicit CLI override is supplied. This value pins
the atmosphere schedule sample during a fixed-camera comparison while FX still
receives real frame time. `simulation_time_seconds`, weather, RNG and complete
mission state remain unknown, and a camera/phase match alone does not establish
visual parity with a native frame.
## Parity risks
`VulkanStaticCamera::from_legacy_d3d7` keeps the D3D7 view and projection
+23 -4
View File
@@ -871,9 +871,12 @@ RVA `0x36610` строит `Rz(z) * Ry(y) * Rx(x)` с переносом в по
Повороты и переносы узлов модели применяются до размещения объекта в миссии.
Горизонтальная плоскость карты — XY, высота — Z. У GOG AutoDemo диапазон XY
ландшафта составляет `0..1190.6976`. Высоты, сохранённые в `Land.msh`, переводятся
в единицы мира делением на 32. Это отдельное преобразование ландшафта;
применять его к координатам размещённых объектов нельзя.
ландшафта составляет `0..1190.6976`, а исходный диапазон Z в `Land.msh` —
`0..94.50981`. Проверенный native frame с той же камерой совпадает с силуэтом
ландшафта при прямом использовании исходного Z; деление на 32 опускало бы этот
горизонт примерно на 185 пикселей. Terrain, camera floor, shadow receivers и
sun-ray queries поэтому используют исходные координаты `Land.msh` без
преобразования Z.
`Ngi32!niGet3DRender` (RVA `0x5640`) возвращает графический интерфейс.
Вызов Direct3D7 `SetTransform` устанавливает projection из RVA `0x7030`
@@ -946,7 +949,23 @@ projected shadows и audio events обновляются вместе с кам
`--atmosphere-seconds` задаёт старт времени, `--frames 0` оставляет цикл
бесконечным, а `--preview-roots` служит только диагностическим ограничителем.
`--legacy-camera-capture` выбирает воспроизводимую камеру без free-flight
управления.
управления: запись selector 0 задаёт D3D7 transform, clip параметры и native
viewport RECT `[left, top, right, bottom]`. Путь сохраняет оси basis из view
matrix вместе с roll, переносит полученный `clip_from_world` в preview camera,
переводит RECT в физический drawable размер `(right - left) × (bottom - top)`
и отклоняет несовпадение размера при
создании или resize вместо растягивания кадра. Результат нужно сравнивать с
тем же crop исходного PNG `[left, top, right, bottom]`. Контроллер
`FreeFlightCamera` в этом режиме не создаётся, но redraw продолжает обновлять
environment и projected shadows от captured camera. Перед environment update и
его audio events listener получает position, forward и up captured camera;
начальная сортировка world draw использует её же position. Phase capture может
содержать наблюдаемый mission path и проверенный `atmosphere_seconds`; viewer
использует эту абсолютную фазу по умолчанию, если не указан явный CLI override,
и удерживает sample атмосферы на ней через readback frames. FX по-прежнему
получает реальный frame time. Это не полное simulation time: `weather`, RNG и
остальное mission state остаются неизвестными, поэтому совпадение камеры и
фазы не доказывает visual parity с native frame.
Для сохранения кадра surface должен поддерживать `TRANSFER_SRC`. Renderer
копирует последний отправленный swapchain image в host-visible buffer и