Нейросеть вместо решателя поля ветра — план

Состояние на 04.10.2026: идущий эксперимент (решение пользователя 04.10). Этап 1 завершён: пилоты П-1, П-2, П-3 (docs/archive/plan/air-nn-p3.md, раздел «0. Итог P3») — заменимость решателя сетью не достигнута, лучшая сеть — P2 на 300 местах (медиана ошибки ветра 0,62 м/с), она в игре с 1.0.5 как «Нейросеть (экспериментально)» (docs/archive/plan/air-onnx.md). Этап 2 идёт отдельно — ветка feature/ann2 (AN-1).

Исходный статус: план (направление выбрано пользователем 02.10.2026). Реализацию начинать по команде пользователя. Числа времени — из замеров (docs/guide/air-model.md, docs/research/air_field_cache.md) или помечены «оценка».

Связанное: модель воздуха — docs/guide/air-model.md, контракты — docs/contracts/air-model.md (C1–C10), код игры — scripts/atmosphere/air_model/; эталонный решатель на CuPy — tools/research/air3d/ (solver.py, air.py, reference.md; GLSL игры — его перенос, C1); проверки по измерениям — tools/research/cases/ (Askervein, Perdigão); покров WorldCover — docs/plan/osm_vector_pack.md; источники рельефа — docs/research/terrain_sources.md; влажность в погоде — scripts/atmosphere/weather_model.gd (точка росы, z_lcl).

Решения пользователя (не обсуждаются)

01.10.2026:

  • Инференс — ONNX, в Godot — нативным расширением.
  • Вход — карты (рельеф, вода, нагрев) + числа погоды/ветра; направление ветра — поворотом карты к одной стороне.
  • Выход — u, v, w, θ′ (и w_mech — без нагрева) на нескольких высотах над рельефом.

02.10.2026:

  • В рантайме решателя нет. Цель модуля — заменить GPU-решатель Пикара (AirPicardJob, окна) ONNX-сетями полностью; режим «тёплый старт решателя от сети» снят.
  • Офлайн — честный решатель на Python + GPU: без ограничений рантайма по времени, сетке и итерациям. Он — единственная «физика» поля; сеть — его сжатие.
  • Сеть области 400 м + отдельные сети мельче (окна 100 и 50 м).
  • Охват — любая точка рельефа Земли. Объём набора данных — вопрос времени счёта, не сложности: считаем столько, сколько нужно для охвата.
  • Пересчёт модели (новая физика → новый набор → новая сеть) за неделю — приемлемо.
  • Новые физические входы добавляются через офлайн-решатель и новые каналы сети: поглощение света подложкой (альбедо/покров), влажность.
  • Светопоглощение поверхности закладываем сразу (не через шлюз): альбедо по покрову входит в офлайн-решатель NN-2 и в вход сети с первой версии набора.
  • Обучение — с честной проверкой сходимости сети: отложенные данные, повторные циклы, кривые обучения (§5).
  • Самым первым — пилот: тестовый цикл обучения, чтобы увидеть, что идея вообще рабочая (этап П, шлюз ШП) — до инструментов и всего остального.
  • Затем инструменты. До честной физики, большого набора и долгого обучения — весь конвейер (решатель-обёртка, генератор, загрузчики, обучение, проверка, экспорт, инференс) собирается и проверяется сквозными тестовыми прогонами на малых данных: должно быть доказано, что конвейер обучения работает (волна 0, шлюз Ш0).
  • Машина счёта: RTX 4070 SUPER, Xeon E5-2666 v3, ОЗУ 32 ГБ; диск под данные — отдельный, размер по §4.3.
  • Читаемая структура файлов и каталогов данных — правила и проверка (§4.4).
  • Всё, что можно делать детерминированно, — делается детерминированно, скриптом; работу скриптов агенты не делают (§4.5).
  • Прерывание на любом месте (Ctrl+C, kill, падение, выключение питания) — повторный запуск той же команды корректно продолжает конвейер без потерь, дублей и ручной чистки (§4.6).
  • Параллельный счёт набора закладывается сразу (пакет случаев на GPU, конвейер CPU ↔ GPU — §4.1, задача NN-T2).
  • addons/debug_draw_3d удаляется полностью (вместе с меню Debug Menu, F2): снимает конфликт символов libstdc++ (STB_GNU_UNIQUE) с ONNX Runtime, найденный пробником NN-7а; графики CPU/GPU не нужны, остаётся только FPS по F1 (perf_text() в scripts/game/debug_overlays.gd); стрелки ветра F5 остаются — перерисовать своим мешем (подтверждено пользователем 02.10), слой не терять (задача NN-7б).

02.10.2026 (итог первого пилота, прогон 2026-10-02_pilot; вердикт скрипта «правим подход и повторяем пилот»):

  • Повторный пилот П-2 — на 300 реальных рельефах (не гладких). Готовые 1890 случаев (main: встроенные, синтетика, 40 процедурных) остаются в наборе; отложенные процедурные — отдельная проверка «чистой аппроксимации».
  • Рельеф — тем же путём, что вход игры в рантайме: тайлы Terrarium как scripts/terrain/terrarium_loader.gd (то же сглаживание — его нет, — осреднение в клетки 400 м); обучение видит ровно то, что увидит игра.
  • Выбор рельефов: 300 квадратов 38,4 км по горам всей суши, стратифицированно по уклону/перепаду (уклон на сетке 400 м до ≥ 0,4, размах до 2–2,5 км — диапазон Онгудая и круче). Целые горные системы — отложены (главный показатель проверки); Онгудай — вне обучения.
  • Вход сети: нормировка рельефа прежняя ((h − mean)/1000 м, фиксированные константы, без min-max на случай); добавить карты уклона вдоль и поперёк ветра (по точному направлению ветра), положения в рельефе (относительно окрестности ~2 и ~8 км) и открытости/затенённости на ветер — только из того, что у игры есть без решателя.
  • Счёт: только область 400 м (без окон 100 м); 10–12 условий на рельеф; если хватает времени — больше рельефов, а не условий. Цель обучения (поле − профиль притока)/max(U10, 1) — не менять.
  • Критерий ШП-2: ветер — ошибка ≤ max(0,3 м/с; 10 % скорости) в ≥ 90 % точек; подъём — < 0,1 м/с в ≥ 90 % точек; отдельно — нет систематического смещения (средняя знаковая ошибка скорости по области и по высотам, в т. ч. у гребней). Оценки: отложенные горные системы (главное), Онгудай, отложенные процедурные, знакомые рельефы + новые условия, кривая по числу рельефов 25/50/100/200/300.
  • Пачки и детерминизм: всё детерминированное — скриптом (§4.5); порядок стадиями пачкой — сначала всё скачать (все тайлы всех мест одним прогоном, с продолжением), потом всё обработать одним прогоном, потом счёт решателя, подготовка, обучение, оценка — без циклов «одно место → посмотрел → следующее». Параллельно по CPU (пул процессов) и GPU (несколько случаев одновременно, конвейер подготовка ↔ GPU), шаги разных стадий на GPU не пересекаются. Каждая стадия прерываема и продолжается той же командой (§4.6), вывод — «этап n из N» + одна строка прогресса, подробный лог в файл. Массовый счёт запускается только по согласованию (пользователь сам, в tmux, или фоном).

02.10.2026 (ответы на вопросы координатора П-2):

  • Верхний предел размаха высот в квадрате — ~3000 м (Гималаи 4–5 км не берём: счёт решателя растёт с размахом).
  • Порог смещения ШП-2 — max(0,1 м/с; 2 %) — подтверждён.
  • Проверка по типам рельефа (для всего модуля, в пилоте П-2 — не обязательно): ошибки отдельно по типам — ровный склон-линия (как Аскарово), одиночный «пупырь», двойной пупырь, две параллельные линии, сложный рельеф (§7).
  • Кориолис при обучении не учитываем — отражение поперёк ветра как аугментация (вопрос пользователя: на 40 км и при точности решателя Кориолис не важен). Проверено: в решателе силы Кориолиса нет, f_cor — только для высоты слоя; отражение точное (§3.2), в П-2 — случайное отражение в обучении (NN-P5, контракт П2 — координатор).
  • Рельеф как набор паттернов (идея пользователя: «как сжатие JPEG — матрица паттернов»): в план модуля, не в пилот — линейная теория обтекания по гармоникам как карта входа и базовая линия (§3.2), покрытие набора в пространстве паттернов (§4).

03.10.2026 (ответы на вопросы координатора П-2, Q1–Q3):

  • Отложенные системы + Южные Альпы Новой Зеландии (15 мест): на tiles/v1 отложенные системы положе пула (медиана уклона 0,068) — добавить крутую систему, перевыбор и нарезка (П6 v2, tiles/v2/; NN-16).
  • Цель на несошедшихся решениях — среднее поздних состояний (вариант Б; снимки 500…1000 через 50 без правки решателя, П1 v3, NN-17); ШП-2 — отдельно для сошедшихся и несошедшихся (П3 v3, NN-18).
  • Отказ от направления — если медиана ошибки ветра сети на отложенных системах ≥ 1,0 × лучшей базовой линии.
  • До утра: на вопросы координатора принимать рекомендованный вариант, а без рекомендации — наименее трудозатратный; П-2 — проверка, стоит ли идея работы. Готовый П-2 (полный ./run_pilot.sh) запустить без дополнительного одобрения, в отдельном окне tmux (2026-10-03) — пользователь спит; ценность идеи ещё не ясна — не тратить силы на шлифовку
  • Стратегия: в игре решатель заменяется ONNX-сетью; решатель остаётся офлайн как источник данных. Скорость важнее точности: небольшая систематическая ошибка (порядка 10–15 % ветра на отдельном месте) допустима — пилот примет её за порыв; это игра, а не прогноз погоды. Вместо эмпирического запасного кода — страж области применимости входа. ШП-2 теперь отвечает на вопрос «насколько сеть готова к замене», отказ маловероятен (2026-10-03) — решатель не всегда сходится, новая физика в шейдерах — тяжёлая возня, .onnx поставляется файлом и быстрее на слабых ПК
  • Главный вопрос ШП-2 — заменимость: воспроизводит ли сеть решатель в пределах собственной погрешности решателя. Ошибку сети сравнивать с неопределённостью самого решателя: разброс поздних состояний (несошедшиеся), невязка или остаточные колебания у сошедшихся, расхождение решателя с измерениями (Askervein) и между версиями решателя. Сеть ≲ погрешности решателя → они взаимозаменяемы (2026-10-03) — игре нужна замена решателя, а не точность выше той, что даёт сам решатель

1. Цель и что меняется

Цель: этап «Рассчитываем ветер» и пересчёт в полёте — несколько проходов сети на CPU вместо итераций Пикара на GPU; поле не хуже нынешнего на ключевых числах пилота (§7), в любой точке мира, на любой машине.

СейчасПосле
Расчёт поляGLSL Пикар на RenderingDevice: 100 + 90 итераций (0,73 с GPU на 4070 SUPER), до 3000 и таймаут на слабом ветре; окна 100/50 м — тем же решателемONNX на CPU: область — оценка 50–150 мс (Ryzen 5 4500), окна — десятки мс
Точность1-й порядок переноса, клетка 400/100/50 м: разгон у бровки занижен на 15–20 % (20–100 м над склоном); на слабом ветре нет сходимостицель обучения — офлайн-решение на мелкой сетке/2-м порядке, до невязки (§2)
Видеокартанужна; AMD — оценка ×2…×11 медленнее, до ~15 с; разные GPU дают разное полене нужна; ORT на CPU, расхождения между машинами ~1e-6
Физиканагрев только по уклону/экспозиции и маске воды; z0 = 0,1 м везде; влажности нет+ альбедо и доля явного тепла по покрову, z0 по покрову, влажность (§2.3)
Смена физикиправка GLSL + CuPy-эталона + калибровкаправка Python → набор → обучение одной командой (≤ неделя)

Что остаётся в рантайме: подготовка входа на CPU (рельеф блоками, солнце по склонам, вода, покров — то же, что сейчас в AirPlace, плюс новые карты), сборка WindField из срезов над рельефом, AirFieldSet/подмена, AirThermals.build, логика окон вокруг пилота и сдвига, подстройка притока под ветер на старте (air-start: проходы сети вместо проходов решателя — по мс каждый), запасная аналитика wind_field.gd.

Что уходит (окончательный список — в задаче NN-13 по grep): air_picard.glsl, air_picard_job.gd, air_line*.glsl, air_mg.glsl, air_multigrid.gd, air_stencil/reduce/vec.glsl, air_gpu*.gd, air_window.glsl, air_window_job.gd, решательная часть air_case.gd/air_window_case.gd, GPU-тесты решателя. air_poisson_job.gd — по итогу шлюза Ш3 (проекция массы на CPU или не нужна).

2. Офлайн-решатель

2.1. Основа

tools/research/air3d/solver.py (Air3D, CuPy + CUDA RawKernel) — эталон AM-01, от которого перенесён GLSL (C1). Переносим в tools/air_nn/solver/ как пакет (не «исследование», а инструмент конвейера), с версией: каждый набор данных и каждая сеть помечаются версией решателя (хеш кода + параметров калибровки).

Первый шаг — связать цепочку: офлайн-решатель в режиме «как игра» (клетка 400/100/50 м, 1-й порядок, тот же критерий остановки) совпадает с GLSL игры в допуске C1 на 4 встроенных местах. Тогда любое последующее отличие поля сети от нынешней игры объяснимо — это улучшение решателя, а не ошибка переноса.

2.2. «Честно» — что меняем, раз время не ограничено

  • Сходимость до невязки, без предела 3000 итераций и таймаута. Слабый ветер (≲ 3 м/с днём, штиль): невязки импульса и θ′ перестают падать (docs/guide/air-model.md, air-start). Варианты по порядку: продолжение по ветру (решение при 4 м/с → тёплый старт 3 → 2 → 1 м/с), псевдовремя с локальным шагом; если установившегося решения нет — нестационарный счёт и осреднение по времени (среднее поле — ровно то, что игра читает как среднее). Какой путь — решает исследование NN-1.
  • Разрешение. Решение области на 200 м (или 400 м с вложенными 200 м под выход) и окна с 2-м порядком переноса или подокном 25 м. Известное: 2-й порядок на крутом рельефе (Аньези H = L) не имеет установившегося решения — тогда тоже осреднение по времени; клетка 12,5 м + 2-й порядок сходится к Askervein (reference.md → «4. Askervein»). Выбор сетки/порядка — по цене и выигрышу на ключевых числах (шлюз Ш1).
  • Проверка по измерениям: Askervein, Perdigão (есть скрипты), при возможности Bolund — до и после каждой правки физики. Калибровка — не подгонка под пилотов (правило «физика прежде „нравится“»).

2.3. Новая физика поверхности (входы, которых сейчас нет)

  • Поглощение света подложкой. Сейчас AirPlace.solar_flux: H = H0 (330 Вт/м²) × косинус к склону + рассеянная доля, вода — 0. Станет: поглощённое коротковолновое (1 − α)·S по склону → радиационный баланс Rn (+ длинноволновое упрощённо) → почвенный поток G (доля Rn) → явный поток H = (Rn − G)·β/(1 + β), β — отношение Боуэна по покрову и влажности почвы. Альбедо α и β по классам ESA WorldCover (лес, луг, пашня, голый грунт/скалы, снег/лёд, вода, город) + сезон (снег по месяцу и высоте — связать с TODO «термики зимой: лес против снега»). Источник чисел — литература (Stull, Oke «Boundary Layer Climates», FLUXNET), не подбор.
  • Шероховатость z0 по покрову (сейчас 0,1 м везде): лес ~1 м, луг 0,03–0,1, снег/вода ~10⁻³ м. Сильно меняет ветер у земли над лесом — физически важнее альбедо для пилота на склоне.
  • Влажность. Погода игры уже знает точку росы и уровень конденсации (weather_model.gd: dew_point_c, z_lcl). В решатель: виртуальная температура в плавучести (θ_v ≈ θ(1 + 0,61 q) — поправка порядка 1 К при q 10–15 г/кг, т.е. сравнима с θ′ термиков), испарение — через β (влажная почва → меньше H), z_lcl — потолок слоя/термиков на выход сети. Скрытое тепло конденсации в облаках — вне модели (как сейчас).
  • Светопоглощение (α по покрову → Rn → H) входит без шлюза (решение пользователя). Остальное (β/влажность почвы, z0 по покрову, θ_v, z_lcl) — сначала замер чувствительности (насколько меняет ключевые числа §7); не меняет заметно — в физику не входит (шлюз Ш2).

3. Сети

3.1. Каскад

  1. Область 38,4 км (сейчас 96 × 96 по 400 м; выход 96² или 192² — шлюз Ш1).
  2. Окно 100 м 64 × 64 (6,4 км) вокруг пилота: вход — рельеф/покров окна с полями запаса + поле сети области, интерполированное в окно; выход — поправка к родителю.
  3. Окно 50 м — так же от окна 100 м. Нужность решается на шлюзе Ш1 (по пилоту ШП): если окно 100 м (при выходе области 192²) держит ключевые числа §7 в допуске, окно 50 м не делаем — каскад из двух сетей, без набора и обучения третьей.

Окна обучаются на выходе обученной сети родителя, а не на эталонном родителе: в игре родитель — сеть, ошибки родителя иначе размножатся в окне. Цель окна — офлайн-решение окна (вложенное в офлайн-решение области, как сейчас клипмапы AM-04). Одна сеть на всю область сразу на 50 м (768²) на CPU — секунды; не берём.

3.2. Вход

Поворот на кратное 90° так, чтобы ветер дул «с запада» в секторе ±45°, остаток угла — вход (cos, sin); поворот вокруг вертикали точный. Отражение поперёк ветра (y′ → −y′) — тоже точное: в решателе нет силы Кориолиса (f_cor — только модуль для высоты пограничного слоя), поэтому отражённый случай с отражёнными картами, поперечной составляющей солнца и остатком угла (sin r → −sin r) — допустимый мир решателя; в выходе меняется знак u⊥ (решение пользователя 02.10). Если офлайн-решатель получит силу Кориолиса или поворот Экмана (NN-1/NN-2) — отражения убрать. Набор ×4 бесплатно.

Карты (float32, фиксированная нормировка, не статистика набора):

КаналНормировка
рельеф hc − среднее по области/1000 м
подсеточная неровность (разброс высот в клетке по слою 25–30 м)/100 м
поглощённая радиация по склону на час (или H, если §2.3 не войдёт)/800 Вт/м²
доля воды0…1
альбедо α0…1
β (Боуэн) или доля испаренияlog β / ограничение
ln z0/ln 1 м
(окна) поле родителя: u∥, u⊥, w_mech, w_conv, θ′ на 13 высотахкак выход

Числа (MLP → FiLM): U10/10, остаток угла, профиль dθ̄/dz (16 значений, К/км), z_i и z_lcl над средним рельефом /1000 м, параметры профиля притока (α, max_profile), облачность, точка росы/q, влажность почвы, широта (|f| Кориолиса), признак «с нагревом / без».

Линейное поле как опора (решение пользователя 02.10 — в план, не в пилот). Рельеф раскладывается по гармоникам (FFT — как DCT в JPEG), отклик ветра на каждую гармонику — передаточная функция линейной теории обтекания холмов (Jackson–Hunt 1975; Mason–Sykes; на ней WAsP, LINCOM, MS-Micro), отклики складываются — поле u, v, w на высотах над рельефом за миллисекунды. Это поле — дополнительная карта входа (сеть учит нелинейную поправку: отрыв, роторы, крутые склоны) и базовая линия в отчётах (сеть обязана её обгонять; сейчас базовая — профиль притока). Проверить: выигрыш на ключевых числах против цены в рантайме (FFT 96² на CPU — доли мс), поведение на крутых склонах (линейная теория там завышает — сети это и править).

Подготовка входа должна быть одинаковой в Python и в игре — главный стык модуля (контракт N1, контрактный тест на совпадение тензоров ≤ 1e-4).

3.3. Выход

На 13 высотах над рельефом (25, 50, 75, 100, 150, 200, 300, 400, 600, 800, 1100, 1500, 2000 м): u∥, u⊥, w_mech, w_conv, θ′ + толщина слоя h_bl, z_lcl → ~67 каналов. Цель — отклонение от профиля притока, нормированное на max(U10, 1 м/с); w_conv и θ′ — на конвективные масштабы w*, θ*. Выше 2000 м — к свободному потоку до верха. Сборка 3D WindField (центры клеток, C3) — перенос срезов над рельефом в высоты над морем на CPU.

3.4. Архитектура

U-Net (CNN) + FiLM: 4–5 понижений — рецептивное поле на всю область (след за гребнем и волны — 10+ км, 25–40 клеток); локальные детали (бровки, седловины) — сильная сторона свёрток; простые операторы ONNX (Conv, Resize, GroupNorm); прецедент — WindSeer (CNN-кодер-декодер на RANS над рельефом, windseer/nn/models/ModelEDNN3D.py, потери с дивергенцией — windseer/nn/losses.py). Запасы, если U-Net систематически ошибается в дальнем следе: FNO (FFT в ONNX — opset 17, поддержан хуже), трансформер по патчам (жаден к данным; при «объём — вопрос времени» это уже не запрет). Размер (оценка): 3–8 млн параметров, 6–16 МБ fp16, 6–15 GFLOP на область.

Вне области обучения (U10 > предела набора, перепады рельефа и z_i вне диапазона): ограничение входа краем диапазона + строка в журнал; аналитика wind_field.gd — только при ошибке ONNX. При охвате «любая точка» диапазоны рельефа задаёт набор (§4), а не место.

Масса. Потери с дивергенцией; после сети — проекция на бездивергентное поле на CPU (многосетка ~96²×30, оценка десятки мс), если без неё |div| заметен на ключевых числах (шлюз Ш3).

4. Набор данных

Рельеф — вся суша. Выборка вырезок 38,4 × 38,4 км по Copernicus GLO-30 + WorldCover 10 м: стратифицированно по перепаду высот, крутизне, покрову, широте, доле воды (горы всех систем, предгорья, плато, побережья, ледники, равнины — равнинам малая доля). 4 встроенных места — обязательно (но Онгудай/Каянча и ещё одно — в отложенной проверке). Оценка объёма: 3–10 тыс. вырезок; окончательно — по кривой обучения (ошибка на отложенных против объёма: продолжаем считать, пока падает).

Условия — квазислучайная выборка (Sobol) на вырезку: U10 0–15 м/с (гуще 0–4), направление, дата и час (солнце), t_max, небо, точка росы, влажность почвы, профиль устойчивости из WeatherModel на эти условия. Каждое — пара «с нагревом / без». Окна — 2–4 положения на решение области (старт, случайные склоны, седловины).

Цена (оценка до NN-1): офлайн-решение пары области на 200 м до невязки — 30–120 с на 4070 SUPER; 30–50 тыс. пар → 250–1700 ч GPU — от 2 недель до 2 месяцев одной картой. Это и есть «вопрос времени»: конвейер считает фоном с продолжения, обучение начинается с первых 5–10 тыс. пар, набор дополняется. Ускорители: аренда GPU (решает пользователь), тёплый старт по соседним часам одной вырезки (уже −40–70 % итераций). Пересчёт после правки физики укладывается в неделю только для части набора → версия набора = версия решателя, переобучение — дообучением на новом наборе (шлюз Ш1 оценит реальную цену).

Покрытие набора в пространстве паттернов (решение пользователя 02.10 — в план). Каждый кусок рельефа (окно ~5–10 км) описывается коэффициентами по паттернам — спектр (FFT/DCT по масштабам и направлениям) или главные компоненты кусков. По ним: (1) отбор мест — равномерно заполнять пространство паттернов, а не только по уклону и размаху; (2) проверка «вне распределения» — насколько кусок места далёк от облака обучающих (ошибку первого пилота — Онгудай круче всего обучения — так видно заранее); (3) в рантайме — флаг «место вне набора» для отчёта/журнала.

4.1. Параллельный счёт (решение пользователя: закладываем сразу)

Машина счёта — RTX 4070 SUPER + Xeon E5-2666 v3 (10 ядер / 20 потоков; docs/research/air_field_cache.md). Замеры решателя (docs/guide/air-model-gpu.md → «Замеры»): на 96² данные целиком в L2 (48 МБ, ~20 МБ на случай), а V-цикл упирается в число запусков и барьеры (61–180 запусков по 10–20 мкс), не в память — карта недогружена. На 192² уже DRAM: resid7 ~85 % полосы, прогонки по y/z — 20–30 % (зебра через клетку). Отсюда:

  • Пакет случаев в одном запуске (ось пакета B в ядрах CuPy: одна вырезка × несколько условий или разные вырезки одного размера сетки): запуски и барьеры делятся на B. Ожидание (оценка): на 96² и окнах 64² — ×2–4 к пропускной способности; B > 2 выходит из L2 — оптимум найти замером B = 1, 2, 4, 8, 16. Сходимость у случаев разная: сошедшиеся выходят из пакета, место занимает следующий (пакет «с подменой»).
  • Конвейер CPU ↔ GPU: подготовка входа (солнце, покров, профиль погоды) — пул процессов на Xeon (8–16 процессов), решатель — один процесс GPU с очередью, запись — отдельный процесс; GPU не ждёт CPU.
  • Несколько потоков CUDA (streams) для разных размеров сетки (область и окна одновременно) — если пакет не загружает карту.
  • 192² (честный решатель): пакет даёт меньше (уже DRAM); выигрыш — от ядер: обход зебры плитками, чтобы вторая чётность шла из L2, шаблон в fp16 (§4.2) — пункты, давно записанные в air_model_gpu.md.
  • Общая карта: генератор берёт замок /tmp/heat_ca_gpu.lock (как остальные исследования) на пакет, а не на весь счёт — между пакетами успевают GPU-тесты других модулей. Обучение и счёт набора одновременно — допустимо по памяти (решатель 1–3 ГБ + обучение 3–5 ГБ из 12), но делят полосу; по умолчанию — по очереди (ночь — счёт, день — обучение), одновременно — по замеру.
  • Приёмка — в NN-T2: кривая «пар в час от B» и загрузка GPU (nvidia-smi dmon), выбранный режим.

4.2. Точность чисел: где fp16, где fp32

Точность до сотых м/с в ответе не нужна, но решатель считает в fp32, и это не про точность ответа:

  • у fp16 мантисса 11 бит (~3 знака, шаг 5·10⁻⁴ от величины): итерации Пикара и многосетка добавляют малые поправки к большим значениям — в fp16 они теряются, невязка встаёт на полку ~10⁻³ (в fp32 пол уже ~10⁻⁵ от max|f|, air_model_gpu.md), сходимость до невязки и проверка массы невозможны;
  • θ около 300 К в fp16 идёт шагом 0,25 К — больше, чем θ′ термиков (храним отклонения, но фон всё равно нужен);
  • на GeForce Ada обычная (не тензорная) арифметика fp16 не быстрее fp32 — выигрыш только в полосе памяти.

Где fp16 полезен:

ГдеТипПочему
счёт решателя (поля, невязки, суммы)fp32 (суммы баланса — fp64, как сейчас)сходимость, см. выше
хранение коэффициентов шаблона/прогонок в решателеfp16 (опыт в NN-1)решатель упирается в полосу; коэффициенты не накапливаются — ожидание ×1,3–1,7 на ядрах 192² (оценка), проверить, что невязка и ключевые числа не меняются
образцы набора (цели, карты входа)fp16цели нормированы (отклонение от профиля / U10) — шаг ~10⁻³ от U10, т.е. < 0,01 м/с при 10 м/с; bf16 (8 бит мантиссы) — нет
обучениеbf16 смешанная точность (веса и оптимизатор — fp32)обычная практика, ×2 к скорости на тензорных ядрах
ONNX в игревеса fp16 в файле, счёт fp32у CPU игроков нет быстрой fp16-арифметики

4.3. Форматы и диск

ДанныеФорматРазмер (оценка)
вырезки рельефа и покрова (источник)GLO-30 и WorldCover читаются из облачных COG окном (без скачивания тайлов 1°); хранятся рельеф 30 м int16 (дм) + покров 30 м uint8 (класс) + доли классов, Zarr + zstd~3 МБ на вырезку → 10 тыс. — ~30 ГБ
статические карты входа на вырезку (рельеф, неровность, α, β, z0, вода)один раз на вырезку, fp16, Zarr192² × 8 × 2 Б ≈ 0,6 МБ → 10 тыс. — 6 ГБ
образец области (пара с нагревом/без): карта солнца на час + выход 13 высот × 5 каналов + 2Dfp16, Zarr, кусок = образец96²: ~1,2 МБ; 192²: ~4,8 МБ
образцы окон 100/50 м (2 положения × 2 уровня на образец области)fp16, Zarr~0,7 МБ на окно → ~2,8 МБ на образец области
условия, время, невязка, сходимость, делениеstate.sqlite при счёте (§4.5) → Parquet для анализадесятки МБ
дешёвый набор «как игра» (multi-fidelity), до 100 тыс. пар 96² + окнакак выше~200–400 ГБ
честный набор 30–50 тыс. пар (192²/96² + окна)как выше~120–380 ГБ
полные 3D-решения — только тест и отложенные места (~15 %)fp16 + zstd, Zarr~20 МБ на пару → ~100–150 ГБ
обучение: чекпойнты (веса + AdamW ≈ 100 МБ), Optuna, кросс-проверка, ансамбль.pt (веса — safetensors)20–50 ГБ
модели для игрыONNX (+ JSON метаданных)6–16 МБ на сеть, в data/air_nn/ (в git — по решению на Ш3; иначе — загрузка при сборке)

Итого на одну версию решателя — ~0,5–1 ТБ; держать две версии (текущую и прежнюю для сравнения) — до ~1,5–2 ТБ. Нужен диск: NVMe SSD 2 ТБ (минимум 1 ТБ — тогда одна версия набора и дешёвый набор удаляется после предобучения). Полные 3D-решения всего набора не храним (~1 ТБ на версию): для пересчёта после правки физики тёплый старт берётся из выхода текущей сети, собранного в 3D (как CFDNet), — архив не нужен.

ОЗУ машины счёта — 32 ГБ: набор в память не влезает, обучение читает его потоком из Zarr (DataLoader, 4–8 процессов с упреждением); с NVMe (2–3 ГБ/с) эпоха на 300 ГБ — ~2–3 мин чтения, сравнимо со счётом обучения. Пул подготовки входа при счёте (§4.1) — не больше 8 процессов (~1 ГБ каждый).

Почему Zarr: куски по образцу, дописывание и продолжение с места, частичное чтение, параллельная запись процессами, сжатие zstd; один каталог на версию решателя, путь — параметр конвейера: <диск данных>/air_nn/<версия>/.

4.4. Структура файлов (решение пользователя: читаемая, не «всё в одну папку»)

Правила для всех задач модуля; нарушение — повод не принять задачу.

  • Каждый файл — в каталоге своего вида (сырьё, набор, прогон обучения, модель, отчёт); ничего в корне диска данных и в корне tools/air_nn/, кроме перечисленного ниже.
  • В имени каталога — что и какая версия, без «test2», «new», «tmp_final»: <вид>/<версия решателя>/<имя>. Версия решателя — s<номер>-<короткий хеш> (например s3-a1b2c3d), прогон обучения — <дата>_<имя опыта>.
  • В каждом каталоге набора, прогона и модели — manifest.json: что это, чем сделано (команда, коммит, версия решателя и контракта), из чего (входные каталоги), размер, дата. Человеку — README.md в корнях видов.
  • Временное — только в tmp/ (чистится командой); удалённое по правилам §4.3 — удаляется целиком каталогом, с записью в CHANGES.md диска данных.
  • Пути только через конфиг конвейера (AIR_NN_DATA=<диск данных>/air_nn), не зашиты в скрипты.
  • rebuild.sh check-layout — проверка: нет лишних файлов в корнях, у каждого каталога есть manifest.json, имена по шаблону, ссылки манифестов на существующие каталоги. Запускается в конце каждого шага конвейера и в приёмке задач.

Диск данных ($AIR_NN_DATA):

air_nn/
  README.md                 — что где лежит, правила (эта структура)
  CHANGES.md                — что и когда удалено/пересчитано
  tiles/                    — вырезки рельефа и покрова (не зависят от версии решателя)
    v1/                     — версия формата вырезок
      manifest.json         — источники, лицензии, выборка, стратификация
      index.parquet         — вырезка: id, координаты, регион, признаки, часть (train/val/test)
      tiles.zarr/           — массивы по id
  datasets/                 — наборы, по версии решателя
    s3-a1b2c3d/
      cheap/                — «как игра» (multi-fidelity)
      honest/
        manifest.json
        plan.parquet        — план условий (Sobol)
        state.sqlite        — статусы случаев, время, невязка (§4.5)
        area.zarr/  win100.zarr/  win50.zarr/
      test3d/               — полные 3D-решения теста
  runs/                     — прогоны обучения
    2026-10-20_area_unet_base/
      manifest.json  config.yaml  metrics.csv  tb/  checkpoints/{best,last}.pt
  models/                   — модели, прошедшие проверку
    area/s3-a1b2c3d_2026-10-21/  model.onnx  meta.json  report/
  reports/                  — отчёты проверки §7 (картинки, таблицы), по дате и шлюзу
  tmp/

Код (tools/air_nn/ в репозитории):

tools/air_nn/
  README.md  rebuild.sh  pyproject.toml
  configs/        — YAML шагов (gen, train, eval) по опытам
  air_nn/         — пакет Python
    solver/  io/  tiles/  gen/  train/  eval/  export/  layout.py
  tests/          — pytest (обратимость, деление, формат, layout)

Игра: модели — data/air_nn/<сеть>.onnx + <сеть>.json; расширение — свой каталог (NN-7).

4.5. Детерминированность и скрипты (решение пользователя)

Всё, что можно сделать детерминированно, делает скрипт конвейера, а не агент «руками». Агент пишет и чинит скрипты, запускает их и читает их отчёты; сам решает только то, где нужно суждение (архитектура, разбор ошибки, вывод отчёта, вопрос на шлюз).

Скриптом, а не агентом:

  • выбор вырезок, план условий (Sobol с зерном), деление train/val/test (по хешу id вырезки/региона, не вручную);
  • счёт, продолжение, повтор упавших, удаление по правилам §4.3, перенос/переименование каталогов;
  • метрики, таблицы ключевых чисел, картинки, сравнение с базовыми линиями, кривые — отчёт строит eval, агент его не пересчитывает и не собирает таблицы вручную;
  • манифесты, check-layout, сводки размеров и времени;
  • экспорт ONNX и проверка ORT, копирование моделей в игру. Нельзя: разовый Python в чате агента вместо шага конвейера, правка данных/манифестов руками, числа в отчёте, которых нет в выводе скрипта. Нужна новая операция — сначала шаг или опция в rebuild.sh, потом запуск.

Детерминированность:

  • все зёрна (план, деление, инициализация, перемешивание, аугментация) — в конфиге и в манифесте;
  • одинаковый вход и версия кода → те же данные: генерация и решатель — побитно (как сейчас у GLSL, два прогона совпадают); пакетный решатель (§4.1) = одиночный побитно, иначе — в допуске решателя, записанном в манифест;
  • обучение: torch.use_deterministic_algorithms и фиксированный порядок данных; если детерминированный режим заметно медленнее или часть ядер cuDNN его не поддерживает — допуск «два прогона ± разброс метрик» с замером разброса (NN-T3), записанный в манифест;
  • каждый результат воспроизводится командой из своего manifest.json — проверяется в приёмке (NN-T6, NN-8). Пилот (этап П) — по тем же правилам: исследовательский код, но команды и зёрна в README, отчёт — скриптом.

Состояние конвейера — SQLite (решение пользователя). Одна база на набор: datasets/<версия>/<набор>/state.sqlite (и runs/state.sqlite для прогонов обучения/Optuna). Хранит статусы и метаданные, не массивы (массивы — Zarr):

  • таблицы: cases (id, вырезка, условия, часть, статус planned/running/done/failed, попытки, процесс, время, итерации, невязка, путь в Zarr), batches (пакет GPU, B, время), events (журнал ошибок), meta (версия схемы, решателя, контракта);
  • захват работы — транзакцией (UPDATE … SET status='running' … RETURNING), WAL, busy_timeout; пишет в основном процесс записи (§4.1), воркеры — короткими транзакциями; упавший процесс → running старше срока возвращается в planned скриптом;
  • агенты смотрят статус только через скрипт (rebuild.sh status — сводка: сколько готово/упало, скорость, ETA, диск), а не запросами руками; правка базы руками запрещена (§4.5);
  • для анализа — выгрузка в Parquet скриптом; runs.jsonl не ведём (источник правды — база);
  • база — только на локальном диске (на сетевой ФС блокировки SQLite ненадёжны); резервная копия — .backup в конце каждого шага. Задачи агентов и их статусы — по-прежнему журнал модуля docs/plan/air-nn_progress.md (процесс .claude/workflow.md), в базе их не дублируем.

4.6. Прерывание и продолжение (решение пользователя)

Любой шаг можно прервать в любой момент; та же команда продолжает с места, итог совпадает с непрерванным прогоном (побитно для данных, в допуске §4.5 для обучения). Ручной чистки после обрыва не бывает.

Как:

  • Порядок записи — сначала данные, потом статус. Образец пишется в Zarr во временный ключ → fsync → переименование (атомарно на локальной ФС) → только затем status='done' в state.sqlite одной транзакцией. Обрыв между шагами даёт «данные есть, статус нет» → при продолжении случай пересчитывается и перезаписывается (детерминированно — тот же результат); «статус есть, данных нет» невозможен.
  • Аренда случая: running с отметкой времени и PID; при старте скрипт возвращает в planned случаи, чей процесс мёртв или аренда истекла. Замок GPU (flock) снимается ОС при смерти процесса.
  • Мягкая остановка (SIGINT/SIGTERM): дописать текущий пакет или бросить его (по флагу), вернуть незавершённые случаи в planned, закрыть базу; второй сигнал — немедленный выход (дальше — как жёсткий обрыв).
  • Шаги идемпотентны. rebuild.sh all пропускает шаги, чей манифест помечен complete и чьи входы (хеши конфигов, версий, входных каталогов) не изменились; незавершённый шаг продолжает, изменившиеся входы — шаг заново в новый каталог (старый не портится).
  • Обучение: чекпойнт каждые N минут и в конце эпохи, атомарно (временный файл → переименование); в чекпойнте — веса, оптимизатор, расписание LR, EMA, состояния ГСЧ (Python/NumPy/torch/CUDA), позиция в данных (эпоха, шаг, порядок перемешивания). Продолжение = непрерванный прогон в допуске §4.5. Optuna — хранилище исследования в SQLite (runs/state.sqlite): прерванный подбор продолжается, оборванные пробы помечаются и повторяются.
  • Загрузка вырезок: докачка по частям с повтором; вырезка готова, только когда все её массивы записаны и проверена сумма.
  • Нехватка диска — проверка свободного места перед пакетом; не хватает — мягкая остановка с понятной строкой, не битые файлы.
  • rebuild.sh status показывает и последствия обрыва: сколько случаев возвращено в очередь, сколько перезаписано.

Проверка — «тест обрывов» скриптом (tools/air_nn/tests/chaos/): прогон мини-шага, kill -9 в случайные моменты (включая между записью данных и статуса) N раз, продолжение до конца → набор побитно равен непрерванному, без дублей и потерь, check-layout чист; то же для обучения (обрыв посреди эпохи и при записи чекпойнта) — метрики равны непрерванному в допуске. Входит в приёмку NN-T2, NN-T3, NN-T6.

5. Обучение и экспорт

5.1. Деление данных — по местам, не по образцам

Случайные 70/30 по образцам здесь врут: один рельеф при разных часах/ветрах даёт похожие поля, сеть «узнаёт» место, и ошибка на проверке выходит заниженной. Поэтому деление — по вырезкам и регионам (пространственные блоки):

ЧастьДоля (оценка)ЗачемКогда смотрим
обучение~75 % вырезокподгонка весоввсегда
проверка (validation)~10 % вырезок, другие регионыранняя остановка, выбор архитектуры и гиперпараметровкаждую эпоху
тест~15 %: целые горные системы + Онгудай/Каянча + одно встроенное местоитоговые числа §7только на шлюзах (Ш3, Ш4), не для настройки — иначе он становится второй проверкой
Соседние вырезки одного региона — в одной части (зазор между частями ≥ 1 вырезки), повёрнутые на 90° копии — в той
же части, что исходник.

Повторные циклы (кросс-проверка по регионам, k = 5): обучение 5 раз, каждый раз отложен свой блок регионов → разброс ошибки между прогонами = насколько оценке можно верить и насколько сеть зависит от того, какие горы видела. Дорого (×5 обучения) — делаем на шлюзе Ш3 и после крупных правок, не на каждом дообучении.

5.2. Кривые: сходится ли сеть и хватает ли данных

  • Кривая обучения по эпохам (ошибка на обучении и на проверке): расходятся — переобучение (больше данных, регуляризация); обе высоки — сеть мала или вход беден.
  • Кривая по объёму набора: обучение на 10/25/50/100 % вырезок, ошибка на тесте. Ещё падает — считать набор дальше (это и есть ответ «сколько считать»); вышла на полку — упираемся в архитектуру/вход, не в данные.
  • Ошибка по срезам (сильный/слабый ветер, час, тип рельефа и покрова, широта) — где сеть слаба, туда и досчитывать.

5.3. Приёмы обучения (современная практика для суррогатов физики)

Брать по порядку, каждое — только если улучшает ошибку на проверке:

  1. База: AdamW, скорость обучения с прогревом и косинусным спадом (или OneCycle), смешанная точность (bf16), ранняя остановка по проверке, EMA весов (скользящее среднее весов — устойчивее итог).
  2. Аугментация: повороты на 90° и отражение поперёк ветра (§3.2) — точные, бесплатно; отражение — случайно с вероятностью 1/2 при каждом показе примера (время эпохи то же, разнообразие ×2).
  3. Физика в потерях: штраф дивергенции; веса у земли и над бровками/стартами.
  4. Подбор гиперпараметров (ширина каналов, глубина U-Net, веса потерь, LR): Optuna, 20–50 коротких прогонов на части данных, оценка — по проверке.
  5. Ансамбль из 3–5 сетей (разные зёрна): разброс между ними — оценка неуверенности. Даёт два выигрыша: в игре можно среднее (точнее одной сети, ×3–5 времени — по бюджету) или одну сеть; в конвейере — активное обучение: прогнать ансамбль по тысячам ещё не посчитанных вырезок/условий, решателем считать в первую очередь те, где сети расходятся сильнее всего. Так «время счёта» тратится там, где сеть хуже знает.
  6. Многоуровневая точность (multi-fidelity): предобучение на дешёвых решениях «как игра» (400 м, 1-й порядок — ×10–20 дешевле, их можно насчитать много), дообучение на меньшем наборе честных решений. Обычно резко снижает нужный объём дорогих данных; проверить в NN-6 сравнением «только честные» против «дешёвые + честные».
  7. Дообучение при смене физики: новая версия решателя → дообучение прежней сети на новом наборе (старт с весов), а не обучение с нуля — так пересчёт укладывается в неделю.

PyTorch на 4070 SUPER (12 ГБ). Оценка времени: 30–60 с на эпоху на 10 тыс. пар, 100–200 эпох — 2–3 ч; на 50 тыс. — ночь; кросс-проверка ×5 и ансамбль ×3–5 — ночи. Экспорт torch.onnx.export (opset 17), проверка ORT против PyTorch ≤ 1e-4, веса fp16 (если ORT CPU теряет время на fp16 — fp32). В метаданные ONNX: версия решателя, версия контракта N4, диапазоны обучения.

Конвейер одной командой: tools/air_nn/rebuild.sh <шаг> — решатель → набор (с продолжения) → обучение → экспорт → проверка §7 → копирование моделей в data/air_nn/ игры.

6. Рантайм в Godot

  • GDExtension (godot-cpp) + ONNX Runtime C API, CPU EP в рабочем потоке, intra-op 4 потока. Основа — godot-onnx-loader / godot_onnx_extension или своя тонкая обёртка (~300 строк): загрузить модель, задать тензоры, запустить, вернуть PackedFloat32Array. Сборки linux-x64, win-x64, macOS universal. +20–35 МБ на платформу.
  • AirRuntime: ветка engine = "nn" рядом с решателем (до шлюза Ш5), затем решатель удаляется. Загрузка: вход места → сеть области (пара: с нагревом и без — один проход с признаком или два) → air-start: k притока 2–3 проходами сети → окна 100/50 → WindField → термики. Полёт: пересчёт по сроку/смене погоды — те же проходы.
  • Сеть (мультиплеер): ORT на CPU почти детерминирован (~1e-6), но маска источников термиков от ведущего (C5) остаётся — пороговые столбцы.
  • Бюджет (цель, проверить в NN-10): этап «Рассчитываем ветер» на Ryzen 5 4500 без GPU-расчёта ≤ 1,5 с вместе с подготовкой входа и AirThermals.build; пересчёт в полёте ≤ 0,3 с работы рабочего потока, кадр не стоит.

Риски: сборка для Windows из Linux (MinGW против MSVC-сборки ORT — через C API и импорт-либу), macOS — подпись dylib и сборка без Mac; размер сборки.

7. Проверка (шлюзы Ш3, Ш4)

  • Отложенные места: Онгудай/Каянча и ещё одно встроенное — целиком вне обучения; + 10 % вырезок по регионам (целые горные системы вне обучения — проверка «любой точки»).
  • Ключевые числа пилота (колонка старта, 60 м над землёй; пороги из air_field_cache.md, air_model_sensitivity.md): ошибка сети против офлайн-решения — ветер < 0,3 м/с, подъём < 0,1 м/с в ≥ 90 % случаев. Плюс RMS ветра в 1 км от старта, полоса подъёма склона, провал за гребнем, разгон в седловине (отдельно — занижение пиков), z_lcl/h_bl.
  • По типам рельефа (решение пользователя 02.10): ошибки отдельно по типам окружения точки — ровный склон-линия (как Аскарово), одиночный «пупырь», двойной пупырь (две вершины с седловиной), две параллельные линии (хребты с долиной), сложный рельеф. Тип — автоматически, скриптом: геоморфоны (Jasiewicz, Stepinski 2013: класс клетки по узору «выше/ниже» в 8 направлениях — гребень, отрог, склон, ложбина, долина, вершина, седловина) → доли классов в окне 5–10 км → тип. Таблица ошибок по типам — в каждом отчёте оценки; отложенные горные системы остаются проверкой «незнакомого места».
  • Что изменилось для пилота: офлайн-решение и сеть против нынешнего GLSL на тех же стартах — таблица отличий с объяснением (мельче сетка, сходимость, покров, влажность). Это не ошибка, но пользователь должен их увидеть.
  • Термики из поля (AirThermals.build): число/сила источников против офлайн-поля.
  • Масса: |div| до и после проекции.
  • Внешний эталон решателя — WindNinja (04.10, массосогласованный режим, 21 случай на 6 местах, 60 м): разгон на гребнях и направление совпадают (1,19 против 1,13 от притока, медиана 10°); торможение массивом и затенение (долины 0,84 против 0,30, подветренные склоны 0,97 против 0,34) он не воспроизводит — нет импульса; проверка торможения — только импульсным режимом или наблюдениями. Паспорт: tools/research/windninja/README.md.
  • Вне распределения: поле на вырезках с краёв диапазонов — нет взрывов, ограничители max_speed_ms/max_w_ms не срабатывают.
  • Время ORT: Ryzen 5 4500 (или ближайший доступный — замер), 4070-машина на CPU.

8. Организация работы

Модуль air-nn: ветка feature/air-nn, копия ~/deltaplan-air-nn, координатор dp-coordinator. Задачи — копии ~/deltaplan-air-nn-<задача>, ветки air-nn/<задача>. Данные — вне git (<диск данных>/air_nn/). Долгие счёты GPU — только через dp job или фоном; GPU одна — набор и обучение не пересекаются во времени с GPU-тестами других модулей (координатор согласует через главную сессию).

8.1. Контракты (до первого исполнителя, docs/contracts/air-nn.md)

№СтыкВладелец → потребителиЧто фиксирует
N1вход: место + условия → тензоры входаNN-T1 (Python), NN-4 (игра); расширяет NN-2 → сеть, рантаймсписок карт и чисел, нормировки, порядок осей (j — север, как C3), поворот на 90°, единицы, что при отсутствии покрова/воды; контрактный тест: Python и GDScript дают одни тензоры ≤ 1e-4 на 4 местах × 3 часа
N2офлайн-решение → образец набораNN-T1 → NN-T2, NN-5каналы, 13 высот над рельефом, перенос из 3D-решения в срезы, нормировка целей (§3.3), окна: родитель/цель
N3формат набораNN-T2 → NN-T3, NN-6, NN-9, NN-8Zarr, схема state.sqlite, версия решателя, деление обучение/проверка/отложенные
N4ONNX-модельNN-T4 (затем NN-6/NN-9) → NN-T5, NN-7, NN-10имена/формы/типы тензоров, opset, метаданные (версия, диапазоны), область/окно 100/окно 50
N5GDExtension APINN-7 → NN-10класс, методы, потоки, ошибки, где лежат библиотеки и модели
N6выход сети → WindField/AirFieldSet/AirRuntimeNN-T5 (черновик), NN-10 → атмосфера, термикипреемник C3/C7/C9: сборка 3D из срезов, сдвиг окон, air-start, подмена; C1/C8 — снимаются в NN-13

8.2. Задачи

Оценки — дни работы агента; «ночи» — счёт GPU фоном.

Этап П — пилот: работает ли идея (решение пользователя: самым первым) Вопрос один: может ли сеть по карте рельефа и числам погоды выдать поле решателя с полезной точностью — в том числе на рельефе, которого не видела. Быстро, на готовом, без инструментов волны 0; код — исследовательский, но воспроизводимый: tools/research/air_nn_pilot/ (README с командами), данные — $AIR_NN_DATA/pilot/ (§4.4).

№ЗадачаАгентСкоуп, файлыПриёмкаОценка
NN-PПилот: набор, обучение, оценкаdp-researcherданные — генератор tools/research/air_lite/gen.py из ветки research/air-lite без правки решателя (эталон AM-01 «как игра», 4 встроенных места + синтетика, область 400 м и окна 100 м, срезы на 13 высотах, fp16): досчитать план (49/690 готовы; решения air-lite не сохранились — счёт заново) + 20–40 процедурных рельефов (хребты, холмы, седловины, долины со случайными размерами и поворотами — дешёвое разнообразие); сеть — малый U-Net+FiLM только для области 96², поворот на 90°, обучение — часы; оценка — три деления: (а) новые условия на знакомом рельефе, (б) незнакомое место (Онгудай/Каянча вне обучения), (в) кривая по числу рельефов в обучении (5/10/20/40)таблица ключевых чисел (ветер и подъём на 60 м над стартами, RMS в 1 км) для (а) и (б) против базовых линий: профиль притока без поправки, среднее по набору, регрессия air-lite (out/fit_metrics.json); кривая (в); 5–8 картинок срезов «решатель / сеть / разница»; время инференса ORT на CPU; вывод и рекомендация2–3 д + 1–2 ночи GPU

Шлюз ШП (пользователю), ориентиры, не окончательные пороги §7:

  • (а) знакомый рельеф: ошибка ветра на 60 м < 0,3 м/с, подъёма < 0,1 м/с в ≥ 90 % случаев — если нет, сеть или вход не годятся, разбираться до всего остального;
  • (б) незнакомое место: заметно лучше базовых линий;
  • (в) ошибка на незнакомом рельефе падает с числом рельефов — тогда идея рабочая, и большой набор (§4) — путь к точности; полка на (в) — менять вход/архитектуру, а не считать больше. Итог шлюза: «идём в волну 0», «правим подход и повторяем пилот» или «отказ от направления».

Итог ШП (02.10, отчёт pilot/reports/2026-10-02_pilot/report.md): «правим подход и повторяем пилот». Сеть заметно лучше базовых линий (ветер на 60 м на знакомом рельефе и отложенных процедурных — в ~2,5 раза точнее профиля притока; подъём 89–92 % < 0,1 м/с), ORT CPU 19 мс (4 потока); но переобучение (проверка на полке ≈ 0,42 с ~30-й эпохи) и систематическое завышение ветра на Онгудае при сильном ветре (+14 %) — вход вне диапазона обучения: медианный уклон 400 м у Онгудая 0,22 (p95 0,39, размах 1964 м) против процедурных 0,016 (p95 0,12, ~720 м). Кривая (в) по ветру падает — число рельефов главный рычаг. Решения пользователя — выше («02.10.2026, итог первого пилота»).

Этап П-2 — повторный пилот на реальных рельефах (контракты П1 v2, П2 v4, П3 v2, П6 v1). Код — тот же каталог tools/research/air_nn_pilot/, данные — $AIR_NN_DATA/pilot/. Порядок стадий одной командой run_pilot.sh: скачивание всех тайлов → нарезка всех мест → счёт решателя (несколько случаев на GPU одновременно) → подготовка тензоров (пул процессов) → обучение → кривая → оценка → отчёт.

№ЗадачаАгентСкоуп, файлыПриёмкаОценка
NN-P4Рельефы: выбор, скачивание, нарезкаdp-researcherterrain_cut.py (или пакет pilotnn_terrain/) + configs/terrain.yaml; стадии screen (грубые тайлы → кандидаты по горам суши), select (стратификация по уклону 400 м/размаху, горные системы, отложенные системы), fetch (все тайлы z12 всех мест одним прогоном, пул загрузок, продолжение), cut (все вырезки пулом процессов: путь игры → сетка 25 м → клетки 400 м, признаки, индекс), status; загрузчик мест t_* в places.py; контрактный тест П6~300 мест пула + отложенные системы (по 10–20 мест), гистограммы уклона/размаха пула и отложенных против Онгудая/Алтая/процедурных — диапазон Онгудая покрыт и превышен (уклон p50 до ≥ 0,4 у части мест, размах до 2–2,5 км); побитный повтор вырезки; прерывание fetch/cut (kill -9, повтор той же команды → те же файлы); hc400 совпадает с T.block_mean; объём диска и время стадий1–1,5 д + загрузка
NN-P5Вход и оценка под П-2dp-researcherpilotnn/prep.py (карты П2 v3), split.py (отложенные системы, кривая 25…300 с примесью прочих мест, точка 300 = основная сеть), data.py (несколько наборов), evaluate.py, report.py (критерий П3 v2, смещение, гребни), pilot.py (только чтение нескольких наборов, кривая, оценка), раздел config.yaml (split/eval/curve), тестыtest_prep.py: карты П2 v3 — формулы на аналитическом рельефе (наклонная плоскость: уклон вдоль = известному, поперёк = 0; гора: TPI > 0 на вершине, затенённость > 0 за горой), поворот ×4 = тождество, контракт MAP_NAMES; smoke с подставным индексом П6 проходит до report.md со всеми разделами П3 v2; вывод ШП-2 — правилом из чисел1,5–2 д
NN-P6Генератор П-2: только область, параллельный счёт, замерdp-researcherdataset.py (режим «только область», план набора по индексу П6, n_cond условий на место, несколько воркеров на GPU под одним замком, CPU-подготовка параллельно со счётом), airlite_gen.py (решение без окон — без правки решателя), configs/dataset.yaml; тестыобласть без окон = срез d400_* прежнего случая побитно (те же условия); кривая «случаев в час от числа воркеров 1…4» и выбор; тест прерывания на воркерах (8/8 побитно, без дублей); пробный замер на 6–10 местах П6 разной крутизны × 2 условия: время случая, доля max, оценка полного счёта (ч GPU, ГБ)1–1,5 д
NN-P7Сборка П-2 в одну командуdp-researcher (Sonnet)pilot.py/run_pilot.sh: стадии рельефа и набора П-2 перед прежними, профиль smoke П-2 (несколько мест t_* × 2 условия + прежний smoke), README./run_pilot.sh --smoke с нуля до отчёта; прерывание на каждой стадии (Ctrl-C/kill -9) и повтор той же командой → тот же итог; status; оценка полного прогона по замерам NN-P4…P6 и NN-16…18 (tiles/v2, П1 v3, П3 v3)0,5–1 д
NN-16Отложенная система Южные Альпы НЗ (Q1)dp-researcher (Sonnet)configs/terrain.yaml (+ система, 15 мест), terrain_cut.py (перевыбор в tiles/v2/, докачка тайлов), tests/test_contract_terrain.py (П6 v2: системы из конфига)tiles/v2/manifest.json complete: пул 300 + 4 × 15; уклон p50/p95 и размах отложенных по системам против пула; побитный повтор вырезки; контрактный тест П6 v20,5 д + загрузка
NN-17Цель несошедшихся — среднее поздних состояний (Q2)dp-researcher (Opus)dataset.py/airlite_gen.py (снимки поздних состояний без правки решателя), configs/dataset.yaml (late_mean), tests/test_contract_sample.py (П1 v3)у max d400_* = среднее снимков (тест против ручного усреднения), у сошедшихся — побитно как v2; late_spread60_p90 на probe; прерывание — побитно; время случая против v20,5–1 д
NN-18ШП-2 раздельно по сходимости, вердикт (Q2, Q3)dp-researcher (Sonnet)pilotnn/report.py, evaluate.py (группы по статусу решения, разброс), config.yaml, тестыsmoke-отчёт с разделами П3 v3 для обеих групп; вердикт — правилом П3 v3 (отказ → сошедшиеся → правим)0,5 д
Зависимости: NN-P4, NN-P5, NN-P6 — параллельно (NN-P5 — на подставном индексе П6; NN-P6 — замер на местах П6 после
NN-P4); NN-16, NN-17 — параллельно после решений 03.10; NN-18 — после NN-P5; NN-P7 — после NN-P5, NN-16…18. Массовый счёт — только после согласования (готовая команда, время и диск — в отчёте).

Шлюз ШП-2 (пользователю; правило в config.yaml, числа — из отчёта): главное — отложенные горные системы: ветер — ошибка ≤ max(0,3 м/с; 10 % скорости решателя) в ≥ 90 % точек области на 60 м; подъём без нагрева и с нагревом < 0,1 м/с в ≥ 90 % точек; нет систематического смещения скорости (по высотам, по U10, у гребней; порог — П3 v2). Плюс Онгудай, отложенные процедурные, знакомые рельефы + новые условия, кривая 25/50/100/200/300. Итог: «идём в волну 0», «правим подход» или «отказ от направления».

Волна 0 — инструменты и сквозной прогон (решение пользователя: обязательно до массового счёта) Цель — до дорогого счёта убедиться, что каждый шаг конвейера делает то, что должен, и что сеть на нём вообще учится. Физика здесь — нынешняя («как игра», 400 м, 1-й порядок), данные — малые; качество сети не цель. Код — tools/air_nn/ (Python-пакет, pytest), одна точка входа tools/air_nn/rebuild.sh <шаг> (solve, gen, train, eval, export, check, all), конфиги шагов — YAML с версией, логи и метрики каждого прогона — в <диск данных>/air_nn/runs/<время>/ (+ TensorBoard или аналог).

№ЗадачаАгентСкоуп, файлыПриёмкаОценка
NN-0Пакет решателя и связка с игройdp-researcherперенос air3d/solver.py + air.py (вход места) в tools/air_nn/solver/, версия по хешу кода и параметров; режим «как игра»; проверка поворота на 90° (Кориолис, приток)совпадение с GLSL игры в допуске C1 на 4 местах × 3 часа (через tools/research/air3d/gpu_block_refs.py/дампы игры); поле повёрнутого входа = повёрнутое поле в допуске решателя; время одной пары1–2 д
NN-T1Вход и выход: обработчики, структура файловdp-researcherструктура §4.4: layout.py (пути из AIR_NN_DATA, манифесты, check-layout), README диска; tools/air_nn/io/: вырезка → карты входа (N1), поворот на 90°, нормировки; 3D-решение → срезы над рельефом (N2) и обратно в 3D; формат шардов (N3), загрузчик PyTorchcheck-layout ловит лишний файл в корне и каталог без манифеста (тест); pytest: поворот ×4 = тождество, срезы → 3D → срезы ≤ 1e-5, нормировка ↔ обратная, шард пишется/читается побитно; срезы → 3D на ровном месте совпадают с решением2 д
NN-T2Мини-генератор, параллельный счёт, мини-наборdp-researchertools/air_nn/gen/ в полном виде: Sobol-план, пары с нагревом/без, пакет B случаев в ядрах решателя с подменой сошедшихся, пул CPU-подготовки, процесс записи (§4.1), замок GPU на пакет, Zarr (§4.3), продолжение с места, state.sqlite и rebuild.sh status, деление по регионам §5.1; на малом наборе: 4 места + ~50 вырезок × ~10 условийпакетный решатель = одиночный побитно или в допуске решателя; кривая «пар в час от B = 1…16» и загрузка GPU, выбранный режим; мини-набор ~500 пар; тест обрывов §4.6 (kill -9 в случайные моменты, включая между данными и статусом) — набор побитно равен непрерванному; деление: ни одна вырезка/регион не попадает в две части (тест); размер образцов на диске со сжатием против §4.3, скорость чтения диска3–4 д + ночь
NN-T3Обучение: каркас и проверки на вменяемостьdp-researchertools/air_nn/train/: U-Net+FiLM малого размера, потери, AdamW + LR-расписание, EMA, ранняя остановка, атомарные чекпойнты с состоянием ГСЧ и позицией в данных (§4.6), логирование кривыхтест обрывов §4.6 для обучения (продолжение = непрерванный прогон в допуске); проверки на вменяемость: (1) переобучение одного батча до ошибки ~0 (сеть и потери связаны верно); (2) синтетика с известным ответом — гладкие холмы с аналитическим полем (потенциальное обтекание) — сеть его выучивает; (3) кривая по объёму на мини-наборе (10/25/50/100 %) падает; (4) перемешанные метки → проверка не лучше константы (нет утечки); (5) базовые линии — «профиль притока без поправки» и «среднее по набору» — сеть заметно лучше на проверке3 д
NN-T4Оценка, экспорт, инференсdp-researchertools/air_nn/eval/: ключевые числа §7, срезы и картинки, отчёт одной командой; export: ONNX + метаданные (N4), сравнение ORT ↔ PyTorchотчёт по мини-сети строится одной командой; ORT ↔ PyTorch ≤ 1e-4; время ORT CPU на область1–2 д
NN-T5Сквозной прогон в игре (заглушка)dp-engineerминимальная обёртка ORT (Linux) + чтение мини-ONNX из GDScript, сборка WindField из выхода (N6 черновик), полёт на поле мини-сети на одном месте; та же модель в Python и в игре на одном входевыход в игре = выход ORT в Python ≤ 1e-4; поле отображается в F3-срезах; время ORT на CPU на машине счёта (Xeon) и на ноутбуке без мощной видеокарты (Ryzen 7 7840HS, 780M) — оценка бюджета для слабых машин; Windows/macOS — не здесь (NN-7)2–3 д
NN-T6Сквозной прогон «всё»dp-mechanicrebuild.sh all на мини-наборе с нуля на чистой копии по READMEпрогон проходит без ручных шагов; check-layout чист; повтор шага по команде из манифеста даёт тот же результат (§4.5); rebuild.sh all, прерванный дважды на разных шагах и запущенный снова, доходит до конца с тем же итогом (§4.6); время каждого шага; отчёт с путями к логам и картинкам0,5 д

Шлюз Ш0 (пользователю): конвейер работает сквозь — числа проверок NN-T3, отчёт мини-сети, время шагов, оценка цены полного набора по замеру. Только после Ш0 — честная физика и массовый счёт.

Волна 1 — честная физика (только Python, игру не трогает)

№ЗадачаАгентСкоуп, файлыПриёмкаОценка
NN-1Честный решатель: сходимость и разрешениеdp-researcherслабый ветер (продолжение по ветру / псевдовремя / осреднение по времени), область 200 м, окна 2-й порядок или 25 м; Askervein/Perdigãoтаблица: невязка и время по ветрам 0,5–12 м/с, доля сошедшихся = 100 % (или осреднено с оценкой разброса); ключевые числа «как игра» против «честно» на 10 стартах; цена пары на 4070; Askervein не хуже нынешнего4–6 д + ночи
NN-19Ускорение решателя: батч случаев (идея пользователя 03.10, до массового счёта NN-5)dp-researcher (Opus — GPU)профиль воркера П-2 (py-spy/nsys: доля Python и запуска ядер против счёта; 1 ядро CPU на воркер в 100 % при GPU 100 % — ядра мелкие, сетка 96²); батч 8–16 случаев одним массивом (ось случая в полях решателя, разные места и условия, маска сошедшихся — сошедшийся случай заменяется следующим); слияние операций (cp.fuse/ElementwiseKernel/RawKernel), CUDA Graphs на итерацию, проверка сходимости раз в N итераций; ожидание GPU без спинаслучаев в час против нынешних 2 воркеров на probe (цель — в разы); поле батча = поле одиночного счёта в допуске решателя (побитно не требуется, порог — как у теста версии); загрузка SM (nsys), а не только utilization; память GPU на батч; прерывание и продолжение как у dataset.py2–3 д
NN-20Тёплый старт решателя от сети (идея пользователя 03.10; не запускать без решения, рядом с NN-19)dp-researcher (Opus — GPU)ответ сети (П-2 или лучшей П-3) → начальное состояние решателя: срезы 13 высот → 3D-сетка, выше 2000 м — профиль притока, турбулентность — из замыкания по этому ветру; несошедшимся — раньше начинать осреднение поздних состояний20 случаев (г) разной крутизны и ветра, половина несошедшихся, холодный и тёплый старт: итерации и время до сходимости, разница итоговых полей у сошедшихся ≪ 0,3 м/с (иначе набор «подтягивается» к сети — нельзя), разница late_mean у несошедшихся; оценка: линейная сходимость → ~20–40 % итераций (ошибка старта в 4 раза меньше профиля притока: 0,62 против 2,46 м/с), «в разы» — только если время уходит на крупномасштабную перестройку; складывается с NN-190,5 д + ~1 ч GPU
NN-2Физика поверхности: светопоглощение (сразу), Боуэн, z0, влажностьdp-researcherчтение WorldCover для вырезки, таблица классов → α, β, z0 с источниками; баланс радиации (1 − α)·S → Rn → H — обязательно; β, z0, θ_v, z_lcl — с замером; документ физики (docs/research/air_surface.md)α в решателе и в N1; Askervein/Perdigão не хуже; чувствительность ключевых чисел к каждому добавлению на 10 стартах × 3 часа; литература к каждому числу; что не вошло и почему3–4 д
NN-3Библиотека вырезок «вся суша»dp-researcher (Sonnet достаточно)загрузка GLO-30 + WorldCover по выборке, стратификация (перепад, крутизна, покров, широта, вода), отложенные регионы, кеш; лицензии в terrain_sources.md3–10 тыс. вырезок с метаданными, гистограммы признаков против суши, список отложенных регионов; продолжение с места2–3 д + загрузка

Шлюз Ш1 (пользователю): сетка и порядок офлайн-решателя, сетка выхода области (96² или 192²), цена пары, план объёма и сроков счёта, нужна ли аренда GPU. Шлюз Ш2: какие входы §2.3 сверх светопоглощения войдут (по чувствительности).

Волна 2 — набор, сеть области, расширение

№ЗадачаАгентСкоуп, файлыПриёмкаОценка
NN-4Вход сети в игреdp-engineerAirPlace + новые карты (покров, α, β, z0, неровность), числа погоды (точка росы, влажность почвы), поворот; данные покрова для встроенных мест и мест с карты (пакет покрова)контрактный тест N1: тензоры GDScript = Python ≤ 1e-4 на 4 местах × 3 часа; время подготовки на CPU3 д
NN-5Массовый счёт набораdp-researcherгенератор из NN-T2 на честном решателе (NN-1/NN-2) и вырезках NN-3; окна (позже — на родителе-сети); rebuild.sh genпервые 5 тыс. пар; доля несошедшихся = 0; сводка по срезам условий1 д + недели фоном
NN-6Сеть областиdp-researcherкаркас NN-T3 на полном наборе: размер сети, приёмы §5.3 по порядку (каждый — с замером на проверке), multi-fidelity, ансамбль, экспорт ONNX, проверка ORT; rebuild.sh trainORT против PyTorch ≤ 1e-4; кривые по эпохам и по объёму (§5.2); таблица «приём → ошибка на проверке»; ключевые числа §7 на проверке (тест не трогать до Ш3)4–6 д + ночи
NN-6бАктивное обучениеdp-researcherансамбль по непосчитанным вырезкам/условиям → очередь генератора NN-5 по разбросуочередь работает; ошибка на проверке после досчёта по очереди против досчёта случайного того же объёма1–2 д
NN-7бУдалить debug_draw_3d (предусловие NN-7)dp-engineerудалить addons/debug_draw_3d и Debug Menu (F2); F5 (стрелки ветра, _wind_frame в scripts/game/debug_overlays.gd) — свой ImmediateMesh/MultiMesh, как меш термиков F6; F1 (perf_text()) остаётся; .godot/extension_list.cfg, ASSETS.md (строки Debug Draw 3D и Debug Menu), configs/controls.json (F2), тестыgrep -ri "debug_draw|DebugDraw|debug_menu" по scripts/ и addons/ пуст; F1 и F5 работают (скриншоты); headless-тесты зелёные; пробник ORT (native/air_nn_probe/) на Linux через обычный dlopen (без dlmopen) при загруженной игре не падает0,5 д
NN-7GDExtension ONNX Runtimedp-engineerот заглушки NN-T5 к расширению: addons/air_nn/ или native/air_nn/, godot-cpp + ORT C API, рабочий поток, API N5; сборка linux/win (из Linux), macOS — способ сборки выяснитьинференс пустышки и сети области в headless-тесте; сборки трёх ОС (или шлюз, если macOS без Mac невозможна); время на CPU3–5 д
NN-8Проверка сети областиdp-researcherкросс-проверка по регионам k = 5 (разброс), тест (первый раз), отчёт §7 по области: отложенные места и регионы, «что изменилось для пилота» против GLSL, термики, масса, вне распределения; картинкитаблица ключевых чисел, 5–8 картинок, рекомендация1–2 д

NN-7 не зависит от NN-6 (пустышка по контракту N4) — идёт с начала волны 2. Шлюз Ш3: точность области достаточна? нужна ли проекция массы? продолжать счёт набора (сколько)?

Волна 3 — окна и игра

№ЗадачаАгентСкоуп, файлыПриёмкаОценка
NN-9Сети окон 100 и 50 мdp-researcherнабор окон на родителе-сети (вход — выход сети области), обучение, экспорт, проверка §7 для окон (бровка, седловина, стык с родителем)ключевые числа окон против офлайн-окна; стык окна с родителем не хуже нынешнего (air_clipmap/seam_plot.py)4–6 д + ночи
NN-10Сети в AirRuntimedp-engineerветка engine = "nn": проходы области/окон, сборка WindField (N6), air-start через сеть, сдвиг окон, пересчёт в полёте, термики; конфиг air_model.engineтесты N6 (форма поля, стык уровней, подмена), полёт на Онгудае/Каянче/месте с карты без GPU-расчёта; бюджет §6 замером; журнал air_model: поле (nn …)3–4 д
NN-11Проекция массы на CPUdp-engineerмногосетка в GDExtension (если Ш3 решил «нужна»)div
NN-12Сборка для полётаdp-mechanicсборки Linux/Windows с engine = "nn", скриншоты этапа загрузки и F3-срезов на 4 местах (tools/shots)сборки, картинки в build/screenshots/air-nn/0,5 д

Шлюз Ш4: проверка окон. Шлюз Ш5: полёт пользователя (сеть против решателя на тех же стартах) → решение «удаляем решатель».

Волна 4 — удаление решателя и документы

№ЗадачаАгентСкоуп, файлыПриёмкаОценка
NN-13Удалить рантайм-решательdp-engineerфайлы §1 «что уходит» по grep, GPU-тесты решателя, RenderingDevice для воздуха, конфиг engine; контракты C1/C3/C7/C8/C9 → N6все headless-тесты зелёные; grep по именам решателя пуст в scripts/; загрузка на машине без GPU-расчёта1–2 д
NN-14Прогон тестов и сборок после удаленияdp-mechanicheadless-тесты, tools/gpu_tests.sh, сборкиотчёт: зелёные/известные падения0,5 д
NN-15Документыdp-writerdocs/guide/air-model.md (раздел поля — сеть, офлайн-решатель, границы модели: что сеть не умеет), tools/air_nn/README.md (пересчёт одной командой), страница для пилотов, дневникпо фактам из отчётов NN-0…NN-131–2 д

Итого (оценка): ~9–11 недель (пилот — ~1 неделя календаря, волна 0 — ~2 недели) работы агентов + счёт набора фоном (недели; зависит от Ш1). Параллельно в каждой волне — ≤ 4 исполнителя. Пилот П-3 (план для координатора, 03.10.2026): физическая кодировка входа и выхода на готовых данных П-2 — docs/archive/plan/air-nn-p3.md (задачи П3-А…П3-Д, шлюз ШП-3).

9. Риски

  • Охват «любая точка» — главный риск качества: рельеф/покров вне набора → гладкое неверное поле без запасного решателя. Меры: стратифицированная выборка суши, отложенные регионы, кривая обучения, ограничение входов; если сеть слаба в редком классе рельефа — дополнять набор именно им.
  • Цена набора при честном решателе — недели GPU; ошибка в физике, найденная поздно, стоит пересчёта (неделя на часть набора — решение пользователя). Меры: шлюзы Ш1/Ш2 до массового счёта, версия набора = версия решателя.
  • Сглаживание пиков (след, седловина, бровка) и ошибки w_conv → неверные источники термиков.
  • Слабый ветер: если установившегося решения нет, «честное» среднее поле требует нестационарного счёта — дороже в разы (NN-1 оценит).
  • Нативное расширение — новая зависимость сборки на трёх ОС; macOS без Mac.
  • Отличия от нынешней игры (мельче сетка, покров, влажность) пилот увидит как «ветер стал другим» — это ожидаемо, но должно быть в отчёте NN-8 и в документах.
  • Решатель «как игра» не зависит от высоты и широты места (NN-17, 03.10): ρ·cp и θ0 постоянны (air.py:38–40), f_cor — для 51° с. ш. (air.py:513; от него hbl, h_mech, z_sat), фон погоды — климат 50–56° с. ш. с опорой 3000 м (weather.py:155–170). Для мест выше ~3 км и вне 50–56° с. ш. поля не откалиброваны. В волну 1 (NN-1/NN-2): ρ(z), θ(z) в плавучести, f(lat).

10. Открытые вопросы пользователю

  1. Диск под данные: NVMe SSD 2 ТБ (минимум 1 ТБ, §4.3) — найдётся?
  2. Аренда GPU для набора (ускоряет недели счёта) — допустима? Если нет — счёт на 4070 SUPER фоном, сеть переобучается по мере роста набора.
  3. macOS: есть ли доступ к Mac для сборки и проверки расширения? Иначе — сборка через CI (GitHub Actions macOS) или macOS позже.
  4. Снег по сезону (альбедо 0,6–0,9 против 0,1–0,2 у леса) — в NN-2 вместе со светопоглощением (рекомендация: да, иначе зимой альбедо неверно) или отдельно (TODO «термики зимой»)?

Литература