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>
This commit is contained in:
2026-11-04 20:38:56 +03:00
parent 46d2274c8d
commit 9217f9a5cc
5 changed files with 106 additions and 13 deletions
+74 -1
View File
@@ -59,12 +59,42 @@ fn line(
format!("{name}: API={available}, включено={enabled}, запрос={accepted}, подготовлено={prepared}, поколение={generation}, {why}")
}
fn camera_line(s: &render::CameraState) -> String {
let cameras: Vec<String> = s
.cameras
.iter()
.map(|c| {
let why = match c.reason {
render::CameraReason::AwaitingFrame => "awaiting-frame",
render::CameraReason::Ready => "ready",
};
format!("{}:{}", c.id, why)
})
.collect();
let main = match s.main_view {
Some(id) => id.to_string(),
None => "нет".into(),
};
format!(
"Камеры: API={}, включено={}, запрос={}, подготовлено={}, поколение={}, {}, main={}, [{}]",
s.available,
s.enabled,
s.request_accepted,
s.prepared,
s.generation,
reason(s.reason),
main,
cameras.join("; ")
)
}
pub fn diagnostics() -> String {
let smoke = render::smoke_status();
let acoustic = acoustics();
let capture = render::capture_owner_status();
let lights = render::point_lights_status();
let sources = render::sound_sources_status();
let cameras = render::camera_status();
let smoke_reason = match smoke.reason {
render::SmokeReason::AwaitingFrame => "awaiting-frame",
render::SmokeReason::Disabled => "disabled",
@@ -72,7 +102,7 @@ pub fn diagnostics() -> String {
render::SmokeReason::Ready => "ready",
};
[
"Возможности движка (API 29; подготовка не означает готовый GPU-кадр, слышимый звук или файл):".into(),
"Возможности движка (API 30; подготовка не означает готовый GPU-кадр, слышимый звук или файл):".into(),
line(
"Дым",
smoke.generation,
@@ -109,6 +139,7 @@ pub fn diagnostics() -> String {
sources.prepared,
reason(sources.reason),
),
camera_line(&cameras),
line(
"Захват этого мода",
capture.generation,
@@ -164,4 +195,46 @@ mod tests {
s.reason = CapabilityReason::Failed;
assert!(capture_problem(&s).is_some());
}
fn camera(id: u32, prepared: bool) -> render::CameraReadiness {
render::CameraReadiness {
id,
request_accepted: true,
prepared,
reason: if prepared {
render::CameraReason::Ready
} else {
render::CameraReason::AwaitingFrame
},
}
}
#[test]
fn the_camera_line_separates_readiness_from_the_screens_selection() {
let line = camera_line(&render::CameraState {
generation: 5,
available: true,
enabled: true,
request_accepted: true,
prepared: true,
reason: CapabilityReason::Ready,
cameras: vec![camera(7, true), camera(8, false)],
main_view: Some(7),
});
assert_eq!(
line,
"Камеры: API=true, включено=true, запрос=true, подготовлено=true, поколение=5, \
ready, main=7, [7:ready; 8:awaiting-frame]"
);
// No accepted submission: no cameras, no main view, and the reason says so.
let line = camera_line(&render::CameraState {
generation: 5,
available: true,
enabled: false,
request_accepted: false,
prepared: false,
reason: CapabilityReason::NotRequested,
cameras: Vec::new(),
main_view: None,
});
assert!(line.ends_with("not-requested, main=нет, []"), "{line}");
}
}
+8 -2
View File
@@ -32,7 +32,13 @@
`/airdef capabilities` показывает оба состояния (`c1a595b`).
`57f8b2853` также устраняет коллизию камер: texture targets, scheduler, HUD/canvas/corner и
main-view адресуются `(run_serial, local_id)`. Проверены два настоящих гостя с одинаковым ID,
поиск target и scheduler, компиляция клиента. Camera-ready API и GPU-проверка ещё не добавлены.
поиск target и scheduler, компиляция клиента.
`efe017ffb`/`278f356aa` добавляют API30 `camera-status`: `prepared` — закодированный в target
кадр камеры или кадр мира с её main-view, причём milestone получает только тот запуск, чей выбор
экран реально использовал (чужой тот же local ID и чужая картинка его не готовят). `enabled` —
последний отмеченный кадр был кадром мира, не заголовком. Пустая `set-views` и смена мира
очищают заявку/выбор/готовность, reload начинает заново; главный вид теряет picture target до
нового кадра. Проверено настоящими Wasmtime-гостями; GPU-приёмка не выполнялась.
- **#96:** engine `e44ad82a9`: opt-in `audio::tests::export_moving_mass_raid` проводит
100 движущихся двигателей и 16 импульсных источников через настоящие `Audio::tick_acoustics`
и `film_mix`: эхо/реверб, камера, инвалидация и смена epoch, новый capture без старого хвоста.
@@ -82,7 +88,7 @@ GUI/MP4 и варианты комплексов (разные ракеты/пр
command max 1013387/1000000000, ошибок 0, build identity совпал.
Не завершены: субъективное прослушивание, настоящий MP4 с акустикой, полный 10-минутный
replay/export, расширенная модельная/ночная/FPS приёмка, camera-ready milestone и полные
replay/export, расширенная модельная/ночная/FPS приёмка, GPU-приёмка camera-ready картинки и полные
query/voice/peak-memory бюджеты. Старый допуск broadband RT60 35% не ужесточался до 15%.
Попытка живого MP4 в этом проходе не дала проверенного файла и за приёмку не считается;
изолированные процессы закрыты. Продолжать с текущего pin, не с временных пакетов агентов.
+18 -5
View File
@@ -63,8 +63,8 @@ runtime закреплённого движка. Используется вре
## Акустика и владение захватом (API 27)
Текущий pin: `73b9d307048fc44d7f21fa3c829fc5a855d144a6` (API 29; поверх `57f8b2853` — очистка
неудачного захвата). Акустические
Текущий pin: `278f356aa604fa21c2d15d0081823bbe0f891df2` (API 30; поверх API29 — нормали моделей
и очистка неудачного захвата). Акустические
`render.acoustics-status` и `render.capture-owner-status` возвращают поколение,
`available`, `enabled`, `request-accepted`, `prepared` и причину. В игре всё это показывает
`/airdef capabilities`. Отсутствие запроса, выключение, ожидание backend и ошибка различаются.
@@ -152,10 +152,23 @@ API28/29 добавляют отдельные `point-lights-status` и `sound-s
после 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-приёмку.
Камеры и их targets изолированы по `(run_serial, local_id)` вместо глобального ID.
Остаток #102: статус камер, полные query/voice/peak budgets,
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 и массовые взрывы
+5 -4
View File
@@ -5,9 +5,10 @@
Новый проверенный объём: [продолжение пяти задач 4 ноября](CONTINUATION-2026-11-04.md).
#87: typed battery/world/damage diagnostics и адаптеры текста; #91: общий content/sound catalog
для Rust и Python; #102: измеренный fuel, API29 light/source milestones и изоляция камер;
#96: массовый deterministic offline capture; #115: authored Lancet/Х-101 и кадры инстансов.
Текущий engine pin — API29; старые номера/числа ниже описывают предыдущие этапы.
для Rust и Python; #102: измеренный fuel, API29 light/source milestones, API30 camera-status
и изоляция камер; #96: массовый deterministic offline capture; #115: authored Lancet/Х-101
и кадры инстансов.
Текущий engine pin — API30; старые номера/числа ниже описывают предыдущие этапы.
Проблема нормалей модели #123 исправлена в engine `45b43f00d`; парные HD-кадры и ночная проверка Lancet записаны в галерее.
| Issue | Реализовано | Что остаётся для полной приёмки |
@@ -19,7 +20,7 @@
| #91 | Общий каталог доступности и потребители меню/инвентаря; явный RM48U-профиль; корректные подписи очередей; `catalog/specs.json` — единственный источник числовых строк, из него генерируются Rust-таблицы для мода и `airdef-core`, CI проверяет свежесть | Варианты комплексов (разные ракеты/профили по батареям) и игровая приёмка; каталог остаётся статичными данными, не динамическим ECS |
| #92 | Authored overrides, проверка ссылок/PNG/границ, manifest, безопасная транзакционная генерация; registry/texture/icon modules | Дальнейшее ручное улучшение контента относится к #115 |
| #96 | Акустические пути; измеренные синтетические occlusion/echo/reverb fixtures; лицензии и исходные notices в архивах; настоящий MP4 с акустикой: пары on/off, статусы ready, окклюзия/поглощение и диффузный wet-остаток в замкнутой геометрии | Прослушивание, игровой массовый звук/CPU, чистый хвост/RT60 в MP4; точность RT60 пока по допуску 35%, не 15% |
| #102 | Pinned engine/API29, packer/build identity; smoke/acoustics/capture-owner/light/source milestones; fallback/владение; WASM27 и flat stress271; измеренный callback fuel; JSON provenance/failure tests; Metal/no-audio smoke; URL/Git-регрессия; owner-scoped camera targets | Старое69-шаговое утверждение о перекрытии отозвано; статус камер, полные query/voice/peak budgets, GPU/готовый MP4 и Windows-приёмка остаются |
| #102 | Pinned engine/API30, packer/build identity; smoke/acoustics/capture-owner/light/source/owner-scoped camera milestones; fallback/владение; WASM27 и flat stress271; измеренный callback fuel; JSON provenance/failure tests; Metal/no-audio smoke; URL/Git-регрессия; camera-ready по закодированному кадру с per-run/per-world сбросом | Старое69-шаговое утверждение о перекрытии отозвано; GPU-приёмка картинки камер, полные query/voice/peak budgets, GPU/готовый MP4 и Windows-приёмка остаются |
| #104 | Движок вынесен в `chexit/horizon-airdef`, ветка `airdef`; AirDef использует точный gitlink | Отдельная Windows-проверка миграции старого standalone checkout |
| #107 | ПУ оценивается по наблюдениям: обратный курс не выдаёт точную ПУ | Повторная игровая проверка сценариев с маневрирующими целями |
| #110 | Изменения чанков инвалидируют акустические снимки/кэш; включено в pin и проверено engine-тестами | Внешняя игровая приёмка на целевых машинах |
+1 -1
Submodule horizon updated: 73b9d30704...278f356aa6