feat(runtime): load Behavior profiles for resource queries
Docs Deploy / Build and Deploy MkDocs (push) Successful in 1m14s

This commit is contained in:
Valentin Popov committed 2026-10-22 15:30:41 +04:00
1 parent 29b81b8723
commit 83db371742
7 files changed
+1109 -45

No files matched your search

+66 -16
View File
@@ -755,27 +755,77 @@ NDP, а `FUN_1000a100` (RVA `0xa100`) добавляет их в общую та
`FUN_100198e0` (RVA `0x198e0`) суммирует успешные запросы IID `0x204` с selector
`0xe00`. В парной `Control.dll` выше `FUN_1002bb40` (RVA `0x2bb40`) для типа
`0x1a` вызывает selector `0x300`, описанный в этом разделе. Затем Behavior query
`0x1002` (RVA `0xa490`) умножает полученную сумму на текущее значение block
`+0x820`, поле `+0x58` `Transfer_Ore_OffBoard` (имя — RVA `0x22e20`, default
5.0 — RVA `0x22d90`; config selector 2 загружает блок через RVA `0x0a1a0`).
Отдельный query `0x1005` (RVA `0xa490`) возвращает поле `+0x60`
`Transfer_Power_Out` (имя — RVA `0x22e20`, default 100.0 — RVA `0x22d90`;
setter — RVA `0xaa40`). В полной загрузке FParkan начальные provider-строки
хранятся вместе с таблицей Control конкретного `ObjectHandle`. Запрос
`0xe00` проходит по ключу устройства в этот снимок и пока поддержан только для
текущего descriptor tag `26` с factory tag `26`; для него результат равен
`0x1002` (RVA `0xa490`) умножает полученную сумму на поле `+0x58`
`Transfer_Ore_OffBoard` блока `+0x820`; отдельный selector `0x1005` возвращает
`+0x60` `Transfer_Power_Out`. Имена регистрируются в RVA `0x22e20`, а конструктор
RVA `0x22d90` задаёт начальные значения 5.0 и 100.0. Восемнадцать точных class
codes выбирают записи `.var` из `behpsp.res`: например, `0x80000004` выбирает
`prof_mine.var`, `0x80000002` — `prof_generator.var`, а три кода bunker-класса
выбирают `prof_bunker.var`. Таблицу этих соответствий задаёт mapper
`FUN_10008a80` (RVA `0x8a80`) в Behavior.dll SHA-256
`ddfbdbde07832358fe6e35e01c8dba9b58e7313789498b6049ed51d4ed9baf99`.
Установленные соответствия mapper-а:
| Class flags | Запись `behpsp.res` |
| --- | --- |
| `0x80000004` | `prof_mine.var` |
| `0x80000008` | `prof_storage.var` |
| `0x80000002` | `prof_generator.var` |
| `0x80000010` | `prof_plant.var` |
| `0x80000040` | `prof_angar.var` |
| `0x80000200` | `prof_mtp.var` |
| `0x80000400` | `prof_institute.var` |
| `0x80010000`, `0x80020000`, `0x80040000` | `prof_bunker.var` |
| `0x80100000`, `0x80200000` | `prof_tower.var` |
| `0x01002000` | `prof_trn.var` |
| `0x01004000` | `prof_bld.var` |
| `0x01008000` | `prof_war.var` |
| `0x01010000` | `prof_hq.var` |
| `0x01020000` | `prof_hero.var` |
| `0x20000000` | `prof_animal.var` |
У бинарной строки `.var` есть имя, индекс типа, размер и две области данных.
Native parser RVA `0x49fa0` читает запись, binder RVA `0x49090` связывает её с
зарегистрированным полем, а apply-loop RVA `0x49170` копирует в поле именно
вторую область. В профиле используются точные C-string имена
`Transfer_Ore_OffBoard` и `Transfer_Power_Out`; регистр учитывается, первый
совпавший ряд побеждает. Rust ограничивает чтение исходной записи NRes её
границами и сохраняет значения как исходные 32-битные слова у соответствующего
`ObjectHandle`. Разбор профиля для полной миссии завершается до создания нового
мира: отсутствие или повреждение профиля известного класса прерывает загрузку,
не подменяя предыдущую миссию. Если mapper не знает class code, остаются
конструкторские значения 5.0 и 100.0; это не догадка о неизвестном профиле.
Preview не загружает профили.
При вычислении `0x1002` native `FUN_100186b0` (RVA `0x186b0`) добавляет в список
только текущие Control descriptor-и tag `26`, после чего
`FUN_100198e0` (RVA `0x198e0`) суммирует их успешные `0xe00` запросы. FParkan
делает то же для загруженной таблицы в её исходном порядке и затем умножает
сумму на профильный `Transfer_Ore_OffBoard`. Записи tag `5`, `10` и `21` в общей
таблице в этот список не попадают. Запрос `0xe00` проходит по ключу устройства
в исходную provider-таблицу и пока поддержан только для текущего descriptor
tag `26` с factory tag `26`; для него результат равен
`provider * fixed_words[11] * multiplier`. Лицензированный тест загружает
оригинальные CTL/NDP и подтверждает, что tag-26 запись с ключом `2` читает
начальное значение строки `1.0`. Это проверка начального состояния после
конструкторов, не результат живого игрового тика.
оригинальные CTL/NDP и полную миссию с этим Unit DAT. В установленном
`min_l_n1.dat` class word равен `0x80000004`, поэтому исходный прогон выбирает
`prof_mine.var`. Для проверки другого отображения второй прогон оставляет тот же
TMA и компоненты, но во временном VFS заменяет слово класса заголовка DAT на
`0x80000002`; это тестовая подмена, а не содержимое лицензированного файла.
Профиль mine даёт `Transfer_Power_Out` 0.0 и `Transfer_Ore_OffBoard` 1.0;
оригинальный профиль генератора — 10.0 и 0.0 соответственно. Для исходного
tag-26 ряда получается `1.0` provider × `1.0` coefficient × `1.0` multiplier;
профиль mine умножает эту сумму на `1.0`, поэтому selector `0x1002` возвращает
`1.0`. Эти запросы используют начальные provider-значения после конструкторов,
а не результаты живого игрового тика.
Поведение provider-строк меняется и после загрузки: например,
`Control.dll!FUN_10010f30` (RVA `0x10f30`) обновляет и ограничивает текущее
значение, а соседние процедуры распространяют его между связанными строками.
Эти изменения пока не отражены в Rust-снимке. Поэтому `0xe00` не является
исполнением всего Control tick-а, а Behavior query `0x1002` не подключён к
этому API; его множитель `Transfer_Ore_OffBoard` требует отдельного источника
текущей конфигурации.
Эти изменения пока не отражены в Rust-снимке. Поэтому запрос Control `0xe00` и
Behavior selector `0x1002`, который его суммирует, используют исходное состояние
полной загрузки. Selector `0x1005` прямо возвращает загруженную ставку профиля.
Ни один из них пока не исполняет Control tick или последующие изменения профиля.
На x87 control word `0x007f` каждое умножение округляет мантиссу до 24 бит,
но промежуточная степень может выйти за диапазон `f32` и вернуться в него при