Files
airdef/docs/ENGINE.md
T
chexit 9217f9a5cc Report camera readiness in /airdef capabilities
Pin the engine at 6a53a626c (API 30) and show render.camera-status: the
calling run's set-views cameras, whether the last noted frame was a world
frame, the request's world generation, each camera's reason and the run's
own main-view selection. As the other lines, this is a host milestone:
availability, request, preparation and why, not a GPU frame or a visible
picture. A unit test pins the line: readiness stays per camera and apart
from the run's main-view selection.

docs/ENGINE.md, the 4 November continuation and the status table now say
what prepared means (a picture encoded into the camera's target, or a
world frame drawn from it as this run's screen view, only for the run
whose selection the screen used), the per-run/per-world resets, and what
remains: GPU acceptance.

Verified: python3 tools/airdef.py check (gitlink, packer, mod needs 30,
fork 30, game 30); test-wasm 27 steps; airdef-mod 65 unit, 1 boundary and
5 scenarios; python3 -m unittest discover -s tools 28 tests.

Co-Authored-By: Kimi Code <noreply@moonshot.cn>
2026-11-04 21:08:23 +03:00

19 KiB
Raw Blame History

Закреплённый движок 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 — это нормально для зависимости.

Изменение движка

  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: 278f356aa604fa21c2d15d0081823bbe0f891df2 (API 30; поверх API29 — нормали моделей и очистка неудачного захвата). Акустические 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; очистка ошибки в 73b9d3070):

  • Смена мира доходит до движка только через снимок мода (set_snapshot). На экране заголовка тики не вызываются, поэтому мод без хука frame не сообщает о закрытии мира, и захват продолжает писать заголовок. AirDef теперь просит frame и завершает показ по game.world-id = none.
  • При ошибке энкодера временные файлы этого захвата (<имя>.video.mp4, <имя>.audio.raw, <имя>.ffmpeg.log) удаляются, а последние строки лога переносятся в текст ошибки: ссылки на уже удалённый лог больше нет. Кодировщик всегда пишет <имя>.video.mp4, а <имя>.mp4 появляется только после успешного mux или переименования, поэтому неудачный, отменённый или беззвучный захват не перезаписывает и не удаляет готовый фильм прежнего экспорта с тем же именем. Исправлено в 73b9d307048fc44d7f21fa3c829fc5a855d144a6 (game/film.rs), проверено реальной ошибкой ffmpeg и контрольным MP4.

Отдельный тяжёлый 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.

API30 добавляет camera-status для камер set-views вызывающего запуска: доступность API, включение пути картинок в последнем отмеченном кадре, принятие заявки поколения и поштучная готовность каждой камеры. prepared означает, что рендерер закодировал кадр камеры в её texture target, либо кадр мира отрендерен с неё как main-view этого запуска: если одинаковый local ID выбрали два мода, экран использует выбор первого, и milestone получает только он; чужая картинка с тем же ID чужую камеру не готовит. enabled — последний отмеченный кадр был кадром мира (не заголовок/загрузка). Это не регистрация target и не GPU fence: видимый пиксель, представление кадра и обход ротации per-frame бюджета не гарантируются. Пустая set-views снимает заявку и готовность; смена мира очищает заявку, выбор main-view и готовность (камеры другого мира не рисуются), перезагрузка мода начинает заново, а повторная отправка и камера, отданная в main-view, ждут нового кадра (у такой камеры нет своего picture target). Проверено двумя настоящими Wasmtime-гостями с одинаковым local ID, пустой заявкой, сменой мира и reload. /airdef capabilities показывает строку камер. Проверки хоста не заменяют 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-сеанс. Данные и воспроизведение.