feat(animation): recover Node38 fallback hierarchy

This commit is contained in:
2026-07-18 16:25:20 +04:00
parent 858dcd0d1d
commit 1a8e1eaed1
7 changed files with 281 additions and 38 deletions
+14 -10
View File
@@ -45,18 +45,22 @@ struct Node38 {
Validated `ModelAsset` также сохраняет decoded type 8 keys и type 19 map как
`ModelAnimation`. `node38_fallback_pose` возвращает pose по `fallback_key`,
то есть доказанный static input. Значение `parent_or_link` сохраняется как raw
field, пока runtime evidence не подтверждает, является ли оно parent index или
другой link semantic.
то есть доказанный static input. `parent_or_link == 0xFFFF` означает root;
иначе это parent index, обязательно меньший индекса child. Этот контракт
подтверждён на licensed animation gates обеих частей и защищён fallback-ом:
модель с нарушенным порядком не получает придуманную hierarchy.
В legacy-camera static preview стандартный узел уже получает свой fallback pose
до внешнего TMA/Iron3D transform: quaternion поворачивает local vertex, затем
добавляется node translation, после чего применяется `Rz * Ry * Rx`, scale и
mission translation. Геометрия намеренно дублируется на draw-range узла, потому
что один source vertex может быть нарисован разными node poses. Это local-pose
assembly, а не skeleton: `parent_or_link` по-прежнему не используется, поэтому
вложенные parent transforms и dynamic frame-map sampling остаются отдельной
задачей runtime recovery.
до внешнего TMA/Iron3D transform. Parent pose поворачивает child translation,
затем translation суммируется, а rotations умножаются; после полученной global
pose применяется `Rz * Ry * Rx`, scale и mission translation. Геометрия
намеренно дублируется на draw-range узла, потому что один source vertex может
быть нарисован разными 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
+11 -10
View File
@@ -1601,24 +1601,25 @@ parent hierarchy и animation-key/fallback semantics. Модели без layout
Чтобы следующий шаг не перечитывал raw NRes streams в renderer, validated
`ModelAsset` теперь переносит `ModelAnimation` с decoded type-8 keys, type-19
map и declared frame count. `node38_fallback_pose` возвращает exact fallback
key pose, а offset `+2` остаётся именованным только как `parent_or_link_raw`.
Licensed gates Part 1/Part 2 повторно подтвердили 435/511 models, 157/200
animated models, 3,469/5,233 node samples и утверждённые captures. Это
доказывает production handoff animation data, не семантику parent links и не
готовую model assembly.
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` не читается как parent, frame map не
семплируется, поэтому это не заявляет восстановленную hierarchy или animation.
получить разные 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=2584`, `validation_warnings=0`,
`validation_errors=0` и readback hash `5769837558651835265`. Рост с 938
видимых вершин подтверждает, что local source poses реально вошли в geometry
один 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 теперь переводит только высоту