test(runtime): verify randomized Control resource cadence
Docs Deploy / Build and Deploy MkDocs (push) Successful in 36s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 36s
This commit is contained in:
4 files changed
+103
-9
No files matched your search
@@ -1892,6 +1892,51 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
"budget dirties amount after the preceding contact/aggregate update");
|
"budget dirties amount after the preceding contact/aggregate update");
|
||||||
Console.WriteLine("native-control-active-clock: class=10 e0=1 period=500 seed=0 sub-ms=skip" +
|
Console.WriteLine("native-control-active-clock: class=10 e0=1 period=500 seed=0 sub-ms=skip" +
|
||||||
" equality=437.5/skip now=500 e8=62.5 resourceDt=0.5 amount=0x42206666 ec=500");
|
" equality=437.5/skip now=500 e8=62.5 resourceDt=0.5 amount=0x42206666 ec=500");
|
||||||
|
|
||||||
|
// Isolate cadence after the budget vector: no device or stage callbacks run.
|
||||||
|
Marshal.WriteInt32(owner, 0x5c0, 0);
|
||||||
|
Marshal.WriteByte(owner, 0x598, 0);
|
||||||
|
Marshal.WriteInt32(owner, 0xe4, 0);
|
||||||
|
Marshal.WriteInt32(owner, 0xec, 0);
|
||||||
|
Marshal.WriteInt32(randomState, unchecked((int)0x12345678));
|
||||||
|
timedUpdate(owner, 0.5f);
|
||||||
|
Expect(unchecked((uint)Marshal.ReadInt32(randomState)) == 0x12345678 &&
|
||||||
|
ReadFloat(owner, 0xe4) == 0.0f, "sub-millisecond active calls do not draw random jitter");
|
||||||
|
uint[] expectedRandom = { 0xb7debec4, 0x91b9ca56, 0x4dc90515, 0x610747e3 };
|
||||||
|
float[] expectedResourceClock = { 0, 0, 502, 502 };
|
||||||
|
for (int i = 0; i < expectedRandom.Length; i++) {
|
||||||
|
float now = 500.0f + i;
|
||||||
|
timedUpdate(owner, now);
|
||||||
|
Expect(unchecked((uint)Marshal.ReadInt32(randomState)) == expectedRandom[i] &&
|
||||||
|
ReadFloat(owner, 0xec) == expectedResourceClock[i] &&
|
||||||
|
ReadFloat(owner, 0xe4) == now,
|
||||||
|
"active clock draws once per accepted call and tests a fresh randomized deadline");
|
||||||
|
Console.WriteLine("native-control-randomized-clock: now=" + Float(now) +
|
||||||
|
" rng=" + Hex(expectedRandom[i]) + " resourceClock=" + Float(expectedResourceClock[i]));
|
||||||
|
}
|
||||||
|
Marshal.WriteInt32(owner, 0xe4, 0);
|
||||||
|
Marshal.WriteInt32(owner, 0xec, 0);
|
||||||
|
Marshal.WriteInt32(randomState, unchecked((int)0xffff0000));
|
||||||
|
timedUpdate(owner, 500.0f);
|
||||||
|
Expect(unchecked((uint)Marshal.ReadInt32(randomState)) == 0x8000ffff &&
|
||||||
|
ReadFloat(owner, 0xec) == 0.0f,
|
||||||
|
"nonzero random state can produce exact deadline equality, which still skips");
|
||||||
|
timedUpdate(owner, 501.0f);
|
||||||
|
Expect(unchecked((uint)Marshal.ReadInt32(randomState)) == 0x3ffe7ffe &&
|
||||||
|
ReadFloat(owner, 0xec) == 501.0f, "next accepted call redraws jitter after equality");
|
||||||
|
Marshal.WriteInt32(owner, 0xd4, 2);
|
||||||
|
timedUpdate(owner, 502.0f);
|
||||||
|
Console.WriteLine("native-control-forced-clock: rng=" + Hex(unchecked((uint)Marshal.ReadInt32(randomState))) +
|
||||||
|
" resourceClock=" + Float(ReadFloat(owner, 0xec)) + " counter=" + Marshal.ReadInt32(owner, 0xd4));
|
||||||
|
Expect(unchecked((uint)Marshal.ReadInt32(randomState)) == 0xdffdc002 &&
|
||||||
|
ReadFloat(owner, 0xec) == 502.0f && Marshal.ReadInt32(owner, 0xd4) == 1,
|
||||||
|
"forced resource cycle still consumes RNG and decrements its counter");
|
||||||
|
Marshal.WriteByte(owner, 0xe0, 0);
|
||||||
|
timedUpdate(owner, 503.0f);
|
||||||
|
Expect(unchecked((uint)Marshal.ReadInt32(randomState)) == 0xdffdc002 &&
|
||||||
|
ReadFloat(owner, 0xec) == 503.0f && Marshal.ReadInt32(owner, 0xd4) == 1,
|
||||||
|
"inactive clock synchronizes resource time without drawing RNG or consuming the forced counter");
|
||||||
|
Console.WriteLine("native-control-randomized-clock-gates: equality=skip redraw=due forced=rng+decrement inactive=no-rng/no-decrement");
|
||||||
} finally {
|
} finally {
|
||||||
Marshal.WriteInt32(period, savedPeriod);
|
Marshal.WriteInt32(period, savedPeriod);
|
||||||
Marshal.WriteInt32(randomState, savedRandom);
|
Marshal.WriteInt32(randomState, savedRandom);
|
||||||
|
|||||||
@@ -369,8 +369,13 @@ Control выставлен flag `0x102`, слово флагов ANI `+0x14` и
|
|||||||
видимость, это отдельная непроверенная граница, а не FX callbacks. Бюджетная
|
видимость, это отдельная непроверенная граница, а не FX callbacks. Бюджетная
|
||||||
часть активного `0xbcf0` исполнена на синтетическом хосте класса `10`:
|
часть активного `0xbcf0` исполнена на синтетическом хосте класса `10`:
|
||||||
при периоде `500` и нулевом RNG проверены пропуск до deadline и на равенстве
|
при периоде `500` и нулевом RNG проверены пропуск до deadline и на равенстве
|
||||||
`437.5`, затем ресурсный `dt = 0.5` при времени `500`. Движение и очереди
|
`437.5`, затем ресурсный `dt = 0.5` при времени `500`. На том же ограниченном
|
||||||
исключены условиями вектора; активация `+0xe0` задана явно. Отдельный вектор
|
хосте без устройств seed `0x12345678` подтверждает новые RNG draw при временах
|
||||||
|
`500`, `501`, `502`, `503` и ресурсные clocks `0`, `0`, `502`, `502`: deadline
|
||||||
|
пересчитывается при каждом принятом активном вызове. Проверены также точное
|
||||||
|
равенство с ненулевым RNG, расход RNG при принудительном `+0xd4` и сохранение
|
||||||
|
RNG/счётчика в неактивной ветви. Движение и очереди исключены условиями этих
|
||||||
|
векторов; активация `+0xe0` задана явно. Отдельный вектор
|
||||||
класса `4` исполняет событие `4` через `0x31ae0` → `0x314f0`, проверяя
|
класса `4` исполняет событие `4` через `0x31ae0` → `0x314f0`, проверяя
|
||||||
активность, начальные часы `time - 100`, округление DWORD в `f32` и одну
|
активность, начальные часы `time - 100`, округление DWORD в `f32` и одну
|
||||||
начальную команду. Геометрические callbacks подставлены явно, таблицы
|
начальную команду. Геометрические callbacks подставлены явно, таблицы
|
||||||
|
|||||||
@@ -771,9 +771,44 @@ DLL. Экспорт `InitializeSettings` (RVA `0x32260`) регистрируе
|
|||||||
до `0x42206666`, долю до `0x3ecd4fdf`; mock-генератор получает `dt = 0.5`
|
до `0x42206666`, долю до `0x3ecd4fdf`; mock-генератор получает `dt = 0.5`
|
||||||
ровно один раз. После вызова dirty равен `1`, а агрегат `+0x588` ещё не
|
ровно один раз. После вызова dirty равен `1`, а агрегат `+0x588` ещё не
|
||||||
пересчитан: бюджетное изменение происходит после предшествующего `0x12a40`.
|
пересчитан: бюджетное изменение происходит после предшествующего `0x12a40`.
|
||||||
Событие активации проверено отдельно выше. Ненулевые состояния RNG,
|
Событие активации проверено отдельно выше.
|
||||||
принудительный цикл по `+0xd4`, движение и непустые очереди остаются отдельными
|
|
||||||
границами.
|
Условие срока не использует сохранённый deadline. Машинные инструкции
|
||||||
|
`0xc74a..0xc7c1` на каждом дошедшем до этой точки вызове изменяют RNG
|
||||||
|
`Control.dll+0x43228` и заново вычисляют порог. Два 16-битных слова состояния
|
||||||
|
обновляются по правилу `low' = u16(low << 1) XOR high`,
|
||||||
|
`high' = (high >> 1) XOR low'`. При периоде `P` четверть периода равна
|
||||||
|
`q = P * 0.25`, коэффициент случайного вклада — `q / 65536`, а порог имеет вид
|
||||||
|
`(high' * (q / 65536) - q * 0.5) + P + last_resource_time`.
|
||||||
|
Порядок этих x87 операций существенен; отдельное сохранение коэффициента в
|
||||||
|
`f32` видно по RVA `0xc78e`. Это RNG данного модуля, его нельзя подменять
|
||||||
|
LCG планировщика Behavior.
|
||||||
|
|
||||||
|
При `P = 500`, исходных `+0xe4 = +0xec = 0` и seed `0x12345678` проверены
|
||||||
|
следующие последовательные вызовы. Таблица устройств здесь пуста: вектор
|
||||||
|
изолирует cadence после отдельной проверки настоящего бюджетного изменения.
|
||||||
|
|
||||||
|
| Время | RNG после проверки срока | Последний ресурсный clock `+0xec` |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `0.5` | `0x12345678`, без изменения | `0` |
|
||||||
|
| `500` | `0xb7debec4` | `0` |
|
||||||
|
| `501` | `0x91b9ca56` | `0` |
|
||||||
|
| `502` | `0x4dc90515` | `502` |
|
||||||
|
| `503` | `0x610747e3` | `502` |
|
||||||
|
|
||||||
|
Оба первых принятых вызова пропускают ресурсный шаг, но всё равно расходуют
|
||||||
|
RNG. На третьем порог меняется и шаг выполняется раньше прежних порогов.
|
||||||
|
Поэтому предварительно выбрать один случайный deadline до следующего
|
||||||
|
ресурсного шага означало бы другую модель времени.
|
||||||
|
|
||||||
|
Отдельный seed `0xffff0000` после одного перехода даёт `0x8000ffff` и порог
|
||||||
|
ровно `500`: равенство снова пропускается. Вызов `501` получает
|
||||||
|
`0x3ffe7ffe` и обновляет ресурсный clock. Затем `+0xd4 = 2` принуждает вызов
|
||||||
|
`502` выполнить ещё один цикл, несмотря на новый порог; RNG становится
|
||||||
|
`0xdffdc002`, а счётчик уменьшается до `1`. Если после этого снять `+0xe0`,
|
||||||
|
вызов `503` синхронизирует `+0xec`, но сохраняет RNG и принудительный счётчик.
|
||||||
|
Движение, непустые очереди и исполнение всего этого пути в миссии остаются
|
||||||
|
отдельными границами.
|
||||||
|
|
||||||
#### Доставка игровых часов в Control
|
#### Доставка игровых часов в Control
|
||||||
|
|
||||||
|
|||||||
@@ -958,10 +958,19 @@ Control пересчитывает дочерние строки в глубин
|
|||||||
Отдельный путь объекта класса `4` умножает интервал на контактный коэффициент и
|
Отдельный путь объекта класса `4` умножает интервал на контактный коэффициент и
|
||||||
передаёт изменение строке. В проверенном примере коэффициент `1`, интервал `2`,
|
передаёт изменение строке. В проверенном примере коэффициент `1`, интервал `2`,
|
||||||
и значение строки уменьшается на `2`; этот коэффициент не является ставкой
|
и значение строки уменьшается на `2`; этот коэффициент не является ставкой
|
||||||
производства из профиля. Проверка времени охватывает только начальную ветвь:
|
производства из профиля. Источник абсолютных часов и их доставка из World3D
|
||||||
активную ветвь не запускали, а источник времени и полный порядок вызовов в игре
|
в Control прослежены по инструкциям. Активная бюджетная часть проверена на
|
||||||
пока не установлены. Поэтому запросы Rust `0xe00` и `0x1002` используют
|
искусственном хосте класса `10`; полного тика объекта на миссии ещё нет.
|
||||||
начальные снимки provider-строк, а `0x1005` — загруженную ставку профиля.
|
|
||||||
|
У ресурсного шага отдельные часы. После принятого межкадрового интервала
|
||||||
|
Control заново разыгрывает срок обновления, даже если предыдущий вызов его
|
||||||
|
ещё не достиг. Например, с периодом `500` и начальным RNG `0x12345678`
|
||||||
|
исходная DLL пропустила времена `500` и `501`, затем выполнила ресурсный шаг
|
||||||
|
на `502`. Выбрать случайный срок один раз и ждать его было бы неверно.
|
||||||
|
Принудительный шаг тоже расходует RNG; неактивный объект синхронизирует часы
|
||||||
|
без такого расхода. Это локальные native проверки, а не работающий Rust
|
||||||
|
Control: запросы Rust `0xe00` и `0x1002` по-прежнему используют начальные
|
||||||
|
снимки provider-строк, а `0x1005` — загруженную ставку профиля.
|
||||||
Точные нативные векторы и открытые границы описаны в [приложении](../appendices/script-vm.md#изменение-provider-состояния-control).
|
Точные нативные векторы и открытые границы описаны в [приложении](../appendices/script-vm.md#изменение-provider-состояния-control).
|
||||||
|
|
||||||
На x87 control word `0x007f` каждое умножение округляет мантиссу до 24 бит,
|
На x87 control word `0x007f` каждое умножение округляет мантиссу до 24 бит,
|
||||||
|
|||||||
Reference in new issue
Block a user