675967ce42
A frame hook ends an active film or export when the world is gone. The game stops ticking at the title screen but keeps rendering, so an export used to keep recording the title with its playback clock frozen (a sec=10 request produced 3898 frames) and went on after re-entering the world. Session cleanup runs once with WorldChange and cancels the capture. Real short exports on a scratch home and world (pin 57f8b2853, API 29, Metal): h264+AAC both 2.07 s with non-silent audio, reported frames equal ffprobe nb_frames; cancel, SDK refusal, encoder failure and restart all leave no capture running; 1920x1080 works. The engine still leaves *.audio.raw and *.ffmpeg.log behind on an encoder error (game/film.rs). Co-Authored-By: Kimi Code <noreply@moonshot.ai>
171 lines
17 KiB
Markdown
171 lines
17 KiB
Markdown
# Закреплённый движок 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: `57f8b2853c4443f7e3ff5701600cbb262b53118f` (API 29). Акустические
|
||
`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 может всё же завершиться:
|
||
это запрос отмены, не гарантия удаления файла.
|
||
|
||
Проверено на реальном экспорте 4 ноября (pin `57f8b2853`):
|
||
|
||
- Смена мира доходит до движка только через снимок мода (`set_snapshot`). На экране заголовка тики
|
||
не вызываются, поэтому мод без хука `frame` не сообщает о закрытии мира, и захват продолжает
|
||
писать заголовок. AirDef теперь просит `frame` и завершает показ по `game.world-id = none`.
|
||
- При ошибке энкодера писатель удаляет только отменённые файлы: `<имя>.audio.raw` и
|
||
`<имя>.ffmpeg.log` остаются. Успешная и отменённая записи временных файлов не оставляют.
|
||
Это ограничение движка, а не мода; исправление — в `game/film.rs`.
|
||
|
||
## Отдельный тяжёлый 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).
|
||
Актуальный чистый API29-прогон с измерением:
|
||
[271 шаг, fuel и build identity](measurements/api29-wasm-budget.json).
|
||
|
||
Проверены реальный WASM guest с переходами состояний/владельца/мира, сборка клиента и два коротких
|
||
offscreen-профиля Metal. При `HORIZON_ACOUSTICS=1` + `HORIZON_NO_AUDIO=1` в игре получено
|
||
enabled/request=true, prepared=false, awaiting-backend; HD smoke переходит в ready после подготовки
|
||
основного кадра. Это не замер производительности и не прослушивание.
|
||
|
||
API28/29 добавляют отдельные `point-lights-status` и `sound-sources-status`: свет подтверждается
|
||
после cache/main-view HD обработки, звук — после source-manager apply с live/offline output.
|
||
Заявки привязаны к запуску мода и поколению мира; звук также к source epoch. Clear/reload/выход
|
||
снимают подтверждение. Это не GPU fence, загрузка сэмпла, наличие голоса или слышимость.
|
||
Камеры и их targets теперь изолированы по `(run_serial, local_id)` вместо глобального ID.
|
||
Camera-ready API пока отсутствует; проверки хоста не заменяют GPU-приёмку.
|
||
|
||
Остаток #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).
|