test(runtime): verify active Control resource clock
Docs Deploy / Build and Deploy MkDocs (push) Successful in 35s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 35s
This commit is contained in:
3 files changed
+117
-13
No files matched your search
@@ -647,7 +647,7 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
Marshal.WriteIntPtr(import, Marshal.GetFunctionPointerForDelegate(AreaMapGetterCallback));
|
Marshal.WriteIntPtr(import, Marshal.GetFunctionPointerForDelegate(AreaMapGetterCallback));
|
||||||
Marshal.WriteIntPtr(queueImport, Marshal.GetFunctionPointerForDelegate(QueueGetterCallback));
|
Marshal.WriteIntPtr(queueImport, Marshal.GetFunctionPointerForDelegate(QueueGetterCallback));
|
||||||
RunSmokeCases(module, controlModule, arealMapModule, aniMeshModule);
|
RunSmokeCases(module, controlModule, arealMapModule, aniMeshModule);
|
||||||
Console.WriteLine("original native Behavior/ArealMap/Control oracle passed (Behavior Add 0x1aac0, normalize 0x1ac90, scheduler 0x1adb0, tick 0x19e80; ArealMap UnitDAT projection 0x14b90; Control INTO override 0x2d890, provider distribution 0x10ba0, class-4 update 0x12a40 and inactive timing baseline 0xbcf0; synthetic host callbacks, GetSystemArealMap/GetQueue IAT slots, and bounded vector insertion are substituted; Control provider selector vtable is original; no full Control update or Rust parity is claimed).");
|
Console.WriteLine("original native Behavior/ArealMap/Control oracle passed (Behavior Add 0x1aac0, normalize 0x1ac90, scheduler 0x1adb0, tick 0x19e80; ArealMap UnitDAT projection 0x14b90; Control INTO override 0x2d890, provider distribution 0x10ba0, class-4 update 0x12a40 and bounded class-10 active clock 0xbcf0; synthetic host callbacks, GetSystemArealMap/GetQueue IAT slots, and bounded vector insertion are substituted; Control provider selector vtable is original; no full Control update or Rust parity is claimed).");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
catch (Exception error) {
|
catch (Exception error) {
|
||||||
@@ -1778,6 +1778,74 @@ internal static class BehaviorDistributorTickOracle {
|
|||||||
Marshal.ReadInt32(generator, 0x18) == expectedAmounts[i],
|
Marshal.ReadInt32(generator, 0x18) == expectedAmounts[i],
|
||||||
"native category priority updates tag 15 before the earlier stored generator");
|
"native category priority updates tag 15 before the earlier stored generator");
|
||||||
}
|
}
|
||||||
|
// Bounded active class-10 host: no motion object, no deferred packets.
|
||||||
|
// These host getters are mocks; the clock, deadline and budget code are native.
|
||||||
|
IntPtr host = fixture.Allocate(4);
|
||||||
|
IntPtr hostVtable = fixture.Allocate(0x94);
|
||||||
|
IntPtr motion = fixture.Allocate(4);
|
||||||
|
IntPtr motionVtable = fixture.Allocate(0x34);
|
||||||
|
Marshal.WriteIntPtr(host, hostVtable);
|
||||||
|
Marshal.WriteIntPtr(motion, motionVtable);
|
||||||
|
byte[] getMotion = { 0xb8,0,0,0,0, 0xc2,0x04,0x00 };
|
||||||
|
WriteImm32(getMotion, 1, motion.ToInt32());
|
||||||
|
Marshal.Copy(getMotion, 0, IntPtr.Add(fastcallThunks, 0x300), getMotion.Length);
|
||||||
|
byte[] hostPresent = { 0xb8,1,0,0,0, 0xc2,0x04,0x00 };
|
||||||
|
Marshal.Copy(hostPresent, 0, IntPtr.Add(fastcallThunks, 0x320), hostPresent.Length);
|
||||||
|
byte[] noMotion = { 0xb8,0xff,0xff,0xff,0xff, 0xc3 };
|
||||||
|
Marshal.Copy(noMotion, 0, IntPtr.Add(fastcallThunks, 0x340), noMotion.Length);
|
||||||
|
Marshal.WriteIntPtr(hostVtable, 0x90, IntPtr.Add(fastcallThunks, 0x300));
|
||||||
|
Marshal.WriteIntPtr(hostVtable, 0x0c, IntPtr.Add(fastcallThunks, 0x320));
|
||||||
|
Marshal.WriteIntPtr(motionVtable, 0x30, IntPtr.Add(fastcallThunks, 0x340));
|
||||||
|
Marshal.WriteIntPtr(owner, 0x40, host);
|
||||||
|
Marshal.WriteIntPtr(owner, 0x1c, host);
|
||||||
|
Marshal.WriteInt32(owner, 0x50, 10);
|
||||||
|
Marshal.WriteByte(owner, 0xe0, 1);
|
||||||
|
Marshal.WriteInt32(owner, 0xe4, 0);
|
||||||
|
Marshal.WriteInt32(owner, 0xec, 0);
|
||||||
|
Marshal.WriteByte(owner, 0x598, 0);
|
||||||
|
Marshal.WriteInt32(owner, 0x588, 0);
|
||||||
|
Marshal.WriteInt32(providerRow, 8, FloatBits(40.0f));
|
||||||
|
Marshal.WriteInt32(providerRow, 0x0c, FloatBits(40.0f));
|
||||||
|
Marshal.WriteInt32(providerRow, 0x14, FloatBits(0.4f));
|
||||||
|
Marshal.WriteInt32(owner, 0x590, FloatBits(40.0f));
|
||||||
|
Marshal.WriteInt32(generator, 8, FloatBits(0.1f));
|
||||||
|
Marshal.WriteInt32(generator, 0x1c, 0);
|
||||||
|
NativeControlTimedUpdate timedUpdate = (NativeControlTimedUpdate)Marshal.GetDelegateForFunctionPointer(
|
||||||
|
IntPtr.Add(module, ControlTimedUpdateRva), typeof(NativeControlTimedUpdate));
|
||||||
|
IntPtr period = IntPtr.Add(module, 0x42480);
|
||||||
|
IntPtr randomState = IntPtr.Add(module, 0x43228);
|
||||||
|
int savedPeriod = Marshal.ReadInt32(period);
|
||||||
|
int savedRandom = Marshal.ReadInt32(randomState);
|
||||||
|
try {
|
||||||
|
Marshal.WriteInt32(period, FloatBits(500.0f));
|
||||||
|
Marshal.WriteInt32(randomState, 0); // Fixed point: random offset is -period/8.
|
||||||
|
timedUpdate(owner, 0.5f);
|
||||||
|
Expect(ReadFloat(owner, 0xe4) == 0.0f && ReadFloat(owner, 0xe8) == 0.5f,
|
||||||
|
"active clock skips less than one millisecond");
|
||||||
|
timedUpdate(owner, 436.0f);
|
||||||
|
Expect(ReadFloat(owner, 0xe4) == 436.0f && ReadFloat(owner, 0xec) == 0.0f &&
|
||||||
|
Marshal.ReadInt32(generator, 0x1c) == 0, "active clock skips before resource deadline");
|
||||||
|
timedUpdate(owner, 437.5f);
|
||||||
|
Expect(ReadFloat(owner, 0xe4) == 437.5f && ReadFloat(owner, 0xec) == 0.0f &&
|
||||||
|
Marshal.ReadInt32(generator, 0x1c) == 0 && ReadFloat(providerRow, 8) == 40.0f,
|
||||||
|
"active resource clock skips exact randomized deadline equality");
|
||||||
|
timedUpdate(owner, 500.0f);
|
||||||
|
Expect(ReadFloat(owner, 0xec) == 500.0f && ReadFloat(owner, 0xe8) == 62.5f &&
|
||||||
|
Marshal.ReadInt32(generator, 0x10) == FloatBits(0.5f) &&
|
||||||
|
Marshal.ReadInt32(generator, 0x1c) == 1 &&
|
||||||
|
Marshal.ReadInt32(providerRow, 8) == expectedAmounts[0] &&
|
||||||
|
Marshal.ReadInt32(providerRow, 0x14) == expectedRatios[0],
|
||||||
|
"active clock converts elapsed since last resource cycle to seconds and executes native budget");
|
||||||
|
Expect(Marshal.ReadByte(owner, 0x598) == 1 && ReadFloat(owner, 0x588) == 0.0f,
|
||||||
|
"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" +
|
||||||
|
" equality=437.5/skip now=500 e8=62.5 resourceDt=0.5 amount=0x42206666 ec=500");
|
||||||
|
} finally {
|
||||||
|
Marshal.WriteInt32(period, savedPeriod);
|
||||||
|
Marshal.WriteInt32(randomState, savedRandom);
|
||||||
|
}
|
||||||
|
GC.KeepAlive(timedUpdate);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -356,16 +356,20 @@ visibility transition `0x11920` с исходными AniMesh callbacks. Отд
|
|||||||
смены видимости, поэтому связанное с ним движение не проверено. Хотя у строки
|
смены видимости, поэтому связанное с ним движение не проверено. Хотя у строки
|
||||||
Control выставлен flag `0x102`, слово флагов ANI `+0x14` и производные байты
|
Control выставлен flag `0x102`, слово флагов ANI `+0x14` и производные байты
|
||||||
`+0x111..+0x113` изначально равны нулю. Вызовы AniMesh `0x93c0` и `0xa2a0` обновляют производную геометрию и
|
`+0x111..+0x113` изначально равны нулю. Вызовы AniMesh `0x93c0` и `0xa2a0` обновляют производную геометрию и
|
||||||
видимость, это отдельная непроверенная граница, а не FX callbacks. Активную
|
видимость, это отдельная непроверенная граница, а не FX callbacks. Бюджетная
|
||||||
clock-ветвь не запускали. Источник времени и доставка прослежены статически:
|
часть активного `0xbcf0` исполнена на синтетическом хосте класса `10`:
|
||||||
|
при периоде `500` и нулевом RNG проверены пропуск до deadline и на равенстве
|
||||||
|
`437.5`, затем ресурсный `dt = 0.5` при времени `500`. Движение и очереди
|
||||||
|
исключены условиями вектора; активация `+0xe0` задана явно. Источник времени
|
||||||
|
и доставка прослежены статически:
|
||||||
World3D RVA `0x6bf0` вычисляет часы из `timeGetTime()` с игровым смещением и
|
World3D RVA `0x6bf0` вычисляет часы из `timeGetTime()` с игровым смещением и
|
||||||
поправкой на паузу, затем выполняет первый обход `6/1`, шаг мировой физики и
|
поправкой на паузу, затем выполняет первый обход `6/1`, шаг мировой физики и
|
||||||
второй обход `6/0x1c`. AniMesh RVA `0x1370` передаёт часы Control; общий
|
второй обход `6/0x1c`. AniMesh RVA `0x1370` передаёт часы Control; общий
|
||||||
обработчик Control RVA `0x7830` численно преобразует unsigned DWORD в `f32`
|
обработчик Control RVA `0x7830` численно преобразует unsigned DWORD в `f32`
|
||||||
перед primary slot `+0x58`. Фабрика выбирает RVA `0xcbb0` для класса `9` и
|
перед primary slot `+0x58`. Фабрика выбирает RVA `0xcbb0` для класса `9` и
|
||||||
RVA `0xbcf0` для остальных. Это подтверждает статическую цепочку вызовов, но не
|
RVA `0xbcf0` для остальных. Это подтверждает статическую цепочку вызовов, но не
|
||||||
её исполнение на миссии: активный путь `0xbcf0` и изменение provider-снимков
|
её исполнение на миссии: загрузочная активация, остальные ветви `0xbcf0` и
|
||||||
Rust остаются открытыми. Источник контактного коэффициента прослежен статически,
|
изменение provider-снимков Rust остаются открытыми. Источник контактного коэффициента прослежен статически,
|
||||||
но не исполнен. Подробные векторы и статические границы приведены в
|
но не исполнен. Подробные векторы и статические границы приведены в
|
||||||
[техническом подразделе о provider-состоянии Control](script-vm.md#изменение-provider-состояния-control).
|
[техническом подразделе о provider-состоянии Control](script-vm.md#изменение-provider-состояния-control).
|
||||||
|
|
||||||
|
|||||||
@@ -640,8 +640,9 @@ oracle вызывает оригинальные `FUN_10010ba0` (RVA `0x10ba0`),
|
|||||||
затем `FMUL` с binary32-константой `0.001` (`0x3a83126f`), после чего результат
|
затем `FMUL` с binary32-константой `0.001` (`0x3a83126f`), после чего результат
|
||||||
сохраняется в `f32`. Код вызывает `FUN_10012a40`, затем `FUN_1002d340` и
|
сохраняется в `f32`. Код вызывает `FUN_10012a40`, затем `FUN_1002d340` и
|
||||||
присваивает `+0xec = +0xe4`. В соседней ветви update packet queue обрабатывается
|
присваивает `+0xec = +0xe4`. В соседней ветви update packet queue обрабатывается
|
||||||
через `FUN_10012ce0`. Активную clock-ветвь oracle не запускал. Источник её
|
через `FUN_10012ce0`. Бюджетная часть активной ветви проверена на ограниченном
|
||||||
входного времени и порядок доставки установлены статически, как описано ниже;
|
хосте класса `10`, как описано ниже. Источник входного времени и порядок его
|
||||||
|
доставки до обработчика установлены статически;
|
||||||
Rust продолжает читать начальные provider snapshots и не моделирует эти изменения.
|
Rust продолжает читать начальные provider snapshots и не моделирует эти изменения.
|
||||||
|
|
||||||
#### Бюджет и обновление устройства tag 15
|
#### Бюджет и обновление устройства tag 15
|
||||||
@@ -657,7 +658,7 @@ RVA `0x22ae0`, его rate RVA `0x22b20` и update RVA `0x22bb0`.
|
|||||||
|
|
||||||
Вектор задаёт provider с максимумом `100`, количеством `40` и долей `0.4`,
|
Вектор задаёт provider с максимумом `100`, количеством `40` и долей `0.4`,
|
||||||
интервал `0.5` секунды, коэффициенты tag 15 равные `1` и нулевое поле
|
интервал `0.5` секунды, коэффициенты tag 15 равные `1` и нулевое поле
|
||||||
descriptor `+0x20`. Исходная rate-функция возвращает `-0.2`.
|
descriptor `+0x20`. Исходная rate-функция возвращает `-0.2`.
|
||||||
Генератор с descriptor tag `14` подставлен явно: mock возвращает положительный
|
Генератор с descriptor tag `14` подставлен явно: mock возвращает положительный
|
||||||
вклад `0.1` или `0.5` и записывает аргументы своего update. Функции tag 15,
|
вклад `0.1` или `0.5` и записывает аргументы своего update. Функции tag 15,
|
||||||
provider getter и изменение количества не подменены.
|
provider getter и изменение количества не подменены.
|
||||||
@@ -672,9 +673,39 @@ provider getter и изменение количества не подменен
|
|||||||
Хотя генератор стоит первым в массиве, его update уже видит изменённое
|
Хотя генератор стоит первым в массиве, его update уже видит изменённое
|
||||||
количество: категория tag 15 обслуживается раньше. Проверены также интервал
|
количество: категория tag 15 обслуживается раньше. Проверены также интервал
|
||||||
и коэффициент, переданные mock-генератору. Его собственная генерация, другие
|
и коэффициент, переданные mock-генератору. Его собственная генерация, другие
|
||||||
категории, активный clock-путь и выполнение миссии этим не проверены.
|
категории и выполнение миссии этим не проверены.
|
||||||
Rust пока не переносит этот изменяемый бюджетный цикл.
|
Rust пока не переносит этот изменяемый бюджетный цикл.
|
||||||
|
|
||||||
|
#### Активный ресурсный clock
|
||||||
|
|
||||||
|
Тот же native budget-вектор проходит через `Control.dll` RVA `0xbcf0` при
|
||||||
|
явно заданных классе `10` и признаке активности `+0xe0 = 1`. Хост подставляет
|
||||||
|
два getter-а: наличие игрового хоста и отсутствие объекта движения. Класс `10`
|
||||||
|
пропускает цикл команд и движения; очереди пакетов и detached-объектов пусты.
|
||||||
|
Поле dirty перед первым ресурсным циклом равно нулю, поэтому `0x12a40`
|
||||||
|
возвращается без пересчёта геометрии. Это исполнение бюджетной части активной
|
||||||
|
ветви, а не полная загрузка или физика объекта.
|
||||||
|
|
||||||
|
Период по RVA `0x42480` явно равен `500`, состояние RNG по RVA `0x43228` —
|
||||||
|
нулю. Эти данные принадлежат изолированному процессу oracle и восстанавливаются
|
||||||
|
после вектора. Нулевое состояние остаётся нулевым и даёт deadline
|
||||||
|
`500 - 500 / 8 = 437.5` миллисекунды при исходном `+0xec = 0`.
|
||||||
|
|
||||||
|
| Абсолютное время | Результат |
|
||||||
|
|---|---|
|
||||||
|
| `0.5` | меньше миллисекунды: `+0xe4` ещё равен `0` |
|
||||||
|
| `436` | время принято, ресурсный deadline ещё не пройден |
|
||||||
|
| `437.5` | равенство deadline: ресурсный цикл пропущен |
|
||||||
|
| `500` | `dt = (500 - 0) * 0.001 = 0.5`, бюджет исполнен, `+0xec = 500` |
|
||||||
|
|
||||||
|
Последний межкадровый интервал `+0xe8` равен `62.5`, но ресурсный интервал
|
||||||
|
берётся от отдельного `+0xec`. Исходные `0x2d340` и tag 15 изменяют amount
|
||||||
|
до `0x42206666`, долю до `0x3ecd4fdf`; mock-генератор получает `dt = 0.5`
|
||||||
|
ровно один раз. После вызова dirty равен `1`, а агрегат `+0x588` ещё не
|
||||||
|
пересчитан: бюджетное изменение происходит после предшествующего `0x12a40`.
|
||||||
|
Активация через загрузочные события, ненулевые состояния RNG, принудительный
|
||||||
|
цикл по `+0xd4`, движение и непустые очереди остаются отдельными границами.
|
||||||
|
|
||||||
#### Доставка игровых часов в Control
|
#### Доставка игровых часов в Control
|
||||||
|
|
||||||
Статическая цепочка относится к GOG-модулям `World3D.dll` (SHA-256
|
Статическая цепочка относится к GOG-модулям `World3D.dll` (SHA-256
|
||||||
@@ -720,8 +751,8 @@ vtable RVA `0x3b8cc` направляет этот слот в RVA `0xcbb0`; у
|
|||||||
vtable RVA `0x3d298` направляет его в RVA `0xbcf0`.
|
vtable RVA `0x3d298` направляет его в RVA `0xbcf0`.
|
||||||
|
|
||||||
Эта цепочка доказана инструкциями и таблицами исходных модулей. Она не является
|
Эта цепочка доказана инструкциями и таблицами исходных модулей. Она не является
|
||||||
проверкой исполнения активной ветви `0xbcf0`, её callbacks или изменения
|
проверкой полного исполнения активной ветви `0xbcf0` или изменения
|
||||||
provider-состояния в реальной миссии.
|
provider-состояния в реальной миссии. Ограниченный активный вектор описан выше.
|
||||||
|
|
||||||
#### Родители, стадии и видимость provider-узлов
|
#### Родители, стадии и видимость provider-узлов
|
||||||
|
|
||||||
@@ -773,8 +804,9 @@ NDP, Node38 и объект хоста синтетические; слот life
|
|||||||
`AniMesh.dll!FUN_100093c0` и `FUN_1000a2a0` (пересчёт производной геометрии и
|
`AniMesh.dll!FUN_100093c0` и `FUN_1000a2a0` (пересчёт производной геометрии и
|
||||||
видимости) не покрыты. Эти результаты подтверждают локальные переходы и порядок
|
видимости) не покрыты. Эти результаты подтверждают локальные переходы и порядок
|
||||||
callbacks, но не полный Control update реальной миссии. Источник часов и порядок
|
callbacks, но не полный Control update реальной миссии. Источник часов и порядок
|
||||||
их доставки установлены статически выше; исполнение активной clock-ветви и её
|
их доставки установлены статически выше; бюджетная часть активной clock-ветви
|
||||||
влияние на состояние реальной миссии остаются непроверенными.
|
проверена на синтетическом хосте класса `10`, её исполнение в реальной миссии
|
||||||
|
остаётся непроверенным.
|
||||||
|
|
||||||
### Начальная MSH-часть строки provider
|
### Начальная MSH-часть строки provider
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user