AM-00: база для сравнения масштабов 1–3 (модель воздуха)
Задача AM-00 (docs/plan/air_model.md → «Задачи → M0»), снято 29.09.2026 в worktree
~/deltaplan-wf, ветка feature/air-model, коммит на момент снятия — см. «Коммиты» в отчёте
координатору. Числа ниже — точка отсчёта для сравнения масштабов 2 и 3 в AM-07/AM-08 (±10–20 % из
плана) и для смежных задач (AM-01…AM-06). Инструмент, которым сняты числа §2–3 (переиспользуется):
tools/research/air_model_baseline/probe.gd + .tscn.
1. Headless-прогон (tools/check.sh / res://tests/run_tests.tscn)
Команда:
godot --headless --path . --import
godot --headless --path . res://tests/run_tests.tscn(временный профиль: XDG_DATA_HOME=$(mktemp -d) godot …)
Итог: 609 тестов, 4 упало, 1 пропущено (пропуск — новый test_gpu_smoke.gd::test_compute_smoke,
см. §4).
Упавшие:
| Тест | Соответствует известной нерегрессии из задания? |
|---|---|
test_atmosphere.gd::test_performance | да — порог времени под нагрузкой |
test_net_screen.gd::test_client_backend_create_embedded_vs_remote | да — порт 8080 |
test_menu_setup.gd::test_loading_screen_follows_progress | нет, новая — см. ниже |
test_net_screen.gd::test_nearby_list | нет, новая — см. ниже |
Из известного списка не упали в этом прогоне: test_compute_time_40km, test_forest_mask_loaded
(пороговые тесты под нагрузкой — не воспроизвелись именно сейчас, ожидаемо нестабильны от прогона к
прогону, а не «стали лучше»).
Две новые FAIL — не регрессия от AM-00. Worktree общий (несколько агентов пишут в один индекс
параллельно, git status на момент снятия показывал незакоммиченные правки другого агента:
scripts/ui/flight_setup_screen.gd, scripts/game/launch_options.gd, scripts/world/sun_clock.gd,
configs/world.json, tests/ui/test_menu_setup.gd, tests/game/test_catch_up_game.gd — по
журналу, это AM-06 часть А, часы старта). test_loading_screen_follows_progress падает с тем же
SCRIPT ERROR: Nonexistent function 'start_hours'/'nearest_start_hour', что валит и
test_cockpit.gd в этом прогоне (тот не входит в 4 FAIL только потому что падение там — внутри
_fly(), тест ловит через check, а не крашится) — прямое следствие незавершённой правки
flight_setup_screen.gd у соседнего агента, не код AM-00. test_net_screen.gd::test_nearby_list не
в списке изменённых файлов соседнего агента; похоже на сетевую нестабильность в песочнице того же
рода, что и известный порт 8080 (LAN-обнаружение), а не на новую регрессию кода — не чинил, не в
скоупе AM-00. Обе — координатору К0: пересобрать headless-набор после того, как AM-06 часть А
закоммитит start_hours/nearest_start_hour.
2. test_ridge_starts (уже в tests/atmosphere/test_ridge_starts.gd, зелёный)
Таблица набора уже задокументирована и не менялась (docs/guide/atmosphere.md → «Склон на стартах»):
| Старт | Набор за 5 мин, м |
|---|---|
| altai/sinyukha_west | 134 |
| altai/sinyukha_east | 242 |
| altai/tugaya_south | −85 (слабый/узкий склон) |
| askarovo/biyagoda_west | 195 |
| askarovo/biyagoda_east | 65 |
| askarovo/biyagoda_south_west | 261 |
| aushkul/aushtau_east | 126 |
| aushkul/aushtau_south | 56 |
| aushkul/ridge_west | −25 (слабый/узкий склон) |
| ongudai/kayancha_south | 225 |
3. air_velocity_at — время вызова
Онгудай, старт kayancha_south, 75 м AGL над рабочей точкой, ветер 20 км/ч в склон, 100 000
вызовов подряд (после разогрева поля одним step):
14,3–14,9 мкс/вызов (три прогона: 14,05 / 14,93 / 14,26; варьирует из-за шума песочницы, не кода — берём медиану ≈ 14,3 мкс).
4. FPS в полёте
Снят существующим tools/bench/frame_bench.sh (не новый инструмент, есть в репозитории), локация
ongudai, пресет графики medium, три отметки времени полёта × 2 ракурса:
| Ракурс@отметка | средний FPS | 1 %-low FPS |
|---|---|---|
| кабина@2с | 117,0 | 43,7 |
| сзади@2с | 112,8 | 65,7 |
| кабина@30с | 112,3 | 51,1 |
| сзади@30с | 112,1 | 67,3 |
| кабина@60с | 89,3 | 15,3 |
| сзади@60с | 112,8 | 14,7 |
Машина — с реальным GPU (RTX 4070 SUPER), не типичная у пилотов; число — только точка отсчёта для сравнения «до/после» на этой же машине, не абсолютный ориентир.
5. Подветренная зона: СКО вертикали и частота «рывков» на 3 стартах
Инструмент: tools/research/air_model_baseline/probe.gd. Метод: Atmosphere (та же схема, что
test_ridge_starts._make_atmo/_scan_lee_point) над рельефом старта; термики выключены (weather
static, пусто — «поле» ещё не существует, эффект тот же, что требуемое «поле выключено»); ветер
20 км/ч, перевёрнутый (дует с обратной стороны хребта) — та же схема, что в
test_lee_zone_all_starts; точка — самая глубокая под линией тени в радиусе 1 км от старта (поиск
_scan_lee_point). В найденной точке неподвижно (пилот не летит, зонд стоит) 180 с симуляционного
времени на 20 Гц (3600 отсчётов) снимается air_velocity_at(pos).y; сид атмосферы фиксирован
(seed = 42), место и час — из configs/locations/*.json (без AtmoDay, погода weather/medium
постоянна — не «час», а «типичный день»).
Определение «рывка» — выброс модуля мгновенной вертикальной производной dw/dt (конечная
разность по отсчётам 1/20 с) за 3·σ(dw/dt) ряда самой производной. Порог самоопределяемый (не
абсолютное число): не зависит от общего уровня болтанки, что и нужно для сравнения «до/после» в
AM-08 (план требует ±10–20 % от базы, не абсолютные пороги). Частота — событий в минуту.
Три старта с явной подветренной зоной (выбраны так, чтобы не попасть в WEAK_LEE_SITES из
test_ridge_starts.gd, где линия тени неглубокая и эффект слабый):
| Старт | ветер, км/ч | час/сид | СКО w, м/с | рывков/мин | порог |dw/dt|, м/с² | глубина тени, м |
|---|---|---|---|---|---|---|
| altai/sinyukha_west | 20 (с обратной стороны) | сид 42, погода medium | 1,139 | 18,0 | 1,954 | 165,7 |
| askarovo/biyagoda_west | 20 (с обратной стороны) | сид 42, погода medium | 1,075 | 7,3 | 1,924 | 58,6 |
| aushkul/aushtau_east | 20 (с обратной стороны) | сид 42, погода medium | 1,274 | 26,0 | 2,024 | 79,0 |
Воспроизвести:
XDG_DATA_HOME=$(mktemp -d) godot --headless --path . \
res://tools/research/air_model_baseline/probe.tscn -- \
--out=tools/research/air_model_baseline/out/baseline.jsonJSON с теми же числами — tools/research/air_model_baseline/out/baseline.json (не в git, считается
заново каждым прогоном; воспроизводимость — по фиксированному сиду в скрипте).
6. Термики за день на 2 местах (ongudai, aushkul)
Тот же скрипт. Метод: постоянная погода weather/medium (без хода дня/AtmoDay — «типичный день»,
упрощение по правилу «не усложняем» для базы; ход дня можно добавить позже, если AM-07/AM-08 это
потребуют), термики dynamic, фокус — первый старт места, радиус генерации по умолчанию (20 км).
4 «игровых» часа симуляционного времени шагом 5 с (2880 шагов): накапливаются все рождённые термики
(сила и потолок — на момент снятия из AtmoThermal, не меняются после рождения кроме огибающей).
Расстояние до соседа — не по всем термикам за 4 часа (один источник переживает ~15–20 циклов за
это время — «соседями» были бы предыдущие/следующие термики той же точки, а не реально соседние
работающие термики), а по срезам: раз в 60 с симуляционного времени берутся термики, живые
одновременно (atmo.field.thermals на этот момент), и для каждого — расстояние до ближайшего
другого живого в этот момент; усреднено по всем срезам.
| Локация | N термиков за 4 ч | сила, м/с (ср.) | потолок AGL, м (ср.) | расстояние до соседа, м (ср., N срезов) |
|---|---|---|---|---|
| ongudai | 13 488 | 2,41 | 1794,7 | 586,4 (227 381) |
| aushkul | 13 226 | 2,42 | 1788,3 | 602,7 (222 952) |
Сверка со здравым смыслом: сила у нижней границы пресета weather/medium (2,4–4,4 м/с) — ожидаемо,
среднее по всем термикам смещено вниз показателем sun_strength_exponent/освещённостью источников,
а не только пиком; потолок ≈ cloudbase_agl_m пресета (1800 м) — термики почти всегда достают до
кромки, что для «хорошего дня» ожидаемо; расстояние до соседа ≈ 0,7–0,75 × thermal_spacing_m (800
м) — источники на сетке клеток, но не все клетки одновременно «горят», так что реальный интервал
между работающими термиками немного меньше шага сетки — физически разумно.
7. GPU-тесты и headless-пропуск (приёмка AM-00)
tests/test_case.gd:TestCase.needs_gpu() -> bool(по умолчаниюfalse), переопределяется в тестах, которым нужен настоящийRenderingDevice.tests/run_tests.gd: флаг--gpu; тест сneeds_gpu() == trueбез флага — пропущен (счётчик «пропущено», не «упало»); с флагом — выполняется как обычно.tests/atmosphere/test_gpu_smoke.gd(заглушка):RenderingServer.create_local_rendering_device(), тривиальный compute-шейдер (v[i] = v[i]*2+1на буфере из 64 float), сверка результата.tools/gpu_tests.sh:godot --path . --audio-driver Dummy --resolution 320x240 res://tests/run_tests.tscn -- --gpu, временныйXDG_DATA_HOME.
Проверено:
- headless (
--headless, без--gpu):RenderingServer.create_local_rendering_device()→null→ тест пропущен,0 тестов, 0 упало, 1 пропущенопри фильтреtest_gpu_smoke; полный прогон —609 тестов, 4 упало, 1 пропущено(см. §1). tools/gpu_tests.sh --filter=test_gpu_smoke(подflock /tmp/heat_ca_gpu.lock, реальный дисплей, Vulkan/RTX 4070 SUPER):1 тестов, 0 упало, 0 пропущено, тест проходит.
Файлы
tools/research/air_model_baseline/probe.gd,probe.tscn— сбор §3, §5, §6 (новые).tools/gpu_tests.sh— новый.tests/atmosphere/test_gpu_smoke.gd— новый.tests/test_case.gd,tests/run_tests.gd— добавленneeds_gpu()/--gpu(правки).docs/guide/atmosphere.md→ «Склон на стартах» — таблица §2 не менялась, переиспользована.