aboutsummaryrefslogtreecommitdiff
path: root/docs/appendices/script-vm.md
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2026-10-29 18:43:09 +0300
committerValentin Popov <valentin@popov.link>2026-10-29 18:43:09 +0300
commit1d20509b052c0b3e2db2a8bd86756f007041ab21 (patch)
tree109f42cb4f8fef5aeeba52969ec847fdab82dc59 /docs/appendices/script-vm.md
parentd3a0f8c7ffafc9ae2d8f9efe5d7a730c1fa0173d (diff)
downloadfparkan-1d20509b052c0b3e2db2a8bd86756f007041ab21.tar.xz
fparkan-1d20509b052c0b3e2db2a8bd86756f007041ab21.zip
feat(runtime): support class-9 and default Control budgets
Diffstat (limited to 'docs/appendices/script-vm.md')
-rw-r--r--docs/appendices/script-vm.md100
1 files changed, 85 insertions, 15 deletions
diff --git a/docs/appendices/script-vm.md b/docs/appendices/script-vm.md
index a362b73..5fb0cd0 100644
--- a/docs/appendices/script-vm.md
+++ b/docs/appendices/script-vm.md
@@ -792,21 +792,23 @@ provider getter и изменение количества не подменен
Rust предоставляет явный CPU-вызов
`advance_loaded_mission_control_budget(engine, handle, dt)`. Он переносит
-диспетчер и callbacks исходных factory tags `15` и `19`, если текущий
-descriptor tag равен `15` или `19` (native categories `0` или `1`). Rates `+0x5dc/+0x5e0`
-сохраняются отдельно. Вызов работает на копии полного состояния: неизвестный
-descriptor, factory callback, provider или численный диапазон возвращает
-ошибку без публикации частичных изменений. Вызов ещё не включён в Control clock;
-нативный oracle также проверяет пустую таблицу с отрицательным нулём в обеих
-выходных ставках и невырожденную пару исходных устройств tag 19/tag 15 при
-`dt = 0.375`. Этот парный вектор проверяет изменения amount, processed amount,
-накопителя, доли, обоих множителей и ставок `+0x5dc/+0x5e0` по битам. Rust-тест
-повторяет его на синтетической provider-таблице. Для лицензированного
-`min_l_n1.dat` отдельный тест подтверждает состав factory/current tags и то, что
-первый неподдержанный current tag `9` отклоняет весь шаг без изменений; полного
-успешного бюджетного прохода для реальной миссионной таблицы пока нет.
-Rust также отклоняет нулевой знаменатель обновления tag 19; тест вызывает эту
-ошибку после изменения provider потребителем и подтверждает откат всей таблицы.
+исходные callbacks factory tags `9`, `15`, `19` и общий default callback, а
+категории выбирает по текущему descriptor tag. Native map подтверждён только
+для префикса тегов `0..=34`; неизвестная категория, специализированный
+callback, provider или численный диапазон возвращает ошибку без публикации
+частичных изменений. Ставки `+0x5dc/+0x5e0` сохраняются отдельно, а сам вызов
+ещё не включён в Control clock.
+
+Нативный oracle проверяет пустую таблицу с отрицательным нулём в обеих выходных
+ставках, невырожденную пару исходных устройств tag 19/tag 15 при `dt = 0.375`
+и tag 9 с шестью значениями и шестью накопителями изменений. Rust-тесты
+повторяют их по битам, включая положительное и отрицательное обновления tag 9.
+В лицензированном `min_l_n1.dat` предварительная проверка распознаёт категории,
+но находит неподдержанный factory tag `26` и отклоняет бюджет до расчёта ставок
+или вызова устройств; транзакционный тест подтверждает, что исходная таблица
+не меняется. Полный бюджетный проход на реальной миссии пока не выполнен. Rust также отклоняет нулевой знаменатель
+обновления tag 19; тест вызывает эту ошибку после изменения provider
+потребителем и подтверждает откат всей таблицы.
#### Генератор tag 19 в том же бюджетном проходе
@@ -846,6 +848,74 @@ tag `15`: смысл изменения этого поля зависит от
Поддержанные Rust-векторы не означают, что любой Control descriptor или
полный миссионный цикл уже работает.
+#### Класс 9: ставка и шесть изменяемых значений
+
+У `Control.dll` SHA-256
+`bcb86318089028548a178b03a21accdce15df0cc85ddd91674cf3c2f55012c6e`
+класс `9` использует constructor RVA `0x25460`, vtable `0x3c938`, rate
+`0x25700`, update `0x257b0` и adjustment `0x25a90`. Constructor начинает шесть
+значений в `+0x98..+0xac` с `1.0`, а шесть накопителей в `+0xb0..+0xc4` — с
+нуля. Текущий `INTO` заменяет descriptor и общие коэффициенты, сохраняя эти
+runtime-массивы.
+
+Для rate `q` — текущее значение provider, `B` — descriptor `+0x20`, `C0..C2` —
+коэффициенты, а `scale` — class-9 поле `+0x11c`. Сначала отдельно округляются
+`G0 = C0 * scale`, сумма шести значений, и свободная величина `6 - sum`; затем
+вычисляются доступная часть `(6 - sum) * G0` и запрос `(C1 * q) * dt`. Rate
+равна `-(dt * B) - C2 * min(доступная часть, запрос)`. В update множитель
+`+0x4c` сначала получает allocation, затем вычисляется
+`S = -(rate(dt) * allocation) - (dt * B)`. При `C2 > 0` изменение равно
+`S / C2`; иначе его знак выбирает `±((C1 * q) * dt)`. Если `+0x618` не
+приостанавливает обновление, а `G0 > 0`, изменение делится на `G0 * 6` и
+передаётся `0x25a90`.
+
+Adjustment умножает упорядоченную сумму шести значений на единственную
+binary32-константу `0x3e2aaaab` — приблизительное `1/6`. Дополнительного деления
+на шесть нет. При положительном изменении native ограничивает фактор сверху
+единицей и приближает каждое значение к `1`; при отрицательном — уменьшает
+каждое пропорционально ему. Каждый фактический шаг также прибавляется к своему
+накопителю. Oracle запускает исходные entry points на оригинальном Control.dll
+при x87 control word `0x007f`:
+
+| Вектор | Вход | Rate / multiplier | Шесть выходных значений | Шесть выходных дельт |
+|---|---|---|---|---|
+| Уменьшение | `q=.4, dt=.5, B=.2, C0=10, scale=1, C1=.6, C2=2`, allocation `.5`; все значения `1`, дельты `0` | `0xbdcccccd`, `0x3f000000` | `0x3f7fe4b1` во всех строках | `0xb9da740e` во всех строках |
+| Смешанный набор | `q=.4, dt=.5, B=.2, C0=.75, scale=1.25, C1=.6, C2=2`, allocation `1`; значения `[0,.25,.5,.75,1,.9]`, дельты `[.1,-.2,0,.3,-.4,.5]` | `0xbeae147b`, `0x3f800000` | `[3d49a637,3e92e795,3f064d32,3f432699,3f800000,3f67a90a]` | `[3e18cff4,be26fda3,3cc9a637,3e9fe6cc,becccccd,3f0142a4]` |
+
+Отдельный прямой вызов `0x25a90` для значения `[0,1,1,1,1,1]`, нулевых
+накопителей и добавки `.1` записывает `0x3f19999c` в первую пару value/delta;
+остальные пять строк остаются неизменными. Этот vector фиксирует умножение на
+сохранённую константу, а не вычисление `sum / 6`. Rust хранит оба массива
+отдельно и сохраняет их при descriptor override; ошибки численного диапазона
+не публикуют частичный результат.
+
+#### Общие default budget callbacks и категории
+
+Ветка фабрики Control RVA `0x2d4b0` направляет обычные классы в constructor
+`0x20800`; он устанавливает vtable `0x3c448` и word `+0x50 = 5`. Rate и update
+этого семейства — исходные `0x21860` и `0x21890`: первая возвращает
+`-(dt * descriptor[+0x20])`, второй записывает allocation в `+0x4c` после
+общей проверки активности provider. Фабричные теги `12`, `13` и `25` идут по
+ветке default; tag `21` явно выбирает ту же пару. У tags `4`, `8` и `27` свои
+vtable/constructor, но их rate/update entries указывают на ту же пару. Native
+oracle проверяет factory routing тегов `12/13`, vtable pointers и прямой
+вызов rate/update общей пары. Rust повторяет формулу и сохраняет исходные
+word-50 defaults, в частности `0x200` у тега `4` и ноль у тега `27`.
+
+Descriptor category lookup `0x2c3a0` возвращает следующие значения для
+проверенного префикса `0..=34`:
+
+```text
+[0,4,4,0,2,3,0,0,2,5,5,0,0,0,1,0,3,2,0,1,3,5,4,0,4,0,3,5,0,0,4,0,1,2,4]
+```
+
+Именно текущий descriptor выбирает категорию; callback остаётся у исходного
+factory-класса. В Rust неизвестный текущий тег за концом префикса и
+специализированный factory callback не подменяются общей формулой. На
+лицензированном Unit DAT полный проход доходит до factory `26` и возвращает
+ошибку без частичного изменения таблицы. Ни этот CPU-вызов, ни локальные
+векторы не означают включения бюджетного шага в Control clock.
+
#### Активация и начальные часы
Для Control класса `4` oracle вызывает исходный конструктор RVA `0x31490`