План: погода из прогноза — температура и ветер вместо пресетов
Запрос пилота (пилот-консультант, опытный дельтапланерист): выбор «Слабый / Средний / Сильный день / Гроза / Волна» для пилота странный. Пилот знает из прогноза температуру воздуха, направление и силу ветра — это и задаём. Всё остальное (сила и частота термиков, кромка, облачность, сухие термики, пылевые вихри, грозы, болтанка…) выводится из этих трёх чисел плюс уже известных даты, места и рельефа — правдоподобно физически.
Дополнение пилота: слово «волна» ей незнакомо, в её практике его не используют. Поэтому «волны» нигде в интерфейсе нет (ни пресета, ни подписи, ни текста на планшете). Физика подветренной волны остаётся только скрытым эффектом (сильный ветер поперёк хребта + устойчивый воздух) и по умолчанию выключена конфигом (см. §2.8, вопрос 2). Роторы и «прижим» за хребтом (подветренная зона) остаются — их знает каждый пилот.
Статус: 28.09.2026 пилот ответила на вопросы §6 — план поправлен (облачность, ветер на старте и с высотой,
опасный ротор, 0…+40 °C). Ход реализации — коммиты «WeatherModel…», «Настройки полёта…», «Погода по солнцу…».
Было: план, код не менялся. Параллельно другой агент переводит интерфейс на английский (scripts/ui/*,
locale/ui.csv, конфиги) — карточки 4–5 начинать после его слияния.
0. Что есть сейчас (коротко)
configs/weather/{weak,medium,strong,storm,wave}.json— пресеты: ~25 полей (wind_speed_kmh,wind_from_deg,cloudbase_agl_m,thermal_strength_ms,thermal_radius_m,thermal_spacing_m,thermal_duty,background_sink_ms,convective_turbulence_ms,cloud_min_strength_ms,cloud_depth_m,street_strength,overdevelopment_chance,cloud_size_factor,cirrus_cover,cb_chance,cb_top_above_base_m,wave_strength,stability_n_per_s,lens_level_above_crest_m,dry_thermal_fraction,thermal_extreme_chance,thermal_extreme_ms,dust_devil_chance,thermal_mode,static_thermals,name).Atmosphere.set_weather(имя | словарь)/configure()— всё дальше читает только этот словарь (thermal_field,wave_field,dust_model,cirrus_layer,cloud_layer,calm_air). Значит, новый вывод погоды может просто собирать такой же словарь — физику атмосферы не трогаем.- Ветер задан на 10 м (
atmosphere.json → wind.reference_height_m), выше — степенной профиль до ×1,8. - Кромка:
cloudbase_agl_mнад средней высотой земли в радиусе 10 км (ground.reference_*). FlightSettings.weather(“weather/medium”) иwind_mode(“into_site” | “preset”) хранятся вuser://last_flight.json;UserSettings.load_last_flightсбрасывает неизвестный пресет.Game.start: рельеф грузится доair.set_weather(...); приinto_siteпосле выбора стартаset_wind(сила пресета, курс старта).- Термики не зависят от времени суток (решение пользователя, docs/guide/game.md → «Время суток»); солнце для источников — статичное. Это сохраняем: модель описывает «день в разгар».
- Бот-пилоты (
bot_pilot.gd,bots.json) и задания/тренировки (configs/tasks/*) погоду не читают. Планшет (страница ветра) оценивает ветер по GPS — от погоды не зависит. - Пресеты по имени используют тесты и инструменты:
test_atmosphere,test_calibration,test_pilot_words,test_hazards,test_wave,test_clouds_and_generation,test_birds,test_cloud_*,test_ridge_starts,test_strong_wind_launch,test_input_launch,test_matrix,test_game_logic,test_menu_setup,xc/xc_run.gd,tools/atmosphere/xc_matrix.sh,tools/flight/roll_sway.gd,tools/shots/cloud_cut_shot.gd,tools/soak.sh,atmosphere_preview,world_objects_preview,launch_options --weather=.
1. Интерфейс «Полёт…»
Строка «Погода» (выпадающий список пресетов) и «Ветер: встречный / из пресета» удаляются. Вместо них три строки (после «Масса пилота», до «Время старта»):
| Строка | Элемент | Диапазон, шаг | Подпись значения |
|---|---|---|---|
| Температура днём | HSlider (UiKit.slider_row) | 0…+40 °C, шаг 1 (пилот, 28.09) | «+26 °C» |
| (подсказка) | HintLabel под ползунком | — | «Обычно днём в этом месяце: +25 °C» — обновляется при смене месяца |
| Ветер | HSlider | 0…12 м/с, шаг 1 | «3 м/с (11 км/ч)» |
| Откуда ветер | OptionButton | «Встречный», С, СВ, В, ЮВ, Ю, ЮЗ, З, СЗ | при выбранной площадке рядом подсказка «встречный для этого старта — З» |
| Облачность | OptionButton | Ясно / Переменная / Облачно (пилот, 28.09) | — |
Почему так:
- Температура — дневной максимум по прогнозу у земли в долине (на посадке) («днём до +26») — то, что
пилот видит в прогнозе. Подпись сразу «Температура днём», чтобы в фазе 2 (ход дня, §7) смысл не менялся:
утром модель сама возьмёт прохладнее, чем максимум. Ползунок, а не поле ввода: мышью/стрелками, как масса и время. Смена месяца не двигает
ползунок (выбор пилота важнее), только подсказку «обычно…» (таблица
typical_max_cпо месяцам). По умолчанию (первый запуск, нет сохранённого) —typical_max_cдля даты по умолчанию (15 июля → +26). - Ветер в м/с: в русских прогнозах и у наших пилотов ветер в м/с; км/ч — в скобках (в игре везде км/ч).
Хранится
wind_speed_kmh= м/с × 3,6. 0…12 м/с ≈ 0…43 км/ч — выше дельтапланеристы не летают. Шаг 1 м/с (точнее прогноз всё равно не даёт). Вопрос 1 — в каких единицах привычнее. - Направление — 8 румбов + «Встречный», а не ползунок в градусах: прогноз даёт румб («ЮЗ 4 м/с»),
16 румбов избыточны (на игру влияет только угол к склону, а «в лоб» покрывает главный случай).
«Встречный» — по умолчанию и первым пунктом (как сейчас). При ветре 0 список неактивен.
Внутри:
wind_into_launch: bool+wind_from_deg: float(0, 45, … 315). - Облачность (пилот, 28.09): «Ясно / Переменная / Облачно», по умолчанию «Ясно». Влажность пилот сочла ненужной — не добавляем. Ожидаемую кромку/силу термиков в меню не показываем — пилот читает день по небу и вариометру (правило «без подсказок», FR-22).
Сводка в главном меню (start_menu.gd, сейчас «Средний день · 13:00») → «+26 °C · ветер 3 м/с, встречный
· 13:00» (или «… ветер 3 м/с, ЮЗ · 13:00»; при 0 — «штиль»).
Сохранение и перенос старых настроек
29.09.2026: перенос убран — пользователей нет:
legacy_presets,WeatherModel.legacy_forecast,FlightSettings.set_legacy_weather, чтениеweather/wind_modeизlast_flight.json, флаги--weather=<id>и--wind=into_site|presetудалены; тесты и инструменты задают прогноз числами (--temp/--wind/--from). Эталоныconfigs/weather/*остались (калибровка, превью, xc_run).
FlightSettings: поля weather, wind_mode → temperature_c: float, wind_speed_kmh: float,
wind_into_launch: bool, wind_from_deg: float. from_dict: если в словаре нет temperature_c, но
есть weather — взять прогноз из таблицы legacy_presets конфига (§3): weak → +20 °C / 2 м/с,
medium → +26 / 3, strong → +31 / 5, storm → +34 / 4, wave → +18 / 10 (волна больше не выбирается);
wind_mode == "preset" → wind_into_launch = false, wind_from_deg = 270 (из старого пресета),
иначе true. Проверка в UserSettings.load_last_flight: температура/ветер — зажать в диапазон
(вместо «неизвестный пресет → по умолчанию»). Значения по умолчанию — configs/game.json:
default_temperature_c (или null → typical_max_c по дате), default_wind_speed_kmh (11),
default_wind_into_launch (true), default_wind_from_deg (270); default_weather/default_wind_mode удалить.
2. Модель: из прогноза — в погодный словарь
Новый класс WeatherModel (scripts/atmosphere/weather_model.gd, RefCounted, только статические
чистые функции, без узлов):
# forecast: {temperature_c, wind_speed_kmh, wind_from_deg}
# ctx: {month, day, lat, valley_msl_m, mean_msl_m} (из рельефа; для тестов — reference_context из конфига)
static func derive(forecast: Dictionary, ctx: Dictionary, cfg: Dictionary, hour: float = NAN) -> Dictionary
# hour = NAN — «разгар дня» (фаза 1); фаза 2 передаёт час и получает день в этот момент (§7)
static func ground_context(height_fn: Callable, radius_m, samples, low_percentile) -> Dictionary
static func legacy_forecast(preset_id: String, cfg) -> Dictionary # "medium" → {26, 11, 270}derive возвращает словарь с теми же ключами, что пресет (+ служебный _derived с промежуточными
величинами для тестов и tools/weather/derive_table.gd). Детерминированно: одни входы → один день
(сид атмосферы и так постоянный, atmosphere.json → seed).
2.1 Скрытые допущения (всё в configs/weather_model.json)
- Температура
T— дневной максимум у земли в долине. Высота долиныh_v— нижние 10 % высот рельефа в радиусе 10 км (ground_context, тот же радиус и сетка, что для отсчёта кромки); средняя высотаh_m— как сейчас. В перемешанном слое воздух сухоадиабатический, поэтому верх термиков и кромка над уровнем моря не зависят от того, на какой высоте «измерена» температура — достаточно одной точки. - Воздух наверху — климатический для месяца: температура
T_u(месяц)наz_u= 3000 м н. у. м. (≈ 700 гПа), таблица по месяцам для 50–56° с. ш. (Алтай и Башкирия одной таблицей). Выше перемешанного слоя градиентΓ_env= 4 К/км (умеренно устойчивый воздух хорошей погоды). То есть жарче у земли при том же воздухе наверху = неустойчивее = выше и сильнее термики. Весной воздух наверху холодный — и +15 °C в апреле даёт хороший термический день (как в жизни: «весенние термики злые»). - Влажность — обычная для месяца: точка росы
Td(месяц)постоянна в течение дня (таблица по месяцам). Кромка ≈ 122 м × (T − Td) — правило, которое пилоты знают. - Перегрев пузыря над окружающим воздухом у земли
δ= 1 К. - Солнце: только сезон — высота солнца в полдень выбранной даты на широте места; от часа старта термики не зависят (прежнее решение пользователя). Облачность/пелену не выбираем.
- Ветер — на старте (10 м над ним; пилот, 28.09), одинаковое направление на всех высотах; профиль над
рельефом — существующий, плюс рост с высотой над стартом (
wind.altitude_gain_per_km, §6 п. 2). - Перистая пелена — небольшая постоянная, растёт при грозах (остатки наковален).
- Волна — по умолчанию выключена (
wave.enabled: false), см. §2.8.
2.2 Высота термиков и кромка
Все высоты ниже — в км; Δ = z − h_v (над долиной).
Пузырь (сухая адиабата): T_p(z) = T + δ − 9,8·(z − h_v)
Окружающий воздух: T_e(z) = T_u + Γ_env·(z_u − z)
Верх сухих термиков: z_dry = h_v + (T + δ − T_u − Γ_env·(z_u − h_v)) / (9,8 − Γ_env)
Уровень конденсации (кромка): z_lcl = h_v + 0,122·(T − Td)
Запас на облако: m = z_dry − z_lcl (м)
Верх подъёма: z_top = min(z_dry, z_lcl)
cloudbase_agl_m = clamp(z_top − h_m, 300, 3800) (над средней землёй, как сейчас)
top_agl = cloudbase_agl_m (ключ для таблиц силы, §2.3)m < −100 м— «голубой» день: пузыри не доходят до конденсации, облаков нет (cloud_min_strength_ms = 99), термики упираются вz_dry(сухие термики, искать по вариометру).m ≥ 0— кучевые, кромка наz_lcl, облака тем мощнее, чем большеm(§2.4).- Устойчивость для волны:
N = √(g/T_K · (9,8 − Γ_env)/1000)≈ 0,0145 1/с (пресет «волна» был 0,012).
Числа для опорного места (Алтай, июль: h_v ≈ 0,4 км, h_m ≈ 0,9 км — уточнить в карточке 1
инструментом derive_table), T_u(июль) = +2 °C, Td(июль) = +8 °C, Γ_env = 4, δ = 1:
| T у земли | z_dry, км | z_lcl, км | m, м | кромка над средней землёй | день |
|---|---|---|---|---|---|
| +12 | 0,5 | 0,9 | −400 | 300 (минимум) | почти мёртвый, голубой |
| +16 | 1,2 | 1,4 | −200 | ~300–500 | слабые сухие термики |
| +20 | 1,87 | 1,86 | ≈ 0 | ~960 | ≈ слабый (редкие облачка) |
| +26 | 2,9 | 2,6 | 300 | ~1700 | ≈ средний |
| +31 | 3,76 | 3,21 | 550 | ~2300 | ≈ сильный, переразвитие |
| +34 | 4,3 | 3,57 | 700 | ~2650 | грозовой: Cb заметно часто |
Рост кромки ≈ 120–140 м на градус — как у пилотов («+1 °C разницы с точкой росы — +120 м»).
Башкирия (h_v ≈ 0,35, h_m ≈ 0,55) при той же температуре — кромка над землёй на ~300 м выше
(ниже рельеф) — правдоподобно.
2.3 Сила, размер и частота термиков — таблица опор по top_agl
Физический смысл — масштаб Дирдорфа w* = (g/T · H0 · zi)^(1/3): выше слой и сильнее прогрев → сильнее
и шире термики, реже источники (Allen: шаг ∝ zi). Но куб. корень слишком плоский против наших откалиброванных
пресетов, поэтому берём кусочно-линейную таблицу опор anchors_by_top_agl, строки которой — ровно
значения старых пресетов (калибровка карточки 03 сохраняется):
| top_agl, м | strength, м/с | radius, м | spacing, м | duty | фон, м/с | болтанка σ | cloud_size | extreme |
|---|---|---|---|---|---|---|---|---|
| 300 | [0,4; 1,0] | [40; 80] | 2200 | 0,5 | −0,3 | 0,25 | 1,0 | 0 |
| 1000 (weak) | [1,2; 2,5] | [60; 110] | 1500 | 0,7 | −0,4 | 0,4 | 1,0 | 0 |
| 1700 (medium) | [2,4; 4,4] | [90; 170] | 800 | 0,85 | −0,5 | 0,8 | 0,8 | 0 |
| 2300 (strong) | [2,5; 5,0] | [110; 210] | 700 | 0,9 | −0,7 | 1,3 | 0,9 | 0,04 |
| 3200 | [3,0; 5,5] | [120; 230] | 700 | 0,9 | −0,8 | 1,5 | 1,0 | 0,06 |
Поправки к таблице:
- Солнце (сезон):
sun_k = (sin el_полдень / sin el_ref)^0,5,el_ref= 59,5° (15 июля, 52° с. ш.), зажать [0,5; 1,05]. Сила ×sun_k,duty×sun_k^0,5. Апрель ≈ 0,93, январь ≈ 0,58. - Ветер рвёт термики (
wind_thermal_factorпо U10, км/ч): 0–15 → 1,0; 25 → 0,85; 35 → 0,65; 45 → 0,5 — на силу;duty× (1; 1; 0,9; 0,75; 0,6). Механическая болтанка, наклон и снос термиков, склон, подветренные роторы уже считаются в атмосфере от ветра — их не дублируем. - Облачные улицы:
street_strength= 0,7 при кучевых, 0,4 в голубой день (улицы сухих термиков); включаются ли — уже решает код по ветру (thermal.street_min/full_wind_ms: при 3 м/с у земли на половине кромки ≈ 5 м/с — частичные улицы, при 5+ м/с — полные). - Пылевые вихри:
dust_devil_chanceпоtop_agl: 1000 → 0; 1700 → 0,15; 2300 → 0,3 × ветровой множитель (0–15 км/ч → 1; 25 → 0,3; 30+ → 0 — ветер их сдувает).
2.4 Облака — по запасу m
| m, м | dry_thermal_fraction | cloud_depth_m | overdevelopment | cb_chance | cb_thermal_chance |
|---|---|---|---|---|---|
| < −100 | 1,0 (облаков нет, cloud_min_strength_ms = 99) | — | 0 | 0 | 0 |
| 0 | 0,55 | 300 | 0 | 0 | 0 |
| 150 | 0,35 | 500 | 0 | 0 | 0 |
| 300 | 0,25 | 700 | 0 | 0 | 0 |
| 450 | 0,2 | 1000 | 0,15 | 0 | 0 |
| 550 | 0,15 | 1300 | 0,2 | 0,05 | 0 |
| 600 | (линейно 550→750) | 0,125 | 0 | ||
| 700 | (линейно 550→750) | 0,275 | 0,0001 | ||
| 750 | 0,1 | 1600 | 0,45 | 0,35 | 0,0004 |
| 800 / 900 / 1100 | 0,1 | 1600 | 0,45 | 0,35 | 0,001 / 0,0025 / 0,005 |
Грозы редкие (решение пилота, 29.09.2026; было:
cb_chance0,2 уже при +26 — десятки Cb в час вокруг старта).cb_chanceтеперь — «грозовость дня» (клеточный день, наковальни в пелене, см. ниже), а доля сильных термиков, которые правда становятся Cb, —cb_thermal_chance(×sky.cb_k). Вокруг старта (15 км) за час рождаются ~200–450 сильных термиков, так что 0,0001 ≈ одна гроза на 20–50 полётов. Онгудай, 100 сидов (tools/weather/storm_rarity.tscn): +26 — Cb ближе 15 км за первый час в 0–1 % полётов, буря у старта в начале — 0 %; +30 — Cb в 20–40 %, у старта 0–4 %; +34 — Cb в 50–70 % (~1 в час), у старта в начале 3–6 %. На низких локациях (Аскарово, Аушкуль, «Алтай») запас меньше — грозы там начинаются лишь к +34…+38.cloud_min_strength_ms— 1,0 (как medium) приm ≥ −100.cb_top_above_base_m = tropopause_msl_m (11 000) − z_lcl.Гроза делает день «клеточным»:
thermal_spacing_m × (1 + 1,3·cb_chance),cloud_size_factor + cb_chance,background_sink_ms − 0,3·cb_chance,cirrus_cover = 0,1 + 0,5·cb_chance,thermal_extreme_chance0,05·cb_chance.
Гроза детерминирована: какие именно термики станут Cb — как сейчас, по сиду клетки (
cb_thermal_chance— доля). В июле в Онгудае Cb начинаются примерно с +28…+30 °C, при +34 — ≈ 0,5 % сильных термиков (гроза где-то в округе — в большинстве полётов, над стартом — редко). Весной тот же запас получается при меньшей температуре (холодный воздух наверху) — весенние ливни из Cb.
2.5 Опорные прогнозы ≈ старые пресеты (опорное место, июль)
| Прогноз | ≈ старый пресет | Что отличается |
|---|---|---|
| +20 °C, 2 м/с | weak | кромка ~960 (было 1100) |
| +26 °C, 3 м/с | medium | кромка ~1700 (было 1800) |
| +31 °C, 5 м/с | strong | кромка ~2300 (было 2600) |
| +34 °C, 4 м/с | storm | кромка ~2650 (было 1700): постоянная точка росы даёт «сухие» высокие грозы; если пилот скажет, что грозы с низкой кромкой — поднять Td при жаре (td_follow_heat, по умолчанию 0) |
+18 °C, 10 м/с (+ wave.enabled) | wave | только для тестов волны |
Расхождения кромки ±15 % — «достаточно хорошо»; T_u, Td, Γ_env подгоняются в калибровке карточки 1,
если пилот скажет, что опоры не те (вопрос 3).
2.6 Холод и зима
Ниже m < −100 и top_agl 300 — «мёртвый» день: слабые пузыри у склонов, полёт — спуск или склон при ветре.
Это честно (ноябрьский полёт так и выглядит). Облака не рисуются — небо чистое.
2.7 Что остаётся постоянным
thermal_mode = “dynamic”, static_thermals = [] (статичные ставит локация, как сейчас), thermal_extreme_ms
= [7; 9], lens_level_above_crest_m = 2200. Поле name больше не нужно (сводка меню строится из прогноза).
2.8 Волна (скрыто, по умолчанию выкл.)
wave.enabled = false → wave_strength = 0 всегда. Если включить (после ответа пилота, вопрос 2):
wave_strength = max_strength (0,5) × clamp((U10 − 24)/16, 0, 1) × clamp((1700 − top_agl)/700, 0, 1) —
только сильный ветер (≥ 7 м/с у земли, ≥ 12 м/с на 1,5 км) и слабая конвекция (устойчивый воздух);
сильные термики ломают волновую картину. stability_n_per_s = N из §2.2. Подветренная зона (прижим,
роторная болтанка, ослабленный ветер за хребтом — atmosphere.json → lee) работает всегда, от ветра.
3. Конфиги
Новый configs/weather_model.json (все числа с _doc, по-русски):
{
"_doc": "Погода из прогноза (FR-16): температура + ветер → словарь погоды (как бывшие пресеты). scripts/atmosphere/weather_model.gd, docs/guide/atmosphere.md → «Погода из прогноза».",
"ui": { "temperature_c": [-10, 40, 1], "wind_ms": [0, 12, 1], "compass_points": 8 },
"typical_max_c": [-12,-10,-2,9,18,23,26,23,16,7,-3,-9], // подсказка и значение по умолчанию, по месяцам
"upper_air": { "z_msl_m": 3000, "temp_c": [-17,-16,-12,-7,-2,1,2,1,-3,-8,-13,-16], "lapse_k_per_km": 4.0 },
"dew_point_c": [-20,-19,-12,-4,1,6,8,7,2,-3,-10,-17],
"td_follow_heat": 0.0,
"parcel_excess_k": 1.0,
"lcl_m_per_k": 122,
"valley_percentile": 0.1,
"cloudbase_agl_clamp_m": [300, 3800],
"tropopause_msl_m": 11000,
"sun": { "ref_elevation_deg": 59.5, "exponent": 0.5, "clamp": [0.5, 1.05] },
"anchors_by_top_agl": [ {"top_agl_m": 300, ...}, {"top_agl_m": 1000, ...}, ... ], // §2.3
"clouds_by_margin": [ {"margin_m": -100, ...}, ... ], // §2.4
"blue_margin_m": -100,
"wind_factors": { "wind_kmh": [0,15,25,35,45], "strength": [1,1,0.85,0.65,0.5], "duty": [1,1,0.9,0.75,0.6], "dust": [1,1,0.3,0,0] },
"street_strength": { "cumulus": 0.7, "blue": 0.4 },
"dust_by_top_agl": { "top_agl_m": [1000,1700,2300], "chance": [0,0.15,0.3] },
"storm": { "spacing_k": 1.3, "cloud_size_k": 1.0, "sink_k": -0.3, "cirrus_k": 0.5, "extreme_k": 0.05 },
"cirrus_base": 0.1,
"wave": { "enabled": false, "max_strength": 0.5, "min_wind_kmh": 24, "full_wind_kmh": 40, "max_top_agl_m": 1700, "fade_top_m": 700 },
"constants": { "thermal_extreme_ms": [7,9], "cloud_min_strength_ms": 1.0, "lens_level_above_crest_m": 2200 },
"reference_context": { "month": 7, "day": 15, "lat": 51.9, "valley_msl_m": 400, "mean_msl_m": 900 },
"legacy_presets": {
"weak": {"temperature_c": 20, "wind_speed_kmh": 7, "wind_from_deg": 270},
"medium": {"temperature_c": 26, "wind_speed_kmh": 11, "wind_from_deg": 270},
"strong": {"temperature_c": 31, "wind_speed_kmh": 18, "wind_from_deg": 270},
"storm": {"temperature_c": 34, "wind_speed_kmh": 14, "wind_from_deg": 270},
"wave": {"temperature_c": 18, "wind_speed_kmh": 36, "wind_from_deg": 270}
}
}(Имя weather_model.json, а не weather.json: рядом каталог configs/weather/, Config.get_config("weather")
и list_configs("weather") путались бы.)
configs/weather/*.json остаются как эталоны для тестов физики (слова пилота, калибровка, гроза, волна,
пыль — эти тесты проверяют атмосферу при известном словаре и не должны ломаться при подгонке вывода). В меню не
показываются. В _doc каждого: «Эталон для тестов и калибровки; в игре погода выводится из прогноза
(configs/weather_model.json), опорный прогноз — legacy_presets.name убрать (не нужно).
atmosphere.json → default_weather = “weather/medium” остаётся (превью, тесты без рельефа).
game.json: default_weather, default_wind_mode → default_temperature_c, default_wind_speed_kmh,
default_wind_into_launch, default_wind_from_deg.
4. Что затронуто
| Где | Что меняется |
|---|---|
scripts/atmosphere/weather_model.gd | новый: derive, ground_context, legacy_forecast, typical_max_c(month) |
scripts/atmosphere/atmosphere.gd | без изменений логики; set_weather принимает словарь (уже умеет). Опционально set_weather("forecast:…") не нужно |
scripts/game/game.gd | после загрузки рельефа: ctx = WeatherModel.ground_context(terrain.height_at, …) + дата/широта → air.set_weather(WeatherModel.derive(settings.forecast(), ctx, cfg)); ветер: into_launch → set_wind(settings.wind_speed_kmh, _start_heading), иначе направление из настроек (уже в словаре) |
scripts/game/calm_air.gd | ничего (берёт словарь) |
scripts/game/flight_settings.gd | новые поля, forecast(), перенос из weather/wind_mode |
scripts/game/user_settings.gd | зажим диапазонов вместо проверки пресета |
scripts/game/launch_options.gd | --temp=<°C> --wind=<м/с> --from=<град|launch>; --weather=<id> оставить → legacy_forecast (скрипты, xc_matrix); старый --wind=into_site|preset → --from |
scripts/ui/flight_setup_screen.gd | три строки §1, подсказки |
scripts/ui/start_menu.gd | сводка из прогноза |
locale/ui.csv | ключи ниже; удалить setup_weather, setup_wind_from_preset, weather_weak/medium/strong/wave (+ «Гроза», если есть) |
| Бот-пилоты, задания, тренировки | не читают погоду — без изменений. Тренировка «вдоль склона» требует ветра — пилот ставит сам (не подсказываем) |
| Планшет / страница ветра | оценка по GPS — без изменений; слова «волна» там нет и не появляется |
| Загрузка | этап «Погода и термики…» — как есть (вывод мгновенный, < 1 мс) |
atmosphere_preview, world_objects_preview, tools/shots/* | добавить --temp/--wind/--from (через derive с reference_context), --weather= оставить |
| Тесты | см. карточку 6 |
| Документы | REQUIREMENTS FR-16 и VR-27, docs/guide/atmosphere.md, docs/guide/game.md, CHANGELOG.md |
Ключи перевода (схема область_что, ru / en)
| Ключ | ru | en |
|---|---|---|
setup_temperature | Температура днём | Daytime high |
setup_temperature_value | %+d °C | %+d °C |
setup_temperature_typical | Обычно днём в этом месяце: %+d °C | Typical daytime high this month: %+d °C |
setup_wind_speed | Ветер | Wind |
setup_wind_speed_value | %d м/с (%d км/ч) | %d m/s (%d km/h) |
setup_wind_calm | штиль | calm |
setup_wind_from | Откуда ветер | Wind from |
setup_wind_into_launch (уже есть) | Встречный | Headwind |
setup_wind_launch_faces | встречный для этого старта — %s | headwind for this launch: %s |
setup_sky | Облачность | Cloud cover |
setup_sky_clear / _partly / _overcast | Ясно / Переменная / Облачно | Clear / Partly cloudy / Overcast |
compass_n … compass_nw (8) | С, СВ, В, ЮВ, Ю, ЮЗ, З, СЗ | N, NE, E, SE, S, SW, W, NW |
menu_summary_forecast | %+d °C · ветер %s | %+d °C · wind %s |
menu_summary_wind | %d м/с, %s | %d m/s, %s |
(Существующий setup_wind «Ветер» можно переиспользовать вместо setup_wind_speed.)
5. Карточки
Порядок: 1 → 2 → 3 → (4, 5 после слияния английского перевода) → 6 → 7. Коммит каждой — git commit -- <свои пути>.
Все запуски Godot агентами — с временным XDG_DATA_HOME (не трогать профиль пилота).
1. WeatherModel + конфиг + таблица для пилота — M, Opus
Владения: scripts/atmosphere/weather_model.gd, configs/weather_model.json, tests/atmosphere/test_weather_model.gd,
tools/weather/derive_table.gd(.tscn).
Шаги: формулы §2 (интерполяция таблиц — общий хелпер, как SunClock._lerp_f); ground_context по
height_fn (сетка reference_samples², процентиль); derive_table печатает для 4 локаций × T {−5, 10, 15, 20,
26, 31, 34, 38} × ветер {0, 3, 5, 7, 10} м/с × месяцы {4, 7, 9}: h_v, h_m, z_dry, z_lcl, m, кромку, силу,
шаг, долю сухих, Cb, пыль — и пишет ту же таблицу простым языком в tmp_weather_table.md (для пилота).
Уточнить reference_context реальными h_v/h_m Алтая.
Приёмка (headless-тест):
- опорные прогнозы §2.5 в
reference_contextдают:thermal_strength_msв ±20 % пресета,cloudbase_agl_mв ±20 %,dry_thermal_fraction±0,12,background_sink_ms±0,15; storm —cb_chance0,2…0,45; medium/weak —cb_chance= 0,thermal_extreme_chance= 0; strong — extreme 0,02…0,06; - монотонность по T (шаг 1 °C, 10…34): кромка и верх силы не убывают;
- по ветру (0…45 км/ч): сила не растёт,
dust_devil_chance= 0 при ≥ 30 км/ч; - голубой день: T = +16, июль →
cloud_min_strength_ms≥ 50; T = +12 → кромка = 300; - апрель +15 °C сильнее июля +15 °C (кромка выше);
wave_strength= 0 при любом входе, покаwave.enabled = false; приtrueи 10 м/с, +18 °C — > 0;- детерминизм: два вызова — равные словари; все ключи пресета присутствуют и числовые;
- время
derive< 1 мс,ground_context< 20 мс.
2. Настройки, перенос, командная строка — S, Sonnet
Владения: flight_settings.gd, user_settings.gd, launch_options.gd, configs/game.json (ключи погоды),
tests/game/test_game_logic.gd (часть про настройки).
Приёмка: старый last_flight.json {weather: "weather/strong", wind_mode: "preset"} → +31 °C, 18 км/ч,
wind_into_launch = false, 270°; {weather: "weather/wave"} → +18 °C, 36 км/ч; неизвестный пресет → значения по
умолчанию; мусорные числа зажаты в диапазон; to_dict/from_dict — туда-обратно без потерь;
--temp=31 --wind=5 --from=launch и --weather=medium работают.
3. Подключение в игре — S, Sonnet
Владения: game.gd (участок start), calm_air.gd при необходимости, atmosphere.md раздел «Погода из
прогноза».
Приёмка: tests/game — полёт с +26/3 м/с на altai даёт в air.weather кромку в ±10 % от derive с ctx
этого рельефа; «в лоб» — wind.from_deg = курс старта; румб — заданный; точка с карты работает (ctx от
загруженного вокруг точки рельефа).
4. Экран «Полёт…» и сводка меню — S, Sonnet (после слияния перевода)
Владения: flight_setup_screen.gd, start_menu.gd, locale/ui.csv (ключи §4), tests/ui/test_menu_setup.gd.
Приёмка: нет строк «Погода» и «Направление из пресета»; нигде нет слова «волна/wave» (grep -ri "волн\|wave" locale scripts/ui — пусто); смена месяца меняет подсказку, но не ползунок; ветер 0 → список направлений неактивен;
«Готово» → FlightSettings с выбранными числами; подписи на ru и en (переключение языка) влезают в панель
без обрезки; скриншот экрана (ru и en) — в отчёт.
5. Тесты и инструменты на прогноз — M, Sonnet
Владения: tests/game/test_input_launch.gd, tests/stability/test_matrix.gd, tests/flight/test_strong_wind_launch.gd,
tests/atmosphere/xc/xc_run.gd, tools/atmosphere/xc_matrix.sh, tools/flight/roll_sway.gd, tools/shots/*,
tools/soak.sh, atmosphere_preview.gd, world_objects_preview.gd.
Шаги: матрицы старта/стабильности — по прогнозам {+20/2, +26/3, +31/5 м/с} вместо трёх пресетов (плюс один
прогон +34/4 и +15/8 на стабильность); превью и кадры — --temp/--wind/--from; --weather= оставить.
Физические тесты (test_pilot_words, test_calibration, test_hazards, test_wave, облака, птицы, склон)
не трогать — они на эталонах configs/weather/*.
Приёмка: весь набор godot --headless --path . res://tests/run_tests.tscn зелёный; xc_matrix.sh с
XC_WEATHERS="medium strong" работает как раньше.
6. Кадры в реальном рендере — S, Sonnet
Инструмент tools/shots/forecast_shot.gd (по образцу itch_shot): Алтай, Синюха-запад, 13:00, 15 июля, вид со
старта на долину и вид сверху на 1 км выше старта (облака и тени). Матрица: T {15, 25, 32} × ветер
{5, 20, 35 км/ч ≈ 1, 6, 10 м/с} = 9 кадров + 2 весенних (апрель +15, +25).
Приёмка (глазами + лог _derived рядом с кадром): +15 — голубое небо или редкие клочья; +25 — кучёвка 2–4 окт.;
+32 — крупные кучевые, переразвитие, при слабом ветре пыльные вихри; 35 км/ч — улицы вытянуты по ветру, облака
рваные, меньше; ни одного лентикуляра (волна выкл.). Принятые кадры — в build/screenshots с русскими
нумерованными именами («01_плюс15_ветер1.png» …).
7. Документы и вопросы пилоту — S, Sonnet
REQUIREMENTS.md: FR-16 → «Погода задаётся как в прогнозе: температура воздуха, сила и направление ветра (или встречный на старте); сила и частота термиков, кромка, облачность, сухие термики, пылевые вихри и грозы выводятся из них, даты и места». VR-27 → «(скрытый эффект сильного ветра поперёк хребтов, по умолчанию выключен)». docs/guide/atmosphere.md (раздел «Погода из прогноза» с формулами и таблицами §2, строки 12 и 356 про пресеты), docs/guide/game.md (экран «Полёт…»), CHANGELOG.md, вопросы §6 — в questions.md простым языком + таблица из карточки 1 (что получается при каких числах).
6. Вопросы пилоту — ответы (28.09.2026)
Ответы пилота (через пилота-консультанта) и что из них сделано:
Ветер — в м/с, и это ветер на старте (не на 10 м в долине). → Ползунок в м/с (км/ч в скобках). Профиль ветра опирается на старт:
Gameпередаёт высоту стартаAtmosphere.set_wind(скорость, курс, ref_msl); на старте (10 м над землёй) — ровно ветер прогноза.Ветер растёт с высотой: в горах Алтая на ~1 км выше старта ветер до 15 м/с — обычное дело, «никуда не улетишь». →
atmosphere.json → wind.altitude_gain_per_km(0,6),altitude_min/max_factor(0,5 / 2,0): множитель 1 + 0,6 × (высота над стартом, км) поверх профиля над рельефом (×1,8). 5 м/с на старте → ~14 м/с на 1 км выше; в долине ниже старта ветер слабее.Волну пилот не знает — остаётся выключенной (
wave.enabled: false), в интерфейсе её нет. За хребтом (подветренная сторона) — смертельно опасно: ротор бьёт сверху сразу, туда нельзя. → Подветренное опускание и роторная болтанка усилены (растут с ветром поперёк хребта, при слабом ветре мягкие), ротор у подветренного склона с обратным потоком — карточка «ротор», без подсказок.Кромка в грозовой день зависит от температуры и высоты инверсии. → Кромка Cb выводится так же, как у кучевых: уровень конденсации по разнице T − Td, ограниченный верхом перемешанного слоя (инверсией):
min(z_dry, z_lcl); утром — ещё и приземной инверсией (§7).Нужна облачность: «ясно / переменная / облачно» (en: clear / partly cloudy / overcast). Облачно — прогрев на порядок слабее и мягче, мало слабых термиков; переменная — между. Влажность не нужна. → Четвёртая строка экрана «Облачность» (
FlightSettings.sky,weather_model.json → sky): пелена (cirrus_cover) ослабляет прогрев в атмосфере, плюс мягкость, меньше рождений, без гроз под облачностью.Температура 0…+40 °C (снег лежащий не рисуем — ниже нуля не нужно). +15 °C весной — хорошие термики (проверено тестом: апрель +15 на Алтае — верх ~1,8 км, 2–4 м/с). Зимой термики бывают — от прогретых солнцем поверхностей (лес, край белого и зелёного), но без снега на рельефе это только заметка к фазе 2 (§7.3 п. 10).
Температура в меню — дневной максимум по прогнозу (пилот подтвердила 28.09.2026). Модель сама считает температуру на каждый час по суточному ходу (фаза 2, §7.2: Parton–Logan от максимума, утренняя инверсия) —
WeatherModel.diurnal_state,weather_model.json → diurnal.
Открытым остаётся только общее «похоже ли на правду» по опорам (июль: +20 — слабо, +26 — хороший день,
+31 — сильно, с +30 грозы) — проверит в игре; таблица — tools/weather/derive_table → tmp_weather_table.md.
7. Фаза 2 — «атмосфера следует за солнцем» (после фазы 1, её не блокирует)
TODO.md, этап 6 → «Время суток»: прогрев и остывание с инерцией, сила и высота термиков по времени дня, вечерняя дымка; слова пилота: утром — мягкие термики; днём — жёсткие, всё острее; вечером — тёплый, очень хороший мягкий воздух, хорошая динамика у склона, термиков мало. Облачно — прогрев на порядок слабее и мягче. Зимой при ясном небе — термики от леса против снежного поля; утром и вечером — только ветер.
7.1 Что уже есть (проверено по коду)
- Источники термиков уже следуют за солнцем.
Game.startподключаетsky.clock.sun_changed → terrain.set_sun;Terrain.thermal_source_strength_at=class_strength[класс] · exposure_gain · (n·sun)^power · (1 + edge)с живым_sun_dir,GroundField.sun()не кеширует. Отсечкаthermal.sun_min= 0,35 уже даёт «утром/вечером — только склоны к солнцу»: ровный луг при солнце ниже ~20° не даёт термиков. (docs/guide/game.md → «Время суток» тут устарел: пишет, что источники берут статичное солнце.) - Перистая пелена ослабляет прогрев (
insolation), тени зрелых облаков гасят новые термики. - Нет: инерции прогрева (поверхность откликается на солнце мгновенно, одинаково для камня и поля);
хода по времени самой силы, частоты, «жёсткости» термиков, кромки и верха (весь день — «разгар»); тени облаков
для воздуха берут статичное солнце (
air.set_sun_directionодин раз изworld.json → sun, аThermalField.sun_dir— изclouds.sun_*); вечерней дымки; сезонного снега; «облачного» дня.
7.2 Что значит температура пилота
Дневной максимум по прогнозу («днём до +26»), как в фазе 1 — не температура на час старта. Почему: в прогнозе крупно пишут максимум; пилот ставит одно число и не пересчитывает его при старте в 9 утра; при ускорении времени (×10, ×60) смысл не «плывёт». Температура в момент t — из суточного хода (Parton–Logan):
T(t) = T_max − A·(1 − f(t)), A = diurnal_range_k[месяц] (июль ≈ 11 К, апрель ≈ 12, январь ≈ 6)
f — синус от восхода (0) до t_peak = солнечный полдень + 2,5 ч (1), после — спад к закату до f ≈ 0,657.3 Модель
- День в момент t:
WeatherModel.derive(forecast, ctx, cfg, hour)берётT(t)→ верх сухих термиков и кромка (§2.2) растут с утра («кромка поднимается весь день»), облака появляются, когдаz_dryдорос до уровня конденсации (утро голубое, кучёвка с ~11–12, Cb — во второй половине дня), вечером тают. - Поток тепла от земли с инерцией (новый
SurfaceHeating, 9 классов): для каждого классаdS_c/dt = (a_c · I(t) − S_c)/τ_c,I(t) = max(0, sin h_солнца) · insolation.τ_c: поле/луг 0,5 ч, скалы/осыпи 1,5 ч, лес 1 ч (иa_cниже — испарение), застройка 2 ч, вода 10 ч (≈ нет хода), снег —a_c≈ 0,1 (альбедо). Экспозиция склона берётся по солнцу с запаздыванием классаsun_dir(t − lag_c)(lag ≈ 0,6 τ_c): вечером камни и деревни ещё греют («вечерние термики с камней»). Стоимость — 9 скаляров на шаг часов + по вектору солнца на класс;thermal_source_strength_at— +1 умножение. - Сила — от потока, высота — от температуры (Дирдорф
w* ∝ (H0·zi)^(1/3)): множитель силыheat_k(t) = (H(t)/H_пик)^(1/3), гдеH— поток над ровным лугом из п. 2. Утро:ziмал,Hрастёт — слабые мягкие; 13–16 ч — пик; вечер:ziещё высокий,Hмал — редкие широкие слабые термики, воздух тёплый и ровный. - «Жёсткие» и «мягкие» — один параметр
sharpness(t) = H(t)/H_пик(0,5…1): при мягких радиус × 1,3, ядро × 0,8, болтанка у края (turbulence.edge_factor) × 0,6, конвективная σ ∝w*. В полдень — как сейчас. - Вечерний спокойный воздух: за 2 ч до заката механическая болтанка × 0,6 (устойчивый приземный слой), склон работает как днём → «хорошая динамика у склона».
- Мягкое обновление атмосферы: раз в
diurnal.update_s(60 с игрового времени)Game.tickзовётair.update_weather(derive(..., hour));Atmosphereплавно (за ~5 мин) ведёт кромку, фон, σ, пороги облаков, аThermalFieldрождает новые термики с новыми параметрами — без пересоздания поля (шаг сеткиthermal_spacing_mфиксирован на весь день — по пику, иначе термики «прыгают»). Живые термики доживают со старыми числами — переход сам собой плавный. - Тени облаков для воздуха — по часам:
sky.clock.sun_changed.connect(air.set_sun_direction)(+ThermalField.sun_dirоттуда же). - Вечерняя дымка: плотность дымки растёт после пика и у низкого солнца, верх дымки — верх
перемешанного слоя в момент t (
sky.set_inversion_height_mslна каждом обновлении). - Облачный день — только если пилот попросит 4-й параметр (вопрос 5):
insolation× 0,15,sharpness0,5, без Cb. Иначе — только перистая пелена и тени своих облаков. - Зима: нужен сезонный снег на открытых классах (луг/поле/кустарник → снег при устойчивом снежном покрове,
месяцы из конфига локации) — карточка группы «рельеф». Тогда лес (
class_strength0,45) против снега (0,1) сам даёт «термики от леса», а низкое солнце иsun_min— «утром и вечером только ветер».
7.4 Карточки фазы 2 (оценка)
| # | Что | Размер | Модель | Владения |
|---|---|---|---|---|
| 2.1 | Суточный ход T(t), derive(..., hour), тесты (утро < полдень, кромка растёт до пика, облака с ~11 ч, Cb после 14 ч) | S | Sonnet | weather_model.* |
| 2.2 | Мягкое обновление погоды в Atmosphere/ThermalField без пересоздания, плавная кромка, дымка за кромкой; вызов из Game.tick | M | Opus | atmosphere.gd, thermal_field.gd, game.gd |
| 2.3 | SurfaceHeating (инерция по классам) + API рельефа: запаздывающее солнце и поток по классам в thermal_source_strength_at | M | Opus | scripts/atmosphere/surface_heating.gd, terrain.gd (стык с группой «рельеф») |
| 2.4 | «Жёсткие/мягкие» (sharpness), вечерняя спокойная болтанка; проверка словами пилота (тест: σ у края в 10 ч < 14 ч; в 19 ч термиков на 10 км² в 3+ раза меньше, чем в 14 ч; набор на склоне в 19 ч ≥ дневного) | S–M | Opus | atmosphere.gd, weather_model.*, тесты |
| 2.5 | Тени облаков по часам для воздуха | XS | Sonnet | game.gd |
| 2.6 | Вечерняя дымка | S | Sonnet | sky_environment.gd, world.json → haze (группа «мир») |
| 2.7 | Сезонный снег + зимний тест («лес > снег, в 10 и 16 ч термиков нет») | M | Opus | группа «рельеф» |
| 2.8 | Облачный день (только по ответу пилота) | S | Sonnet | weather_model.*, UI |
| 2.9 | Кадры: 9, 12, 15, 18, 20 ч при +26 и +31; XC-бот по времени дня (старт в 11 ч проходит больше, чем в 17 ч) | S | Sonnet | tools/shots/*, tests/atmosphere/xc/* |
Итого фаза 2 ≈ L (6–8 карточек). Главный риск — 2.2 (непрерывность облаков и термиков при обновлении) и
производительность 2.3 (замер thermal_source_strength_at до/после, допуск +10 %).