01. Бот-маршрутник: ищет термики по вариометру и летит маршрут (FR-34a)

Цель: headless-бот, который летит маршрут N км на реальном рельефе локации, находя термики только по вариометру (как пилот), и пишет статистику полёта в JSON — основа измерения проходимости.

Контекст: scripts/atmosphere/atmosphere.gd (API), scripts/flight/flight_model.gd (step(dt, input, air_fn, ground_fn)), scripts/flight/flight_telemetry.gd, tests/flight/flight_sim.gd (образец обвязки), tests/terrain/test_locations.gd (как грузить Terrain.new() headless), docs/research/thermals.md §1–2.

Владения: tests/atmosphere/xc/ (новые: xc_pilot.gd, xc_run.gd, xc_run.tscn), tools/atmosphere/xc_run.sh. Чужой код (FlightModel, Terrain, scripts/game/autopilot.gd) — только через API, не править.

Шаги:

  1. xc_run.tscn -- --location=ongudai --weather=medium --seed=1 --wind=15,270 --km=30 --out=<json>: грузит Terrain, Atmosphere (configure, set_ground(height_at, thermal_source_strength_at, surface_at), focus = бот), FlightModel крыла sport; старт в воздухе над стартом локации на +300 м AGL; шаг 1/60 с.
  2. XcPilot.drive(telemetry, dt) -> ControlInput — честный бот (FR-22): видит только вариометр (усреднение 1–2 с), высоту, AGL, курс, позицию, путевую скорость. thermals_near НЕ использовать. Режимы:
    • переход: курс на поворотную точку (прямая по умолчанию), скорость по МакКриди от ожидаемого набора;
    • вход: усреднённый вариометр > порог (≈ +0,5 м/с сверх собственного снижения) → вираж 35–40° в сторону, где был подъём (асимметрия крена от FR-8 — подсказка);
    • центровка: правило Райхмана (сильнее → положе, слабее → круче / смещать круг к максимуму);
    • выход: средний набор за круг < порога МакКриди, или до кромки < 150 м под крупным облаком (FR-14b, «близко не подходят»);
    • ниже 300 м AGL — режим «спасения» (берёт любой подъём); коснулся земли → вынужденная посадка, конец.
  3. Опционально флаг --clouds: бот видит облака (центры видимых облаков из CloudModel), выбирает цель перехода под растущим облаком в секторе ±30° от курса — как пилот читает небо.
  4. Метрики в JSON: пройдено км, время, средняя путевая, доля времени в кружении, число термиков, средний набор в термике (по кругам), распределение набора (гистограмма 0,5 м/с), среднее снижение на переходах, эффективное качество переходов, макс. вариометр, время с вариометром ≥ +7, время в облаке (cloud_density_at > 0,3), доля найденных термиков без облака, причина конца (цель / посадка / лимит времени).

Критерий приёмки (измеримо):

  • Синтетика (плоская земля, статичные термики 3 м/с R=100 м сеткой через 2 км вдоль курса, без ветра, фон −0,5): бот проходит 30 км без посадки в 5/5 сидах; находит ≥ 90 % термиков, чей центр ≤ 150 м от линии курса; средний набор в термике ≥ 65 % от (ядро − снижение крыла на вираже).
  • Без термиков бот пролетает дистанцию ≈ высота × качество ±15 % (проверка переходов и учёта снижения).
  • Детерминизм: один сид → одинаковый JSON. Прогон 30 км ≤ 60 с CPU.
  • Юнит-тест tests/atmosphere/test_xc_bot.gd (синтетика, короткая 10 км) — в общем прогоне ≤ 10 с.

Зависимости: после агента atmosphere; Terrain headless (есть). Модель: opus. Размер: M.