From 374db10daf5db88a52596edebc6cf5f2d560065f Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Tue, 20 Oct 2026 09:06:17 +0400 Subject: fix(runtime): model distributor share normalization --- docs/appendices/knowledge-boundaries.md | 18 +++++++----- docs/appendices/script-vm.md | 50 +++++++++++++++++++++++++-------- docs/tomes/06-behavior.md | 30 +++++++++++++------- 3 files changed, 69 insertions(+), 29 deletions(-) (limited to 'docs') diff --git a/docs/appendices/knowledge-boundaries.md b/docs/appendices/knowledge-boundaries.md index b10a002..873497c 100644 --- a/docs/appendices/knowledge-boundaries.md +++ b/docs/appendices/knowledge-boundaries.md @@ -157,13 +157,17 @@ Behavior (`Behavior.dll`, RVA `0x3600`) вызывает `World3D!stdGetGameMode подтверждает только состав списков, не точную эквивалентность всей загрузки или регулярного шага Behavior. -Нативный `Behavior.dll!Add` (RVA `0x1aac0`) создаёт для каждого участника запись -размером `0x1c` с начальной долей `1 / count`, а `normalize` (RVA `0x1ac90`) -перенормирует прежние доли после добавления. Rust пока хранит только членство: -поля записи, доли и их изменение ещё не исполняются. Не восстановлено и -соединение с реальными интерфейсами ArealMap, необходимыми для регулярного -вызова `FUN_10019e80`; остальной Behavior tick flow и общий dispatcher событий -также остаются открытыми. +Нативный `Behavior.dll!Add` (RVA `0x1aac0`) и `normalize` (RVA `0x1ac90`) +проверены прямыми x86-вызовами на синтетических записях при x87 control word +`0x007f`, измеренном также у игры на входах обоих методов. Rust повторяет +начальные доли и нормализацию отдельными операциями `f32`, а повторную запись +сопоставляет по `ObjectHandle`, меняя только долю существующего участника. +Пять DWORD после доли хранятся сырыми; ненулевое первое слово закрепляет вес, +смысл четырёх следующих не установлен. Лицензированный AutoDemo-тест проверяет +список TMA-объектов и получившиеся начальные доли по кланам, но это не сравнение +живых весов миссии с полями исходных runtime-записей. Не восстановлено соединение +с реальными интерфейсами ArealMap, нужными `FUN_10019e80`; полный Behavior tick +flow и общий dispatcher событий также остаются открытыми. Для `Handler(30)` отдельно подтверждены обратный вызов `iron3d.dll` RVA `0x611d0` (SHA-256 `d1dc4ea8535e2069b0a05c9e8be2724dd438ba44cca7a83007d8b2e50f9e35fa`), diff --git a/docs/appendices/script-vm.md b/docs/appendices/script-vm.md index 4020ebb..c15b208 100644 --- a/docs/appendices/script-vm.md +++ b/docs/appendices/script-vm.md @@ -441,15 +441,36 @@ selector `1` на интерфейсе получателя становится `0x3f2aaaab`). Эти векторы проверяют арифметику исходной функции, но не распределение реальной карты. -Текущие типы Rust не дают честно наполнить этот список: мир хранит -идентификаторы и преобразования объектов, но не нужные tick ресурсные поля; -`MissionObjectDraft.properties` сохраняет сырые слова без связи с QI -`0x16`/`0x204` или свойством `0x02000100`. Методы добавления и перенастройки -строк `FUN_1001a9a0` и `FUN_1001aac0` доступны через entries vtable -`0x10059794` по смещениям `+0x0c` и `+0x10`; кто именно вызывает эти методы и -какие значения передаёт, пока не установлено. TMA-anchors и сырые preview -drafts не используются вместо неизвестной регистрации. `step_headless` по-прежнему -шагает только `World`. +Полная загрузка Rust теперь собирает этот список из runtime `ObjectHandle` и +сохраняет веса вместе с пятью исходными DWORD хвоста каждой записи. Членство +строится только для полного режима; preview остаётся статическим. Это пока не +даёт ресурсных полей и интерфейсов ArealMap для `FUN_10019e80`, а +`step_headless` по-прежнему шагает только `World`. + +### Добавление и нормализация долей + +В этой же GOG `Behavior.dll` (SHA-256 +`ddfbdbde07832358fe6e35e01c8dba9b58e7313789498b6049ed51d4ed9baf99`) +`FUN_1001aac0` (RVA `0x1aac0`) добавляет запись размером `0x1c`. Её слово по +`+0x04` — доля `f32`, а слова `+0x08..+0x18` копируются как сырой хвост. +Новая запись начинается с доли `1 / count` и нулевого хвоста. При повторном +ключе запись не вставляется снова: меняется только её доля, остальные пять +DWORD сохраняются. После каждого вызова выполняется `FUN_1001ac90` (RVA +`0x1ac90`). Она считает долю целевого ключа и записей с ненулевым первым словом +хвоста фиксированной. Если сумма остальных долей положительна, остаток от +единицы распределяется пропорционально; при нулевой или отрицательной сумме он +делится поровну между изменяемыми записями. Проверенный ветвящийся путь также +направляет unordered-сумму в пропорциональную ветвь, но стандартный набор этих +тестов содержит конечные значения. + +Инструкции Add и normalize выполнялись при x87 control word `0x007f`, то есть +с 24-битной точностью мантиссы; такой же CW измерен у оригинальной игры на +входах обоих методов. Поэтому Rust повторяет их в порядке списка отдельными +операциями `f32`, без раннего перехода в `f64`. Native x86 oracle проверяет +три последовательных Add, повторный ключ с sentinel-хвостом, фиксированную +запись, нулевую сумму и пограничный вектор округления. В последнем доли имеют +биты `[0x3f000000, 0x3f000000, 0x33000000]`. Эти сценарии сверяют локальную +арифметику на стандартных конечных данных, а не весь игровый запуск. Для проверки использованы три x86 helper-а на синтетической памяти. Caller oracle вызывает оригинальные `iron3d.dll` entry points и проверяет преобразование @@ -485,7 +506,8 @@ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } exit $LASTEXITCODE ``` -Отдельный oracle distributor tick компилируется и запускается так: +Native oracle Add, normalize и distributor tick компилируется и запускается +так: ```powershell $env:FPARKAN_ORIGINAL_BEHAVIOR_DLL = 'C:\GOG Games\Parkan - Iron Strategy\Behavior.dll' @@ -498,8 +520,12 @@ exit $LASTEXITCODE Oracle проверяет SHA-256 оригинального `Behavior.dll`: `ddfbdbde07832358fe6e35e01c8dba9b58e7313789498b6049ed51d4ed9baf99`. Harness -вызывает функцию по RVA `0x19e80` при единственной подмене импортного -слота `GetSystemArealMap`; машинный код tick не патчится. Проверяются нулевой +явно устанавливает x87 CW `0x007f` для вызовов Add (`0x1aac0`) и normalize +(`0x1ac90`), проверяет CW до и после каждого вызова и восстанавливает прежнее +значение перед tick suite. Для этих двух методов подменяется только bounded +вставка синтетического vector; их машинный код не патчится. Для tick helper +подменяет только IAT-слот `GetSystemArealMap` и вызывает RVA `0x19e80`. +Проверяются нулевой шаг, обе ветви селекторов скорости, пропуск состояния `0xfffe`, missing lookup, динамическое повторное чтение `0x1002`, ограниченный запас руды, разные веса, достаточное и недостаточное значение мощности. Это синтетические ArealMap diff --git a/docs/tomes/06-behavior.md b/docs/tomes/06-behavior.md index b30bc7d..c00705b 100644 --- a/docs/tomes/06-behavior.md +++ b/docs/tomes/06-behavior.md @@ -207,7 +207,9 @@ SuperAI для записей кланов, затем обновляет Behavi исполнен на исходной DLL с искусственными объектами. Rust пока не вызывает регулярный шаг: игровой мир ещё не предоставляет нужные ресурсные интерфейсы. При полной загрузке Rust уже восстанавливает членство объектов в клановых -списках Behavior, но сами записи распределителя и их доли пока не моделирует. +списках Behavior и задаёт их начальные доли. Остальные слова записи хранятся +сырыми: семантика только первого из пяти слов хвоста установлена для +нормализации. Runtime также не подменяет очередь оригинала: вызывающая сторона сама передаёт время, а локальный генератор Rust пока учитывает только случайные числа восстановленного стартового шага. @@ -241,10 +243,17 @@ Rust-тест повторно читает лицензированную ми TMA-записями и проверяет реальные `ObjectHandle`, но помечен `#[ignore]`, чтобы не требовать оригинальных ресурсов от остальных разработчиков. -Это пока только перечень участников. Оригинальный `Behavior.dll!Add` создаёт для -каждого участника запись размером `0x1c` с долей `1 / count`, после чего -нормирует прежние доли. Rust ещё не хранит эти поля и не исполняет их регулярный -обмен ресурсами. Поля не выводятся из TMA-соседнего слова или `OwnerId`. +Список распределителя хранит не только участников, но и долю каждого. При +добавлении двух объектов оба получают по половине общего веса. Если добавить +третий, движок оставляет долю новой записи около одной трети, а оставшийся вес +делит между прежними изменяемыми записями. Помеченная запись сохраняет свой вес, +как закреплённая часть общего набора. Повторное добавление того же объекта не +создаёт вторую строку и оставляет остальные слова записи нетронутыми. + +Rust хранит этот небольшой начальный список с долями. Он не смешивает selector +из TMA, владельца мирового объекта и внутренний ключ Behavior: связь в Rust +опирается на уже созданный `ObjectHandle`. Регулярный обмен ресурсами пока +остаётся отдельным этапом, потому что для него нужны интерфейсы игровых объектов. ### Регулярная передача ресурсов @@ -263,11 +272,12 @@ Resource Distributor работает со списком зарегистрир исходной `Behavior.dll`, но объекты и ответы интерфейсов заданы тестом; это ещё не воспроизведение реальной миссии. -Членство списка связано с реальным путём регистрации, но выполнение этой -миссии не сравнивалось с тем, как исходная DLL заполняет поля записи `0x1c` и -нормирует доли. Rust пока не хранит эти поля, а игровому миру ещё не хватает -ресурсных интерфейсов ArealMap для регулярного шага. Открытые вопросы и границы -проверки приведены в [границах знания](../appendices/knowledge-boundaries.md). +Для правила долей есть отдельная проверка исходной `Behavior.dll` на синтетических +записях при игровом x87 control word `0x007f`: проверены добавление, повторный +ключ, закреплённая запись, нулевая сумма и округление. Это позволяет Rust +сохранять начальный список, но пока не подтверждает веса реальных объектов в +живой миссии. Для регулярного шага по-прежнему не хватает интерфейсов ArealMap. +Остальные ограничения собраны в [границах знания](../appendices/knowledge-boundaries.md). Первый часто встречающийся table entry с side effect — `Handler(2)` (176 records в corpus). Он берёт active instruction, разрешает семь slots через -- cgit v1.2.3