# Передача разработки AirDef Это журнал фактически сделанных изменений и ограничений проверки, а не список обещанных функций. Рабочая ветка `kimi-work`; issues не закрывать, публиковать комментарии с коммитом и проверками. Движок — отдельный `chexit/horizon-airdef`, ветка `airdef`, подключён точным gitlink `horizon/`. ## Текущее состояние: 3 ноября 2026 Код предыдущих четырёх задач интегрирован до `6e2d661`. Продолжение #102/обратного вывоза опубликовано до `aab3e78`; текущий движок — `c89c78c47047fcfc0dfb0b8968600c650bf12d2b`, API 27. README описывает доступное поведение; `IMPLEMENTATION-STATUS.md` отделяет реализацию от приёмки. Ни одно issue не закрыто. Участники: Codex (интеграция, #119, ревью, документация) и три GPT-6.1 Terra (энергетика, типизированная доставка, излучение). Новые числа — только игровые. | Issue | Опубликованные контрольные коммиты | |---|---| | #119 | `c96676f`: укрытия, read-only geometry, четыре проверки | | #118 | `c775f31`, `952a10f`: доставка ТЗМ, возврат снятой ракеты ПВО, реальное движение и save/restore | | #121 | `26b258f`, `0df225a`, `88f9405`, `9e1e0f5`, `b377a5d`, `ce76f20`: излучение, UI/save/replay, наблюдения, last-known SEAD | | #120 | `143d9b2` → `e38ca0d`, затем `a7b8cb9`: конечное горение, coast, swept impact, диагностика, звук, исправленное ветвление фаз | | WASM | `6e2d661`: проверка On/Off и запрета управления отсутствующим радаром, 24 шага | Для обновления существующей установки сначала `git switch kimi-work`, затем `git pull --ff-only`, `python3 tools/airdef.py all`, `python3 tools/airdef.py play`. `all` обновляет точный сабмодуль, собирает игру/мод и устанавливает пакет. На Windows обычно используется `python` вместо `python3`. ### Продолжение #102 и обратного вывоза RM48U - `d3378b3`: `fork()` синхронизирует URL из `.gitmodules` даже на fast path совпавшего pin. Реальная Git-регрессия воспроизводит старый upstream URL и новый коммит, отсутствующий в локальном clone. Это исправляет наблюдавшееся на Windows `upload-pack: not our ref 86b1014…`; пользователь подтвердил ручное исправление через `git submodule sync --recursive` до публикации автоматизации. - `bb931ab`, `e9fbaba`, `b0085cb`: `ground-unload` вдали от склада резервирует одну RM48U и вызывает ТЗМ. `ground-cancel` отменяет ещё ожидающий вывоз/доставку; после забора груз продолжает путь на склад. Проверены физическое движение и полный save/restore до забора и после забора, без телепортации запаса. - `910f02c`: потеря зарезервированной ракеты вместе с ячейками сбрасывает заявку на вывоз. Иначе можно было создать сохранение, которое loader сам отвергал как несовместимое состояние. - Двигатель: `f4c1131d3`, затем `c89c78c47` опубликованы в `horizon-airdef:airdef` до обновления gitlink. API27 добавляет acoustics-status и capture-owner-status, scoped к запуску мода и поколению мира. Остановка capture проверяет владельца; смена мира снимает незапущенный запрос или посылает cancel. - `910f02c`: команда `/airdef capabilities`; дальний звуковой fallback сохраняется до подготовки акустического пути, экспорт принимает только своё подтверждённое состояние; Arming ждёт до 600 тиков. `prepared` — milestone хоста, не GPU fence, не результат каждой акустической трассы и не готовый MP4. - `4dddea2`, `8d5d0d1`: opt-in `test-budgets`, отдельный ручной workflow и JSON provenance; тесты ошибок сборки, stale identity и ненулевого runtime exit. Native success не заменяет actual WASM. - `aab3e78`: repo-local packer использует target-cache закреплённого движка; `CARGO_TARGET_DIR` не переопределяется для сборки самого мода и не уводит `.hmod` в чужое дерево. Проверки объединённого состояния: core release **190 + 4 сценария**, 7 ignored; адаптер **60 + 1 boundary + 4 сценария**; лёгкий WASM **26 шагов**, тяжёлый **69 шагов**; Git-регрессия и три теста failure report прошли. Тяжёлый профиль: 64 ПВО + 160 целей, 602 tick / 300 frame; JSON фиксирует clean `aab3e78`, pin API27 и совпадающий build identity. Полные данные: [budget report](measurements/api27-wasm-budget.json). Дополнительно два свежих профиля игры на Metal/no-audio: smoke off не ready, HD smoke ready; при HORIZON_ACOUSTICS=1 + HORIZON_NO_AUDIO=1 акустика enabled/request=true, prepared=false, awaiting-backend. Выход через quit подтверждён. Это не прослушивание, не benchmark и не новый MP4. Правила API, пределы проверок и команды: [ENGINE.md](ENGINE.md). ## Исходная контрольная точка этой серии Исходное состояние этой серии: `2689d93`; движок закреплён на `86b1014a9218e68041444cbf52db02a85107c815`, уровень API 26. Рабочее дерево было чистым, коммиты опубликованы. Текущий точный pin всегда смотреть в `git ls-files --stage horizon`, а происхождение собранного пакета — в `airdef-mod/target/horizon-mod/build-identity.json`. Четыре задачи были заказаны владельцем отдельно: | Issue | Заказанный остаток | |---|---| | #120 | Конечная работа двигателя перехватчика, coast, энергия манёвра, согласованные эффекты | | #118 | Типизированная доставка RM48U через ТЗМ; не создавать неподтверждённые носители | | #121 | Переключение излучения, сохранение разрешённого внешнего целеуказания, разведка излучателей | | #119 | Ослабление непрямого урона известным укрытием, ограниченная политика Unknown | Исполнители работали в отдельных worktree; их проверенные коммиты перенесены в `kimi-work`. В ветках `codex/issue-*` сохранены промежуточные версии; продолжать нужно от `kimi-work`, где разрешены конфликты общего формата сохранений и сигнатур боевых функций. ## Что уже сделано в предыдущей серии Codex - `airdef-core` компилирует исходники `airdef-mod/src/sim/mod.rs` через `#[path]`. Это единый источник симуляции, но разделение домена и представления ещё не завершено (#87). - Исправлены остаточные терминальные коллизии ответных ударов и скачок после world-query (#106/#117). Восстановление ответов мира не даёт права телепортироваться к плановой точке. - Доработаны запись ответных ударов, launch one-shot и live-след (#109), lifecycle показа и часы отложенного звука (#88/#89). Полная унификация всех потребителей сцены ещё не закончена. - Движок вынесен в отдельную репу; сборщик берётся из закреплённого форка, а не случайного PATH. Есть build identity, настоящий WASM contract и handshake доступности дыма API26 (#102/#104). - Обновлён upstream Horizon 0.2.97, исправления очереди/инвалидации акустики сохранены. Измерительные fixtures, notices и лицензии добавлены; прослушивание и массовый CPU-тест ещё нужны (#96). - Pipeline поддерживает authored overrides, manifest и экспорт итоговых габаритов в `sim/generated_bounds.rs`. Навигация учитывает footprint. Generated output вручную не редактировать. - Улучшены модели наземной техники; степень сходства и материалы ещё промежуточные (#115). Галерея: `MODEL-PILOTS.md`, `VEHICLE-MODELS.md`; это не приёмка всех летательных аппаратов. - `966b52f`: раздельные ПУ С-400/С-500, роли Radar Search/Engagement/Height/Multi. Чужая подсказка разрешает огневому радару уточнение, но не подменяет собственное измерение. - `966b52f`: резерв конкретной ячейки, отдельные крышки ТПК, выброс до включения двигателя. RM48U — условный игровой профиль С-400; AA и ground rounds делят четыре физические ячейки. Боеприпас списывается при выбросе, не при выдаче отложенного приказа; отмена не расходует его. - Конфигурация, маска использованных ячеек и положение крышек включены в save/replay. Smoke начинается в точке зажигания. С-500 ограничен двумя ракетами по числу ТПК; старое сохранение с четырьмя загруженными ракетами при загрузке ограничивается двумя. - `2689d93`: документированы источники архитектуры батареи, команды, скриншоты и границы визуальной проверки. ## Проверки и воспроизведение На `966b52f`: 169 тестов ядра + 4 сценария прошли, 7 ignored; адаптер — 53 теста, 1 boundary и 4 сценария прошли; WASM contract — 21 шаг. Это исходная контрольная точка, не результат новых четырёх задач. На `2689d93` был собран чистый пакет (`airdef_dirty=false`). На итоговом объединённом коде `6e2d661` прошли: - `airdef-core --release`: **187 passed, 7 ignored**, плюс **4 сценария**. - `airdef-mod`: **56 passed**, **1 boundary**, **4 сценария**. - Настоящий WASM-пакет: **24 шага**, включая команды излучения и typed ammunition. - `gen_models.py --check`: различий нет; в этой серии геометрия ассетов не менялась. Полный core здесь запускался с `--release`; это не прогон всех семи ignored-тестов. Отдельно выполнен сравнительный ignored-тест баланса (см. ниже). ```sh cargo test --release --manifest-path airdef-core/Cargo.toml cargo test --manifest-path airdef-mod/Cargo.toml python3 tools/airdef.py test-wasm python3 tools/gen_models.py --check python3 tools/test_gen_assets.py git diff --check ``` На этой macOS cargo находится в `~/.cargo/bin/cargo`. GUI-проверки запускать с отдельным `HORIZON_HOME` и рабочим каталогом тестового мира; не использовать реальные сохранения владельца. Metal-проверки не подтверждают Windows/Linux, HDR всех платформ или прослушивание звука. Скриншоты крышек подтверждают только их позу: полного видео выброса/зажигания пока нет. ## Технические детали новой серии ### #120: энергетика и единственный исход перехвата `MissileKind::burn_ticks` задаёт конечное время горения: простой профиль равен 2/3 игрового `spec.life`; для электрического P1-SUN используется весь срок жизни. Это не отдельная модель топлива и не реальные кривые тяги. `Missile::engine_lit` — общий источник состояния мотора. Три ветки скорости разделены явно: cold (`age <= cold`, множитель 0.97 с прежним минимумом 0.4), powered (`+accel`, ограничение `spec.speed`), coast (множитель 0.995 без холодного минимума). После выгорания доступный поворот масштабируется текущей скоростью; при падении ниже 25% игрового максимума перехват заканчивается диагностикой `MotorEnergyLost`. Срок жизни остаётся отдельным ограничением. Полноценной аэродинамики, гравитационной баллистики и расхода топлива по тяге нет. Swept fuse учитывает движение обоих объектов за тик; `Impact` получает точку closest approach и относительную скорость того же проверенного отрезка, включая удержание на Unknown. Геометрию не заменяет отдельный бонус «встречная/догонная». Один остаточный pk зависит от фактической скорости перехватчика и существующего состояния носителя/дальности. Повторный штраф по stealth и старый штраф по абсолютной скорости цели удалены из этого броска; обнаружение/сопровождение по-прежнему учитывают stealth в своих местах. `MotorBurnout` и `MotorEnergyLost` записываются в scene journal; tally отличает потерю энергии. Live и replay прекращают факел и новый дым по одному состоянию. `Out::engine_stops` немедленно снимает голос мотора на burnout, не ждёт обычной трёхтиковой grace пропавшего источника; при переходе на replay/export live-команды остановки отбрасываются вместе с live-выводом. Перемотка восстанавливает моторное состояние из записи, включая возврат до выгорания. При ревью был найден и исправлен недостижимый coast: ранний прототип проверял `!lit`, что перехватывало и cold, и coast. Не переносить один `143d9b2` без последующих исправлений. `a7b8cb9` содержит отдельный тест точных правил скорости всех трёх фаз. Сравнительная выборка **только изменения #120**, отдельно от #119/#118/#121: baseline `2689d93`, агентская итоговая ветка `508f9a0` (её исправление перенесено как `a7b8cb9`). 12 одинаковых seed, `SAMS=20 SIZE=12`: 204 цели в каждой версии; baseline — 202 сбитых, 2 прорыва, 367 ракет; новое — 203, 1, 355. Это небольшой регрессионный ориентир, не доказательство баланса всех типов/дальностей. Данные каждого seed: [CSV](measurements/issue-120-balance.csv). ```sh SAMS=20 SIZE=12 cargo test --release --manifest-path airdef-core/Cargo.toml sim::balance::one_raid -- --ignored --nocapture ``` ### #118: один физический груз и восстанавливаемая заявка `ground_request` у С-400 — заявка на **одну** RM48U. Рядом со складом остаётся прямая передача; вне радиуса склада та же команда `ground-load` создаёт заявку, если есть запас и живая ТЗМ. ТЗМ забирает одну ракету из конечного запаса, едет к исправному развёрнутому носителю и передаёт её в общие ячейки. Если вытеснена ракета ПВО, ТЗМ везёт её обратно на склад. Generic reload не создаёт RM48U. Ударная заявка имеет приоритет перед generic reload. Поля `ground_stock` / `air_48n6_stock` на складе — остатки, на ТЗМ — физический груз двух типов. Заявка, груз и марш сохраняются; оперативный `job` реконструируется после загрузки. Передача запрещена во время движения, подготовки пуска, перезарядки, отказа ПУ и при отсутствии вместимости. Разрушение очищает заявку/груз; repair не восстанавливает уничтоженный typed stock. Проверены настоящее движение `Sim.step` по плоскому WorldSource, передача/возврат, истощение, прерывание, несовместимость, заполненные ячейки, unload и полный `state_text → apply_state` с продолжением маршрута. GUI-доставка на сложном рельефе ещё не проверена. Новых носителей вроде Бука не добавляли: один публичный игровой профиль не даёт основания приписывать его всем ЗРК. Обратный вывоз из удалённого носителя автоматизирован в продолжении выше. Новая команда `ground-cancel` отменяет ожидающую заявку, но не возвращает уже забранный груз в ПУ. ТЗМ вмещает два типизированных места RM48U/48Н6; прежние абстрактные generic reload-пакеты учитываются отдельно. Вывезенная ракета возвращается в конечный запас; ПВО восстанавливается при заборе только из реально имеющейся у ТЗМ ракеты 48Н6 и свободной ячейки. Резерв одной RM48U учитывается при выборе носителя удара. ### #121: излучение отдельно от сети `radar_on` — сохраняемая настройка, default=true для старых миров. Команда `/airdef radar <номер> on|off` и кнопка карты меняют её; у launcher-only нет своего активного радара. `radar_up` учитывает этот флаг. Off запрещает новые own measurements, но старые треки стареют обычно и разрешённый сетевой приём сохраняется. Это не выключение корпуса/связи/ГСН ракеты. Огневой радар, обязательный для отдельной ПУ, в Off не авторизует новые пуски. Условный наблюдатель-противник раз в 20 тиков обнаруживает работающие радары в 1600 блоках. Это упрощённый игровой датчик по расстоянию, без LOS/радиопеленгации и ошибки оценки позиции. Контакт хранится как последняя наблюдённая точка и стареет за 6000 тиков. Off не стирает контакт, не телепортирует его вслед за носителем и не предотвращает раскрытие пуском/РЭБ. В `raid::radar_strikes` удалён отдельный обход: раньше SEAD выбирал любую Radar и её настоящий `pos`, даже если общий planner уже работал по наблюдениям. Теперь SEAD берёт те же last-known контакты и их возраст. Явный sandbox-режим `Aim::ObjectsAndSams` по-прежнему выбирает расстановку напрямую; не путать его с умным противником. Проверки покрывают необнаруженную молчащую РЛС, смену позиции без наблюдения, повторное раскрытие, старение, relay freshness и seek. ### Совместимость текстового состояния Сохраняем строки `sam` расширением справа; индексы ниже включают слово `sam` как поле 0. | Индекс | Значение | |---|---| | 22–24 | ground_rounds, ground_stock, air_48n6_stock | | 25–27 | mode, radar_role, spent_cells | | 28 | ground_request: 0/1, только С-400 | | 29 | radar_on: true/false, отсутствие означает true | | 30 | ground_return: true/false, резерв вывоза одной RM48U из С-400 | | 31 | return_cargo: true/false, возвращаемая RM48U на ТЗМ | Две агентские ветки сначала заняли поле 28 одновременно; при интеграции излучение перенесено в 29, опубликованная совместимость заявки сохранена. Проверка полного roundtrip одновременно содержит ожидающую доставку и выключенный радар. Новые значения входят в durable mark; настройка излучения также в `SamKey` и ghost replay. Не переиспользовать эти позиции для нового поля. ### #119: непрямой взрыв и укрытия `WorldCache::blast_cover` читает текущую occupancy без запросов мира и без загрузки чанков. Три луча к репрезентативным точкам корпуса дают общий множитель для корпуса и узлов; для отмеченного объекта считается своя тройка. Известное укрытие оставляет 15% игрового воздействия по перекрытому лучу, открытый — 100%; края/проёмы дают промежуточное значение. Все лучи этого вызова читаются до отправки разрушения в движок. Исходный воксель пропускается, чтобы контакт на поверхности стены не закрывал взрыв сразу во все стороны. Unknown, stale и слишком старые секции считаются **неразрешёнными**. Ожидание равно нулю: для них сохраняется прежний неослабленный урон как консервативная игровая политика, а не утверждение, что там воздух. После ответа мира этот взрыв не пересчитывается. Нет очереди отложенного урона и повторного применения на world reply. Частичные блоки на этом этапе считаются занятым кубом; тонкая collision shape для blast отдельно не запрашивается. Прямой вооружённый контакт не ослабляется этим множителем. Проверки `sim::damage::cover_tests`: стена/проём для корпуса, узлов и объекта; Unknown и поздний ответ без повторного урона/запросов; прямое попадание; устаревшая геометрия; пропуск исходного блока. Все четыре прошли в объединённом наборе. ## Что следующему агенту проверить в первую очередь 1. На Windows: обновление сабмодуля через `all`, сохранение/перезаход, рабочий цикл игры. 2. В GUI: полный холодный пуск крупным планом; карточка On/Off; маршрут ТЗМ по сложной местности; разрушение стены одним взрывом и защита от того же взрыва по исходной геометрии. 3. Прослушать прекращение голоса двигателя при burnout и seek/экспорт вокруг этой границы. 4. Расширить баланс #120 на разные батареи, пределы дальности и классы целей; исходная CSV — ориентир. 5. Продолжить оставшиеся архитектурные #87/#88/#89/#91/#102 и художественный #115 по статусной таблице. Это незавершённая платформенная/визуальная приёмка, а не отсутствие перечисленного выше кода. Прогон четырёх задач не запускал GUI/MP4. В продолжении API27 есть два коротких игровых smoke, но новый MP4, прослушивание и платформенная приёмка Windows/Linux по-прежнему не выполнены. ## Правила продолжения - Не закрывать issue даже после исправления; сообщать проверенный объём и остаток в комментарии. - Не переписывать gitlink на плавающую ветку. Изменения движка сначала публикуются отдельно. - Не путать наблюдённый трек с настоящим положением цели и `own_tick` с relay. - Не менять live-мир при seek; replay воспроизводит последствия, не применяет урон повторно. - Не считать Unknown свободным пространством или подтверждённой стеной без явной игровой политики. - Новые числа — игровой масштаб, не реальные характеристики вооружения. - Токен Gitea читать из локального файла во время запроса; не коммитить и не печатать.