# Закреплённый движок 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//`, глобальный `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: `c89c78c47047fcfc0dfb0b8968600c650bf12d2b`. Новые `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_budget.toml` запускается в изолированном временном `tests/`, поскольку CLI читает только непосредственные `tests/*.toml`. Обычный `test-wasm` остаётся лёгким. Ручной workflow `.gitea/workflows/wasm-budget.yml` не добавляет нагрузку на каждый push. Проверенный профиль: 64 комплекса, две перекрывающиеся волны по 80 целей на 4000 блоках, 602 вызова tick и 300 frame, 69 шагов. Это не полный лимит расстановки (2000) и не нагрузочный тест GPU, mixer voices или всех физических world replies: sandbox не выполняет настоящий рендер/звук. Отчёт `.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. **Фактический расход fuel и peak memory не измеряются этим отчётом.** Сохранённый успешный прогон: [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 и платформенная приёмка. Эти результаты текущими тестами не объявляются выполненными.