aboutsummaryrefslogtreecommitdiff
path: root/docs/tomes/06-behavior.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/tomes/06-behavior.md')
-rw-r--r--docs/tomes/06-behavior.md160
1 files changed, 137 insertions, 23 deletions
diff --git a/docs/tomes/06-behavior.md b/docs/tomes/06-behavior.md
index aa8fd96..3a4e51c 100644
--- a/docs/tomes/06-behavior.md
+++ b/docs/tomes/06-behavior.md
@@ -169,8 +169,10 @@ handler лишь инициирует execution context (`+0x50 = 1`); его ga
показывает предупреждение для чисел выше `10000`, но не отменяет создание AI.
Rust исполняет подтверждённый поднабор первого `Init`: действие `Handler(19)`
-и числовые literal assignments, описанные ниже. Поддержанные записи применяются
-к состоянию лишь после успешного прохода всего первого `Init`.
+и формулы из одного атома либо двух атомов с бинарным `+` или `-`. Здесь атом —
+число или имя переменной. Поддержанные шаги сразу обновляют временную копию
+varset, поэтому следующая формула читает уже записанные значения. К состоянию
+клана записи попадают лишь после успешного прохода всего первого `Init`.
Если встретилась неподдержанная форма, preview сохраняет пакет, сообщает
`Unsupported` и оставляет значения по умолчанию; при полной загрузке та же форма
становится ошибкой. Это политика Rust для ограниченного исполнителя, а не правило
@@ -186,15 +188,54 @@ Rust исполняет подтверждённый поднабор перво
нули в `dT8` и `dT9`.
Клан 1 той же миссии использует первые девять строк `scream.fml`: восемь
проверенных значений попадают в `DWORD` с усечением дробной части, а `0.8`
-сохраняется как число `float`. Пока Rust вычисляет только числовой литерал,
-начинающийся с цифры; операторы, ссылки на переменные и другие формы формул
-остаются явными ошибками. В тесте полной загрузки миссия `Mission.01`
-проходит этот `Init`; `Single.01` останавливается на следующем неподдержанном
-`Handler(43)` в clan 1, записи 10. Никакое значение не подставляется вместо
-этого действия.
+сохраняется как число `float`. Например, если `source` равен `10`, выражение
+`source + 2.0` записывает `12`; следующее `target - source` читает новое значение
+`target` и получает `2`. Каждая операция работает с `f32`: значение ячейки
+`DWORD` сначала читается как беззнаковое число и переводится в `f32`, а результат
+сохраняет тип целевой ячейки. Поддерживаются только один или два атома и один
+бинарный `+` либо `-`; произвольная грамматика формул и остальная VM не
+восстановлены. В отдельной точечной проверке вычисления и записи оригинальной
+DLL и Rust совпали для 13 выбранных формул. Эта сверка проверяет только
+эти значения и не запускает исходную миссию. Тест полной загрузки подтверждает,
+что `Mission.01` проходит этот `Init`; у `Single.01` следующий неподдержанный
+`Handler(43)` встречается в clan 1, записи 10. Никакое значение не подставляется
+вместо этого действия.
### Первый запуск сценария
+Перед настройкой первого шага каждый AI получает начальный массив DWORD. В
+`iron3d.dll` SHA-256
+`d1dc4ea8535e2069b0a05c9e8be2724dd438ba44cca7a83007d8b2e50f9e35fa`
+`FUN_100392e0` (RVA `0x392e0`) повторяет вызов слота AI `+0x38` столько раз,
+сколько задаёт `second_resource.tag` этой строки TMA. Этот слот ведёт в
+`ai.dll` SHA-256
+`c19cd6cd31cd2896f8c4ba0f0e700dd3408aec8182daaf4863d98a7d233a2abc`:
+метод RVA `0x2010` передаёт коллекцию при `AI+0x83c` в `FUN_10002980`
+(RVA `0x2980`), а она добавляет один DWORD через `FUN_100048f0` (RVA
+`0x48f0`). Каждый вызов копирует `0xffffffff`; входное слово не меняется.
+Поэтому тег `3` даёт `[0xffffffff, 0xffffffff, 0xffffffff]`. Это число
+начальных ячеек, а не назначение трёх ID.
+
+Изолированная x86-проверка вызовом оригинальной `ai.dll` подтвердила пары
+«число слотов/ёмкость» `0/0`, `1/17`, `17/17` и `18/34`. Все добавленные слова
+равны `0xffffffff`, а входное значение, контрольные поля, память вне заголовка
+массива и указатель стека сохраняются. Это подтверждает добавление начального
+слова, но не работу последующих обработчиков.
+
+Rust хранит такой снимок в отдельном `MissionAiStartupState` для каждого TMA-клана,
+а не в пакете сценария. При полной загрузке (`Full`) создаётся запись для каждого
+клана, в том числе если у него нет пути сценария или пути таблицы исследований. Счётчик
+берётся из сырых битов `second_resource.tag` как `u32` у обеих форм записи —
+`Standard` и `Spatial`; нулевой счётчик даёт существующее состояние с пустым
+списком. Статический режим предпросмотра не создаёт состояний AI.
+
+Загрузка отклоняет суммарный объём свыше 1 000 000 DWORD и резервирует память
+проверяемой операцией; это защита Rust от чрезмерного выделения памяти, а не
+ограничение оригинальной игры. Rust сохраняет начальные слова, но не
+восстанавливает работу последующих обработчиков и не назначает этим словам ID
+объектов. Этот массив также не является списком
+`BehaviorDistributorState.registered_objects`.
+
До регулярной работы Behavior настраивает расписание своего первого шага. Он
берёт текущее 32-битное время игровой очереди и назначает проверку через три
интервала по 64 мс, добавляя небольшую случайную задержку от 0 до 63 мс. Эта
@@ -213,7 +254,7 @@ SuperAI для записей кланов, затем обновляет Behavi
всё равно устанавливается. В полной загрузке Rust вызывающая сторона передаёт
значение времени очереди; после регистрации объектов загрузчик последовательно
подготавливает каждый сценарный пакет и выполняет восстановленные действия
-`Handler(19)` и числовые literal assignments из первого `Init`. Статический
+`Handler(19)` и поддержанные формульные присваивания из первого `Init`. Статический
просмотр не запускает Behavior, но может сохранить эти записи `Init` в сценарном
varset.
@@ -260,6 +301,25 @@ Rust-тест повторно читает лицензированную миÑ
TMA-записями и проверяет реальные `ObjectHandle`, но помечен `#[ignore]`, чтобы
не требовать оригинальных ресурсов от остальных разработчиков.
+После подготовки Control-таблицы можно разобрать и её начальную группировку для
+Behavior. Важны текущая метка записи и её порядковый номер в общей таблице:
+метка `1` начинает пустую группу, метка `2` становится кандидатом последней
+группы, а метка `30` записывает строку в отдельный список той же группы.
+Кандидат, встретившийся до первого заголовка, остаётся в списке без группы;
+ранняя метка `30` игнорируется. Например,
+для текущих меток `[2, 30, 1, 30, 2]` получаются незагруппированный кандидат
+`[0]` и группа с заголовком `2`, кандидатом `[4]` и строкой с меткой `30` под
+порядковым номером `[3]`. Все номера начинаются с нуля и отсчитываются по общей
+Control-таблице, а не по ключам устройств или записям CTLD.
+
+Полная загрузка Rust сохраняет это начальное состояние рядом с подготовленными
+Control-таблицами по исходному TMA ID и `ObjectHandle`. Если таблица недоступна,
+проекция сохраняет причину ошибки; доступная пустая таблица означает пустую
+группировку. При предварительном просмотре список группировок не создаётся. Здесь
+разбирается только подготовленный порядок текущих Control-дескрипторов. Проекция
+не устанавливает, когда список присоединяется к живому Behavior, как он затем
+обновляется и как работает полный игровой цикл.
+
Список распределителя хранит не только участников, но и долю каждого. При
добавлении двух объектов оба получают по половине общего веса. Если добавить
третий, движок оставляет долю новой записи около одной трети, а оставшийся вес
@@ -527,8 +587,14 @@ let cost = calc_full_research_cost(&candidate, &table)?;
штатный Handler(43) и запуск миссии здесь не проверялись.
Владелец начальной записи исследования — индекс текущего прохода по кланам.
-Движок сохраняет его в `descriptor[0]`, а затем переносит в контекст, чтобы
-связать запрос с AI-данными того же клана.
+В GOG-сборке `iron3d.dll!FUN_1005ff40` (RVA `0x5ff40`) проходит записи кланов
+и вызывает для каждой `FUN_100392e0` (RVA `0x392e0`), которая создаёт AI;
+`ai.dll!CreateSuperAI` (RVA `0xf710`) выделяет объект и вызывает конструктор
+по RVA `0x1000`, не проверяя наличие сценарного пути. Для исследовательского
+пути нужны отдельный флаг включения и непустой `second_resource.path`; наличие
+сценарного пакета условием не является. Индекс клана сохраняется в
+`descriptor[0]` и переносится в контекст, чтобы связать запрос с AI-данными
+того же клана.
Это значение отдельно от аргумента 1 `AddNewObjectToGame`, берущегося из
`GetNetPlayerNum`, и от TMA-тега `second_resource`, задающего число вызовов
@@ -538,12 +604,14 @@ Handler(43) и штатный запуск миссии остаются непÑ
Полный Rust-загрузчик принимает явный `MissionStartupInput`: время очереди
и отдельный `research_creation_gate`. Режим миссии и имя файла этот
-флаг не заменяют. При включённом флаге таблица загружается только для клана,
-чей сценарный пакет уже прочитан и успешно разобран; путь берётся из второго
-ресурса этого клана как C-строка. Пустая C-строка пропускается, а не-ASCII
-байты дают явную ошибку. Клан индексируется по порядку в TMA, тег ресурса на
-владение не влияет. Если несколько кланов указывают на один файл, каждый
-получает отдельно декодированную таблицу с собственными строками TRF0.
+флаг не заменяют. При включённом флаге загрузчик использует уже имеющееся
+`MissionAiStartupState` каждого TMA-клана, даже если у него нет сценарного
+пакета; путь берётся из второго ресурса как C-строка до первого NUL. Пустой
+путь и выключенный флаг пропускают чтение, а не-ASCII байты дают явную ошибку.
+Предпросмотр тоже не читает TRF. Клан индексируется по порядку в TMA, а тег
+`second_resource` задаёт число слотов AI, не владельца. Если несколько кланов
+указывают на один файл, каждый получает отдельно декодированную таблицу со
+своими строками TRF0.
```rust
MissionStartupInput {
@@ -552,9 +620,10 @@ MissionStartupInput {
}
```
-Это сохраняет исходные таблицы и их связь с владельцами для следующих этапов.
-Оно ещё не создаёт полный исследовательский контекст оригинала и не запускает
-`Handler(43)`; эта обработка пока остаётся явно неподдержанной.
+Это сохраняет декодированные таблицы и связь с владельцами для следующих
+этапов. Оно ещё не создаёт полный исследовательский контекст оригинала, не
+строит AI-каталог и не запускает `Handler(43)`; эти части пока не
+восстановлены.
Отдельная frozen-матрица непосредственно вызвала chooser GOG `ai.dll` SHA-256
`c19cd6cd31cd2896f8c4ba0f0e700dd3408aec8182daaf4863d98a7d233a2abc`: функция
@@ -1070,8 +1139,31 @@ NDP, а `FUN_1000a100` (RVA `0xa100`) добавляет их в общую та
последовательность provider-строк: её длина берётся из NDP, она не дополняется
до числа узлов MSH и не участвует во второй перенумерации ключа.
-Так этот селектор включён в цепочку добычи руды оригинала. В той же сборке
-`Behavior.dll` (SHA-256
+Индексный selector `0x400` выдаёт значение выбранной строки provider как есть.
+Сначала общий индекс устройств выбирает дескриптор, затем его ключ выбирает
+строку NDP; это разные номера. Результатом становится текущее type-1 значение
+этой строки, а не запас `CurrentOre`, предел или значение после умножения на
+коэффициент и `M`. Например, provider-строка с `0.625` возвращает `0.625`, а
+записанный в ней ноль тоже остаётся успешным результатом. Rust использует уже
+подготовленную таблицу и сообщает отдельную ошибку, если provider-таблица или
+строка по ключу недоступна. Этот запрос читает снимок состояния и сам не меняет
+таблицу и не запускает её обновление.
+
+У правила есть два разных подтверждения. Разбор AutoDemo нашёл восемь таблиц
+Control и 100 устройств; все 100 значений по ключам устройств в этом корпусе
+равны `1.0`, поэтому Rust-проверка подтверждает реальные ключи и доступность
+строк, но сама по себе не различает выбор по ключу и по номеру устройства.
+Отдельная x86-проверка исходного `Control.dll` включала шесть вызовов на
+синтетической таблице, где значения строк были заданы вручную и различались.
+Например, при номере устройства `2`, ключе `1` и нулевом слове
+`device+0x50` оригинал вернул `0.625` из выбранной строки. Точные биты ответов
+приведены в [техническом разборе](../appendices/script-vm.md#индексный-control-getter-сырой-селектор-0x400).
+Этот опыт не создавал оригинальные factory-объекты и не исполнял полную миссию:
+полный интерфейс IID `0x204`, интеграция Behavior и игровое поведение остаются
+непроверенными.
+
+В цепочке добычи руды оригинал использует описанный выше selector `0x300`.
+В той же сборке `Behavior.dll` (SHA-256
`ddfbdbde07832358fe6e35e01c8dba9b58e7313789498b6049ed51d4ed9baf99`)
`FUN_100186b0` (RVA `0x186b0`) перечисляет устройства типа `0x1a`, а
`FUN_100198e0` (RVA `0x198e0`) суммирует успешные запросы IID `0x204` с selector
@@ -1483,7 +1575,7 @@ offsets 119–121. В `fr_l_gener.ctl` найдено 35 строк type 4, вк
`f_gener_ball` с offset 85.
Control.dll `+0x8B10` передаёт указатель текущей записи диспетчеру `+0x2D4B0`.
-Для tags `2`, `3`, `4`, `5`, `8`, `10`, `21` и `27` подтверждён общий читатель
+Для tags `2`, `3`, `4`, `5`, `8`, `10`, `17`, `21` и `27` подтверждён общий читатель
`+0x21D50`: таблица виртуальных методов tag `2` (`+0x3CBD8`) вызывает его через
обёртку `+0x29650`, остальные перечисленные vtable указывают прямо на него в
позиции `+0x1C`. Читатель потребляет
@@ -1494,6 +1586,28 @@ Control.dll `+0x8B10` передаёт указатель текущей запÐ
а также отказ при оборванном завершающем NUL. Это подтверждает структурный
формат, но не назначает смысл всем коэффициентам устройств.
+Путь записи с меткой `17` проверен на GOG `Control.dll` с SHA-256
+`bcb86318089028548a178b03a21accdce15df0cc85ddd91674cf3c2f55012c6e`:
+ветвь `0x11` фабрики `+0x2D4B0` вызывает конструктор `+0x24760`, который
+устанавливает указатель на виртуальную таблицу `0x1003C840`. Ячейка `+0x1C`
+этой таблицы ссылается прямо на общий читатель `+0x21D50`. В ресурсах
+`weapon.rlb:bm_m_01.ctl` и `weapon.rlb:bm_l_01.ctl` таблица привязок
+заканчивается на смещении `0x120`, где начинается запись с меткой `17`.
+Общий префикс размером `0xB0` байт включает саму метку; счётчик по `+0xAC` и
+слово длины в этих двух ресурсах равны нулю, поэтому указатель чтения
+переходит на `0x1D4`. Там начинаются `0x54` байта заголовка таблиц эффектов;
+таблицы содержат соответственно `4`, `2`, `2` и `2` строки по 100 байт и
+заканчиваются на `0x620`. Записи из реестра объектов `bm_m_01` и `bm_l_01`
+при обычной подготовке ресурсов сохраняют явно указанные списки точек CPT
+(`bm_m_a.cpt` и `bm_l_a.cpt`) и таблицы узлов NDP (`bm_m_01.ndp` и
+`bm_l_01.ndp`); в первой таблице эффектов находятся три строки type 4.
+Синтетический тест проверяет непустые массивы значений и строки у записи с
+меткой 17 и следующей записи с меткой 4, затем переход к таблице эффектов.
+Если считать 17 неизвестной, чтение остановится перед
+ней; общий разбор позволяет пройти запись и прочитать следующие таблицы,
+оставляя слова tag 17 числами без толкования. Обработчики этой метки и их
+игровое поведение ещё не восстановлены.
+
Resolver прототипов хранит явную registry-ссылку на `.cpt` отдельно от ссылки
на `.ctl`. Если строки type 4 разобраны и явная ссылка на CPT есть, подготовка
читает ровно указанные archive/name; имя CPT не выводится из имени CTL. В CPT