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>
17 KiB
Закреплённый движок 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: обновление и запуск
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 — это нормально для зависимости.
Изменение движка
- В
horizon/создать рабочую ветку от закреплённого SHA. Проверить изменение и push в репозиторий движка. - В AirDef выполнить
git add horizon, проверить мод/игру, коммитить обновление gitlink отдельно. - Push AirDef выполняется после движка. Не коммитить Git URL с токенами.
- Для возврата выбрать прежний 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
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.
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 с поправкой покрытия.
Актуальный чистый API29-прогон с измерением:
271 шаг, fuel и build identity.
Проверены реальный 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-сеанс. Данные и воспроизведение.