Ленточка на тросе трапеции («ниточка», yaw string)

Просьба пилота: ленточка, привязанная к переднему тросу трапеции (сначала висела на боковом; перевешена по просьбе пользователя, wire: "front"). На старте показывает направление и силу ветра, в полёте — поток воздуха у крыла (скольжение, рыскание). Подсказок нет: ленточка — просто предмет в мире, как у настоящих пилотов.

Решения

  • Обе стороны (sides: ["left","right"]): видно и при взгляде влево, и вправо; при скольжении они отклоняются вместе, разница видна сразу. Одну можно оставить в конфиге.
  • Боковой трос (wire: "side"), узел в along_wire_m = 0,3 м от угла трапеции. Из кабины (FOV 60°) лента видна при взгляде вбок на 50–60° (на старте — вбок-вниз на 40°), как у настоящего пилота: периферия и быстрый взгляд. wire: "front" — на передний трос (к носу), ближе к центру поля зрения.
  • Конфиг — configs/instruments.json → telltale: ленточка — простейший прибор (показывает поток), а sail.json — только параметры шейдера паруса.
  • Цвет — насыщенный красный (слова пилота), лёгкое свечение (флуоресцентная ткань видна в тени).

Узел — из геометрии крыла, без координат в коде

Telltale.find_anchor(wing, side, cfg):

  1. Пустышки TelltaleL / TelltaleR в модели крыла, если есть (будущие модели могут задать узел явно, ось +Y пустышки — вдоль троса).
  2. Иначе — поверхность с материалом Wire у меша ControlFrame (контракт docs/guide/models.md): угол трапеции — нижние точки тросов этой стороны; боковой трос — к самой дальней по размаху точке (узел поперечины на передней кромке), передний — к самой передней точке у киля (нос). Узел = угол + along_wire_m вдоль троса. Работает для всех крыльев из build_gliders.py. Нет тросов (заглушка без модели) — предупреждение в лог, ленточки нет.

Физика (TelltaleModel, RefCounted, headless)

  • Поток у узла: w = ветер(точка узла) − (скорость планера + вращение), ветер — Atmosphere.air_velocity_at (порывы, термики, склоновый поток — всё, что в нём есть); вращение — по смене ориентации планера за шаг (airflow_at: (B·r − B_prev·r)/dt).
  • Цепочка из segments звеньев; равновесие звена — ŵ·(|w|/v45)² + вниз (сопротивление ткани против веса; lift_45_ms — поток, при котором лента на 45°). Без потока висит вниз.
  • Подстройка экспоненциальная (безусловно устойчива): темп follow_k·|w|/длина звена + pendulum_rate. Хвост тянется за предыдущим звеном (chain_coupling), трепет — бегущая синусоида с амплитудой от flutter_start_ms до flutter_full_ms, частота flutter_hz_per_ms·|w| (не выше flutter_max_hz).
  • Цена: 1 запрос ветра на ленточку за шаг физики, 6 пар вершин ImmediateMesh за кадр.

Подключение

  • GliderVisual.build → Telltale.build_on(wing) (ноды TelltaleL/R — дети крыла, едут с тряской крыла). Glider.step → visual.step_telltales(dt, prev_basis, cur_xform, model.velocity, air_fn).
  • До первого шага ленточка скрыта: у ботов (BotGlider) шагов нет — ленточек не видно.

Ограничение модели полёта

FlightModel летит без скольжения: курс каждый шаг ставится по воздушной скорости в центре крыла. Поэтому в полёте лента показывает только то, что даёт вращение (ввод в крен: узел ~0,7 м над ногами, ~1–2°) и разницу ветра в точке узла и в центре (болтанка) — в ровном вираже она строго назад. Появится скольжение в модели полёта (обратное рыскание при вводе в крен, боковой порыв) — лента покажет его сама, код ленточки менять не нужно.

Проверка

  • tests/instruments/test_telltale.gd: встречный и боковой ветер на месте, скольжение 12° в полёте (угол ленты в осях планера = углу потока ±3°), провис при 1 м/с, без потока — вниз, вращение, узел на тросе у каждого крыла (симметрично, над штангой), планер на старте в боковом ветре.
  • Кадры: tools/shots/telltale_shot.tscn (камера у крыла --eye, --set-wind=<км/ч>,<откуда> для бокового ветра на старте; без --eye — кадр камеры игры с --camera=cockpit --look).

Проверено пилотом (28.09.2026): в вираже лента смотрит строго назад, бокового сноса в полёте нет — это правильно (крыло летит носом в поток). Скольжение в лётную модель ради ленты не добавлять.