feat(assets): decode research tables and calculate candidate costs

This commit is contained in:
Valentin Popov committed 2026-10-31 04:28:05 +04:00
1 parent dbb59b00d1
commit 97c73118a1
5 files changed
+1981 -23

No files matched your search

+46 -9
View File
@@ -485,16 +485,53 @@ Tag выбирает конкретное правило. Для tag 1 запи
### TRF и preload-данные
TRF-файлы проходят структурный разбор. `auto.trf`, `data.trf` и tutorial
variants имеют сигнатуру [NRes](../reference/nres.md) и содержат большие
таблицы имён игровых прототипов: оружия, башен, сооружений и других объектов.
Также найдены preload-записи, ANI и SKE resources.
TRF-архивы используют несколько потребителей; один подтверждённый путь ведёт
из `Handler(43)` к вычислению стоимости исследования. В проверенной сборке GOG
`CalcFullResearchCost` из `ArealMap.dll` получает таблицу, загруженную через
`MisLoad.dll`. Для этого пути `TRF0` содержит строки по десять сырых `u32`,
`TRF1` — байты флагов, `TRFB` — пары «смещение имени и номер строки», а
`TRF6` — пул строк с нулевыми окончаниями. Слово 8 строки связывает её с
`TRFB` через младшие 16 бит; старшая половина сохраняется как неизвестное
значение. `TRF2` и `TRF3` образуют упорядоченные списки предварительных
исследований, которые читает этот путь стоимости. `TRF4`/`TRF5` образуют
отдельную пару таблиц, но `CalcFullResearchCost` её не использует. Непрозрачные
`TRF7`–`TRFA` остаются в архиве и требуются загрузчику.
По содержимому, порядку загрузки и consumers TRF с высокой вероятностью
предоставляет AI/сценарному слою заранее подготовленную таблицу типов и
связанных данных. Framing и имена подтверждены corpus-ом, но полная семантика
каждой TRF-записи ещё не закрыта. Имена должны разрешаться через тот же
resource registry, что и миссионные объекты.
Модуль `fparkan-assets` разбирает этот профиль в `ResearchTrfDocument` и
вычисляет пару результатов для одного Unit DAT:
```rust
let table = ResearchTrfDocument::decode(archive_bytes)?;
let cost = calc_full_research_cost(&candidate, &table)?;
```
Расчёт проходит компоненты Unit DAT в порядке дерева. Компонент с нулевым
`kind` завершает весь свой участок вместе с потомками. Для остальных ключ
ресурса ищется среди имён `TRFB`; повтор строки добавляется лишь один раз.
После прямых компонентов алгоритм обходит список `TRF2`/`TRF3` в его исходном
порядке, углубляясь сразу в найденные предварительные исследования. Общая
таблица посещений не даёт повторно добавлять узел и завершает циклы. Два бита
флагов `TRF1` управляют включением строки и обходом её зависимостей; остальные
сырые биты и слова не получают игровых имён без отдельного доказательства.
Результат первого native-выхода подписан в отладочной строке как ore и
суммирует слово 1 `TRF0`, второй подписан как power и суммирует слово 0. Каждое
сложение сохраняется как `f32` в порядке найденной последовательности. В
принятом наборе из 23 синтетических тестов оригинальных DLL проверялись
`CalcFullResearchCost` и два AI-предиката; именно вызовы `CalcFullResearchCost`
подтвердили порядок обхода, дедупликацию, циклы, флаги и отсечение дерева.
Две проверки суммы показали чувствительность округления: одна и та же сумма
`1, 2^24, -2^24` даёт `0`, а в другом порядке `2^24, -2^24, 1` даёт `1`.
Эта проверка использовала синтетический исследовательский контекст и заданное
для опыта x87-слово управления `0x007f`; она не запускала штатный `Handler(43)`.
Локальный Rust-core принимает таблицу и одного Unit DAT, но пока не выбирает
кандидата в AI-каталоге и не связывает таблицу с владельцем полного старта
миссии. Поэтому `Single.01` всё ещё останавливается на неподдержанном
`Handler(43)`. Разбор с настоящими `scream.trf` и `12hel1.dat` проверяет
совместимость ресурса с API, но сам по себе не сравнивает стоимость с
оригинальным живым контекстом. Проверки границ байтов защищают Rust от плохих
входных данных и не приписываются нативному загрузчику.
### Стабильность AI-слоя