aboutsummaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/appendices/knowledge-boundaries.md44
-rw-r--r--docs/appendices/script-vm.md100
-rw-r--r--docs/tomes/06-behavior.md30
3 files changed, 143 insertions, 31 deletions
diff --git a/docs/appendices/knowledge-boundaries.md b/docs/appendices/knowledge-boundaries.md
index a45d752..8551afa 100644
--- a/docs/appendices/knowledge-boundaries.md
+++ b/docs/appendices/knowledge-boundaries.md
@@ -79,8 +79,7 @@ control word процесса.
`[(500.2857, 751.8217), (728.0, 449.0)]` и записи `[(500, 752, 0),
(728, 449, 1)]`; preview кампании сохраняет package и статус `Unsupported`,
не применяя частичные записи. Это подтверждает загрузчик и узкий Init resolver
-Rust, но не доказывает, что штатный host вызывает `Init` при старте оригинальной
-миссии.
+Rust.
Нативный конструктор показывает предупреждение при координате выше `10000`, но
продолжает работу. Rust моделирует числовую запись, а не это UI-предупреждение;
@@ -92,9 +91,44 @@ Rust, но не доказывает, что штатный host вызываеÑ
Rust исполнителя, не правило оригинального dispatcher-а. Ошибочные ссылки и
типы остаются ошибками в обоих режимах.
-Открыты вызов `Init` при штатном старте Iron3D и проверка другой миссии с
-непустым `Init`. Не восстановлены host-ingress `Init` в игровом цикле и общий
-dispatcher остальных событий.
+Для GOG-сборки также найден статический host call path в `Init`. В
+`iron3d.dll!FUN_1005ff40` (RVA `0x5ff40`) одна ветвь сначала проходит записи
+кланов и вызывает для них `FUN_100392e0`, затем вызывает `FUN_10039440` (RVA
+`0x39440`). Эта функция передаёт AI callback-у пару явных аргументов через
+vtable slot `+0x14`. В `ai.dll!FUN_10001ae0` (RVA `0x1ae0`) аргумент callback-а
+записывается в `AI+0x3fc`; затем объект `AI+0x438` получает вызов Behavior
+vslot `+0x2c`. В проверенной `Behavior.dll` (SHA-256
+`ddfbdbde07832358fe6e35e01c8dba9b58e7313789498b6049ed51d4ed9baf99`)
+`FUN_1001adb0` (RVA `0x1adb0`) выполняет начальную подготовку расписания.
+Метод ищет первый `Init` только при нулевом latch
+`AI+0x8a8`, а при dispatch временно ставит и снимает recursion guard
+`AI+0x78`. В этой ветви поэтому известны callback ingress и порядок
+warm-start → `Init`, но смысл аргумента callback-а здесь не сопоставлен с Rust
+`MissionMode`.
+
+Изолированный x86 oracle вызывает исходные `iron3d.dll!FUN_10039440`,
+`ai.dll!FUN_10001ae0` и script dispatcher на синтетических записях и проверяет
+режим, guard, latch, Handler(19) writes и повторный вызов. Отдельный oracle
+исполняет `Behavior.dll!FUN_1001adb0` с искусственно заданной очередью, сверяя
+срок, шаг LCG и очистку полей. Это подтверждает ABI и локальные переходы исходных
+функций, но не доказывает, что полная штатная загрузка выбранной миссии в
+оригинальном процессе доходит до этой ветви в том же порядке. Метод
+`FUN_10001ae0` оставляет latch `+0x8a8` сброшенным, когда `Init` не найдено;
+если оно найдено при уже занятом guard `+0x78`, dispatch пропускается, но latch
+всё равно устанавливается. Rust пока запускает обработанный поднабор при каждой
+полной загрузке и не моделирует аргумент callback-а `AI+0x3fc`, guard `+0x78` или
+latch `+0x8a8`.
+
+Rust `load_mission` принимает явный `queue_time_ms`, а не читает оригинальное
+время очереди. Новый `Engine` начинает локальный Behavior RNG с seed `1` и
+учитывает шаги генератора этой стартовой последовательности; не восстановлены
+другие потребители исходного генератора, поэтому равенство полной случайной
+последовательности не утверждается. Начальная фаза выполняется после регистрации
+объектов в создаваемом мире Rust;
+это интеграционный выбор runtime, а не подтверждённая эквивалентность всех
+host construction phases. Не проверена другая лицензированная миссия с
+непустым `Init`, не восстановлены игровой tick Behavior и общий 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 d12363a..9bb33db 100644
--- a/docs/appendices/script-vm.md
+++ b/docs/appendices/script-vm.md
@@ -33,12 +33,13 @@ consumer-inferred.
## Безопасная модель исполнения
-Новая VM разделяет immutable package (bytecode, symbols, events, constants),
-per-mission variables/timers/frames, bindings logical-name/ObjectId/clan/
-research key и typed commands к World3D/Behavior/UI/campaign. После varset
-defaults и bindings она dispatches Init/start, на каждом tick обновляет timers,
-ставит готовые events в стабильную очередь и исполняет bounded instruction
-budget. Опасное удаление идёт через World3D queue и общий deferred lifecycle.
+Целевая VM должна разделять immutable package (bytecode, symbols, events,
+constants), per-mission variables/timers/frames, bindings
+logical-name/ObjectId/clan/research key и typed commands к
+World3D/Behavior/UI/campaign. После varset defaults и bindings она будет
+запускать Init/start, обновлять timers на каждом tick, ставить готовые events
+в стабильную очередь и исполнять bounded instruction budget. Опасное удаление
+должно идти через World3D queue и общий deferred lifecycle.
До восстановления opcode table package mode читает header/strings/symbols/
event offsets/raw bytecode losslessly. Статический анализ уже выделил отдельный
@@ -267,11 +268,14 @@ scheduler path внутри запущенной игры.
На границе mission runtime выбранный TMA clan `first_resource`
материализуется как отдельный `MissionScriptBundle`: loader нормализует
-`<base>.scr`, декодирует его тем же bounded reader-ом и публикует immutable
-package вместе с clan provenance. Для первого события `Init` runtime исполняет
-только подтверждённый фрагмент dispatcher-а с `Handler(19)`; остальные события
-пока остаются только прочитанными данными. Ошибка чтения по-прежнему сохраняет
-transactional rollback mission loader-а.
+`<base>.scr`, декодирует его тем же bounded reader-ом и сохраняет clan
+provenance. Полная загрузка получает от вызывающей стороны значение
+32-битного времени очереди, затем для каждого bundle вычисляет начальное
+состояние Behavior и пробует выполнить только восстановленный поднабор первого
+`Init` с `Handler(19)`. Остальные события пока остаются прочитанными данными.
+Статический просмотр не запускает warm-start Behavior, но сохраняет результат
+поддержанных `Handler(19)` writes и создаёт ячейки varset; при неподдержанной
+инструкции оставляет default-значения и статус `Unsupported`.
TMA properties остаются four raw `u32` words плюс имя, пока consumer/schema не
задаст тип (integer/float bits/ObjectId/enum/fixed-point/index). В том числе
@@ -359,34 +363,78 @@ nearest-even `FISTP qword`, младший DWORD, преобразование D
Это правила ограниченного Rust-исполнителя, а не обнаруженное поведение
оригинального dispatcher-а.
-Для проверки использованы два x86 helper-а на синтетической памяти. Первый
-вызывает оригинальные `iron3d.dll` entry points и проверяет преобразование
-TMA-координат в аргументы `CreateSuperAI`. Второй загружает оригинальную
-`ai.dll`, вызывает `FUN_10011f40` и `FUN_10011f10`, а записи читает штатным
-getter-ом `FUN_10013570` (RVA `0x13570`). В нём проверены оба набора AutoDemo,
-первая дублирующаяся запись `Init`, имя `Init\\0suffix\\0`, отсутствие записи
-при пропущенном `Init` и связка `FUN_10005c70` → Handler(19): для входных DWORD
-`(0xffffffff, 10001)` поля стали `(2^32, 10001.0)`, а varset получил
-`(0, 10001, 2)`. Наборы AutoDemo дали `(500, 752, 0)` и `(728, 449, 1)`.
-Эти синтетические вызовы подтверждают изолированные участки ABI, но не доказывают,
-кто в Iron3D запускает `Init` при полном старте игровой миссии.
-
-Обе проверки можно повторить из корня репозитория в Windows PowerShell. Первая
-вызывает оригинальный caller `iron3d.dll`; вторая — original dispatcher, setter
-и getter `ai.dll`:
+### Первый вызов Behavior и сценарного `Init`
+
+В той же GOG-сборке `Behavior.dll` (SHA-256
+`ddfbdbde07832358fe6e35e01c8dba9b58e7313789498b6049ed51d4ed9baf99`) функция
+`FUN_1001adb0` (RVA `0x1adb0`) обновляет один Behavior distributor. Она получает
+текущее время через `GetQueue` и queue vslot `+0x38`. Если distributor `+0x10`
+равен нулю, функция записывает туда текущее время очереди, считает начальный
+deadline `+0x14` по формуле `время + 3*64 + jitter`, где `jitter` лежит в
+`0..63` и получен одним шагом глобального MSVCRT LCG. В этой ветви она также
+обнуляет пять слов `+0x30..+0x40`. При повторном вызове до deadline ненулевой
+`+0x10` оставляет deadline прежним, не расходует новое случайное число и не
+очищает эти слова. Поведение после истечения deadline этим опытом не проверено.
+Если само время очереди равно нулю, `+0x10` остаётся нулевым, и повторный вызов
+снова проходит ветвь начальной подготовки.
+
+Путь host callback в проверенной паре DLL теперь статически известен. В
+`iron3d.dll` функция `FUN_1005ff40` (RVA `0x5ff40`) сначала вызывает
+`FUN_100392e0` для TMA clan records, а затем для записей вызывает
+`FUN_10039440` (RVA `0x39440`). Последняя передаёт AI callback-у аргументы через
+vtable slot `+0x14`. Метод `ai.dll!FUN_10001ae0` (RVA `0x1ae0`) сохраняет это
+слово аргумента в AI `+0x3fc`, сначала обновляет Behavior через объект `AI+0x438`
+и его vslot `+0x2c`, а затем, если latch `AI+0x8a8` ещё сброшен, находит первый
+`Init`. При отсутствии события latch остаётся нулевым. Если событие найдено,
+метод запускает dispatcher только при нулевом recursion guard `AI+0x78`,
+временно ставит guard и снимает его после dispatch; затем ставит latch, даже
+если занятый guard пропустил dispatch. Это определяет одну штатную
+callback-точку входа и порядок внутри неё, но не раскрывает всех смыслов
+входного слова аргумента и не подменяет динамическую трассу полного запуска игры.
+
+`fparkan-runtime::load_mission` получает значение времени очереди явным
+аргументом `queue_time_ms`; Rust не читает оригинальную очередь. Для полной
+загрузки Rust после регистрации объектов запускает последовательность
+подготовки Behavior → ограниченный `Init` по каждому bundle. Путь использует
+локальный RNG engine, начиная с состояния `1`; его шаги LCG общие между
+bundle, но остальные потребители исходного глобального RNG не моделируются.
+В Rust также нет значения аргумента в `AI+0x3fc`, recursion guard `AI+0x78`
+или одноразового latch `AI+0x8a8`. Поэтому это связанный начальный поднабор, а
+не полная активация SuperAI и не обновление Behavior на игровых тиках. При
+ошибке ограниченного `Init` Rust отклоняет полную загрузку и сохраняет прежний
+мир и состояние генератора; такая политика отката принадлежит Rust loader-у.
+
+Для проверки использованы три x86 helper-а на синтетической памяти. Caller
+oracle вызывает оригинальные `iron3d.dll` entry points и проверяет преобразование
+TMA-координат в аргументы `CreateSuperAI`. AI oracle проходит оригинальный
+`iron3d.dll!FUN_10039440` → `ai.dll!FUN_10001ae0` → реальный `Init` dispatcher;
+также проверяет оба AutoDemo-набора, повторный/missing/busy callback и связку
+`FUN_10005c70` → Handler(19). Для входных DWORD `(0xffffffff, 10001)` поля стали
+`(2^32, 10001.0)`, а varset получил `(0, 10001, 2)`. Behavior oracle вызывает
+`FUN_1001adb0` с искусственно заданной очередью и проверяет шаг генератора,
+срок, очистку и повторный вызов. Эти проверки исполняют оригинальные DLL-функции на заданных
+структурах, но не являются трассой штатного старта или последующего игрового
+цикла.
+
+Проверки можно повторить из корня репозитория в Windows PowerShell:
```powershell
$env:FPARKAN_ORIGINAL_IRON3D_DLL = 'C:\GOG Games\Parkan - Iron Strategy\iron3d.dll'
$env:FPARKAN_ORIGINAL_AI_DLL = 'C:\GOG Games\Parkan - Iron Strategy\ai.dll'
+$env:FPARKAN_ORIGINAL_BEHAVIOR_DLL = 'C:\GOG Games\Parkan - Iron Strategy\Behavior.dll'
$env:FPARKAN_ORIGINAL_DEFAULT_SCR = 'C:\GOG Games\Parkan - Iron Strategy\MISSIONS\SCRIPTS\default.scr'
New-Item -ItemType Directory -Force -Path target\handler19-native-oracles | Out-Null
& "$env:WINDIR\Microsoft.NET\Framework\v4.0.30319\csc.exe" /nologo /platform:x86 /out:target\handler19-native-oracles\AnchorOracle.exe crates\fparkan-runtime\tests\handler19_anchor_native_oracle.cs
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
& "$env:WINDIR\Microsoft.NET\Framework\v4.0.30319\csc.exe" /nologo /platform:x86 /out:target\handler19-native-oracles\InitOracle.exe crates\fparkan-runtime\tests\handler19_init_native_oracle.cs
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+& "$env:WINDIR\Microsoft.NET\Framework\v4.0.30319\csc.exe" /nologo /platform:x86 /out:target\handler19-native-oracles\BehaviorWarmStartOracle.exe crates\fparkan-runtime\tests\behavior_warm_start_native_oracle.cs
+if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
& .\target\handler19-native-oracles\AnchorOracle.exe
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
& .\target\handler19-native-oracles\InitOracle.exe
+if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+& .\target\handler19-native-oracles\BehaviorWarmStartOracle.exe
exit $LASTEXITCODE
```
diff --git a/docs/tomes/06-behavior.md b/docs/tomes/06-behavior.md
index f5c7a7b..62c7b56 100644
--- a/docs/tomes/06-behavior.md
+++ b/docs/tomes/06-behavior.md
@@ -177,6 +177,36 @@ Rust исполняет только подтверждённую форму эÑ
событие считается исполненным без записей. Остальные события пока не образуют
исполняемую VM.
+### Первый запуск сценария
+
+До регулярной работы Behavior настраивает расписание своего первого шага. Он
+берёт текущее 32-битное время игровой очереди и назначает проверку через три
+интервала по 64 мс, добавляя небольшую случайную задержку от 0 до 63 мс. Эта
+задержка нужна каждому экземпляру, но случайные числа берутся из общего
+генератора, поэтому порядок запуска кланов влияет на их значения. При первой
+подготовке controller также очищает несколько внутренних слов. В проверенном
+повторном вызове до назначенного срока ненулевое сохранённое время оставило
+срок прежним, не потребовало нового случайного числа и не очистило эти слова;
+поведение после срока пока неизвестно.
+
+В оригинале вызов идёт через обратный вызов движка: Iron3D сначала создаёт
+SuperAI для записей кланов, затем обновляет Behavior и ищет `Init` для
+соответствующего AI. Если `Init` не найдено, одноразовая метка остаётся
+выключенной. Если оно найдено, диспетчер запускается только при свободной защите
+от повторного входа; при занятой защите вызов пропускается, но одноразовая метка
+всё равно устанавливается. В полной загрузке Rust вызывающая сторона передаёт
+значение времени очереди; после регистрации объектов загрузчик последовательно
+подготавливает каждый сценарный пакет и выполняет только подтверждённые действия
+`Handler(19)` из первого `Init`. Статический просмотр не запускает Behavior, но
+может сохранить подтверждённые записи `Init` в сценарном varset.
+
+Это полезное начало сценария, но ещё не целая игровая жизнь AI. Текущий runtime
+не воспроизводит аргумент обратного вызова в `AI+0x3fc`, защиту от повторного
+входа `AI+0x78`, одноразовую метку `AI+0x8a8` или обновления Behavior на игровых
+тиках. Он также не подменяет очередь оригинала: вызывающая сторона сама
+передаёт время, а локальный генератор Rust пока учитывает только случайные
+числа этого восстановленного шага.
+
Первый часто встречающийся table entry с side effect — `Handler(2)` (176
records в corpus). Он берёт active instruction, разрешает семь slots через
varset, приводит три значения к float по observed kinds `5`/`3`, затем