feat(assets): restore ordered research candidate selection
Docs Deploy / Build and Deploy MkDocs (push) Successful in 37s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 37s
This commit is contained in:
5 files changed
+880
-27
No files matched your search
@@ -177,11 +177,12 @@ token и типизированную `float`/`DWORD` ячейку. Неизве
|
||||
всей цепочки кампании. Обычная `Single.01` доходит до следующего blocker-а:
|
||||
`Handler(43)` в clan 1, `Init` instruction 10. Rust не перескакивает его;
|
||||
preview оставляет varset этого клана на defaults, а полный load завершает ошибкой
|
||||
с rollback. Для самого расчёта уже есть отдельный bounded reader research-таблицы
|
||||
и pure ore/power core в `fparkan-assets`. Они принимают один Unit DAT и не
|
||||
исполняют script instruction: runtime по-прежнему не создаёт владельца живого
|
||||
контекста, не перечисляет AI candidates и не выбирает результат Handler(43).
|
||||
Это ещё не загрузка/запуск игры и не общий dispatcher.
|
||||
с rollback. В `fparkan-assets` теперь есть bounded TRF reader, pure ore/power
|
||||
core и `ResearchCandidateSet::refresh_selection`: последний получает уже
|
||||
упорядоченные entries и отдельный TRF-контекст, затем читает Unit DAT через VFS.
|
||||
Он не строит AI-каталог и не исполняет script instruction. Runtime всё ещё не
|
||||
создаёт владельца живого контекста, не передаёт реальные AI candidates и не
|
||||
выбирает результат Handler(43). Это не общий dispatcher и не загрузка игры.
|
||||
|
||||
Для GOG-сборки также найден статический host call path в `Init`. В
|
||||
`iron3d.dll!FUN_1005ff40` (RVA `0x5ff40`) одна ветвь сначала проходит записи
|
||||
@@ -767,6 +768,15 @@ stdcall `[context, candidate, out1, out2]`, `RET 0x10`, и восстановл
|
||||
установлен в `0x007f`. У набора synthetic context, поэтому он не измеряет
|
||||
реальную миссионную стоимость.
|
||||
|
||||
Отдельный прямой вызов chooser-а `ai.dll` SHA-256
|
||||
`c19cd6cd31cd2896f8c4ba0f0e700dd3408aec8182daaf4863d98a7d233a2abc`
|
||||
`FUN_10010f90` (RVA `0x10f90`) прошёл 3 guard-case и frozen-матрицу из 15
|
||||
проверок при заданном порядке кандидатов. Тот же код использует конструктор RVA
|
||||
`0x10480`, parser первого Unit DAT дерева RVA `0x102c0` и предикаты RVA
|
||||
`0x115f0`/`0x11530`. Wrapper Handler(43) по RVA `0xd54a` и catalog loader
|
||||
`0x104e0` не вызывались. Эта матрица не подтверждает полный Handler(43),
|
||||
миссионный источник списка или штатный порядок AI-каталога.
|
||||
|
||||
`fparkan-assets` декодирует используемые NRes chunks, сохраняя сырые TRF0 words
|
||||
и непрозрачные данные. Native cost outputs сопоставлены только с двумя raw
|
||||
words: out1 складывает TRF0 word 1 и подписан как ore, out2 складывает word 0
|
||||
@@ -774,12 +784,31 @@ words: out1 складывает TRF0 word 1 и подписан как ore, out
|
||||
не установлены. Non-ASCII case folding research keys также остаётся открытым;
|
||||
Rust явно возвращает unsupported для таких сравнений.
|
||||
|
||||
Rust `ResearchCandidateSet` получает ordered entries, а TRF-context передаётся
|
||||
отдельно. Ошибка чтения VFS, неверный Unit DAT magic или неполное первое
|
||||
дерево пропускают entry; missing key даёт false predicate. Cost вызывается и
|
||||
для пустой таблицы: он возвращает `EmptyResearchTable`, после чего этот entry
|
||||
пропускается с сохранением второго status.
|
||||
Другие ошибки таблицы и
|
||||
`UnsupportedNonAsciiLookup` остаются явными Result-ошибками. Уже записанные
|
||||
statuses и выбранный индекс на ошибке не откатываются; это политика Rust API,
|
||||
не доказательство полного native error ABI.
|
||||
|
||||
Два Rust-предиката повторно обходят один декодированный tree с той же
|
||||
immutable TRF-таблицей. Общий helper может остановить второй проход при первом
|
||||
ложном флаге; native RVA `0x11530` продолжает обход и накапливает
|
||||
неиспользуемые float sums. Первый проход уже проверил ключи всех enabled
|
||||
компонентов, поэтому подтверждается только эквивалентность state/selection
|
||||
для такого входа, не идентичность callback trace или вычисления этих sums.
|
||||
|
||||
Открыты 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.
|
||||
исследовательского context при запуске миссии, построение AI candidate list и
|
||||
его штатный порядок, live x87 policy, inventory/economy transitions и точная типизация
|
||||
всех 16-byte TMA property values. Strict ore comparison и выбор первого
|
||||
кандидата при равенстве известны только для supplied-order chooser.
|
||||
Ignored acceptance проверяет реальные Part 1 AI DAT prefix и `scream.trf`, но
|
||||
byte-sorted порядок не заявляется как native catalog order и не сравнивает
|
||||
цену с вызовом chooser-а в mission context.
|
||||
|
||||
Закрывающий эксперимент для оставшихся границ: связать startup owner, research
|
||||
context и AI candidate selection в реальном `Init`; затем сравнить контекст,
|
||||
|
||||
@@ -471,6 +471,15 @@ struct UnitDatRecord112 {
|
||||
Требование обязательного terminator применяется только к полям, где оно доказано
|
||||
форматом.
|
||||
|
||||
У AI есть отдельный потребитель Unit DAT: исследовательский chooser читает
|
||||
заголовок с magic `F0F1`, сохраняет второй DWORD как непрозрачный и завершает
|
||||
разбор на границе первого полного корневого дерева. `immediate_child_count`
|
||||
закрывает это дерево по preorder; байты после него, включая неполную запись или
|
||||
orphan-record, chooser уже не читает. Поэтому Rust для этого пути находит
|
||||
bounded 112-byte prefix и передаёт его существующим `decode_unit_dat` и
|
||||
`unit_component_tree`. Общий декодер остаётся строгим к размеру всего файла
|
||||
`8 + 112 * N`; частный framing AI не меняет этот контракт.
|
||||
|
||||
Проверено 425 файлов / 5 219 records Части 1 и 676 файлов / 8 145 records
|
||||
Части 2. Все соответствуют формуле размера, `kind == 1` и
|
||||
`archive_name == "objects.rlb"`.
|
||||
|
||||
@@ -523,15 +523,40 @@ let cost = calc_full_research_cost(&candidate, &table)?;
|
||||
Две проверки суммы показали чувствительность округления: одна и та же сумма
|
||||
`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 от плохих
|
||||
входных данных и не приписываются нативному загрузчику.
|
||||
Эти результаты относятся к синтетическому исследовательскому контексту;
|
||||
штатный Handler(43) и запуск миссии здесь не проверялись.
|
||||
|
||||
Отдельная frozen-матрица непосредственно вызвала chooser GOG `ai.dll` SHA-256
|
||||
`c19cd6cd31cd2896f8c4ba0f0e700dd3408aec8182daaf4863d98a7d233a2abc`: функция
|
||||
`FUN_10010f90` (RVA `0x10f90`) прошла три guard-case и все 15 проверок.
|
||||
Конструктор находится в RVA `0x10480`, raw DAT parser — `0x102c0`,
|
||||
предикаты — `0x115f0` и `0x11530`. Матрица передавала кандидатов в заданном
|
||||
порядке. Обёртка `Handler(43)` по RVA `0xd54a` и загрузчик AI-каталога по RVA
|
||||
`0x104e0` не вызывались. Cost принадлежит `ArealMap.dll` SHA-256
|
||||
`a25f9254ebb339d70e2e07c8121950191e9584ca0858d8e9c2e2b6c671edf6ea`,
|
||||
`CalcFullResearchCost` RVA `0x16280`; для сравнения x87 был явно задан как
|
||||
`0x007f`.
|
||||
|
||||
`ResearchCandidateSet` держит упорядоченный каталог, выбранный индекс, байт
|
||||
scan-status и флаг `loaded` каталога; TRF-контекст передаётся отдельно.
|
||||
При наличии каталога и контекста set-status начинает со значения `1`, затем
|
||||
перед чтением каждого entry со status `0` становится `0`. Ненулевой entry
|
||||
пропускается. Первый предикат требует info words 1 и 2, соответствующие битам
|
||||
TRF1 0 и 2; второй требует words 0 и 2, то есть биты 1 и 2. Второй status
|
||||
сохраняется в entry, а cost вызывается даже при status `1`. Выбор записывается
|
||||
сразу, когда second predicate ложен и ore строго ниже текущего минимума,
|
||||
изначально `f32::from_bits(0x4b18967f)`. Равенство сохраняет первого, NaN индекс не меняет.
|
||||
|
||||
В Rust ошибка VFS-чтения и невалидный DAT пропускают entry; неизвестный TRF-
|
||||
ключ даёт false predicate. Ошибка стоимости `EmptyResearchTable` тоже
|
||||
пропускает entry и сохраняет его второй status. Остальные ошибки таблицы и
|
||||
`UnsupportedNonAsciiLookup` возвращаются явно, причём уже сделанные изменения
|
||||
индекса и status остаются. Общий Rust-предикат может закончить обход раньше
|
||||
native callback `0x11530`, который продолжает накапливать неиспользуемые float
|
||||
sums. Первый проход уже проверил enabled keys в той же неизменной таблице,
|
||||
поэтому подтверждается эквивалентность состояния и выбора для этого входа,
|
||||
но не callback-trace parity. Это не подтверждение цены на живой миссии или
|
||||
порядка AI-каталога.
|
||||
|
||||
### Стабильность AI-слоя
|
||||
|
||||
|
||||
Reference in new issue
Block a user