diff options
Diffstat (limited to 'docs/appendices/knowledge-boundaries.md')
| -rw-r--r-- | docs/appendices/knowledge-boundaries.md | 402 |
1 files changed, 367 insertions, 35 deletions
diff --git a/docs/appendices/knowledge-boundaries.md b/docs/appendices/knowledge-boundaries.md index eac2321..7fb6798 100644 --- a/docs/appendices/knowledge-boundaries.md +++ b/docs/appendices/knowledge-boundaries.md @@ -10,6 +10,62 @@ authoring API без динамического подтверждения. Не доказаны символами точные имена renderer vtable slots, полный набор CShade state transitions и окончательный порядок части transparent/FX/shadow subpasses. +После ограничения цели рельефом интерактивная камера проверяет движение +против каждого отрисовываемого корневого строения класса `FORT` и его +отрисовываемых компонентов. Для этого пути используются текущая Node38-поза +компонента и его преобразование присоединения. При попадании камера сохраняет +прежнюю позицию, но оставляет новый поворот. Так приложение связывает мировой +MSH-запрос со свободной preview-камерой; это не подтверждает алгоритм +стратегической или следящей камеры оригинала. + +Локальный MSH-запрос `query_slot_segment` сверён с оригиналом на фабричной +стене и дверном проёме. Runtime-адаптер `query_building_segment_world` теперь +повторяет helper batch bit `0x0008` через связанный узел, указанный raw-словом +`Batch20 +0x06`. При повторе он обходит Node38-поддерево: сначала допустимых +потомков, затем сам узел. Флаги ветвления сверены с оригиналом. Синтетические +тесты покрывают связи, отсутствие геометрии у промежуточного узла, маски, +равные попадания и предел в 200 пересечений. + +Игнорируемый тест приложения загружает настоящий PLANT 4 из AutoDemo и его +исходную CTLD-привязку Node3 (`frame_a = 0`, `frame_b = 1`, blend `0`). В +контроллере корневого PLANT нет записей tag-21 для открытия двери. В исходной +закрытой позе камера блокируется о дверь через обычный путь обновления +приложения. Для второго запроса тест вручную задаёт время `1` только Node3, +обновляет иерархию позы компонента и преобразование присоединения, затем +проверяет, что камера проходит через проём. Это подтверждает только подачу +выбранных поз в коллизию приложения: тест не запускает событие открытия, не +доказывает видимое открытие двери и не сравнивает полный игровой цикл с +оригиналом. + +Открыты выбор первого корня и правила состава узлов/slot-ов оригинального +мирового запроса, сравнение полного обхода с оригиналом, а также связь с +игровыми позами и событиями. Текущий путь приложения использует все +отрисовываемые компоненты строений `FORT`, но их выбор ещё не сопоставлен с +поведением оригинала для первого корня и всего мира. Совпадение со +стратегической и следящей камерами не подтверждено +([MSH](../reference/msh.md)). + +Для MSH без потока цветов type 16 подтверждён только один постоянный alpha +override для диапазонов с CPU-цветом и raw-флагом `Batch20` `0x0008`: GOG +`Terrain.dll`, SHA-256 +`af87d1b2e728a0be73c52be3b44cc196ab46da7799f25a15d40f8c9b0b425ead`, RVA +`0x1B0F0`, задаёт alpha `0`, если установлен bit `0x0040` и сброшен `0x0010`. +Это покрывает `0x0148`; `0x0108`, `0x0118` и `0x0058` сохраняют alpha MAT0, +причём в `0x0058` bit `0x0010` имеет приоритет. Поток type 16 или отсутствие +`0x0008` исключают override. Проверка заводской сцены с фиксированной нативной +камерой подтвердила на одном кадре входа восстановление X-образного рисунка +двери, который виден и в оригинале; прежний Vulkan-readback показывал на этом +месте сплошной чёрный прямоугольник. Сравнение выполнено при времени сцены +2000 мс и трёх кадрах, запуск с Vulkan validation завершился без предупреждений +и ошибок. Это подтверждает только структуру проёма в этой сцене: правильность +освещения и полный попиксельный паритет не установлены. + +Общее fade-поведение по расстоянию в preview не реализовано. Открыты точная +метрика расстояния, выбор первого CPU-принятого треугольника диапазона и +эквивалентность frustum/culling-отбора оригинала; эти вопросы нельзя вывести из +захвата заводской двери. Данные потока type 16 ещё не декодированы, поэтому +правило выше относится только к MSH-диапазонам без этого потока цветов. + Закрывающий эксперимент: запустить оригинал в совместимой Windows/DirectX среде, перехватить DirectDraw/Direct3D calls и surface flips, сохранить state log на минимальных сценах с одним типом материала. @@ -74,22 +130,124 @@ Iron3D и указатели `GetSuperAI` из `ai.dll` (таблица RVA `0x5 tie-векторы проверяют, как сам `FISTP` реагирует на каждый RC, но не измеряют control word процесса. -Отдельно проверены загрузка лицензированного AutoDemo в Rust и preview -лицензированной кампании. AutoDemo даёт raw TMA anchor-ы -`[(500.2857, 751.8217), (728.0, 449.0)]` и записи `[(500, 752, 0), -(728, 449, 1)]`; preview кампании сохраняет package и статус `Unsupported`, -не применяя частичные записи. Это подтверждает загрузчик и узкий Init resolver -Rust. +Лицензированный AutoDemo по-прежнему загружается с raw TMA anchor-ами +`[(500.2857, 751.8217), (728.0, 449.0)]` и записями `[(500, 752, 0), +(728, 449, 1)]`. Rust теперь также читает optional `.fml` sibling для каждого +script base и поддерживает узкий набор атомарных формул в первом `Init`. +Отсутствующий `.fml` не мешает пакету с одними `Handler(19)`; другие ошибки +чтения распространяются как VFS errors. Нативный конструктор показывает предупреждение при координате выше `10000`, но продолжает работу. Rust моделирует числовую запись, а не это UI-предупреждение; на первом `FISTP qword` он явно завершает загрузку ошибкой для NaN, infinity и значений вне signed-qword диапазона. В первом событии `Init` Rust поддерживает -только action-форму `Handler(19)` (`control word` и `result reference` равны -`0xffffffff`). Неизвестная инструкция даёт `Unsupported` в preview, не применяя -накопленные частичные записи; при полной загрузке это ошибка. Это ограничение -Rust исполнителя, не правило оригинального dispatcher-а. Ошибочные ссылки и -типы остаются ошибками в обоих режимах. +два доказанных действия: `Handler(19)` и formula assignment selector `-1` при +source `-1`, нулевой глубине условий и корректных индексах. У `Handler(19)` +control находится в `header_words[3]`, а `header_words[1]` — result-save +reference; оба равны `0xffffffff` в поддержанной форме. В дисковом заголовке +formula assignment `header_words[1]` — индекс целевой varset ячейки, +`header_words[2]` — source, а индекс formula row занимает `header_words[6]`; +индексы цели и формулы в native record находятся по смещениям `+0x08` и `+0x1c` +соответственно. Маппинг подтверждён +`ai.dll!FUN_10011b20` (RVA `0x11b20`) и исполнителем `FUN_10012020` (RVA +`0x12020`), SHA-256 `c19cd6cd31cd2896f8c4ba0f0e700dd3408aec8182daaf4863d98a7d233a2abc`. +`Handler(19)` (`FUN_1000aa38`, RVA `0xaa38`) использует references `0..2`; +`header_words[2]` его result-save guard-ом не проверяется. + +Formula table загружается из `<bundle-base>.fml`; native `FUN_100123b0` +(RVA `0x123b0`) проверяет ordinal, compiler `FUN_100152a0` (RVA `0x152a0`) +строит числовые, переменные и операторные узлы, а evaluator `FUN_10015b30` +(RVA `0x15b30`) в проверенных путях `+` и `-` считывает значения переменных и +сохраняет результаты как `f32` при x87 control word `0x007f`. В GOG-сборке +`ai.dll` с SHA-256 +`c19cd6cd31cd2896f8c4ba0f0e700dd3408aec8182daaf4863d98a7d233a2abc` поиск +переменной через varset vtable slot `+0x10` ведёт на RVA `0x16ad0`: сравнение +байтов точное, регистр учитывается, при повторах берётся первое объявление. +Getter `FUN_10013190` (RVA `0x13190`) читает type `3` как `f32`, type `5` как +беззнаковый `DWORD` и возвращает число через x87. Затем evaluator сохраняет +его как `f32` до операции. Оператор `+` +считает right+left, `-` — left-right; результат также сохраняется как `f32`. +Target type `3` сохраняет эти bits; type `5` проходит через `FUN_10012c00` +(RVA `0x12c00`) и `__ftol` (RVA `0x1df70`) к raw DWORD с усечением к нулю. +Изолированный x86 oracle на оригинальном `ai.dll` и настоящих ресурсах +подтвердил таблицы `tut1_pl2`/`scr_pl_1`/`scream` с +26/5/149 строками и 15 присваиваний: кампанийные `df0..df3` и `Single.01` +`dT8/dT9` получают `0`; `scream` задаёт `fDifficulty = 0.8f` и остальные +проверенные DWORD `0, 0, 1, 10, 0, 100, 400, 2`. + +Граница Rust остаётся узкой: один числовой токен либо ASCII-имя переменной, +или два таких атома с одним бинарным `+`/`-`. Имя проверяется раньше числового +преобразования, сравнивается с учётом регистра и выбирает первое совпадение; +публичный `FormulaTable::evaluate_numeric_literal` принимает только числовые +литералы. Временная копия значений начинается со значений по умолчанию и +обновляется после каждого успешного действия `Init`, поэтому следующая формула +видит предыдущие записи. Нет +поддержки неизвестных операторов, унарных форм, третьего атома, отсутствующих +или не-ASCII имён и source-copy ветви `source != 0xffffffff`; native source-copy +переносит всю запись varset. Preview сохраняет package, но не применяет частичные +writes; полный load отклоняет новый мир и сохраняет прежние world/RNG. Это +политика ограниченного Rust resolver-а, а не правило оригинального dispatcher-а. + +Ресурсные строки с переменными найдены в поздних событиях: `c1m3e.fml` row 19 +(`0.8 + fT`) вызывается из `Fort_Captured`, row 22 +(`dBuildingProtectHits - dT2`) — из `PBM_BUILDING_PROTECT_Start`, а `c1m4e.fml` +row 0 (`fTemp + 0.00001`) — из `PBM_BUILDING_INF_CAPTURE_Start`. Декодирование +этих `.scr` не показывает эти формулы в первом `Init`. Игнорируемая проверка +читает настоящие `.fml` и `varset.var`, но вставляет строки в синтетический +`Init`; она проверяет resolver, а не оригинальное исполнение. Одноразовый x86 +oracle на исходной GOG `ai.dll` с SHA-256 +`c19cd6cd31cd2896f8c4ba0f0e700dd3408aec8182daaf4863d98a7d233a2abc` проверил +13 отдельных присваиваний: 9 контролируемых выражений и 4 вызова с настоящими +строками `.fml` (`c1m3e` rows 19/22, `c1m4e` row 0 дважды подряд). В каждом векторе +native harness записывал вычисленный `f32`, типизированное raw-слово и getter; +scratch-проход через публичный Rust parser/resolver совпал со всеми 13 +векторами по `f32`-результату и raw-записи. Для native вызовов control word +временно принудительно устанавливался в `0x007f`: 87 наблюдений сохранили +`0x027f` на входе и после восстановления, +а canary-проверки прошли. Менялись только varset-копии в памяти. Это +подтверждает компиляцию формулы, локальное вычисление и typed writes для этих +входов, но не запуск исходного поиска и обхода события `Init`, исполнение поздних +событий или поведение живой миссии. + +Лицензированный test загружает одну первую кампанийную миссию +`CAMPAIGN/CAMPAIGN.00/Mission.01/data.tma` после literal writes; это не проход +всей цепочки кампании. Обычная `Single.01` доходит до следующего blocker-а: +`Handler(43)` в clan 1, `Init` instruction 10. Rust не перескакивает его; +preview оставляет varset этого клана на defaults, а полный load завершает ошибкой +с 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 и не загрузка игры. + +Начальный массив слота AI `+0x38` теперь восстановлен отдельно от этого +исследовательского пути. В `iron3d.dll` SHA-256 +`d1dc4ea8535e2069b0a05c9e8be2724dd438ba44cca7a83007d8b2e50f9e35fa` +`FUN_100392e0` (RVA `0x392e0`) вызывает слот для каждого значения счётчика +`second_resource.tag`. В `ai.dll` SHA-256 +`c19cd6cd31cd2896f8c4ba0f0e700dd3408aec8182daaf4863d98a7d233a2abc` метод +RVA `0x2010` передаёт `AI+0x83c` в `FUN_10002980` (RVA `0x2980`), где +`FUN_100048f0` (RVA `0x48f0`) добавляет переданный DWORD. Вызывающая сторона +повторно передаёт `0xffffffff`; эта функция не записывает значение обратно. +Поэтому подтверждён только начальный массив sentinel-слов. Неизвестно, какие +поздние обработчики читают эти ячейки и заменяет ли кто-либо их ID. В начальном +массиве нет назначенных ID объектов; дальнейший смысл этих слов пока не +установлен. Сам массив не является списком +`BehaviorDistributorState.registered_objects` и не определяет владельца +контекста исследований. + +Rust хранит этот снимок как `MissionAiStartupState` в состоянии загруженной +миссии. Полная загрузка проходит по всем TMA-кланам, включая кланы без пакета +сценария и без пути таблицы исследований; при предпросмотре список состояний AI +остаётся пустым. Знаковое поле `second_resource.tag` читается по сырым битам как +`u32`. Бюджет в 1 000 000 DWORD на миссию и выделение памяти с обработкой ошибки +защищают только память Rust; это ограничение не приписывается оригинальному +движку. Искусственные Rust-тесты проверяют обе формы TMA-клана, индекс клана, +независимые массивы, пустой путь сценария, предпросмотр, чтение тега как +беззнакового числа, лимит и откат при ошибке. Проверка на оригинальных ресурсах +для `Single.01` подтверждает состояния обоих кланов (`9/9`) до выполнения +`Init`; это не полный запуск миссии и не исполнение исходных DLL. Для GOG-сборки также найден статический host call path в `Init`. В `iron3d.dll!FUN_1005ff40` (RVA `0x5ff40`) одна ветвь сначала проходит записи @@ -169,6 +327,26 @@ Behavior (`Behavior.dll`, RVA `0x3600`) вызывает `World3D!stdGetGameMode с реальными интерфейсами ArealMap, нужными `FUN_10019e80`; полный Behavior tick flow и общий dispatcher событий также остаются открытыми. +`loaded_mission_behavior_control_groupings` публикует только начальную +группировку подготовленной Control-таблицы. Ветка с меткой `2` исходного +`Behavior.dll!FUN_1001be10` (RVA `0x1be10`) дополнительно вызывает +`FUN_1001b4b0` и обращается к свойствам через QI `0x204`, включая запросы с +номерами `0x700`, `6` и `0x400`. Результат `0x700` попадает в строку кандидата +по смещению `+0x3c`, результат `6` — в `+0x40`, а `0x400` может обнулить второе +поле. Их доменный смысл, множитель `0x700`, агрегаты Behavior QI `0x204` и +прочие числовые поля кандидата не восстановлены. Rust не подставляет множитель +`0x700`, нулевые агрегаты, выбранный индекс или эффект. Список для метки `30` не +встречается в ресурсах AutoDemo и проверяется только на искусственном наборе. + +Игнорируемая проверка AutoDemo сравнивает порядковые номера записей начальной +группировки, отдельно выведенные по снимку текущих меток дескрипторов, с +состоянием Rust после полной загрузки. Это подтверждает согласованность разбора +подготовленной таблицы и правила группировки на этих ресурсах, но не является +захватом состояния Behavior оригинала и не устанавливает, когда список +прикрепляется или обновляется в живом объекте. Полный штатный путь загрузки и +запуска, агрегаты Behavior QI `0x204`, обновление после изменений Control, +`Handler(43)` и полный игровой цикл остаются отдельными границами. + Начальные свойства `CurrentOre`/`MaximumOre` восстановлены отдельно от регулярного обмена. В проверенных `Behavior.dll` (SHA-256 `ddfbdbde07832358fe6e35e01c8dba9b58e7313789498b6049ed51d4ed9baf99`) RVA @@ -312,7 +490,9 @@ Global Control getters `1/2` и setter `1` проверены в native x86 orac этого класса native oracle не запускал. Rust API обращается к строкам проекции конкретного живого `ObjectHandle`; это запрос к уже загруженной таблице, а не исполнение Control или Behavior tick. Setter `17`, остальные factory tags и -внешние эффекты их методов не включены. Для tag `9` selector `0` использует +внешние эффекты их методов не включены. Структурное чтение записи CTLD с +меткой 17 ещё не восстанавливает её обработчики и игровое поведение. +Для tag `9` selector `0` использует отдельный scale `+0x11c`; остальные поддержанные классы используют коэффициент напрямую. Пустой набор и нулевой знаменатель остаются native no-result, а неподдержанный factory tag и расчётный диапазон произведения/отношения за @@ -419,12 +599,48 @@ callbacks. Licensed двухстадийный узел шахты подтвеÑ новый API при доле `0.5`, без FX и window path. Гибель root/critical узла и неизвестные flags/aggregate также остаются явными ошибками. -Rust query обращается к загруженному объекту и поддерживает `0xe00` только для -текущего descriptor tag `26` с factory tag `26`; другие активные typed tags и -factory paths остаются явными границами. Запрос читает текущее type-1 значение. -Licensed `min_l_n1.dat` подтверждает изменение `0xe00` и Behavior `0x1002` с -`1.0` до `0.5` после явного уменьшения количества вдвое; чтение получает -текущее значение provider, который также меняет явный бюджетный шаг. +Rust query обращается к загруженному объекту и поддерживает `0xe00` для пар +текущего descriptor tag и factory tag `(5,5)`, `(5,26)`, `(26,5)` и `(26,26)`; +прочие специализированные tag/factory пути остаются явными границами. Запрос +читает текущее type-1 значение provider. Licensed `min_l_n1.dat` подтверждает +изменение `0xe00` и Behavior `0x1002` с `1.0` до `0.5` после явного уменьшения +количества вдвое; чтение получает текущее значение provider, который также +меняет явный бюджетный шаг. + +Indexed selector `0x400` в том же Control IID `0x204` возвращает текущее type-1 +значение строки provider, выбранной через key устройства, при любом descriptor +и factory tag. Rust принимает конечное normal-or-zero значение, включая оба +знака нуля, и сохраняет его биты; недоступная таблица, отсутствующий key и +значения вне этой численной области дают типизированную ошибку. В оригинальном +`Control.dll` с SHA-256 +`bcb86318089028548a178b03a21accdce15df0cc85ddd91674cf3c2f55012c6e` ветвь +`FUN_1002bb40` (RVA `0x2bc3c`) читает provider pointer `device+0x44` и key +`device+0x04`, вызывает provider slot `+0x0c` в режиме `1` и не проверяет +descriptor/factory tag. Rust-проекция читает сохранённый снимок; она не вычисляет +value из amount, maximum, coefficients или множителя `M`. Native selector-cell +alias/fallback за пределами валидной записи и поведение native при невалидном +key остаются неисследованными; Rust не подставляет для них `0.0` или `None`. +Это покрывает перечисленные query-пути. Полный интерфейс IID `0x204`, его +взаимодействие со всеми Behavior-вызовами и полный игровой tick остаются за +границей подтверждённого поведения. + +Объём свидетельств для `0x400` ограничен и состоит из трёх частей. Native +oracle на настоящем `Control.dll` проверил шесть выходов по raw bits для +ограждённого синтетического owner/provider table; он вызвал indexed query и +реальный provider getter, но не создавал оригинальные factory-объекты. Матрица +выходов находится в [техническом разборе](script-vm.md#индексный-control-getter-сырой-селектор-0x400). +Независимый разбор AutoDemo обнаружил восемь таблиц и 100 устройств с +доступными ключами, но все выбранные provider values равны `1.0`; такой набор +подтверждает загрузку и область ключей, однако не различает, какую именно +строку выбирает алгоритм. Игнорируемый Rust-тест опрашивает эти 100 устройств и +проверяет те же биты, не выдавая равенство ресурсных данных за доказательство +индексирования. + +Вместе native-векторы и resource acceptance не подтверждают состояние +оригинальных factory instances, полный контракт IID `0x204`, полный +Behavior-call flow или живую миссию/gameplay. Поведение при ошибочных native +указателях и ключах, а также selector-cell alias/fallback вне валидной строки +по-прежнему не описано; Rust сохраняет typed errors на этой границе. Native oracle проверил изолированные пути распределения, обновления класса `4`, стадийную арифметику `0x11220`, обход дочерних provider-строк `0x11130` на interleaved parents `[-1, 0, 0, 1, 3]` с callback-порядком `1, 3, 4, 2` и @@ -644,26 +860,142 @@ Native interoperability подтверждается только успешны Граница shell подтверждена exports `createShell/getIShell`, `IGUIServer`, верхнеуровневым UI-pass и файлами `ui/*.cfg`, `DATA/TextRes.cfg`, -`gamefont.rlb` и `sprites.lib`. RsLi framing библиотек закрыт, но widget tree, -layout rules, glyph metrics, sprite command semantics, focus/navigation и HUD -state machine пока не восстановлены до field-level спецификации. - -Закрывающий эксперимент: трассировать загрузку `shell_ctrls.cfg`, -`menu_resources.cfg`, `cursor.cfg`, `game_resources.cfg` и `hq.cfg`, сопоставить -GUI object factories и снять command/event captures для меню, HUD, briefing и -диалогов. +`gamefont.rlb` и `sprites.lib`. Для полной GOG-сборки подтверждены раздельные +форматы: `gamefont.rlb` читается общим RsLi reader, но его native consumer этим +не установлен; для меню подтверждена цепочка `menu_resources.cfg` → NRes +`font.lib` → native lookup `MENU_FONT` с получением реального font-объекта. +Reader `fparkan-font` разбирает 20-байтовый Tfnt prefix, 256 записей фиксированного +шага и передаёт вложенный Texm общему reader-у. Native consumer подтверждает +два целочисленных параметра, per-byte spacing и signed advance; смысл трёх glyph DWORD +и поля заголовка по `0x0c` пока не установлен. Это не реализует выбор шрифта, текстовый +layout или glyph drawing. `menu_resources.cfg` и шесть вариантов +`MENU_FONT`/`GAME_FONT` сопоставлены с NRes-записями в +[разборе shell и шрифтов](ui-shell.md). Также остаются открытыми выбор dimension +на фактическом дисплее `1280x1024`, widget tree, layout rules, sprite command +semantics, focus/navigation и HUD state machine. + +Закрывающий эксперимент: сопоставить оставшиеся детали `shell_ctrls.cfg`, +`cursor.cfg`, `game_resources.cfg` и `hq.cfg` с GUI object factories и снять +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, поэтому он не измеряет +реальную миссионную стоимость. + +Отдельный прямой вызов 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 +и подписан как power. Смыслы остальных полей полного 32-byte callback record +не установлены. 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. + +Статический перенос владельца исследовательского контекста подтверждён по трём +GOG-модулям. В `iron3d.dll` (SHA-256 +`d1dc4ea8535e2069b0a05c9e8be2724dd438ba44cca7a83007d8b2e50f9e35fa`) +`FUN_1005ff40` (RVA `0x5ff40`) передаёт индекс клана в `FUN_100392e0` (RVA +`0x392e0`), где он сохраняется в `row+0x1c` и копируется в `descriptor[0]`. +Thunk RVA `0xcd0a0` ведёт к `World3D!GetNetPlayerNum` (RVA `0x9110`), а RVA +`0xcd08e` — к `World3D!AddNewObjectToGame` (RVA `0x8310`); SHA-256 +`World3D.dll`: `17e4a3089b2583a8cf2356c9db0390b1aba138356a09130d79b4e7e4791da61e`. +Вызов getter-а без аргументов завершается обычным `RET`; его результат становится +arg1 следующего вызова. `AddNewObjectToGame` принимает девять DWORD, включая +указатель на descriptor в arg9, и очищает стек через `RET 0x24`. RVA `0x83e9` +читает arg9; ветвь class `0xb`/QI `0x502` на RVA `0x853e` загружает +`descriptor[0]` и передаёт его в слот `+0x5c` на RVA `0x8544`; +`MisLoad.dll` (SHA-256 +`c607762bb67db14ca530927c8e1347339b62b0a163ac90c708ee83142292e2bf`) +`FUN_10002a30` (RVA `0x2a30`, `RET 8`) записывает его в `inner+8`. TMA tag +`row+0x20` ограничивает число вызовов AI slot `+0x38`, но не задаёт владельца. + +Создание AI не фильтруется по наличию сценарного пути. В `iron3d.dll` SHA-256 +`d1dc4ea8535e2069b0a05c9e8be2724dd438ba44cca7a83007d8b2e50f9e35fa` +`FUN_1005ff40` (RVA `0x5ff40`) проходит записи кланов и вызывает для каждой +`FUN_100392e0` (RVA `0x392e0`); в `ai.dll` SHA-256 +`c19cd6cd31cd2896f8c4ba0f0e700dd3408aec8182daaf4863d98a7d233a2abc` +`CreateSuperAI` (RVA `0xf710`) выделяет AI и вызывает конструктор по RVA +`0x1000` без проверки пути сценария. Создание исследовательского контекста +следует за успешным созданием AI и требует включённого флага создания +исследований и непустого `second_resource.path` до первого NUL. Переданный в +`descriptor[0]` индекс клана, а не `second_resource.tag`, задаёт владельца; +тег остаётся счётчиком начальных вызовов AI slot `+0x38`. + +В Rust эта граница представлена только загрузкой таблиц. Полный +`load_mission` требует явный `MissionStartupInput { queue_time_ms, +research_creation_gate }`; этот флаг не выводится из `MissionMode` или имени +пути. Если флаг равен `true`, загрузчик проходит по существующим +`MissionAiStartupState` всех TMA-кланов, в том числе без сценарного пакета, +читает их `second_resource.path` до первого NUL и отклоняет не-ASCII путь. +Пустая строка пропускается; выключенный флаг и статический preview не читают +TRF. На каждого +владельца сохраняется отдельный `ResearchTrfDocument` с нормализованным путём, +даже когда разные кланы указывают на один файл. Обычные тесты на искусственных +данных проверяют владение у кланов со сценариями и без них, повторное +декодирование, пропуски, ошибки и откат; игнорируемая проверка оригинальных +ресурсов связывает реальные `Campaign.00/Mission.01` пары путей с индексами владельцев. +Rust пока сохраняет только декодированные данные и связь с владельцем: он не +создаёт полный исследовательский контекст, не строит AI-каталог и не исполняет +`Handler(43)`. + +Нативный metadata loader нельзя считать простым копированием таблицы. В GOG +`Behavior.dll` (SHA-256 +`ddfbdbde07832358fe6e35e01c8dba9b58e7313789498b6049ed51d4ed9baf99`) selector +`0x204` по RVA `0xa490` обновляет сохранённое float-значение `+0x678`. В GOG `Control.dll` +(SHA-256 `bcb86318089028548a178b03a21accdce15df0cc85ddd91674cf3c2f55012c6e`) +selector `0x36` проходит цепочку RVA `0x31af0` → `0xe6c0` и записывает в общую +ячейку памяти по RVA `0x434bc`; selector `0x91` выполняет одноразовую +инициализацию, только если функция ещё не выполнила её. В Rust пока хранятся +только декодированные TRF-данные и сведения о владельце: эти побочные записи, +очередь и lifetime контекста не моделируются. + +Открыты runtime-выполнение Handler(43), создание и lifetime research context, +включение factory-цепочки в полный штатный startup миссии, построение 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. + +Для проверки оставшихся границ нужно на живой миссии установить, проходит ли +штатный `Init` по статически восстановленной передаче owner, зафиксировать +создание и lifetime исследовательского контекста и сравнить порядок кандидатов +и cost outputs оригинала с Rust. Покупки, inventory transitions и typed +property schema требуют отдельных контролируемых исследований. ## Rare branches |
