feat(runtime): update live Control provider amounts
Docs Deploy / Build and Deploy MkDocs (push) Successful in 37s

This commit is contained in:
Valentin Popov committed 2026-10-29 16:55:09 +04:00
1 parent f35c9ec1f0
commit acdf635ffb
6 files changed
+522 -34

No files matched your search

+42 -10
View File
@@ -891,8 +891,9 @@ TMA и компоненты, но во временном VFS заменяет
оригинальный профиль генератора — 10.0 и 0.0 соответственно. Для исходного
tag-26 ряда получается `1.0` provider × `1.0` coefficient × `1.0` multiplier;
профиль mine умножает эту сумму на `1.0`, поэтому selector `0x1002` возвращает
`1.0`. Эти запросы используют начальные provider-значения после конструкторов,
а не результаты живого игрового тика.
`1.0`. Эти числа описывают начальное состояние после конструкторов.
Сами запросы читают текущую долю provider; отдельное прямое изменение строки
уже может изменить ответ, хотя полный игровой Control tick ещё не исполняется.
У Control есть и начальные сведения о модели узла, но их таблица не совпадает
с NDP. Sidecar задаёт состав и порядок provider-строк, а поле type-1 `+0x14`
@@ -920,12 +921,42 @@ initial_mesh_state: Option<MissionControlNodeInitialMeshState>
На той же начальной стадии Control вычисляет количество строки по NDP
`max_volume`. Для загрузки Unit DAT проверенные множители равны `1.0`, поэтому
количество совпадает с исходным значением NDP. Оригинал начинает с одинакового
текущего и последнего обработанного количества; Rust сохраняет его один раз как
`initial_amount_bits`. Это исходный снимок, а не неизменяемая вместимость:
позднее Control меняет оба количества. В лицензированном `min_l_n1.dat`
текущего и последнего обработанного количества. Rust оставляет исходное число
в `initial_amount_bits`, а текущее и обработанное количество хранит отдельно.
Вместимость при этом берётся из NDP; исходный снимок не заменяет её. В лицензированном `min_l_n1.dat`
проверены, например, root row `0` со значением `1.0` и строка `EXTO` с local
row `1` в общей позиции `22` со значением `6000.0`.
Для одной строки перенесён прямой путь `Control.dll+0x10f30` с выключенным
флагом накопления анимационного изменения. API
`apply_loaded_mission_control_node_delta` принимает живой объект, глобальный
ключ строки и добавку. Например, у строки с максимумом `100` и количеством
`40` добавка `20` даёт количество `60` и долю `0.6`. Переполнение ограничивается
максимумом, отрицательный результат — нулём; при нуле ставится флаг истощения
`0x10`, а пополнение снимает его. Общий итог изменяется именно на разницу
нового и прежнего количества:
```rust
let amount = (previous + delta).clamp(0.0, maximum);
let total = (total + (amount - previous)).max(0.0);
let ratio = amount / maximum;
```
Это три отдельные операции над `f32`, с проверкой поддерживаемого численного
диапазона до записи состояния. Исходные `initial_*` поля сохраняются;
`processed_amount_bits` тоже не меняется, поскольку его обновляет другой
нативный проход — пересчёт стадий. API возвращает обязательную публикацию
`ControlNodeProviderPublication` для узла и selector `0x200`; сам callback
AniMesh и пересчёт стадий этим API не исполняются. Истощение корня либо узла
с критическим флагом `1` пока возвращает ошибку без изменения состояния:
эта ветвь требует полного lifecycle объекта, а не только нового количества.
На исходном `min_l_n1.dat` прямое уменьшение количества строки ресурсного
устройства вдвое меняет ответы `0xe00` и Behavior `0x1002` с `1.0` на `0.5`.
Обычный последующий шаг runtime сохраняет это состояние. Это воспроизводимый
тест связки загрузки и запросов, но пока не автоматическое изменение через
clock Control.
После загрузки количество в строках может меняться. Контроллер распределяет
добавку либо пропорционально свободному месту, либо по очереди: при максимумах
`[30, 60, 50]`, текущих значениях `[10, 20, 0]` и добавке `22` первый способ
@@ -946,14 +977,15 @@ Control пересчитывает дочерние строки в глубин
`[102, 570]`. В проверенных векторах одинаковые данные при скрытии и повторном
включении восстановили суммы; переходы с разными слотами — нет. Эти проверки
исполняют исходные DLL на тестовых строках, но не являются запуском Control на
полной миссии; Rust пока сохраняет только начальное состояние provider-строк.
полной миссии; Rust пока не выполняет эти переходы стадий и видимости.
Прямой путь изменения количества остаётся отдельной операцией.
Подробные границы приведены в [техническом приложении](../appendices/script-vm.md#родители-стадии-и-видимость-provider-узлов).
Для точного повтора важен порядок округления. При исходных значениях `[2^24, 2]`
и запросе `-1` оригинал сначала складывает общий итог с изменением, затем
вычитает прежний итог. Из-за округления фактическое изменение получается `-2`.
Нативная проверка подтвердила это поведение; Rust пока использует начальный
снимок provider-строк и не исполняет их дальнейшее распределение.
Нативная проверка подтвердила это поведение; общий распределитель
`0x10ba0` в Rust ещё не перенесён, хотя прямая добавка к одной строке уже доступна.
Отдельный путь объекта класса `4` умножает интервал на контактный коэффициент и
передаёт изменение строке. В проверенном примере коэффициент `1`, интервал `2`,
@@ -969,8 +1001,8 @@ Control заново разыгрывает срок обновления, да
на `502`. Выбрать случайный срок один раз и ждать его было бы неверно.
Принудительный шаг тоже расходует RNG; неактивный объект синхронизирует часы
без такого расхода. Это локальные native проверки, а не работающий Rust
Control: запросы Rust `0xe00` и `0x1002` по-прежнему используют начальные
снимки provider-строк, а `0x1005` — загруженную ставку профиля.
Control: запросы Rust `0xe00` и `0x1002` читают текущее состояние строк,
которое пока меняет только прямой API, а `0x1005` — загруженную ставку профиля.
Точные нативные векторы и открытые границы описаны в [приложении](../appendices/script-vm.md#изменение-provider-состояния-control).
На x87 control word `0x007f` каждое умножение округляет мантиссу до 24 бит,