2026-07-18 20:10:26 +04:00
|
|
|
|
# Сценарная VM, формулы и игровые свойства
|
|
|
|
|
|
|
|
|
|
|
|
## Подтверждённый surface
|
|
|
|
|
|
|
|
|
|
|
|
Миссионный сценарный слой задаёт стартовые события, completion/failure,
|
|
|
|
|
|
messages, teleports, задачи, research и campaign transitions. Точки входа и
|
|
|
|
|
|
файлы: `ai.dll: CreateSuperAI/GetSuperAI`, `MisLoad.dll: LoadResearch`,
|
|
|
|
|
|
`ArealMap.dll: CalcFullResearchCost`, `MISSIONS/SCRIPTS/*.scr`, `*.fml`,
|
|
|
|
|
|
`*.trf`, `varset.var`, `MISSIONS/dispatcher.ini`, `mission.cfg`, `messages.cfg`
|
|
|
|
|
|
и `briefing.cfg`.
|
|
|
|
|
|
|
|
|
|
|
|
`.scr` — binary package с version checks, symbol/event sections и offsets;
|
2026-07-18 20:40:17 +04:00
|
|
|
|
полная opcode grammar не доказана. Его внешний framing теперь читает
|
|
|
|
|
|
`fparkan-script`: первый little-endian `u32` является числом required opcode
|
|
|
|
|
|
handlers, второй — числом event records. Каждый event хранит `name_len`,
|
|
|
|
|
|
`name_len + 1` raw bytes с обязательным NUL, opaque event word и count вложенных
|
|
|
|
|
|
records. Вложенный record сохраняет семь `u32` header words (в disk order),
|
|
|
|
|
|
список `u32` references после шестого header word и trailing seventh word.
|
|
|
|
|
|
Никакой из этих words ещё не получает semantic name. `.fml` — текстовый
|
|
|
|
|
|
symbol/formula oracle; `varset.var` задаёт `VAR(...)`/`STRING(...)` defaults;
|
|
|
|
|
|
`.trf` — NRes tables, чей framing подтверждён, а field semantics местами лишь
|
|
|
|
|
|
consumer-inferred.
|
2026-07-18 20:10:26 +04:00
|
|
|
|
|
|
|
|
|
|
## Безопасная модель исполнения
|
|
|
|
|
|
|
|
|
|
|
|
Новая 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.
|
|
|
|
|
|
|
|
|
|
|
|
До восстановления opcode table package mode читает header/strings/symbols/
|
2026-07-18 20:32:56 +04:00
|
|
|
|
event offsets/raw bytecode losslessly. Статический анализ уже выделил отдельный
|
|
|
|
|
|
five-way evaluator condition records (`ai.dll` VA `0x10005180`): tags `1..5`,
|
|
|
|
|
|
type guards, object lookup и completion flag. Это не следует выдавать за
|
|
|
|
|
|
instruction dispatcher или jump table `.scr`: bytecode opcode table всё ещё
|
|
|
|
|
|
требует отдельного доказательства. Unknown opcode нельзя пропустить как один
|
|
|
|
|
|
byte: это ломает синхронизацию. Для каждого доказанного opcode фиксируются
|
|
|
|
|
|
number, size, operands, control flow, effects, errors и минимальный test.
|
2026-07-18 20:10:26 +04:00
|
|
|
|
|
2026-07-18 20:40:17 +04:00
|
|
|
|
GOG `ai.dll` доказывает этот framing двумя consumer-ами: loader по
|
|
|
|
|
|
`0x10001000` открывает `<bundle>.scr`, `varset.var`, `<bundle>.fml`, затем
|
|
|
|
|
|
собирает ровно 73 pointers handlers; `0x10011b20` читает описанную count-driven
|
|
|
|
|
|
структуру. Команда
|
|
|
|
|
|
|
|
|
|
|
|
```powershell
|
|
|
|
|
|
cargo run -p fparkan-cli -- script inspect `
|
|
|
|
|
|
'C:\GOG Games\Parkan - Iron Strategy\MISSIONS\SCRIPTS\c1m2p.scr' --format json
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
на исходном пакете возвращает `opcode_handler_count=73`, 9 events, 17 nested
|
|
|
|
|
|
records, 20 references и 0 trailing bytes. Это corpus evidence для reader-а,
|
|
|
|
|
|
но не разрешение на исполнение неизвестных 73 opcodes.
|
|
|
|
|
|
|
2026-07-18 20:44:10 +04:00
|
|
|
|
Теперь установлен selector: loader `0x10001000` создаёт 73 pointers в
|
|
|
|
|
|
фиксированном порядке, а `0x10011e70` копирует их без перестановки в runtime
|
|
|
|
|
|
array. Во всех 58 GOG `.scr` первый header word каждого nested record равен
|
|
|
|
|
|
`0..72` либо `0xffff_ffff`: соответственно 2095 handler selectors и 3992
|
|
|
|
|
|
sentinel records. Поэтому `ScriptInstruction::dispatch_selector()` возвращает
|
|
|
|
|
|
`Handler(0..72)`, `Sentinel` или сохраняемый `Unknown(u32)`. Первый handler
|
|
|
|
|
|
(`Handler(0)`, VA `0x10008034`) только устанавливает current context и flag
|
|
|
|
|
|
`+0x50 = 1`; это не даёт ему игрового имени и не заменяет runtime trace.
|
|
|
|
|
|
|
2026-07-18 21:01:46 +04:00
|
|
|
|
`Handler(1)` — второй table entry, VA `0x10007fd0`, — не создаёт игровую
|
|
|
|
|
|
команду. Он сохраняет active VM context, берёт один instruction-derived index
|
|
|
|
|
|
через current event/instruction offsets `+0x48/+0x4c`, а затем разрешает его
|
|
|
|
|
|
в varset object по `this + 0x18`. Resolver `0x10002d30` проверяет
|
|
|
|
|
|
`0 <= index < count` и возвращает record `base + index * 0x30`; invalid index
|
|
|
|
|
|
вызывает C++ exception, а не становится нулём. Полученный 48-byte record
|
|
|
|
|
|
передаётся в `0x10013190`, который возвращает x87 floating result: kinds `0`
|
|
|
|
|
|
и `4` идут через отдельный opaque conversion path, kind `1` — signed integer,
|
|
|
|
|
|
kind `2` выбирает одну из двух static scalar constants по нулевости payload,
|
|
|
|
|
|
kind `3` — float, kind `5` — unsigned integer; остальные и пустые cases дают
|
|
|
|
|
|
один fixed fallback scalar. Это доказанный numeric
|
|
|
|
|
|
bridge для VM, но пока не Rust handler: неизвестны точный disk operand slot,
|
|
|
|
|
|
ownership значения на FPU stack и следующий consumer, поэтому нельзя назвать
|
|
|
|
|
|
его арифметическим opcode или silently заменить portable `f32` execution.
|
2026-07-18 21:03:36 +04:00
|
|
|
|
Отдельный проход по всем 58 GOG `.scr` (6 087 instruction records) не нашёл
|
|
|
|
|
|
ни одного selector `1`: из них 2 095 записей выбирают один из handlers, а
|
|
|
|
|
|
3 992 являются sentinel. Значит, это установленная, но не corpus-reachable
|
|
|
|
|
|
ветка данного издания; её нельзя делать приоритетным execution path без
|
|
|
|
|
|
отдельного dynamic/evidence route.
|
2026-07-18 21:01:46 +04:00
|
|
|
|
|
2026-07-18 20:46:37 +04:00
|
|
|
|
`Handler(2)` (третья entry table, VA `0x10009610`) уже имеет статический
|
|
|
|
|
|
contract, но ещё не Rust execution: он выбирает active event/instruction через
|
2026-07-18 21:05:55 +04:00
|
|
|
|
runtime offsets `+0x48/+0x4c` и разрешает семь 32-bit slots через varset object
|
|
|
|
|
|
`+0x18`. Их доказанный dataflow: slot 0 даёт один `u32` и base string, slot 1
|
|
|
|
|
|
даёт numeric scalar, slots 2 и 3 — по `u32`, slots 4, 5 и 6 принимают только
|
|
|
|
|
|
kind `5`/`3` и иначе дают `0.0`. Затем он вызывает `0x100059f0` объекта по
|
|
|
|
|
|
`this + 0x7c` и очищает flag `+0x50`.
|
|
|
|
|
|
|
|
|
|
|
|
Этот callee больше не opaque. Он строит key из семи значений, ищет matching
|
|
|
|
|
|
record в своей collection по `this + 0x24` и при совпадении обновляет только
|
|
|
|
|
|
record fields `+0x0c` и `+0x14`, затем вызывает его refresh path `0x10005070`.
|
|
|
|
|
|
При отсутствии record он лениво ищет в event table имена `<base>_Start` и
|
|
|
|
|
|
`<base>_Continue`, сохраняет их IDs в indexed state и materializes новый
|
|
|
|
|
|
internal record. В этой ветке не видно прямого World3D/Behavior call, поэтому
|
|
|
|
|
|
это доказанная scheduler/event-record boundary, а не команда движения, атаки
|
|
|
|
|
|
или строительства. Semantic names семи slots, key equality и consumer нового
|
|
|
|
|
|
record остаются открытыми; до dynamic capture Rust возвращает явный
|
|
|
|
|
|
unsupported result, а не «примерный» game command.
|
2026-07-18 20:46:37 +04:00
|
|
|
|
|
2026-07-18 20:56:34 +04:00
|
|
|
|
На границе mission runtime выбранный TMA clan `first_resource` теперь
|
|
|
|
|
|
материализуется как отдельный `MissionScriptBundle`: loader нормализует
|
|
|
|
|
|
`<base>.scr`, декодирует его тем же bounded reader-ом и публикует immutable
|
|
|
|
|
|
package вместе с clan provenance. Headless report выводит число таких packages
|
|
|
|
|
|
и их named events. Это именно wiring входных данных, не VM execution: Init и
|
|
|
|
|
|
остальные events пока не dispatch-ятся, а ошибка чтения сохраняет
|
|
|
|
|
|
transactional rollback mission loader-а.
|
|
|
|
|
|
|
2026-07-18 20:10:26 +04:00
|
|
|
|
TMA properties остаются four raw `u32` words плюс имя, пока consumer/schema не
|
|
|
|
|
|
задаст тип (integer/float bits/ObjectId/enum/fixed-point/index). В том числе
|
|
|
|
|
|
сохраняются `NOT USED`; corpus подтверждает `Invulnerability`, life state,
|
|
|
|
|
|
`ClanID`, ore, speed и free-time properties.
|
|
|
|
|
|
|
|
|
|
|
|
Research/economy работают в simulation: `LoadResearch` и
|
|
|
|
|
|
`CalcFullResearchCost` доказывают данные и вычислимую стоимость, но не полный
|
|
|
|
|
|
layout prerequisites/modifiers/unlocks. Formula evaluator требует strict
|
|
|
|
|
|
grammar/version, typed operands, deterministic numeric policy, bounded stack и
|
|
|
|
|
|
явных errors; x87-compatible rounding нужен там, где оно выбирает ветку.
|
|
|
|
|
|
|
|
|
|
|
|
## Готовность
|
|
|
|
|
|
|
|
|
|
|
|
Все demo packages должны проходить package/version checks, offsets оставаться
|
|
|
|
|
|
в bytecode, а confirmed disassembler — не терять синхронизацию. VM считается
|
|
|
|
|
|
готовой после deterministic Init/basic mission events, stable object bindings,
|
|
|
|
|
|
typed research/property tests и save/load script state. Для закрытия остаются
|
|
|
|
|
|
dispatcher/jump table, minimal differential packages и traces world/variable
|
|
|
|
|
|
effects; до них unknown opcode — явная unsupported branch, не no-op.
|