feat(msh): decode triangle descriptors and query segments
Docs Deploy / Build and Deploy MkDocs (push) Successful in 38s

This commit is contained in:
Valentin Popov committed 2026-10-30 22:05:34 +04:00
1 parent 1bce6e2a67
commit eaf5df3ad0
6 files changed
+933 -3

No files matched your search

+4
View File
@@ -14,6 +14,10 @@ state transitions и окончательный порядок части transp
рельефом. Она пока не проверяет стены и проёмы строений; эти ограничения не
подтверждают поведение стратегической камеры или камеры робота оригинала.
Локальный MSH-запрос `query_slot_segment` сверён с оригиналом на фабричной
стене и дверном проёме. Мировое преобразование, фильтры категории и
подключение к свободной камере остаются открытыми ([MSH](../reference/msh.md)).
Закрывающий эксперимент: запустить оригинал в совместимой Windows/DirectX
среде, перехватить DirectDraw/Direct3D calls и surface flips, сохранить state
log на минимальных сценах с одним типом материала.
+39
View File
@@ -154,6 +154,45 @@ Byte `material_index` выбирает строку WEAR; high word at `+0x02`
индекс через `base_vertex` попадает в position stream. `vertex_count` и
`local_batch_index` доступны renderer для исходного draw contract.
## Type 7 и сегментный запрос
Type 7 необязателен для обычного MSH preview. Если stream есть, каждые 16 bytes
описывают одну грань: `tri_flags` находится по `+0`, четыре неинтерпретируемых
слова — по `+2`, `+4`, `+6` и `+14`, а signed `normal[3]` — по `+8`, `+0xA` и
`+0xC`. `ModelAsset.triangle_descriptors` сохраняет декодированную таблицу как
`Option`; отсутствие Type 7 не делает статическую модель недействительной.
`query_slot_segment` использует Node38 header mask, Batch20 leading DWORD
`batch_flags | (material_index_hi << 16)` и `tri_flags`. Для каждого поля
include mask требует все установленные биты, exclude mask пропускает запись при
любом совпавшем бите. Descriptor cursor начинается с `Slot68.tri_start` и
сдвигается на `Batch20.index_count / 3` после каждой записи, даже если batch
или грань отфильтрованы. Индексы грани читаются из type 6 с учётом
`base_vertex`.
Правило восстановлено для GOG-бинарей `AniMesh.dll`
(`65659bf67f20e5be77d67a5b2f59968f61b94293143807fa59ca451c560c4f7e`) и
`Ngi32.dll` (`bab9840d94f4e4e74ffc26677724fa896cf4823845504d09a9e025f80016edf5`).
Callback `AniMesh.dll+0x11090` переводит signed type-7 normal множителем
`1 / 32767`; функция `AniMesh.dll+0x11EB0` записывает этот scale в глобальное
поле. Callback вызывает FastProc slot `+0xB4`: в этой сборке он указывает на
`Ngi32.dll+0x1D560`. После обычной загрузки DLL порог `Ngi32.dll+0x37224`
равен нулю, поэтому plane test принимает только переход от положительного
расстояния к неположительному (`dot(normal, end - start) < 0`, расстояние в
начале `> 0`, в конце `<= 0`). `Batch20.batch_flags & 0x0002` разрешает повторный
plane test в обратном направлении, только если прямой plane test не пересёк
плоскость. Пересечение проверяется export ordinal 202
(`Ngi32.dll+0x1E60`, `mrnPointInPoly`): проекция выбирает ось с наибольшим по
модулю normal component, а точка проходит грань, пока ни одно edge expression
не меньше нуля. Равенство нулю принимается, поэтому попадание на ребро или
вершину считается попаданием. Поздний кандидат с тем же squared distance
заменяет предыдущий; расстояние считается от исходного первого endpoint.
Native helper получает точку пересечения через `RCPSS` и один шаг уточнения;
Rust использует обычное деление `f32`, поэтому последние биты координат могут
отличаться. Игнорируемый тест с ресурсами проверяет исходную стену, дверной
проём и обратное направление на `fortif.rlb/fr_l_plant.msh`.
## Сторона грани в цветовом проходе
В цветном проходе `Terrain.dll`, функция `0x100296F0`, сравнивает камеру с