План: офлайн-данные мира — свой пакет региона и подложка поверхности из OSM

30.09.2026. Статус: отложено (решение пользователя 30.09.2026: план сохраняем, реализацию откладываем). Замер размеров сделан (этап 0–1). Замер вектора OSM — docs/plan/osm_vector_pack.md (сделан, 30.09.2026). Идея-альтернатива (01.10.2026): список мест полётов из OSM и закачка полных данных по выбранному месту вместо пакетов регионов — docs/plan/on_demand_location.md.

Цель

  1. Без интернета. Всё нужное для полёта (рельеф, земной покров, OSM) заранее перепаковано в свой компактный формат и лежит рядом с игрой пакетами регионов.
  2. Подробнее поверхность. Подложку 0,5–2 м/пикс (дороги, поля, лес, вода, скалы, посёлки) игра рисует сама на GPU из вектора OSM плитками вокруг пилота. Сейчас подложка — WorldCover 10 м, а OSM только в воде и только у встроенных локаций.
  3. Любая точка пакета — как встроенная локация. Сейчас при старте с карты нет ЛЭП, дорог и рек OSM. С пакетом они есть везде, где есть данные OSM.

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

  • Источник OSM — .osm.pbf (Geofabrik), не OsmAnd OBF.
  • Берём только нужное, перепаковываем в свой компактный формат, кладём рядом с игрой.
  • Растр подложки генерится из вектора, не хранится.
  • Без домов: дома ставятся процедурно там, где есть посёлок. Без заборов.
  • Ключевые объекты для пилота — ЛЭП; также аэродромы и прочее по оценке (см. «Состав пакета»).
  • Замер размеров — на небольшой гористой европейской стране (Словения).
  • Сеть убрать совсем, когда пакеты заработают: загрузка Terrarium/WorldCover по точке и кеш user://terrain_cache удаляются, старт возможен только внутри пакетов.
  • Регион — пока только Алтай (и то под вопросом, см. вопрос в конце).
  • Встроенные локации — из пакетов, Overpass-путь (fetch_osm.py, data/osm) удалить.
  • Карта выбора старта — из того же вектора (реки, дороги, посёлки, старты); пункт TODO про martin закрыт этим планом.
  • Рельеф — 30 м как основа. Из OSM в рельеф берём только обрывы, ровные озёра и врезанные русла. Подробный рельеф (лидарные модели 1–5 м, z13) не делаем: физика на другой сетке может не сойтись.

Как сейчас (по коду)

  • Рельеф. Встроенные локации: data/terrain/<id>/ (detail.f32.br, far.f32.br), 8–11 МБ на локацию. Произвольная точка: тайлы Terrarium z12/z9 по сети, кеш user://terrain_cache (terrarium_loader.gd, configs/world.json → runtime_terrain).
  • Покров. Карта классов SurfaceLayer (surface_layer.gd): лес, трава, поле, кусты, грунт, вода, застройка, снег. По ней красится земля, ставятся деревья, считаются источники термиков (surface.thermal.class_strength). Встроенные — PNG из WorldCover. Произвольная точка — WorldCover COG по HTTP range (worldcover_loader.gd).
  • Маска «деталь 10 м» (только встроенные): R — доля леса (WorldCover), G — доля воды, растеризуется из OSM (tools/terrain/osm_water.py).
  • OSM. Только офлайн через Overpass (tools/osm/fetch_osm.py → data/osm/<id>.json, 1–2,8 МБ): дороги, дома, ЛЭП, реки, озёра, посёлки, поля, заборы. Читает OsmData (scripts/world_objects/osm_data.gd), дальше road_mesher, building_placer, power_line_planner, obstacle_index.
  • Карта выбора старта — своя отмывка из Terrarium (map_picker.gd). Пункт TODO «Векторная карта для выбора старта (рендер через martin)».

Состав пакета

СлойТеги OSM / источникДля чего в игре
РельефTerrarium z12 (≈24–30 м) и z9 для дали, или Copernicus GLO-30высоты, физика склона и потока
Покров (фон)ESA WorldCover (классы, 10–25 м)классы там, где OSM пусто
Дорогиhighway=* (с track/path — по замеру), railway=rail/narrow_gaugeподложка, ориентиры, посадка
Водаwaterway=river/stream/canal/ditch/drain, natural=water, water=*, natural=wetland/glacierподложка, класс «вода»
Землепользованиеlanduse=forest/meadow/farmland/orchard/vineyard/grass/residential/industrial/quarry/allotments, natural=wood/scrub/heath/grassland/bare_rock/scree/shingle/sand, natural=cliff (линия)подложка, классы для термиков
Посёлкиplace=* (точки и полигоны) + landuse=residentialпроцедурные дома, подписи на карте
Объекты для пилотаpower=line/minor_line/tower/pole, aerialway=*, aeroway=aerodrome/airstrip/runway/helipad, sport=free_flying (старты и посадки), power=generator + generator:source=wind, man_made=mast/tower/communications_tower/chimney, natural=peak/saddleстолкновения, места посадки, готовые старты, ориентиры

Не берём: building=*, barrier=*, адреса, POI, служебные поля (авторы, версии).

Физика и границы модели

  • Подложка меняет физику, а не только вид. Класс поверхности задаёт силу источника термика. Внутри полигона OSM класс берётся из OSM (поле среди леса, деревня, осыпь); вне полигонов — из WorldCover. Сетка классов для термиков — 10–25 м, агрегируется из того же вектора.
  • Рельеф: сетка ≈24–30 м (z13 не даёт новой информации там, где исходник 30 м). Из OSM в высоты вносятся три поправки: уступ вдоль natural=cliff, ровная поверхность озера по уровню воды (вода не «висит» на склоне), неглубоко врезанное русло реки. Насыпей и выемок нет. Поправки не должны ломать физику потока: они мельче шага сетки или сглажены до него.
  • Полнота OSM разная: в глуши подложка = WorldCover, объектов нет. Дата данных — дата выгрузки PBF. Это граница модели, не ошибка.
  • Высоты препятствий: из height/ele, если есть; иначе типовые (как сейчас для ЛЭП в power_line_planner.gd: решётчатая опора по напряжению, иначе столб).

Устройство

  • Сборка (офлайн, один раз на регион) — tools/pack/build_pack.py: PBF + рельеф + WorldCover → один файл пакета: заголовок, индекс ячеек (например 0,25°×0,25°), в ячейке — рельеф (int16, сжатие), классы, вектор по слоям (целые координаты в сетке ячейки ~0,5–1 м, дельты + varint, упрощение ~1 м), brotli. Формат — по итогам замера.
  • Игра: читатель пакета (FileAccess + смещения по индексу) отдаёт HeightLayer, SurfaceLayer, OsmData тем же потребителям, что сейчас. Сетевой загрузки нет (terrarium_loader.gd, worldcover_loader.gd по сети — удалить).
  • Карта выбора старта рисуется из пакета: отмывка рельефа + вода, дороги, посёлки, ЛЭП, старты free_flying; выбрать точку можно только внутри пакетов.
  • Подложка на GPU: плитки (например 2×2 км при 1 м) вокруг пилота рисуются из вектора (SubViewport или RenderingDevice), вблизи мельче, вдали грубее; шейдер рельефа берёт из них класс и маски вместо «детали 10 м». Треугуляция полигонов — на CPU, в фоне.
  • Где лежат пакеты: папка рядом с исполняемым файлом (и/или user://packs). Раздача — отдельными файлами на itch. Атрибуция: © OpenStreetMap contributors (ODbL), ESA WorldCover (CC BY 4.0), рельеф — по источнику.

Размеры (замер на Словении, osm_vector_pack.md)

Словения (20,4 тыс. км², самая плотная разметка OSM): ≈ 80 МБ — вектор OSM 63,2 МБ (81 % из него — землепользование), рельеф 30 м (int16, шаг 1 м, предсказатель, brotli) 13,6 МБ, покров 25 м 2,4 МБ, дальний рельеф 0,5 МБ. Рельеф и покров — пересчёт с пробных квадратов.

Оценки по замеру: Республика Алтай ≈ 90 МБ (OSM в 23 раза реже Словении, основной объём — растры), Швейцария ≈ 150 МБ, Австрия ≈ 230–330 МБ, Россия ≈ 14 ГБ, мир ≈ 110–125 ГБ. Растеризация 10×10 км при 1 м на CPU (skia, один поток) — ≈ 3–4,5 с.

Задачи

#ЗадачаИсполнительПриёмкаДни
0Замер вектора OSM на Словении (идёт)dp-researcherтаблица по слоям, байт/км², время сборки; растр 10×10 км @1 м из пакета и время; пересчёт на Альпы/Россию/мир → osm_vector_pack.md0,5
1Замер рельефа и покрова в нашем формате на Словении (int16 + сжатие; классы)dp-researcherМБ на страну и на км², время; итоговая таблица «пакет Словении целиком»0,5
2Формат пакета и контракты: раскладка файла, координаты (lat/lon → ячейка), слои, классы, версия; docs/offline_data_contracts.md + контрактный тестdp-coordinatorконтракт принят; тест формы данных0,5
3Сборщик tools/pack/build_pack.pydp-engineerсобираются Словения и Республика Алтай; повторная сборка даёт тот же файл; размеры в пределах замера1,5
4Чтение пакета в игре: рельеф, покров, OsmData из пакета; сетевую загрузку и кеш удалитьdp-engineerс отключённой сетью старт в любой точке пакета работает; в коде нет HTTP-загрузки рельефа и покрова1,5–2
4аПоправки рельефа из OSM при сборке: обрывы, ровные озёра, руслаdp-engineerозёра ровные (разброс высот воды < 0,5 м); скриншоты обрыва, озера, русла1
5Подложка на GPU плитками вокруг пилота; шейдер рельефа берёт из неё класс и маскиdp-engineerплитка 2×2 км @1 м рисуется без рывка кадра (замер); скриншоты до/после на одном месте2–3
6Классы для термиков из OSM + WorldCoverdp-engineerтест: поле внутри леса даёт класс «поле», осыпь — «грунт»; термики стоят на местах полигонов1
7Объекты для пилота из пакета: ЛЭП и канатки (столкновения через obstacle_index), аэродромы и старты free_flying (места посадки, точки старта на карте), ветряки и мачтыdp-engineerв Словении есть ЛЭП/канатки с столкновением; старты видны на карте выбора1,5–2
8Процедурные дома по place/landuse=residential через building_placer.gddp-engineerпосёлки застроены, дома не на дорогах и не в воде1
9Встроенные локации — из пакетов; убрать Overpass-путь (fetch_osm.py, data/osm)dp-mechanic / dp-engineer4 локации летаются как раньше, data/osm удалён0,5–1
9аКарта выбора старта из вектора пакета; пункт TODO про martin закрытьdp-engineerна карте видны реки, дороги, посёлки, ЛЭП, старты; точка вне пакетов не выбирается1,5–2
10Раздача: пакеты на itch отдельными файлами, где игра их ищет, атрибуцияdp-writer + главная сессияинструкция и строка в ASSETS.md0,5

Сейчас в работе только волна A — анализ размера; остальное не начинаем. Шлюз после A: делать ли вообще (раздавать пакет порядка 1 ГБ, возможно, бессмысленно) и какие регионы.

Волны: A — 0, 1 (замеры) → шлюз. B — 2 → 3, 4, 4а. C — 5, 6, 7, 9а (параллельно, после 4). D — 8, 9, 10. Итого ~14–16 дней.

Риски

  • Рельеф большого региона — гигабайты (Россия целиком, вероятно, не нужна: только регионы полётов).
  • Склейка линий и полигонов на границах ячеек (реки, ЛЭП, большие лесные полигоны).
  • Большие полигоны OSM (лес на сотни км²) — дорогая треугуляция; резать по ячейкам при сборке.
  • Рывки кадра при дорисовке плиток — треугуляция в фоне, рисование малыми порциями.
  • Полнота OSM в глуши — подложка там не лучше WorldCover.

Вопросы пользователю

  1. Регионы при одном Алтае. Встроенные локации — две на Алтае (Онгудай, «Алтай» у Горно-Алтайска) и две на Южном Урале (Аскарово, Аушкуль, Башкортостан). Если сеть убрана, а пакет только алтайский, уральским нужен свой пакет. Варианты: (а) пакет «Республика Алтай» + маленький пакет Южного Урала вокруг Аскарово и Аушкуля; (б) пакеты только квадратами вокруг встроенных локаций (старт с карты — лишь внутри них); (в) убрать уральские локации. Решить после замера (шлюз волны A).

Почему не OsmAnd OBF (исследование 30.09.2026)

Первая версия этого документа разбирала OsmAnd OBF (эксперимент — tools/research/obf_region/, свой читатель на Python). Итог:

  • ToS OsmAnd (п. 9.2) запрещает автоматическое скачивание, кеширование и раздачу их файлов без письменного согласия.
  • Формат нестандартный (свой тип провода protobuf, версии без обещаний совместимости), живых библиотек вне OsmAnd нет.
  • Рельефа и сплошного покрова нужного качества нет (изолинии или GeoTIFF ~75 м).
  • Всё полезное в OBF — данные OSM, которые законно и проще взять из .osm.pbf.

MBTiles/PMTiles и martin для полёта хуже своего пакета (объекты порезаны по тайлам, в стандартных схемах нет ЛЭП); для карты выбора старта не нужны — рисуем из своего вектора.

Источники