diff options
| author | Valentin Popov <valentin@popov.link> | 2026-10-31 03:28:05 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-10-31 03:28:05 +0300 |
| commit | 97c73118a1390c8a08cc34f4ce9435ba71df2af3 (patch) | |
| tree | fe1078c414e1f5e63a391b243eaa1cb65cb06a97 /docs | |
| parent | dbb59b00d13d18a8c94b2bdacd6c14b4dc20df29 (diff) | |
| download | fparkan-97c73118a1390c8a08cc34f4ce9435ba71df2af3.tar.xz fparkan-97c73118a1390c8a08cc34f4ce9435ba71df2af3.zip | |
feat(assets): decode research tables and calculate candidate costs
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/appendices/knowledge-boundaries.md | 45 | ||||
| -rw-r--r-- | docs/appendices/script-vm.md | 70 | ||||
| -rw-r--r-- | docs/tomes/06-behavior.md | 57 |
3 files changed, 147 insertions, 25 deletions
diff --git a/docs/appendices/knowledge-boundaries.md b/docs/appendices/knowledge-boundaries.md index a64b68e..bbf0e4b 100644 --- a/docs/appendices/knowledge-boundaries.md +++ b/docs/appendices/knowledge-boundaries.md @@ -177,7 +177,11 @@ token и типизированную `float`/`DWORD` ячейку. Неизве всей цепочки кампании. Обычная `Single.01` доходит до следующего blocker-а: `Handler(43)` в clan 1, `Init` instruction 10. Rust не перескакивает его; preview оставляет varset этого клана на defaults, а полный load завершает ошибкой -с rollback. Это ещё не загрузка/запуск игры и не общий dispatcher. +с rollback. Для самого расчёта уже есть отдельный bounded reader research-таблицы +и pure ore/power core в `fparkan-assets`. Они принимают один Unit DAT и не +исполняют script instruction: runtime по-прежнему не создаёт владельца живого +контекста, не перечисляет AI candidates и не выбирает результат Handler(43). +Это ещё не загрузка/запуск игры и не общий dispatcher. Для GOG-сборки также найден статический host call path в `Init`. В `iron3d.dll!FUN_1005ff40` (RVA `0x5ff40`) одна ветвь сначала проходит записи @@ -752,15 +756,36 @@ command/event captures для меню, HUD, briefing и диалогов. ## Research, economy and properties -Экспорты `LoadResearch`, `CalcFullResearchCost`, TRF/preload resources и TMA -properties доказывают отдельный слой исследований, стоимости, добычи и -производственных параметров. Формулы стоимости, dependency graph технологий, -inventory/economy transitions и точная типизация всех 16-byte property values -не закрыты. - -Закрывающий эксперимент: сопоставить research functions с ресурсами и UI, -снять изменения state на контролируемых покупках/исследованиях и построить -typed schema свойств по consumers, а не по одному имени. +Для GOG `ArealMap.dll` SHA-256 +`a25f9254ebb339d70e2e07c8121950191e9584ca0858d8e9c2e2b6c671edf6ea` +`CalcFullResearchCost` по RVA `0x16280` имеет подтверждённый ABI +stdcall `[context, candidate, out1, out2]`, `RET 0x10`, и восстановленный +алгоритм для одного candidate tree: zero-kind subtree pruning, direct preorder, +общая дедупликация, ordered DFS по `TRF2`/`TRF3`, raw gates из `TRF1` и +последовательное `f32` сложение. Принятый набор из 23 synthetic native cases +включал Calc и два AI predicate; для контролируемых сравнений x87 word был +установлен в `0x007f`. У набора synthetic context, поэтому он не измеряет +реальную миссионную стоимость. + +`fparkan-assets` декодирует используемые NRes chunks, сохраняя сырые TRF0 words +и непрозрачные данные. Native cost outputs сопоставлены только с двумя raw +words: out1 складывает TRF0 word 1 и подписан как ore, out2 складывает word 0 +и подписан как power. Смыслы остальных полей полного 32-byte callback record +не установлены. Non-ASCII case folding research keys также остаётся открытым; +Rust явно возвращает unsupported для таких сравнений. + +Открыты host binding полного Handler(43), factory/ownership/lifetime реального +исследовательского context при запуске миссии, AI candidate enumeration и tie +selection, live x87 policy, inventory/economy transitions и точная типизация +всех 16-byte TMA property values. Контролируемый тест с настоящими +`scream.trf` и `12hel1.dat` проверяет разбор и API одного candidate, но не +сравнивает результат с native вызовом реального миссионного context. + +Закрывающий эксперимент для оставшихся границ: связать startup owner, research +context и AI candidate selection в реальном `Init`; затем сравнить контекст, +порядок кандидатов и cost outputs оригинала с Rust на одной миссии. Покупки, +inventory transitions и typed property schema требуют отдельных контролируемых +исследований. ## Rare branches diff --git a/docs/appendices/script-vm.md b/docs/appendices/script-vm.md index 49af347..be8c8b3 100644 --- a/docs/appendices/script-vm.md +++ b/docs/appendices/script-vm.md @@ -34,6 +34,60 @@ VM state; `.trf` — NRes tables, чей framing подтверждён, а field semantics местами лишь consumer-inferred. +### Исследовательский профиль TRF и стоимость кандидата + +Для одной ветви `Handler(43)` уже восстановлен путь вычисления стоимости. +Результат привязан к GOG `ArealMap.dll`, SHA-256 +`a25f9254ebb339d70e2e07c8121950191e9584ca0858d8e9c2e2b6c671edf6ea`, +`CalcFullResearchCost` по RVA `0x16280`. Реальный ABI — четыре аргумента +stdcall на стеке: `[context, candidate, out1, out2]`; функция снимает с него +`0x10` байт и не принимает `candidate` через `ECX`. + +Источник этих таблиц привязан к GOG `MisLoad.dll`, SHA-256 +`c607762bb67db14ca530927c8e1347339b62b0a163ac90c708ee83142292e2bf`. +`FUN_10002fe0` (RVA `0x2fe0`) загружает required TRF chunks. Внутренний +research context разрешает ключ через `FUN_10002a40` (RVA `0x2a40`), выдаёт +32-byte row record через `FUN_10002aa0` (RVA `0x2aa0`) и возвращает ordered +dependency array через `FUN_10002cf0` (RVA `0x2cf0`). Эти методы соответствуют +виртуальным слотам context `+0x08`, `+0x0c` и `+0x24`; native Calc получает их +через таблицу вызовов, а не напрямую из файлов. + +`ResearchTrfDocument` в `fparkan-assets` повторно использует NRes reader и +сохраняет исходный архив. `TRF0.attr1` задаёт число строк по 40 байт; native +загрузчик требует `TRF0.attr2 == 3`. `TRF1` задаёт один флаговый byte на строку: +Rust воспроизводит native zero-fill при коротком payload и отклоняет более +длинный. `TRFB.attr1` задаёт пары little-endian `(u16 name_offset, u16 row)`; +имя берётся из NUL-terminated pool `TRF6`. Поиск идёт по исходному порядку и +берёт первую ASCII-case-insensitive пару. Связь строки с парой использует +только младшие 16 бит сырого слова `TRF0` по смещению `+0x20`. + +Две таблицы зависимостей имеют собственные размеры: `TRF2` считает группы, +`TRF3` содержит их плоский упорядоченный поток индексов; `TRF4`/`TRF5` образуют +отдельную пару. Только `TRF2`/`TRF3` участвуют в `CalcFullResearchCost`. +Отсутствующий `TRF3` или `TRF5` означает пустой поток лишь при нулевой сумме +счётчиков соответствующей таблицы. Required `TRF7`–`TRFA` остаются +непрозрачными. Reader проверяет фактически читаемые срезы, строки, ссылки и +размеры выделений для памяти Rust; это дополнительная безопасность, а не +утверждение, что native loader отвергает те же плохие файлы. + +Из флагового byte `TRF1` native provider строит слова record `+0`, `+4` и `+8` +из битов `(1, 0, 2)`. `CalcFullResearchCost` использует первые два как raw gate, +не присваивая им общего игрового имени: `+0` определяет, добавлять ли строку, +`+4` — раскрывать ли её зависимости. Нулевой `kind` компонента Unit DAT +отсекает весь узел с потомками. Иначе дерево сканируется в preorder; найденные +строки добавляются один раз, затем для каждой прямой строки выполняется +упорядоченный DFS по `TRF2`/`TRF3`. Общая таблица посещений завершает циклы и +удаляет повторы. + +По debug-строкам `out1` — ore и суммирует `TRF0` word 1, а `out2` — power и +суммирует word 0. Native код хранит каждый шаг суммы как `f32`; один cost-вектор +`1, 2^24, -2^24` даёт первый результат `0`, а в другом порядке +`2^24, -2^24, 1` даёт `1`. Rust core сохраняет тот же порядок накопления. В +принятом наборе из 23 синтетических вызовов оригинальных DLL были случаи +`CalcFullResearchCost` и два AI-предиката; x87 control word был установлен в +`0x007f` для контролируемых сравнений. Набор проверяет выбранные ветви на +синтетическом контексте, но не использует реальный контекст миссии. + ## Безопасная модель исполнения Целевая VM должна разделять immutable package (bytecode, symbols, events, @@ -285,9 +339,12 @@ TMA properties остаются four raw `u32` words плюс имя, пока c сохраняются `NOT USED`; corpus подтверждает `Invulnerability`, life state, `ClanID`, ore, speed и free-time properties. -Research/economy работают в simulation: `LoadResearch` и -`CalcFullResearchCost` доказывают данные и вычислимую стоимость, но не полный -layout prerequisites/modifiers/unlocks. General formula evaluator требует +Для одного кандидата и одной research-таблицы порядок prerequisite-closure, +дедупликация и две выходные стоимости `CalcFullResearchCost` восстановлены и +проверены отдельно, как описано в +[исследовательском профиле TRF](#исследовательский-профиль-trf-и-стоимость-кандидата). +Это не закрывает production-modifier/unlock data, экономические переходы или +полное исполнение Handler(43). Общий evaluator формул всё ещё требует versioned grammar, typed operands, deterministic numeric policy, bounded stack и явных errors; x87-compatible rounding нужен там, где оно выбирает ветку. Описанный ниже literal-путь первого `Init` не закрывает общий evaluator. @@ -448,8 +505,11 @@ Rust сохраняет всю таблицу строк, но вычисляеÑ полную загрузку кампании Mission.01 после этих присваиваний; у обычной `Single.01` clan 1 после формул остаётся `Handler(43)` в instruction 10, который не поддержан и не обходится. Preview оставляет значения этого клана без -частичных записей, а полный load возвращает ошибку транзакционно. Это не -подтверждение запуска самой игры или полной VM. +частичных записей, а полный load возвращает ошибку транзакционно. В +`fparkan-assets` теперь отдельно доступны bounded TRF reader и pure cost core +для одного Unit DAT, но script/runtime пока не создают нужный research context, +не перечисляют AI candidates и не выполняют этот handler. Это не подтверждение +запуска самой игры или полной VM. ### Первый вызов Behavior и сценарного `Init` diff --git a/docs/tomes/06-behavior.md b/docs/tomes/06-behavior.md index 9350d94..b4bddb7 100644 --- a/docs/tomes/06-behavior.md +++ b/docs/tomes/06-behavior.md @@ -485,16 +485,53 @@ Tag выбирает конкретное правило. Для tag 1 запиÑ ### TRF и preload-данные -TRF-файлы проходят структурный разбор. `auto.trf`, `data.trf` и tutorial -variants имеют сигнатуру [NRes](../reference/nres.md) и содержат большие -таблицы имён игровых прототипов: оружия, башен, сооружений и других объектов. -Также найдены preload-записи, ANI и SKE resources. - -По содержимому, порядку загрузки и consumers TRF с высокой вероятностью -предоставляет AI/сценарному слою заранее подготовленную таблицу типов и -связанных данных. Framing и имена подтверждены corpus-ом, но полная семантика -каждой TRF-записи ещё не закрыта. Имена должны разрешаться через тот же -resource registry, что и миссионные объекты. +TRF-архивы используют несколько потребителей; один подтверждённый путь ведёт +из `Handler(43)` к вычислению стоимости исследования. В проверенной сборке GOG +`CalcFullResearchCost` из `ArealMap.dll` получает таблицу, загруженную через +`MisLoad.dll`. Для этого пути `TRF0` содержит строки по десять сырых `u32`, +`TRF1` — байты флагов, `TRFB` — пары «смещение имени и номер строки», а +`TRF6` — пул строк с нулевыми окончаниями. Слово 8 строки связывает её с +`TRFB` через младшие 16 бит; старшая половина сохраняется как неизвестное +значение. `TRF2` и `TRF3` образуют упорядоченные списки предварительных +исследований, которые читает этот путь стоимости. `TRF4`/`TRF5` образуют +отдельную пару таблиц, но `CalcFullResearchCost` её не использует. Непрозрачные +`TRF7`–`TRFA` остаются в архиве и требуются загрузчику. + +Модуль `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-слоя |
