422 lines
37 KiB
Markdown
422 lines
37 KiB
Markdown
# Границы знания
|
||
|
||
Этот раздел перечисляет области, где контракт ещё не закрыт полностью. Они не
|
||
мешают безопасному чтению и lossless сохранению, но не должны превращаться в
|
||
authoring API без динамического подтверждения.
|
||
|
||
## Render state
|
||
|
||
Доказаны frame boundaries, world traversal, material resolve и крупные проходы.
|
||
Не доказаны символами точные имена renderer vtable slots, полный набор CShade
|
||
state transitions и окончательный порядок части transparent/FX/shadow subpasses.
|
||
|
||
Закрывающий эксперимент: запустить оригинал в совместимой Windows/DirectX
|
||
среде, перехватить DirectDraw/Direct3D calls и surface flips, сохранить state
|
||
log на минимальных сценах с одним типом материала.
|
||
|
||
## FXID field-level semantics
|
||
|
||
Размеры команд, resource references, lifecycle, flags families и используемые
|
||
time modes известны. Не закрыто значение каждого поля body opcodes 1--10,
|
||
отсутствующий во всех проверенных каталогах opcode 6 и точные формулы редких
|
||
time modes.
|
||
|
||
Закрывающий эксперимент: изменять по одному полю копии эффекта, воспроизводить
|
||
его в контролируемой сцене и логировать runtime command object, emitted
|
||
primitives, sound events и reads в `Effect.dll`.
|
||
|
||
## Script VM
|
||
|
||
Доступны packages, symbols, event sections, variable declarations и version
|
||
checks. Полная instruction grammar `.scr`, semantics opcodes и serialization
|
||
state ещё не восстановлены.
|
||
|
||
Для GOG `ai.dll` статически восстановлены пять ветвей evaluator-а 16-byte
|
||
записей условий по RVA `0x5180`, включая проверки типов, поиск объектов,
|
||
короткие выходы из обхода списков и запись байта завершения. Однако не найден
|
||
код-производитель этого массива и контракт его владельца с runtime. Reader
|
||
`FUN_10011b20` хранит описатели событий и вложенных записей `.scr` во внутренних
|
||
контейнерах с шагом `0x14` и `0x20`; сами дисковые структуры имеют переменную
|
||
длину из-за length-prefixed имён и списков ссылок. Связь этих контейнеров с
|
||
массивом evaluator-а не доказана. Виртуальные методы,
|
||
через которые проходят проверки, остаются внешней границей, а сам evaluator
|
||
не сравнивался с работающим оригиналом. Описание этих ветвей — статический
|
||
вывод по одному известному `ai.dll`, а не утверждение о совпадении во время
|
||
исполнения.
|
||
|
||
Для начального сценарного события закрыт более узкий путь. В проверенной GOG
|
||
сборке `ai.dll` SHA-256
|
||
`c19cd6cd31cd2896f8c4ba0f0e700dd3408aec8182daaf4863d98a7d233a2abc` функции
|
||
`FUN_10011f40` (RVA `0x11f40`) и `FUN_10011f10` (RVA `0x11f10`) выбирают первое
|
||
имя-событие C-string `Init` и запускают его инструкции по порядку. Для координат
|
||
из TMA найден и проверен ещё один участок: `iron3d.dll` SHA-256
|
||
`d1dc4ea8535e2069b0a05c9e8be2724dd438ba44cca7a83007d8b2e50f9e35fa`,
|
||
`FUN_100392e0` (RVA `0x392e0`) выполняет x87 `FISTP qword` и передаёт младшие
|
||
DWORD координат пятым и шестым аргументами `CreateSuperAI`. Затем
|
||
`ai.dll!FUN_10005c70` (RVA `0x5c70`) преобразует DWORD в `float`, а `Handler(19)`
|
||
возвращает его к DWORD усечением.
|
||
|
||
Две x86-проверки вызвали эти оригинальные DLL-функции на изолированной
|
||
синтетической памяти. Caller oracle подтвердил AutoDemo-координаты
|
||
`(500.2857, 751.8217) -> (500, 752)`, ties-to-even в nearest-режиме и переход
|
||
`-0.6 -> 0xffffffff`. AI oracle проверил оба набора AutoDemo anchor-ов,
|
||
дублирующий `Init`, имя `Init\\0suffix\\0`, отсутствие события, а также
|
||
нативную связку `FUN_10005c70` → `Handler(19)`: DWORD `(0xffffffff, 10001)` стал
|
||
полями `(2^32, 10001.0)` и varset-записями `(0, 10001, 2)`. Эти проверки
|
||
подтверждают только изолированные участки ABI.
|
||
|
||
В одном read-only snapshot запущенной GOG AutoDemo были связаны записи кланов
|
||
Iron3D и указатели `GetSuperAI` из `ai.dll` (таблица RVA `0x55398`). Клан 0 имел
|
||
исходные float-координаты `(500.2857, 751.8217)` и поля AI
|
||
`(+0x80, +0x84, +0x7c) = (500.0, 752.0, 0)`; клан 1 имел `(728.0, 449.0)` и
|
||
`(728.0, 449.0, 1)`. Первый набор связывает те же значения в одном процессе и
|
||
выбирает ближайшее округление среди направлений округления x87. Синтетические
|
||
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.
|
||
|
||
Нативный конструктор показывает предупреждение при координате выше `10000`, но
|
||
продолжает работу. Rust моделирует числовую запись, а не это UI-предупреждение;
|
||
на первом `FISTP qword` он явно завершает загрузку ошибкой для NaN, infinity и
|
||
значений вне signed-qword диапазона. В первом событии `Init` Rust поддерживает
|
||
только action-форму `Handler(19)` (`control word` и `result reference` равны
|
||
`0xffffffff`). Неизвестная инструкция даёт `Unsupported` в preview, не применяя
|
||
накопленные частичные записи; при полной загрузке это ошибка. Это ограничение
|
||
Rust исполнителя, не правило оригинального dispatcher-а. Ошибочные ссылки и
|
||
типы остаются ошибками в обоих режимах.
|
||
|
||
Для GOG-сборки также найден статический host call path в `Init`. В
|
||
`iron3d.dll!FUN_1005ff40` (RVA `0x5ff40`) одна ветвь сначала проходит записи
|
||
кланов и вызывает для них `FUN_100392e0`, затем вызывает `FUN_10039440` (RVA
|
||
`0x39440`). Эта функция передаёт AI callback-у пару явных аргументов через
|
||
vtable slot `+0x14`. В `ai.dll!FUN_10001ae0` (RVA `0x1ae0`) аргумент callback-а
|
||
записывается в `AI+0x3fc`; затем объект `AI+0x438` получает вызов Behavior
|
||
vslot `+0x2c`. В проверенной `Behavior.dll` (SHA-256
|
||
`ddfbdbde07832358fe6e35e01c8dba9b58e7313789498b6049ed51d4ed9baf99`)
|
||
`FUN_1001adb0` (RVA `0x1adb0`) выполняет начальную подготовку расписания.
|
||
Метод ищет первый `Init` только при нулевом latch
|
||
`AI+0x8a8`, а при dispatch временно ставит и снимает recursion guard
|
||
`AI+0x78`. В этой ветви поэтому известны callback ingress и порядок
|
||
warm-start → `Init`, но смысл аргумента callback-а здесь не сопоставлен с Rust
|
||
`MissionMode`.
|
||
|
||
Изолированный x86 oracle вызывает исходные `iron3d.dll!FUN_10039440`,
|
||
`ai.dll!FUN_10001ae0` и script dispatcher на синтетических записях и проверяет
|
||
режим, guard, latch, Handler(19) writes и повторный вызов. Отдельный oracle
|
||
исполняет `Behavior.dll!FUN_1001adb0` с искусственно заданной очередью, сверяя
|
||
срок, шаг LCG и очистку полей. Это подтверждает ABI и локальные переходы исходных
|
||
функций, но не доказывает, что полная штатная загрузка выбранной миссии в
|
||
оригинальном процессе доходит до этой ветви в том же порядке. Метод
|
||
`FUN_10001ae0` оставляет latch `+0x8a8` сброшенным, когда `Init` не найдено;
|
||
если оно найдено при уже занятом guard `+0x78`, dispatch пропускается, но latch
|
||
всё равно устанавливается. Rust пока запускает обработанный поднабор при каждой
|
||
полной загрузке и не моделирует аргумент callback-а `AI+0x3fc`, guard `+0x78` или
|
||
latch `+0x8a8`.
|
||
|
||
Rust `load_mission` принимает явный `queue_time_ms`, а не читает оригинальное
|
||
время очереди. Новый `Engine` начинает локальный Behavior RNG с seed `1` и
|
||
учитывает шаги генератора этой стартовой последовательности; не восстановлены
|
||
другие потребители исходного генератора, поэтому равенство полной случайной
|
||
последовательности не утверждается. Начальная фаза выполняется после регистрации
|
||
объектов в создаваемом мире Rust;
|
||
это интеграционный выбор runtime, а не подтверждённая эквивалентность всех
|
||
host construction phases. Не проверена другая лицензированная миссия с
|
||
непустым `Init`. Вызов `Behavior.dll!FUN_10019e80` (RVA `0x19e80`, SHA-256
|
||
`ddfbdbde07832358fe6e35e01c8dba9b58e7313789498b6049ed51d4ed9baf99`) исполнялся
|
||
на синтетических интерфейсах. Путь членства в реальных distributor-списках
|
||
связан с полями TMA и Unit DAT: `World3D!CreateObject` передаёт selector
|
||
и class word через дескриптор в Behavior (RVA `0x7e55`, `0x7e62`), а
|
||
`Behavior.dll!FUN_10005e60` (RVA `0x5e60`) проверяет высокий бит registration
|
||
key, AI и distributor. Selector из первого DWORD после имени ресурса выбирает
|
||
слот `GetSuperAI`; таблица этой функции находится в `ai.dll` RVA `0x55398`.
|
||
Хеши `MisLoad.dll`, `iron3d.dll`, `ArealMap.dll`, `World3D.dll` и
|
||
`Behavior.dll`, использованных для цепочки, перечислены в
|
||
[справочнике TMA](../reference/tma.md).
|
||
|
||
Отдельный режимный бит Behavior нельзя выводить из `MissionMode`. Конструктор
|
||
Behavior (`Behavior.dll`, RVA `0x3600`) вызывает `World3D!stdGetGameMode` (RVA
|
||
`0x14750`), который читает host-global по RVA `0x795130` в `World3D.dll` (SHA-256
|
||
`17e4a3089b2583a8cf2356c9db0390b1aba138356a09130d79b4e7e4791da61e`). В
|
||
обычном пути `stdInitGame` (RVA `0x13e90`, запись режима около `0x14040`)
|
||
формируется отдельное host-значение; в наблюдавшемся запуске AutoDemo getter
|
||
вернул `4`, и бит `0x2` был снят. Это объясняет соответствующий gate при
|
||
создании Behavior, но не устанавливает отображение между host-режимом и Rust
|
||
`MissionMode`.
|
||
|
||
Наблюдение обычного запуска лицензированной GOG AutoDemo подтвердило четыре
|
||
добавления с ключами `0x80000003..0x80000006` в два клановых списка. Полная
|
||
загрузка Rust сохраняет соответствующие `ObjectHandle` в точном доступном слоте
|
||
`GetSuperAI` до стартового `Init`; preview не создаёт членств. Лицензированный
|
||
тест связывает эти записи с TMA ID 4–7, selector и Unit DAT class. Это
|
||
подтверждает только состав списков, не точную эквивалентность всей загрузки или
|
||
регулярного шага Behavior.
|
||
|
||
Нативный `Behavior.dll!Add` (RVA `0x1aac0`) и `normalize` (RVA `0x1ac90`)
|
||
проверены прямыми x86-вызовами на синтетических записях при x87 control word
|
||
`0x007f`, измеренном также у игры на входах обоих методов. Rust повторяет
|
||
начальные доли и нормализацию отдельными операциями `f32`, а повторную запись
|
||
сопоставляет по `ObjectHandle`, меняя только долю существующего участника.
|
||
Пять DWORD после доли хранятся сырыми; ненулевое первое слово закрепляет вес,
|
||
смысл четырёх следующих не установлен. Лицензированный AutoDemo-тест проверяет
|
||
список TMA-объектов и получившиеся начальные доли по кланам, но это не сравнение
|
||
живых весов миссии с полями исходных runtime-записей. Не восстановлено соединение
|
||
с реальными интерфейсами ArealMap, нужными `FUN_10019e80`; полный Behavior tick
|
||
flow и общий dispatcher событий также остаются открытыми.
|
||
|
||
Начальные свойства `CurrentOre`/`MaximumOre` восстановлены отдельно от
|
||
регулярного обмена. В проверенных `Behavior.dll` (SHA-256
|
||
`ddfbdbde07832358fe6e35e01c8dba9b58e7313789498b6049ed51d4ed9baf99`) RVA
|
||
`0x8790` выбирает начальные значения по class word, `0xb110` регистрирует
|
||
дескрипторы кодов 4/5, `0xb470` пишет только код 5, а `0xb5b0` читает код 4
|
||
как дополнительное слово и код 5 как основное. `MisLoad.dll` (SHA-256
|
||
`c607762bb67db14ca530927c8e1347339b62b0a163ac90c708ee83142292e2bf`) RVA
|
||
`0x40b0` выбирает первое свойство TMA с точным именем `CurrentOre` и передаёт
|
||
его `raw_value[1]`; setter сохраняет биты напрямую. В AutoDemo snapshot на этих
|
||
входах было восемь объектов Unit DAT, у всех raw `CurrentOre` равен нулю;
|
||
четыре объекта класса `0x01008000` не вошли в distributor, но остались среди
|
||
инициализированных Rust-состояний. Поэтому путь нулевого значения наблюдался
|
||
наживую, но ненулевое TMA-значение в живой миссии пока не проверено.
|
||
|
||
Синтетический x86 oracle вызвал `Behavior.dll!FUN_10008790` напрямую для классов
|
||
`0x80000004`, `0x80000008`, `0x01002000`, `0x01004000` и `0x01008000`; перед
|
||
вызовом в записи были значения `CurrentOre=123` и `MaximumOre=-1`, так что
|
||
проверено их перезаписывание. Тест выставляет `a64=1`, чтобы отключить отложенный
|
||
side effect обёртки setter-а. Это подтверждает ветви initializer-а, но не весь
|
||
путь его запуска и связи с остальными фазами объекта. Другой изолированный x86
|
||
oracle проверяет ненулевое raw-слово `CurrentOre`, только чтение `MaximumOre` и
|
||
перестановку строк дескрипторов на синтетических записях свойств.
|
||
|
||
Не проверен селектор 4 для внешнего config-like ресурса: в наблюдавшемся
|
||
AutoDemo его вызова нет, а установленный `Behavior.ini` не содержит
|
||
`Mine_MaxOre`, `Storage_MaxOre` или `Transport_MaxOre`. Поэтому Rust пока
|
||
воспроизводит доказанные class defaults (500, 4000 и 2000) и не заявляет
|
||
поддержку конфигурационного переопределения. Отдельно не доказано, что каждый
|
||
Unit DAT создаёт тот же Behavior-компонент во всех host factory paths. Rust
|
||
ограничивает новую запись полными TMA-объектами с Unit DAT; preview, прямые
|
||
prototype roots без Unit DAT и отложенные spawn после исходной загрузки вне
|
||
этой модели. Путь в `ArealMap.dll` (SHA-256
|
||
`a25f9254ebb339d70e2e07c8121950191e9584ca0858d8e9c2e2b6c671edf6ea`) к
|
||
`CreateObject` по RVA `0x14fd0`/`0x15290` показывает выбор World3D class 3 или 4
|
||
по старшему биту. Для ветки GOG `AniMesh.dll` (SHA-256
|
||
`65659bf67f20e5be77d67a5b2f59968f61b94293143807fa59ca451c560c4f7e`) RVA
|
||
`0x2ff0` показывает одну ветку создания: после QueryInterface для IID `0x204`
|
||
выполняется отдельный импорт `GetQueue()`, а на возвращённом объекте вызывается
|
||
метод через vtable slot `+0x30`. `CreateBehaviour` вызывается только при
|
||
ненулевом результате этого метода и host class `3` или `4`; class `4` получает
|
||
адреса полей prototype `+0x140` и `+0x160`. Семантика метода slot `+0x30` не
|
||
установлена. Другие factory paths, нулевой результат и spawn после исходной
|
||
загрузки остаются за границами подтверждённой ветки.
|
||
|
||
Для `Handler(30)` отдельно подтверждены обратный вызов `iron3d.dll` RVA
|
||
`0x611d0` (SHA-256 `d1dc4ea8535e2069b0a05c9e8be2724dd438ba44cca7a83007d8b2e50f9e35fa`),
|
||
начальные поля IGame и проверки режима/состояния. Обработчик выбора миссии
|
||
выставляет режим 1 (RVA `0x29142`), а обработчик таймера — режим 3 (RVA `0x70f3`).
|
||
Небольшая тестовая x86-программа сравнивает этот обратный вызов с исходной DLL
|
||
на синтетических структурах. Пока не восстановлен сквозной путь от выбора и
|
||
инициализации миссии к первому событию Handler(30): Rust загружает миссию, но
|
||
ещё не выполняет штатный `stdInitGame`, переход состояния `5 -> 4`, запуск и
|
||
игровой цикл с полной диспетчеризацией событий. Поэтому
|
||
совпадение изолированного обратного вызова не доказывает совместимость живого
|
||
сценария.
|
||
|
||
Закрывающий эксперимент: найти dispatcher loop в `ai.dll`, сопоставить jump
|
||
table с instruction sizes, построить disassembler и сравнить выполнение
|
||
коротких scripts с оригиналом. Для evaluator-а условий отдельно проследить
|
||
создание записей `this + 0x34`, выполнить те же входные данные в оригинале и
|
||
сверить байт завершения, порядок поиска, ранние выходы и граничный случай
|
||
`payload == count`.
|
||
|
||
## Saves and campaign state
|
||
|
||
Найдены `saveslots.cfg` и `missions/dispatcher.ini`, но binary savegame payload,
|
||
serialization World3D/AI/script/RNG и migration rules не закрыты.
|
||
|
||
Нужны сохранения оригинала в контролируемых состояниях: старт миссии, изменение
|
||
позиции, здоровья, order/path, FX/timer, script variable, research/economy,
|
||
mission completion, pause и non-default game time.
|
||
|
||
## Physical/control formats
|
||
|
||
CTLD и связанные resources структурно читаются, count patterns и variants
|
||
известны. Не названы все секции, shape types, coefficients и точный contact
|
||
solver. То же относится к редким MSH auxiliary streams и части CTPT/NDPR flags.
|
||
|
||
Одна цепочка Unit DAT → AniMesh descriptor → Control теперь восстановлена
|
||
статически в [главе о Behavior и Control](../tomes/06-behavior.md): raw
|
||
`parent_or_link` копируется в descriptor. В non-`EXTO` registry path parent ID
|
||
разрешается в индекс устройства и сдвигает локальный индекс только когда parent
|
||
ID не равен `-1` и IID `0x202` доступен; иначе используется raw локальный индекс.
|
||
В этом потоке нулевой raw kind пропускает узел вместе с потомками, поэтому общий
|
||
assigned ID нельзя получать из preorder-позиции записи. Полная загрузка миссии
|
||
в FParkan строит упорядоченную базовую проекцию таблицы
|
||
Control для объектов с Unit DAT: корневые записи добавляются, `EXTO` добавляет
|
||
строки с ключами глобальной MSH-иерархии, а `INTO` обновляет первую строку
|
||
выбранного индекса либо добавляет список при sentinel `-1`. Проверка полной
|
||
загрузки на оригинальном `min_l_n1.dat` получает 11 строк: 10 корневых записей,
|
||
четыре замены `INTO` и одну добавленную строку дочернего `EXTO`. Ветви C4/C8 и
|
||
добавление `EXTO` подтверждены машинными инструкциями оригинальных модулей, но
|
||
полный результат FParkan не сравнивался с таблицей из запущенной игры. Тест на
|
||
искусственном дереве также проверяет ID после пропущенного поддерева с raw kind
|
||
`0`. Проекция хранит общие поля и происхождение записей; она не исполняет все
|
||
обработчики Control.
|
||
|
||
Control различает добавление устройства и применение одной записи к уже
|
||
выбранному устройству. Исполнение оригинальной 32-битной DLL на искусственных
|
||
данных подтверждает общую ветвь переопределения для получателей tags `9`, `15`,
|
||
`19` и `25`: остаются прежними слот, ключ, ID компонента, множитель и класс,
|
||
а коэффициенты копируются из новой записи. Искусственный tag `9`-объект создан
|
||
без FX-хоста (`+0x3c == 0`), а запись не содержит строк таблицы map; поэтому
|
||
работа FX-хоста здесь не проверялась. В Rust parser-е
|
||
`Unsupported { typed_record_index: Some(_) }` означает, что разбор остановился
|
||
внутри списка typed records; без индекса остановки unsupported может относиться
|
||
к effect-tail после уже разобранного списка. Эти случаи нужно различать.
|
||
Ветка default native-фабрики вызывает конструктор по RVA `0x20800`, поэтому
|
||
неизвестный Rust parser-у tag не доказывает отказ оригинального loader-а. Подготовка assets
|
||
сейчас пропускает Control refs у non-geometric prototype, поэтому отсутствие
|
||
подготовленного CTL должно оставаться явным unavailable result, а не
|
||
трактоваться как пустой список.
|
||
|
||
Global Control getters `1/2` и setter `1` проверены в native x86 oracle для
|
||
искусственных factory/descriptor pairs `9`, `15` и `19`, а в Rust реализованы
|
||
для текущего descriptor tag `19` с factory tags `9`, `15`, `19` и `25`. Для tag
|
||
`25` общий getter slot `+0x10` подтверждён чтением vtable, но агрегатный вызов
|
||
этого класса native oracle не запускал. Rust API обращается к строкам проекции
|
||
конкретного живого `ObjectHandle`; это запрос к уже загруженной таблице, а не
|
||
исполнение Control или Behavior tick. Setter `17`, остальные factory tags и
|
||
внешние эффекты их методов не включены. Для tag `9` selector `0` использует
|
||
отдельный scale `+0x11c`; остальные поддержанные классы используют коэффициент
|
||
напрямую. Пустой набор и нулевой знаменатель остаются native no-result, а
|
||
неподдержанный factory tag и расчётный диапазон произведения/отношения за
|
||
нормальным диапазоном `f32` становятся явной ошибкой Rust.
|
||
|
||
Проекция полного mission load недоступна для неполного списка typed records,
|
||
Control resource у non-geometric prototype и некоторых неразрешённых EXTO node
|
||
mappings; preview и объекты без Unit DAT root её не строят. Её соответствие
|
||
полной загрузке игры для всех типов компонентов и factory paths ещё не
|
||
установлено. Setter `1` меняет multiplier `+0x4c` выбранных строк, но расписание
|
||
и lifetime этих объектов после конструктора и в отложенной загрузке остаются
|
||
непроверенными.
|
||
|
||
Для `Control.dll` (SHA-256
|
||
`bcb86318089028548a178b03a21accdce15df0cc85ddd91674cf3c2f55012c6e`) tag-26
|
||
getter selector `0x300` восстановлен локально: RVA `0x21d00` читает provider по
|
||
ключу `fixed_words[1]`, затем умножает provider, CTLD coefficient `fixed_words[11]`
|
||
и поле объекта `+0x4c` именно в таком порядке. Provider getter RVA `0xdc40`
|
||
читает type-1 поле `+0x14` строки, которую `FUN_10008b10` (RVA `0x8b10`)
|
||
добавляет из NDP в таблицу с шагом `0x2c`; `FUN_1000b240` (RVA `0xb240`)
|
||
инициализирует это поле как `1.0`. Начальный Rust snapshot повторяет порядок
|
||
NDP строк корневого ресурса и примонтированных `EXTO` (без первой строки
|
||
последнего), сохраняя исходные таблицу и строку как provenance. Ключ устройства
|
||
сначала меняется по подтверждённому MSH mapping для `EXTO`, затем это готовое
|
||
значение применяется напрямую как индекс provider sequence. Длина этой
|
||
sequence определяется NDP: она не дополняется по числу узлов модели и ключ не
|
||
пересчитывается ещё раз по числу provider rows. `INTO` provider строк не
|
||
добавляет. Для примонтированного `EXTO` с пустым NDP невозможно безопасно
|
||
применить native skip-first путь, поэтому Rust явно сообщает unavailable;
|
||
таблица из одной строки допустима и даёт нулевой вклад. Если обязательный NDP
|
||
sidecar недоступен, запрос `0xe00` сообщает это отдельно, оставляя независимые
|
||
агрегатные selectors `1/2` доступными.
|
||
|
||
Rust query обращается к загруженному объекту и поддерживает `0xe00` только для
|
||
текущего descriptor tag `26` с factory tag `26`; другие активные typed tags и
|
||
factory paths остаются явными границами. Этот запрос возвращает начальный
|
||
снимок provider amount: Native `FUN_10010f30` (RVA `0x10f30`) изменяет и
|
||
ограничивает количество, а связанные callbacks переносят его между строками;
|
||
этот переход в Rust ещё не реализован.
|
||
|
||
Отдельный API `advance_loaded_mission_resources` принимает явное время очереди
|
||
и обновляет представленные поля `CurrentOre`, строк распределителя и Control
|
||
selector `1`. Его граница — статические снимки профиля и provider-значений:
|
||
динамическое распространение Control amount, смена профилей, special
|
||
object-state gate и жизненный цикл созданных/удалённых объектов здесь не
|
||
моделируются. Если обязательный запрос позднего объекта не поддержан,
|
||
транзакция возвращает ошибку и не применяет подготовленные изменения. API не
|
||
подключён к игровому циклу; AutoDemo-тест подтверждает связь настоящих профилей
|
||
с этим шагом, но не полный паритет миссии. Отдельный aggregate getter Behavior
|
||
RVA `0xa490` в oracle не исполнен.
|
||
|
||
Behavior selectors `0x1002` и `0x1005` используют значения выбранного
|
||
профиля из `behpsp.res`, разобранного до создания нового мира. `0x1002` складывает
|
||
только текущие descriptor tag `26` запросы `0xe00`, затем применяет
|
||
`Transfer_Ore_OffBoard`; `0x1005` возвращает `Transfer_Power_Out`. Это начальный
|
||
снимок профиля и provider-строк. Полная загрузка сохраняет результат у
|
||
`ObjectHandle` и возвращает
|
||
ошибку, если известный код класса не имеет корректной записи; класс,
|
||
отсутствующий в таблице выбора, сохраняет начальные значения конструктора.
|
||
Preview и объекты без полного Unit DAT не получают профильное состояние.
|
||
Native oracle подтвердил соответствия кода класса выбранной записи, разбор
|
||
`.var` и применение второй области данных;
|
||
оригинальный aggregate getter Behavior RVA `0xa490` остаётся статически
|
||
исследованным, но не исполнен в oracle.
|
||
|
||
В x86 oracle при x87 control word `0x007f` проверены порядок, signed zero,
|
||
отрицательные входы, расширенный промежуточный диапазон и subnormal return.
|
||
Rust запрос ограничивает себя normal-or-zero значениями, поэтому явно отклоняет
|
||
промежуточные случаи, где результат выходит за поддерживаемый диапазон f32,
|
||
даже если позднее умножение вернуло бы его в диапазон. Это ограничение
|
||
поддержки Rust snapshot, а не неверный native-результат.
|
||
|
||
В Unit DAT fixture `UNITS/BUILDS/MINE/min_l_n1.dat` проверена реальная связка
|
||
`fr_l_mine.ctl` + `fr_l_mine.ndp`: tag `26`, ключ `2` в локальной NDP таблице
|
||
из `22` строк, коэффициент `1.0`. Игнорируемый runtime-тест выполняет полный
|
||
загрузочный путь, подтверждает root/`EXTO` размеры `22` и `4` и итоговые `25`
|
||
provider rows после пропуска первого `EXTO` row. Он вызывает Rust query по
|
||
восстановленному native правилу адресации и получает начальное значение `1.0`;
|
||
getter оригинальной DLL здесь не исполняется. Это не запись живого
|
||
resource-device значения во время игры.
|
||
|
||
Закрывающий эксперимент: трассировать `LoadControlSystem`,
|
||
`LoadPhysicalModel`, `CreateCollManager` и создание collision objects; связать
|
||
каждый изменяемый field с созданным shape, contact или реакцией на движение.
|
||
|
||
## DirectPlay wire
|
||
|
||
DirectPlay lifecycle и имена игровых messages известны. Wire framing, payload
|
||
schema, reliability flags и `netZipData` требуют записи обмена двух
|
||
оригинальных клиентов.
|
||
|
||
Native interoperability подтверждается только успешным обменом original client
|
||
<-> compatibility implementation в обе стороны.
|
||
|
||
## Shell, HUD, шрифты и локализация
|
||
|
||
Граница 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 и
|
||
диалогов.
|
||
|
||
## 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, а не по одному имени.
|
||
|
||
## Rare branches
|
||
|
||
- `Land.map poly_count > 0`;
|
||
- RsLi adaptive methods `0x080` и `0x0A0`;
|
||
- Texm formats 556 и 88;
|
||
- FX opcode 6;
|
||
- редкие material flags и MSH auxiliary streams.
|
||
|
||
Такие ветки реализуются по бинарному коду и synthetic tests, а статус
|
||
corpus-verified получают только после реального файла или runtime trace.
|