Files
airdef/docs/ENGINE.md
T
2026-11-04 15:55:45 +03:00

153 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Закреплённый движок AirDef
Движок: https://gitea.maoniao.team/chexit/horizon-airdef, ветка `airdef`.
AirDef хранит точный SHA как gitlink `horizon/`; новый push движка не меняет старые сборки мода.
История upstream и всех 44 прежних патчей сохранена в форке. Старые patch-файлы и HORIZON_COMMIT
доступны в истории AirDef и больше не являются вторым источником движка. Baseline описан в AIRDEF-FORK.md.
## Windows: обновление и запуск
```powershell
git fetch origin
git switch kimi-work
git pull --ff-only
python tools/airdef.py all
python tools/airdef.py play
```
Первое клонирование: `git clone --recurse-submodules https://gitea.maoniao.team/chexit/airdef.git`.
Обычный clone тоже поддерживается: `python tools/airdef.py fork` или команда submodule выше.
`all` инициализирует зависимость сам, включая переход со старой папки `horizon/`.
Для чистого checkout можно отдельно выполнить `git submodule update --init --recursive`. Нужны Git, Python и Rust согласно INSTALL.md.
Сборка клиента и мод устанавливаются в обычное место; ресурсы Minecraft готовятся отдельно по INSTALL.md.
При первом переходе со старого отдельного clone `horizon/` скрипт сохраняет его Git-историю и ветки,
переводит в сабмодуль и получает закреплённый SHA. Если там есть изменения при другом SHA,
обновление останавливается с объяснением: сначала закоммитьте/сохраните свои изменения.
Скрипт не выполняет reset --hard или clean. Чистый checkout может остаться detached HEAD — это нормально для зависимости.
## Изменение движка
1. В `horizon/` создать рабочую ветку от закреплённого SHA. Проверить изменение и push в репозиторий движка.
2. В AirDef выполнить `git add horizon`, проверить мод/игру, коммитить обновление gitlink отдельно.
3. Push AirDef выполняется после движка. Не коммитить Git URL с токенами.
4. Для возврата выбрать прежний commit AirDef и выполнить `git submodule update --init --recursive`.
Упаковщик собирается repo-local в `.horizon-cache/packer/<engine-identity>/`, глобальный
`cargo-horizon-mod` не используется. Изменения tracked-исходников движка входят в identity.
Рядом с airdef.hmod записывается build-identity.json: SHA AirDef, pin/identity движка,
SHA256 WIT и артефакта. API-диагностика остаётся в `python tools/airdef.py check`.
Нативные тесты не заменяют отдельную игровую проверку графики, звука и экспорта.
## Проверка настоящего WASM
`python tools/airdef.py test-wasm` собирает `.hmod` и запускает `tests/*.toml` в настоящем
runtime закреплённого движка. Используется временное storage; игровые миры не нужны.
Сценарий проверяет загрузку/регистрацию команды, два комплекса, налёт, tick/frame,
выход из мира и перезагрузку мода. Действуют лимиты runtime: 64 MiB linear memory,
300 млн fuel на tick, 10 млн на frame, 1 млрд на event, 2 млрд на init.
Это проверка ABI и fallback при отсутствии render replies; она не измеряет GPU,
не подтверждает акустику/MP4 и не заменяет максимальный боевой полигон.
В CI проверка assets входит в лёгкий build. Wasmtime tier вынесен в ручной workflow
`wasm-contract`, чтобы не компилировать тяжёлый runtime на каждом push слабого общего runner.
## Готовность объёмного дыма (API 26)
`render.smoke-status` разделяет доступность API, включённый режим, принятую отправку этого мода
и готовность smoke/fog пути последнего подготовленного основного кадра. Возвращаются поколение мира
и причина (`awaiting-frame`, `disabled`, `path-unavailable`, `ready`). AirDef убирает обычный дым
только когда выполнены все четыре условия; после смены мира старые сегменты и часы очищаются.
Это подтверждение выбранного пути рендера, а не завершения GPU или видимости каждого сегмента.
Сам `smoke-status` не подтверждает акустику и capture; их отдельные статусы добавлены в API 27 ниже.
## Акустика и владение захватом (API 27)
Текущий pin: `f95f03c08ec2916a0e5bd3b2d4a2a790e7937b47` (API 27). Новые
`render.acoustics-status` и `render.capture-owner-status` возвращают поколение,
`available`, `enabled`, `request-accepted`, `prepared` и причину. В игре всё это показывает
`/airdef capabilities`. Отсутствие запроса, выключение, ожидание backend и ошибка различаются.
Для акустики `prepared` означает выполненный шаг обработки с геометрией при доступном live/offline
микшере. Шаг может поставить worker job или принять результаты; это **не** подтверждение результата
для каждого источника, завершения worker, слышимого звука или точного отражения. В режиме no-audio
без offline-микшера готовность не наступает. AirDef подтверждает текущее глобальное предпочтение,
не меняя его, и сохраняет собственные дальние варианты звука до подтверждённой подготовки.
Для capture запрос принадлежит конкретному запуску мода и поколению мира. `prepared` соответствует
его стадиям Recording/Encoding/Done; состояние файла и ошибка по-прежнему читаются из `capture-status`.
AirDef не принимает чужие Recording/Done за свой экспорт. Ожидание первоначальной подготовки
ограничено 600 тиками. Остановка проверяет владельца; смена мира отменяет модовый захват и
сбрасывает готовность. Уже переданный writer файл на Encoding может всё же завершиться:
это запрос отмены, не гарантия удаления файла.
## Отдельный тяжёлый WASM tier
```sh
python3 tools/airdef.py test-budgets
python3 tools/test_budget_report.py
python3 tools/test_airdef_build.py
```
Сценарий `airdef-mod/tests/budgets/wasm_flat_budget.toml` запускается в изолированном временном `tests/`,
поскольку CLI читает только непосредственные `tests/*.toml`. Обычный `test-wasm` остаётся лёгким.
Ручной workflow `.gitea/workflows/wasm-budget.yml` не добавляет нагрузку на каждый push.
Исходный профиль запрашивал 64 комплекса и две волны по 80 целей на 4000 блоках,
602 вызова tick и 300 frame, 69 шагов. Позднее обнаружено: второй заказ во время подготовки
первого отклонялся, а fixture не проверял ответ. Поэтому тот прогон **не доказывает перекрытие
160 целей**; сохранённый JSON помечен `coverage_correction`. Новый сценарий проверяет принятие
обоих заказов и отличает requested counts от неизвестного пика в воздухе.
Это не полный лимит расстановки (2000) и не нагрузочный
тест GPU, mixer voices или всех физических world replies: sandbox не выполняет настоящий рендер/звук.
Текущий default заменён на flat render-world: ground/sections/rays отвечают для плоскости y=64,
GPU не имитируется. Проверяются два принятых налёта по 40 «Орешников» и 64 ПВО; в середине
включаются HD и grid. На тике 2102 assertion подтверждает **184 цели в воздухе**, а не пик за всё время.
Прогон `2932364` прошёл: 271 шаг, 2102 tick и 2052 вызова frame стенда. У AirDef нет собственного
frame hook; эти вызовы не являются нагрузкой на GPU или измерением 10M frame fuel мода.
Точный отчёт: [flat-world budget](measurements/flat-wasm-raid-budget.json).
Fixture копируется из тех же байтов, чей SHA записан до сборки. Проверенные 2 принятия и точка
184/2102 заносятся только после success и совпадения `BUDGET_ASSERTED_SHA256` с точной версией файла.
После изменения fixture нужно повторно проверить её и обновить SHA/метаданные; простой список
запрошенных целей не заменяет assertions. Пик остаётся null, если он не измерен.
Отчёт `.horizon-cache/wasm-budget-report.json` содержит SHA/dirty/diff AirDef, pin/identity движка,
API/WIT/артефакт, проверку build identity, сценарий, Wasmtime и исходные memory/fuel limits.
`AIRDEF_BUDGET_REPORT` меняет путь отчёта. Stale identity запрещает запуск, ошибки сборки/тестов
записываются как failed и дают ненулевой exit code.
Лимиты берутся из исходников: memory 64 MiB; fuel init/tick/frame/event — 2B/300M/10M/1B.
Начиная с продолжения 4 ноября `test-budgets` включает `--fuel-profile` и сохраняет
`measured_fuel.callbacks`: число вызовов, максимум guest fuel, бюджет и ошибки отдельно по callback.
Отсутствие запрошенных счётчиков делает проверку неуспешной, даже если сценарий завершился.
Это не время host/GPU и не peak memory; невызывавшиеся callbacks, включая init до включения профиля,
не получают выдуманного нуля. Первый прогон: tick 2102 вызова, max 106132646/300000000;
command 69 вызовов, max 1013370/1000000000; ошибок нет.
Исторический отчёт без измерения fuel:
[API27 budget report с поправкой покрытия](measurements/api27-wasm-budget.json).
Проверены реальный WASM guest с переходами состояний/владельца/мира, сборка клиента и два коротких
offscreen-профиля Metal. При `HORIZON_ACOUSTICS=1` + `HORIZON_NO_AUDIO=1` в игре получено
enabled/request=true, prepared=false, awaiting-backend; HD smoke переходит в ready после подготовки
основного кадра. Это не замер производительности и не прослушивание.
Остаток #102: аналогичные статусы света/камер/источников звука, полные query/voice/peak budgets,
GPU/готовый MP4 и платформенная приёмка. Эти результаты текущими тестами не объявляются выполненными.
## Измеренный fuel и массовые взрывы
В runtime добавлен выключенный по умолчанию профиль вызовов. У тестового CLI флаг
`test --fuel-profile` выводит mod/callback, число вызовов, максимальный израсходованный guest fuel,
бюджет и число ошибок; результат печатается и при неуспехе сценария. Init до включения профиля
и предыдущие поколения после reload в эти значения не входят. Это гостевые инструкции,
не время CPU, не heap peak и не работа GPU/host. Обычный `test-budgets` без этого флага по-прежнему
не выдаёт свои исходные caps за измеренный расход.
С профилем воспроизведён остановившийся tick на двух максимальных составах меню и HD:
полный обход height-cache при каждом взрыве доводил его до 300M. `1f28f83` заменяет обход
удалением только затронутого квадрата. В контрольном диагностическом WASM-прогоне после исправления
3602 тика прошли, max tick fuel = 135273690/300M, failures=0. Лимиты не менялись.
Диагностический гость содержит только временный способ запуска точного меню-состава;
это не исходный пользовательский Windows-сеанс. [Данные и воспроизведение](RAID-STABILITY.md).