Модель полёта (scripts/flight/)

Реализует FR-1…FR-10. Логика — в RefCounted-классах (тестируются headless), нода Glider — тонкая обёртка: время, ввод, визуал. Исследование массы пилота и источники поляр — research/pilot_mass.md.

ФайлЧто
flight_model.gd — FlightModelполёт: силы, тангаж, крен, срыв, касание земли, телеметрия
wing_polar.gd — WingPolarполяра крыла CL→CD из точек (V, снижение)
ground_run.gd — GroundRunстоит / идёт / разбег, отрыв, ошибки разбега
landing_flare.gd — LandingFlareвыравнивание, «подвешивание», касание, пробежка
landing_judge.gd — LandingJudgeоценка посадки
flight_telemetry.gd — FlightTelemetryзаполнение Telemetry
load_meter.gd — LoadMeterперегрузка n = L/W (FlightModel.load), сглаживание 0,15 с
glider.gd — Glider (scenes/glider/glider.tscn)нода: шаг физики, интерполяция, сигналы
glider_visual.gd — GliderVisual (scenes/glider/glider_visual.tscn)крыло + пилот, маркеры, заглушки

Модель

Точечная масса с ориентацией. Состояние: положение, скорость относительно земли, курс, крен, угловая скорость крена, тангаж киля θ, угол атаки α, флаг срыва. Шаг — полу-неявный Эйлер с фиксированным dt (120 Гц из sim.json).

  • Силы. Подъёмная сила ⊥ воздушной скорости, наклонена на крен; сопротивление вдоль потока; вес. q = ½ρV²S, L = q·CL(α), D = q·CD(CL).

  • Поляра → CL/CD. Точки поляры (км/ч, м/с) при эталонной массе переводятся в безразмерную таблицу CL→CD (CL = 2Mg·cosγ/(ρSV²), CD = CL·tgγ). В спокойном воздухе модель сходится точно к поляре. За быстрым краем — парабола CD0 + k·CL².

  • Трапеция (FR-4). Трапеция задаёт угол атаки: pitch 0 — трим, −1 — скорость «полностью на себя», +1 — «полностью от себя» (ниже сваливания). Клавиши в полёте — раскладка дельтапланериста: W/↑ и стик вперёд — от себя (нос вверх, торможение), S/↓ — на себя (разгон); инверсия тангажа в настройках — «как в самолёте». Скорость между ними линейна по отклонению; значения — при эталонной массе. Крыло выходит на заданный угол атаки с постоянной времени pitch_time_constant_s (инерция). Раскачка скорость↔высота (фугоида) гасится добавкой к углу атаки ∝ dV/dt (flight.json → phugoid_damping, реальное крыло устойчиво по скорости).

  • Масса и высота (FR-3). Раз трапеция задаёт CL, все скорости и снижение ∝ √(M/ρ), качество неизменно; постоянные времени по крену/тангажу ∝ √(M/M_эт). Плотность ρ(h) = 1,225·e^(−h/10400).

  • Крен (FR-5). Два режима управления креном (controls.json → roll_control_mode, пункт настроек «Управление креном»); смысл ControlInput.roll в полёте задаёт ControlInput.weight_shift.

    • «Как раньше» (rate, по умолчанию). Смещение веса задаёт угловую скорость крена (roll_rate_max_dps, множитель (V/V_трим)^0,5), выход на неё — с постоянной времени roll_time_constant_s. Без управления крен держится. Переложение 45→45 ≈ 2,3–2,7 с.
    • «Смещение веса» (weight_shift). roll — положение пилота поперёк трапеции (−1..+1, 0 — центр, как в прямолинейном полёте). Техника как в жизни: качнулся вправо — крыло кренится и поворачивает; встал в центр — крыло само выравнивается и летит прямо в новом направлении. Параметры — wing.weight_shift: угловая скорость, к которой крыло выходит с постоянной времени roll_time_constant_s, p = u·roll_rate_max_dps·k(V) − (roll_stability_per_s + roll_stability_steep_per_s·(φ/45°)²)·φ (+ разница потоков на концах, FR-8). k(V) — эффективность смещения: ниже трима (V/V_трим)^roll_speed_exponent (крыло вялое), выше — (V_трим/V)^roll_heavy_exponent (на скорости тяжелее). Держишь смещение — установившийся крен ∝ смещению, спирали нет: при полном смещении в вираже ≈ 41° учебное / 45° мачтовое (Laminar) / 44° спортивное. Из центра после 2 с полного смещения крыло выравнивается (|φ| < 3°) за ≈ 2,0 / 2,6 / 3,3 с: устойчивость у учебного сильная, у спортивного на малом крене почти нейтральна. Круче roll_overbank_deg (70 / 65 / 60°, смещением не достать — болтанка в облаке, сваливание на крыло) устойчивость за 10° сходит на нет — спираль, выход только смещением в обратную сторону. Переложение 45→45 полным смещением ≈ 2,2 с. roll_input_for_bank(φ, V) — смещение для крена φ.
    • Ввод (controls.json → keyboard). Как раньше: A/D ведут ручку крена (roll_rate_per_s/roll_return_per_s). Смещение веса: A/D смещают вес до упора за roll_shift_time_s, отпустил — пружиной в центр (roll_center_time_s), у центра плавная нейтраль roll_neutral_zone. X (center) в обоих режимах — «в центр»: трапеция плавно в нейтраль (center_time_s), крыло в горизонт (как раньше — ручкой против крена, center_rate_bank_deg). Мышь в режиме «трапеция» и стик — пропорционально: ход = ручка. Автопилот (Game.autopilot) и боты тестов всегда в режиме «как раньше».
    • Вираж координированный (без скольжения): курс идёт по воздушной скорости, радиус = V²/(g·tgφ), в вираже V ×√n, снижение ×n^1,5.
  • Сваливание (FR-6). α > α_крит → срыв. За lift_loss_time_s обтекание переходит к сорванному (динамический срыв): парус работает как пластина поперёк потока, CL = CN·sinα·cosα, CD = CN·sin²α (post_stall_cn = 2, как купол парашюта). Через nose_drop_delay_s нос сам опускается на nose_drop_deg, крыло разгоняется и выходит из срыва; если держать трапецию от себя — срывы повторяются. В крене > wing_drop_bank_deg — сваливание на крыло: опущенная консоль продолжает падать, управление креном ослаблено.

  • Воздух (FR-7, FR-8). air_fn(pos) опрашивается в центре и на концах крыла (0,8 полуразмаха). Путевая = воздушная + ветер, снос получается сам. Разница вертикальных потоков на концах даёт угловую скорость крена (w_лев − w_прав)/размах — крыло кренит от термика на его краю. Порывы меняют воздушную скорость и угол атаки (толчки, «болтанка»). Резкий боковой ветер разворачивает нос в поток.

  • Земля (FR-9, контракты С1 v2 / К3 v3 — docs/contracts/control-fix.md). Фазы: standing → walking (input.walk, поворот на месте — input.turn, скорость шага падает на крутом склоне) → running (input.run: сила ног F·(1 − v/v_ног) линейно падает к пределу ног v_ног = run.speed_max_ms·(1 + unload_speed_bonus·разгрузка); это предел ног (частота и длина шага), а не скорости пилота: быстрее него ноги тормозят той же линейной силой, но только через долю веса на ногах (N/W), а склон и тяга крыла разгоняют и выше — под крутую горку (круче угла планирования) в штиль разгружённого крылом пилота «несёт», отрыв короче (CF-1: раньше скорость обрезалась по v_ног, и в штиль на высоком старте крылья с Vmin выше ≈ 30 км/ч не взлетали — «спорт», combat — weak_run через 66 м; теперь отрыв за 13–15 м на старте по умолчанию). Границы: спотыкание и падение на бегу быстрее ног не моделируются. Курс на бегу — дугой от крена крыла. Во всех фазах pitch — нос крыла (угол атаки киля launch.alpha_neutral_deg + pitch·alpha_range_deg, 0 — угол разбега крыла), roll — заданный крен «руки пилота» (±ground_bank.command_max_deg). Рука держит крыло с ограниченной силой, боковой ветер его кренит — раздел «Крен на земле». Отрыв — когда вертикальная составляющая подъёмной силы ≥ веса (и крыло не сорвано). Срыв взлёта один — wingtip: конец консоли, опущенной креном (полуразмах от киля на высоте takeoff.wing_height_m), на земле — крыло опрокинуто (и стоя тоже; на ровном это крен ≈ asin(h/полуразмах) ≈ 17–21°). Нос высоко (крыло за срывом), нос низко (крыло давит вниз), попутный ветер (мала воздушная скорость) — не отдельные срывы: подъёмной силы не хватает, отрыва просто нет. Срывов и переходов по времени нет (К3 v3): отрыв — только L ≥ W от воздушной скорости (бег + ветер), без бега и ветра крыло не отрывается, в штиль пилот бежит, сколько нужно, бросил бежать — снова стоит (tests/flight/test_liftoff_physics.gd). Текст — GroundRun.failure_text(reason).

  • Выравнивание (FR-10, LandingFlare, flight.json → flare). Ниже height_m (4 м над ногами) трапеция «от себя» сильнее push_threshold ставит киль на тангаж до pitch_deg (50°) к горизонту за pitch_time_s; нос при срыве не опускается. Парус на закритике тормозит (нормальная сила ×cn_factor, кратковременный рост CL ×dynamic_cl_factor), крыло подскакивает, и с верхней точки «подвешивается»: опускает пилота на ноги не быстрее hang_sink_ms (0,7 м/с), пока не опустится на hang_drop_m (1,6 м) — дальше динамический эффект кончается и крыло падает (феноменологическая модель: вихрь динамического срыва, экран земли, пилот встаёт под крыло). Правильно: ноги ~0,5 м над землёй, при ~1,2·V_св — от себя до упора: касание ~1,2 м/с вертикально, путевая ≤ 1,2 м/с, пилот встаёт на ноги (пробежка runout_decel_ms2, < 1,5 м). На 1 м выше — всё ещё мягкая (~1,3 м/с); на 3 м выше — «плюх» ~6,4 м/с, жёсткая. Во встречный ветер выравнивают позже (при ~0,9·V_св) — путевая почти 0; полное раннее выравнивание в ветер тащит крыло назад.

  • Посадка (FR-10). Касание: скорость раскладывается по нормали к склону («вертикальная») и вдоль него («горизонтальная»). Пороги по ответу пилотов («с 5 м/с посадка уже жёсткая, при правильной посадке скорость ≈ 0»): мягкая — вертикальная < 5 м/с, горизонтальная ≤ 3 м/с, крен ≤ 15°; жёсткая — вертикальная 5…7 м/с или горизонтальная 3…12 м/с (на триме без выравнивания ~10 м/с); авария — вертикальная > 7 м/с (вдвое больше энергии удара), горизонтальная

    12 м/с или крен > 35°. После мягкой/жёсткой посадки пилот добегает и может идти или снова разбегаться. После отрыва пилот ещё на ногах (держит стойки, бежит), пока ступни не поднялись над землёй выше takeoff.upright_clear_m (1 м — длина ноги): в это время угол атаки задают руки, как на разбеге (launch.alpha_neutral_deg + pitch·alpha_range_deg), а касание — снова шаги разбега (GroundRun.resume, отрыв опять при L ≥ W), не посадка. Выше — пилот в подвеске, трим по трапеции, касание — посадка. Геометрия вместо прежней «форы» 1,5 с (К3 v3).

  • Перегрузка. FlightModel.load.load_factor — n = подъёмная сила / вес, сглаженная (flight.json → load_factor.filter_s); есть load_raw, load_max, load_min. Если в Telemetry есть поле load_factor, оно заполняется. Поломка крыла при превышении (FR-14c) отложена.

Допущения

  • Нет скольжения и рыскания как отдельной степени свободы; нет момента от вращения по курсу.
  • Тангаж — кинематический (первый порядок), без моделирования момента трапеции и центровки.
  • Нет экранного эффекта у земли; на земле пилот и крыло — одна точка (кроме крена крыла на плечах — «Крен на земле»). Выравнивание — феноменологическое (cn_factor, dynamic_cl_factor): маятник пилота не моделируется.
  • Поляры синтезированы по паспортным ориентирам (производители не публикуют полных поляр).
  • FR-1 внутренне противоречив (качество 15 @ 42 ⇒ снижение 0,78 < «мин.» 0,9); принято мин. снижение 0,82 @ 33–36, качество 15 @ 45 км/ч (questions.md).

Параметры

  • configs/wings/<id>.json — площадь, размах, масса крыла, диапазон и эталон массы пилота, поляра (точки), скорости трима/«на себя»/«от себя», наклон CL(α), крен, сваливание, разбег, визуал. reference — ориентиры поляры для тестов.
  • configs/pilot.json — масса пилота, ходьба, разбег, визуал пилота.
  • configs/flight.json — плотность воздуха, инерция от массы, демпфирование фугоиды, опрос воздуха, разбег и ошибки, крен крыла на земле, оценка посадки, визуал.

Крылья

Девять крыльев с реальными прототипами (план и источники — docs/archive/plan/wings-lineup.md). Группы — configs/wing_groups.json (порядок в меню «Полёт…»), модели в группе — по reference.best_glide, затем wind_max_ms (WingCatalog, scripts/game/wing_catalog.gd):

Группаid: прототипКачествоВетер до, м/с
soviet — Советские 1980-хslavutich_ut: Славутич-УТ; apogee: «Апогей» (Мысенко); atlas: «Атлас»6,2 / 7 / 8,47 / 10 / 8
trainer — Учебные однообшивочныеtarget: Aeros Target 16; training: WW Falcon 1707,3 / 98 / 8
kingpost — Мачтовые двухобшивочныеmagic: Airwave Magic IV 166; laminar: Icaro Laminar Easy 1410,6 / 13,210 / 10
topless — Безмачтовыеsport: Moyes Litespeed RS 4; combat: Aeros Combat GT 13.215 / 1612 / 12

Поля крыла для меню (у каждого _doc): group, wind_max_ms (комфортный ветер на старте для опытного пилота — подсказка, не запрет), prototype, era, kingpost, double_surface_pct (0 — однообшивочное). Управляемость задаётся прежними параметрами: «Апогей» — самый устойчивый и лёгкий в крене, мягкое сваливание (слова пилота). Wills Wing Sport 2 (kingpost) убран 28.09.2026. Тесты: tests/flight/test_wings.gd, tests/game/test_wing_catalog.gd; поляра и сваливание — для всех крыльев из Config.list_configs("wings").

API

# FlightModel (RefCounted)
signal took_off
signal landed(result: Dictionary)   # {grade: "soft"|"hard"|"crash", vertical_speed_ms,
                                    #  horizontal_speed_ms, bank_deg, position, flight_time_s}
signal takeoff_failed(reason: String)
var telemetry: Telemetry
var mode: FlightModel.Mode           # GROUND, AIR, LANDED, FAILED
func setup(wing: Dictionary, pilot: Dictionary, flight_override := {}) -> void
func reset_on_ground(pos: Vector3, heading_deg: float) -> void
func reset_in_air(pos: Vector3, heading_deg: float, airspeed_ms := 0.0, wind := Vector3.ZERO) -> void
func step(dt: float, input: ControlInput, air_fn: Callable, ground_fn: Callable) -> void
func phase() -> String               # standing/walking/running/flying/landed/failed
func trim_speed() -> float; func stall_speed() -> float; func steady_glide(v) -> Vector2
static func clamp_pilot_mass(wing: Dictionary, mass_kg: float) -> float

# Glider (Node3D, scenes/glider/glider.tscn); начало координат — ноги пилота
signal telemetry_updated(t: Telemetry)   # каждый шаг физики, один и тот же объект
signal landed(result: Dictionary)
signal takeoff_failed(reason: String)
signal took_off
@export var wing_id := "sport"; @export var pilot_mass_kg := 0.0; @export var auto_start
var control: ControlInput
func setup(wing_name: String, mass_kg := 0.0) -> void   # масса ограничивается диапазоном крыла
func set_input(c: ControlInput) -> void
func set_air_fn(f: Callable) -> void      # f(pos: Vector3) -> Vector3
func set_ground_fn(f: Callable) -> void   # f(x: float, z: float) -> float
func reset_on_ground(pos: Vector3, heading_deg: float) -> void
func reset_in_air(pos: Vector3, heading_deg: float, airspeed_ms := 0.0) -> void
func step(dt: float) -> void            # шаг физики; _physics_process зовёт его сам —
                                        # интегратор может выключить physics_process и шагать сам
func get_telemetry() -> Telemetry
func phase() -> String
func get_marker(name: String) -> Node3D   # PilotHead, InstrumentMount, BaseBar, HangPoint, WingTipL/R

Визуал и контракт моделей

Glider создаёт сцену flight.json → visual.scene (scenes/glider/glider_visual.tscn, скрипт GliderVisual) и вызывает build(wing, pilot, visual_cfg). Нода Visual стоит в начале координат планера (ноги пилота). Оси моделей: 1 ед. = 1 м, вперёд −Z, вверх +Y (glTF «+Y up»).

Крыло — wings/<id>.json → visual.visual_model (например res://assets/models/glider_sport.glb). Начало координат = точка подвески. Ноды по имени:

  • Sail — парус (меш), ControlFrame — трапеция;
  • маркеры-пустышки: HangPoint (подвеска, ≈ начало координат), BaseBar (центр ручки), InstrumentMount (крепление прибора, +Z маркера — нормаль экрана, смотрит на пилота), WingTipL, WingTipR. Модель ставится так, что HangPoint оказывается на высоте visual.hang_height_m над ногами.

Пилот — pilot.json → visual.visual_model (res://assets/models/pilot.glb). Начало координат — карабин (крепится в HangPoint), тело лёжа ниже и чуть позади, голова в −Z; пустышка Head — точка глаз. Модель висит под нодой обёртки Pilot, которая сдвигается по крену (±pilot_shift_m) и тангажу (±pilot_bar_m). На земле обёртка ставит пилота вертикально (поворот лежачей модели на 90° вокруг центра тела pilot.json → visual.body_*). Маркер PilotHead обёртки следует за Head (get_marker("PilotHead"), GliderVisual.get_head_transform()) — для кабинной камеры.

Парус — после загрузки крыла SailMaterial.apply(Sail, "<имя модели>") (шейдер агента models, параметры — configs/sail.json), каждый кадр GliderVisual.set_flight(airspeed, stall, turbulence): stall = FlightModel.stall_amount() (доля сорванного потока 0..1), turbulence = средний разброс мгновенной перегрузки FlightModel.load.jitter / sail.json → turbulence_full_g (0,5 g): в спокойном воздухе ~0, в термике ~0,1–0,3, в облачной болтанке — до 1.

Нет файла модели — предупреждение в лог и заглушка из примитивов с теми же именами нод.

Тесты

godot --headless --path . res://tests/run_tests.tscn -- --filter=flight — тесты: поляра и трим каждого крыла, ориентиры FR-1/FR-2, √M и неизменное качество, инерция, переложение 45→45 (2,3 / 2,3 / 2,7 с), удержание крена; режим «смещение веса» (test_roll_weight_shift): крен от смещения, выравнивание в центре (учебное быстрее спортивного), крен без спирали, 45→45, клавиша X; радиус и снижение в вираже, сваливание (нос вниз, потеря высоты, на крыло, выход), поток +2 м/с, снос и путевая, несимметричный подъём, порыв, разбег со встречным / попутным / боковым ветром, нос высоко/низко, слабый разбег, ходьба и склон, оценка посадки, выравнивание (штиль / на 1 м и 3 м выше / встречный ветер), пробежка, склон, нода Glider.

Таблица поляры

godot --headless --path . res://tests/flight/polar_table.tscn — прогон модели в спокойном воздухе (плотность уровня моря) при разных положениях трапеции, для мин./эталонной/макс. массы пилота. «срыв» — трапеция за сваливанием, крыло парашютирует (значения усреднены по циклам срыва).

Учебное (training), пилот 64 кг, полная масса 87 кг: сваливание 22.3 км/ч, трим 28.5 км/ч
  трапеция |  V км/ч | снижение м/с | качество
  +1.00    |   33.3  |    4.77      |   1.9 срыв
  +0.75    |   28.8  |    2.95      |   2.7
  +0.50    |   23.3  |    1.10      |   5.9
  +0.25    |   25.9  |    0.94      |   7.7
  +0.00    |   28.4  |    0.93      |   8.4
  -0.10    |   31.1  |    0.98      |   8.8
  -0.20    |   33.7  |    1.05      |   9.0
  -0.30    |   36.4  |    1.13      |   8.9
  -0.45    |   40.3  |    1.32      |   8.5
  -0.60    |   44.3  |    1.57      |   7.8
  -0.80    |   49.5  |    2.02      |   6.8
  -1.00    |   54.7  |    2.57      |   5.9

Учебное (training), пилот 80 кг, полная масса 103 кг: сваливание 24.3 км/ч, трим 31.0 км/ч
  трапеция |  V км/ч | снижение м/с | качество
  +1.00    |   36.2  |    5.19      |   1.9 срыв
  +0.75    |   31.0  |    1.43      |   6.0
  +0.50    |   25.3  |    1.20      |   5.9
  +0.25    |   28.1  |    1.02      |   7.7
  +0.00    |   30.9  |    1.02      |   8.4
  -0.10    |   33.8  |    1.07      |   8.8
  -0.20    |   36.7  |    1.14      |   9.0
  -0.30    |   39.6  |    1.23      |   8.9
  -0.45    |   43.9  |    1.43      |   8.5
  -0.60    |   48.2  |    1.71      |   7.8
  -0.80    |   53.9  |    2.20      |   6.8
  -1.00    |   59.6  |    2.80      |   5.9

Учебное (training), пилот 100 кг, полная масса 123 кг: сваливание 26.5 км/ч, трим 33.9 км/ч
  трапеция |  V км/ч | снижение м/с | качество
  +1.00    |   39.6  |    5.67      |   1.9 срыв
  +0.75    |   32.1  |    2.40      |   3.7 срыв
  +0.50    |   27.7  |    1.31      |   5.9
  +0.25    |   30.7  |    1.11      |   7.7
  +0.00    |   33.8  |    1.11      |   8.4
  -0.10    |   36.9  |    1.17      |   8.8
  -0.20    |   40.1  |    1.24      |   9.0
  -0.30    |   43.2  |    1.35      |   8.9
  -0.45    |   48.0  |    1.56      |   8.5
  -0.60    |   52.7  |    1.87      |   7.8
  -0.80    |   58.9  |    2.40      |   6.8
  -1.00    |   65.1  |    3.06      |   5.9

Мачтовое (laminar), пилот 65 кг, полная масса 93 кг: сваливание 24.2 км/ч, трим 32.2 км/ч
  трапеция |  V км/ч | снижение м/с | качество
  +1.00    |   30.6  |    2.94      |   2.9 срыв
  +0.75    |   29.1  |    1.07      |   7.6 срыв
  +0.50    |   26.1  |    1.10      |   6.6
  +0.25    |   29.1  |    0.92      |   8.8
  +0.00    |   32.1  |    0.85      |  10.5
  -0.10    |   36.7  |    0.84      |  12.2
  -0.20    |   41.3  |    0.88      |  13.0
  -0.30    |   45.9  |    0.98      |  13.0
  -0.45    |   52.8  |    1.28      |  11.5
  -0.60    |   59.6  |    1.72      |   9.6
  -0.80    |   68.7  |    2.48      |   7.7
  -1.00    |   77.8  |    3.45      |   6.3

Мачтовое (laminar), пилот 82 кг, полная масса 110 кг: сваливание 26.3 км/ч, трим 35.0 км/ч
  трапеция |  V км/ч | снижение м/с | качество
  +1.00    |   33.6  |    3.33      |   2.8 срыв
  +0.75    |   32.6  |    2.41      |   3.8
  +0.50    |   28.3  |    1.20      |   6.6
  +0.25    |   31.6  |    1.00      |   8.8
  +0.00    |   34.9  |    0.92      |  10.5
  -0.10    |   39.9  |    0.91      |  12.2
  -0.20    |   44.9  |    0.96      |  13.0
  -0.30    |   49.9  |    1.07      |  13.0
  -0.45    |   57.4  |    1.39      |  11.5
  -0.60    |   64.8  |    1.87      |   9.6
  -0.80    |   74.8  |    2.70      |   7.7
  -1.00    |   84.6  |    3.79      |   6.2

Мачтовое (laminar), пилот 95 кг, полная масса 123 кг: сваливание 27.8 км/ч, трим 37.0 км/ч
  трапеция |  V км/ч | снижение м/с | качество
  +1.00    |   37.7  |    3.97      |   2.6 срыв
  +0.75    |   34.0  |    1.82      |   5.2
  +0.50    |   30.0  |    1.27      |   6.6
  +0.25    |   33.5  |    1.06      |   8.8
  +0.00    |   36.9  |    0.98      |  10.5
  -0.10    |   42.2  |    0.96      |  12.2
  -0.20    |   47.5  |    1.01      |  13.0
  -0.30    |   52.8  |    1.13      |  13.0
  -0.45    |   60.7  |    1.47      |  11.5
  -0.60    |   68.5  |    1.97      |   9.7
  -0.80    |   79.2  |    2.87      |   7.7
  -1.00    |   89.3  |    4.03      |   6.2

Спортивное безмачтовое (sport), пилот 70 кг, полная масса 104 кг: сваливание 25.4 км/ч, трим 33.7 км/ч
  трапеция |  V км/ч | снижение м/с | качество
  +1.00    |   32.5  |    2.88      |   3.1
  +0.75    |   32.4  |    2.28      |   3.9
  +0.50    |   27.5  |    0.86      |   8.9
  +0.25    |   30.6  |    0.77      |  11.0
  +0.00    |   33.6  |    0.77      |  12.2
  -0.10    |   39.1  |    0.78      |  14.0
  -0.20    |   44.6  |    0.83      |  14.9
  -0.30    |   50.1  |    0.99      |  14.1
  -0.45    |   58.4  |    1.37      |  11.9
  -0.60    |   66.5  |    1.92      |   9.6
  -0.80    |   77.6  |    3.02      |   7.1
  -1.00    |   88.0  |    4.30      |   5.7

Спортивное безмачтовое (sport), пилот 85 кг, полная масса 119 кг: сваливание 27.2 км/ч, трим 36.0 км/ч
  трапеция |  V км/ч | снижение м/с | качество
  +1.00    |   34.5  |    2.88      |   3.3 срыв
  +0.75    |   33.7  |    1.51      |   6.2
  +0.50    |   29.4  |    0.92      |   8.9
  +0.25    |   32.7  |    0.83      |  11.0
  +0.00    |   35.9  |    0.82      |  12.2
  -0.10    |   41.8  |    0.83      |  14.0
  -0.20    |   47.7  |    0.89      |  14.9
  -0.30    |   53.6  |    1.05      |  14.1
  -0.45    |   62.4  |    1.46      |  11.9
  -0.60    |   71.2  |    2.04      |   9.7
  -0.80    |   83.0  |    3.26      |   7.1
  -1.00    |   93.8  |    4.59      |   5.7

Спортивное безмачтовое (sport), пилот 105 кг, полная масса 139 кг: сваливание 29.4 км/ч, трим 38.9 км/ч
  трапеция |  V км/ч | снижение м/с | качество
  +1.00    |   37.3  |    4.67      |   2.2
  +0.75    |   36.4  |    1.66      |   6.1
  +0.50    |   31.8  |    0.99      |   8.9
  +0.25    |   35.3  |    0.89      |  11.0
  +0.00    |   38.8  |    0.89      |  12.2
  -0.10    |   45.2  |    0.90      |  14.0
  -0.20    |   51.6  |    0.96      |  14.9
  -0.30    |   58.0  |    1.14      |  14.1
  -0.45    |   67.4  |    1.57      |  11.9
  -0.60    |   77.1  |    2.21      |   9.7
  -0.80    |   89.6  |    3.57      |   7.0
  -1.00    |  100.9  |    4.91      |   5.7

Крен на земле (SF-3, GroundRun._ground_bank, flight.json → ground_bank)

Пилот стоит или бежит с крылом на плечах; крыло поворачивается по крену вокруг оси киля у плеч. Крен FlightModel.bank — относительно горизонта, не склона: на косом склоне крыло ровно по горизонту, верхняя консоль ближе к земле (так и в жизни). Крен ведёт рука пилота к заданному φ_зад = input.roll · command_max_deg (К3 v3; без ввода — горизонт); поворот стоя (input.turn) крыло не кренит. Уравнение (полунеявный Эйлер, шаг физики):

I·φ̈ = M_скольж + M_несимм + M_веса + M_центроб + M_пилота − D·φ̇

  • I — инерция по крену, кг·м²: конструкция span_mass_fraction (0,75) массы крыла по размаху как стержень, m·b²/12 (киль, мачта, трапеция — у оси), плюс присоединённая масса воздуха пластины π/4·ρ·c² на метр размаха (c = S/b): π/48·ρ·c²·b³. «Спорт» (34 кг, 10,4 м, 14,1 м²): 230 + 165 ≈ 400 кг·м²; остальные крылья — таблица ниже.
  • M_скольж — поперечная устойчивость от скольжения. Стреловидное крыло при скольжении β: наветренная консоль видит поток ближе к нормали к передней кромке, её подъёмная сила больше. Полосовая теория (подъёмная сила ∝ квадрату нормальной к кромке скорости, эллиптическая нагрузка, ЦД полукрыла на 4/(3π) полуразмаха): C_l = −0,42·tgΛ·C_L·½sin2β; стреловидность передней кромки по геометрии крыльев ≈ 15–18° ⇒ |C_lβ| = slip_roll_per_cl · C_L = 0,12·C_L. Тогда M = ½ρ·S·b·|C_lβ|·u·s, где u — встречная, s — боковая составляющие потока на крыло (V²·½sin2β = u·s): при чисто боковом ветре стоя (u = 0) момента нет, момент растёт со встречной скоростью — на бегу. C_L — по текущему углу атаки (обтекаемая часть, до C_L max): нос ниже — крыло спокойнее в боковой ветер (C_L меньше), это и есть техника старта в боковик. Ветер слева (воздух движется вправо) поднимает левую консоль — крен вправо (+).
  • M_несимм — несимметрия вертикального ветра по размаху (как в воздухе, FR-8): воздух опрашивается на концах (air_sampling.tip_fraction полуразмаха) на высоте крыла; M = D·air_roll_gain·(w_лев − w_прав)/b — в воздухе тот же момент даёт установившуюся угловую скорость (w_лев − w_прав)/b.
  • D — аэродемпфирование по крену: C_lp = −CLα/8 (эллиптическая нагрузка, CLα из крыла), D = ¼·ρ·V·S·b²·|C_lp| (V — воздушная скорость). «Спорт» при 4,5 м/с ≈ 1200 Н·м·с.
  • M_веса = m_крыла·g·h·sinφ, h = wing_cg_above_axis_m (0,2 м: ЦТ крыла чуть выше киля на плечах) — опрокидывающий: чем больше крен, тем сильнее.
  • M_центроб = −m_крыла·v·ω·h·cosφ — центробежная сила на ЦТ крыла на дуге разбега (наружу); при a = 2 м/с² ≈ 14 Н·м, крен ≈ 0,2°.
  • M_пилота — «рука пилота»: регулятор к заданному крену φ_зад «пропорционально отклонению + демпфирование», M = −I/τ²·(φ − φ_зад) − 2I/τ·φ̇ (GroundRun.pilot_moment(m, φ − φ_зад, …)), жёсткость I/τ², демпфирование 2I/τ (критическое), τ = pilot_response_s = 0,3 с (реакция на наклон груза в руках 0,15–0,3 с), ограниченный pilot_moment_max_nm·N/W. M_max = 200 Н·м — две оценки сходятся: руки на стойках ≈ ±0,35 м от киля, по ≈ 300 Н вверх/вниз: 2·300·0,35 ≈ 210; опора — пилот с крылом не может передать на землю момент больше W·d, не опрокинувшись через ребро стоп: (85+30)·9,81·0,18 ≈ 200 (полуширина стойки ≈ 0,15 м плюс смещение тела). Второе объясняет множитель N/W = clamp(1 − L_верт/W, 0, 1) — доля веса на ногах (GroundRun.feet_load): стоит — полная сила, крыло несёт половину веса — половина, на отрыве (L_верт = W) — ноль, крен свободен. Предел достигается при отклонении от φ_зад ≈ 2–3°: стоя в штиль рука доводит крыло до φ_зад (против веса крыла: m·g·h·sin15° ≈ 18 Н·м ≪ M_max), в сильный встречный — только пока хватает M_max·N/W.
  • Заданный крен φ_зад, command_max_deg = 15°: кисти на стойках у плеч ≈ ±0,35 м от киля; с крылом на плечах одну руку можно поднять, другую опустить на ≈ 0,1 м — 0,2 м на базе 0,7 м: atan(0,2/0,7) ≈ 16°. Взято 15° — меньше крена касания консоли ровной земли (≈ 17–21°): полный ход руки сам консоль на землю не кладёт, ветер или дуга — могут. Ввод с клавиатуры ведёт ручку к ±1 так же, как в полёте (тот же ход и возврат), поэтому на отрыве roll непрерывен.
  • Опрокидывание: если |M_ветра| > M_max·N/W, рука «проскальзывает» — крен растёт (избыток момента против аэродемпфирования, дальше помогает вес крыла); опущенная консоль коснулась земли (на ровном — крен ≈ asin(1,5/полуразмах) ≈ 17°) — срыв wingtip (итог в интерфейсе, без подсказок). Проверяется и стоя.
  • Переход в полёт: предел руки уходит в ноль вместе с N/W, поэтому к отрыву крыло уже свободно; bank и roll_rate на отрыве непрерывны, в воздухе их подхватывает прежняя модель крена (_update_roll, удержания нет). Боковой ветер на отрыве поднимает наветренную консоль (слабый боковик 2,5 м/с — крен на отрыве 6–10°, 10°/с) — в воздухе скольжения нет, и крен дальше гасится моделью полёта.
  • Поворот (_turn, К3 v3): стоя и шагом — на месте от input.turn, pilot.walk.turn_rate_dps (45°/с), крыло не кренится. На бегу — дугой от крена крыла (input.turn не используется). Пилот бежит по дуге заданного рукой крена φ_зад — наклоняется в поворот вместе с крылом на плечах: a_зад = g·tgφ_зад. Боковая составляющая подъёмной силы крыла при крене φ тянет вбок: a_кр = (1 − N/W)·g·sinφ (L ≈ L_верт = (1 − N/W)·W). Разницу дают ноги трением, но не больше, чем позволяет равновесие наклонённого бегуна с весом N на ногах: N/W·a_max. Итого a = a_кр + clamp(a_зад − a_кр, ±N/W·a_max), |a| ≤ a_max = pilot.run.turn_accel_max_ms2 = 2 м/с² (наклон тела с ≈ 30 кг крыла на плечах на бегу ≈ 10–12°: g·tg11° ≈ 2; сцепление на траве μ ≈ 0,5 допускает ≈ 5 — не оно ограничивает). ω = a/v, но не больше поворота на месте. Пока ноги нагружены, курс — от φ_зад (предел a_max при φ_зад ≈ 11,5°, ≈ ¾ хода руки), R_min = v²/a_max: 18 м на 6 м/с, 29 м на 7,7 м/с. К отрыву (N → 0) курс — от крена крыла, a = g·sinφ — тот же вираж g·tgφ/v, что в воздухе, без излома. Без ввода крена (φ_зад = 0) — бег прямо; крен от бокового ветра ноги перебарывают, пока нагружены, а почти разгруженного пилота крыло уводит в дугу по ветру — крен растёт быстрее (боковик сильнее). Пилот исправляет креном руки: у крупных крыльев, долго бегущих почти разгруженными (ww_cross_country, ww_ultra_sport), в боковик 2,5 м/с при встречном 4 без поправки крен к отрыву переходит 20° (срыв wingtip), с поправкой — взлёт с креном 13–18°. Порог бокового ветра стоя (M_ветра = M_max·N/W; бисекция по самой модели, как в test_side_gust_threshold): боковая составляющая, м/с, при которой рука перестаёт держать, пилот лицом во встречный ветер. Нос нейтрально (α ≈ 16–17°, C_L у C_L max) / трапеция наполовину на себя (нос на 10° ниже):
КрылоI, кг·м²встречный 2 м/с4 м/с6 м/с
training3053,7 / 6,01,8 / 3,50,7 / 2,0
apogee3563,5 / 5,71,7 / 3,30,6 / 1,8
sport3963,8 / 6,01,9 / 3,40,9 / 2,0
combat3794,1 / 6,42,1 / 3,71,0 / 2,2

(остальные — в пределах тех же чисел; atlas при нейтральном носе срывается по носу.) Порог падает со встречным ветром вдвойне: момент ∝ u·s, а крыло разгружает ноги (N/W) — в сильный ветер надо держать нос ниже и стоять точно против ветра. Выше порога крен растёт медленно: избыток момента против аэродемпфирования (порыв ×1,2 — опрокидывание за ≈ 10 с, ×2 — за ≈ 3,5 с); на 20 % ниже — крен ≈ 1,6°. На бегу рука ослабевает с разгрузкой, и в сильный боковик крыло отрывается с креном (6 м/с сбоку при встречном 1: «спорт» 17°, combat 22°, apogee 8°) — срыв wingtip на разбеге получается, только если консоль успеет коснуться земли до отрыва.

Чего модель не умеет: ноги пилота неподвижны относительно крыла (нет шага в сторону, подсаживания под опущенную консоль); наклон крыла в ветер — только рукой до ±15°; поза тела не моделируется — только предел момента; C_lβ — полосовая теория стреловидности без анэдра и выгиба паруса и за пределами малых β (½sin2β); порывы по размаху — только вертикальные; утомление рук нет; при опрокидывании крыло не касается земли физически — только срыв wingtip по касанию. Следствие модели (находка, не подгонка): при нейтральном носе порог бокового ветра стоя быстро падает со встречным — при встречном 6 м/с всего 0,6–1,0 м/с сбоку (≈ 5–10° от оси ветра), с носом на 10° ниже ≈ 2 м/с: момент ∝ C_L·u·s, а крыло уже несёт половину веса и рука слабеет вдвое. Лечится техникой (нос ниже, точно против ветра), а не параметрами; если пилоты скажут, что в 6 м/с крыло держится заметно легче, — первые кандидаты на уточнение C_lβ (анэдр, выгиб паруса) и M_max.

Тесты — tests/flight/test_ground_bank.gd (условия SF-3), test_takeoff.gd::test_crosswind_*, test_liftoff_physics.gd.

Поза крыла на земле (SF-4, зазор крыла над рельефом)

Поза планера на земле — Telemetry.basis = from_euler(θ, −курс, −крен) с началом в ступнях (FlightModel.position), карабин на visual.hang_height_m выше (К4, не менялось). Замер — tools/flight/wing_clearance.gd: вершины реальной модели крыла (консоли, нос/хвост киля, трапеция) в этой позе, зазор до рельефа по вертикали; таблица по фазам и стартам — tools/flight/wing_clearance_run.tscn, данные — docs/archive/plan/sf4_wing_clearance/.

Почему крыло уходило в землю (по замерам, разложение на кандидатов):

  • Крен от A/D — главная причина на ровном: A/D стоя кренили крыло до −17,9°, консоль в землю на 0,20 м (combat); на склоне 20° стоя с A пилот разворачивается поперёк склона — до −2,2 м. Исправлено SF-3: A/D на земле — только курс, крен держит «рука пилота» (раздел «Крен на земле»).
  • Тангаж стоя от горизонта: пока потока нет, угол атаки считался от горизонта, и на склоне 20° вниз нос стоял на 20° выше, чем на первом шаге разбега; с трапецией от себя концы консолей уходили в склон на 0,56 м. Теперь опорное направление без потока — вдоль склона по курсу (плавный переход к потоку до min_airspeed_ms), а первый шаг после reset_on_ground сразу ставит установившийся тангаж (без переходного наклона от горизонта).
  • Не причины: поворот базиса вокруг ступней (против поворота вокруг карабина — зазор того же порядка), высота подвески и сглаживание позы.

Зазор после исправлений (штиль, крен 0, все крылья; tests/flight/test_wing_clearance.gd): ровно и склон 20° вниз по курсу — стоит (трапеция нейтрально и до упора), шагом, разбег, A/D стоя и на бегу по ровному — ≥ 0,35 м (порог теста 0,3 м).

Границы модели:

  • Рельеф рисуется треугольниками по узлам сетки (25 м), ступни стоят на билинейной высоте — видимая земля под крылом может быть до ≈ 0,3 м выше расчётной (замер clear_rendered на стартах локаций). Рельеф не трогаем, пока на стартах локаций при крене 0 зазор по видимой земле ≥ 0 (таблица ниже).
  • Косой склон и поворот поперёк склона — крыло ровно по горизонту, верхняя консоль у склона или в нём (косой 15°: от −0,8 до +0,06 м; стоя с A на склоне 20° поперёк — до −0,3 м, на бегу по дуге поперёк — до −0,57 м): так и в жизни консоль чиркает по склону; столкновения крыла с землёй на земле нет — срыв wingtip, только если консоль опущена креном.
  • Тело пилота стоя вертикально визуально: GliderVisual._ground_pose снимает тангаж крыла поворотом тела вокруг ступней (иначе пилот «сидит», отклонившись назад на 16–37° вместе с крылом); руки тянутся к стойкам (IK) как получится; физическая поза (К4) не меняется.

Старт в сильный ветер (F01)

Нос на земле без ввода — угол разбега крыла launch.alpha_neutral_deg (16–17°, CF-3; прежняя надбавка игрока run_nose_neutral = 0,3 — 22–23° — у «Славутича» с α_срыва 21,4° была за срывом: нос высоко, отрыва нет). Нос пилот ведёт сам на полный ход (±20° угла атаки). В сильный ветер нос выше нейтрали близок к срыву: в затишье, когда у склона дует снизу, поток разворачивается и угол атаки уходит за срыв (крыло сорвано — отрыва нет). Правильная техника, как в жизни: при ветре ≥ 3,5 м/с держать нос ниже — угол атаки ≈ 18°; в штиль — чуть выше нейтрали (≈ 19–20°), чтобы крыло несло на малой скорости. Автопилот (scripts/game/autopilot.gd) так и делает; тест — tests/flight/test_strong_wind_launch.gd.