Модуль «start-fixes» — фиксы старта с земли
Ветка feature/start-fixes (от main @ 113878d), копия ~/deltaplan-start. Журнал — docs/archive/plan/start-fixes-progress.md, контракты — docs/contracts/start-fixes.md (+ tests/game/test_start_fixes_contracts.gd).
Источник — TODO.md, строки 22–28 (отзыв пользователя 30.09.2026).
Цель
Старт с земли в любой точке карты должен быть возможен и выглядеть правдой: вокруг старта нет леса и кустов; под деревьями растёт трава; пилот с крылом на земле стоит ровно по горизонту, крен держит «рука пилота» с физически ограниченной силой, переход в полёт без рывка; крыло не проходит сквозь землю на ровном старте.
Решения пользователя (не обсуждаются)
- Пустырь: вокруг точки старта убирать деревья и кусты; радиус пустыря — не меньше 2× длины разбега (иначе сразу за разбегом стоят деревья и набрать высоту до них невозможно). Тест проверяет, что в радиусе ≥ 2× длины разбега нет деревьев и кустов.
- Трава на подложке «лес»: рисовать траву и там (деревья не должны стоять на голой плоской текстуре).
- Пилот на земле до взлёта:
- стоит — всегда вертикален, только поворот вокруг вертикальной оси; бежит — A/D заворачивают его по дуге;
- переход в полёт без рывка: удержание крена ослабевает по мере того, как крыло забирает вес с ног (стоит — полностью, крыло несёт половину веса — наполовину, оторвался — крен свободен);
- не жёсткая блокировка, а сильная «рука пилота» (момент с физическим смыслом, ограниченный силой пилота): слабый ветер перебарывает, сильный боковой порыв может поднять консоль и опрокинуть — опасность учит последствием, без подсказок и сообщений;
- «вертикален» — относительно горизонта, не склона: на косом склоне верхняя консоль близко к земле — правда жизни;
- если крыло цепляет землю на ровном старте — это отдельная ошибка столкновения/постановки пилота на рельеф: найти причину с доказательством (числа/кадры), а не прятать удержанием.
- Пустырь = только убрать деревья (30.09, уточнение решения 1): траву и всё остальное (кусты, камни, подложку) не убирать — пустырь на старте с травой. Новая поляна: только FOREST → GRASS (деревья, импостеры, маска леса уходят); SHRUB и кусты не трогаются. Приёмка SF-1: в радиусе ≥ 2× разбега нет деревьев, трава на месте; координатор проверяет по скриншоту старта (в т.ч. старт в лесу).
- Коридора разбега для старта с карты нет (30.09): кусты не убирать — при боковом ветре игрок может развернуться и бежать вдоль склона, направление разбега заранее неизвестно. К1 не меняется.
- Отдельной задачи по
test_takeoff::test_headwind_launch(atlas, nose_high) нет (30.09): не заводить; падение — известное, было на базе до модуля. - SF-4 передаётся исполнителю SF-3 (30.09): связанные задачи ведёт один исполнитель. Исполнитель SF-4 коммитит наработки и находки в
start/sf4и останавливается; исполнитель SF-3 вливаетstart/sf4вstart/sf3и доводит обе задачи вместе — приёмка по условиям SF-3 + SF-4; копия~/deltaplan-start-sf4удаляется после слияния.
Разведка (что есть в коде, 30.09)
- Поляны у встроенных стартов уже есть:
Terrain.set_surfaces→SurfaceLayer.replace_in_circle(FOREST|SHRUB → GRASS)радиусомlocations/<id>.json → start_clearing_radius_m(70 м) вокруг_sites; маска леса 10 м обнуляется там же. Деревья (TreePlacer/TerrainTreeModels), импостеры, кусты (ShrubScatter),forest_at(столкновение с кронами,CollisionCheck) и трава — все читают эту карту поверхности. Старт с карты (game.gd::_choose_start,StartPlacement.find_launch) выбирается после загрузки рельефа — поляны там нет; в лесуCollisionCheckещё и засчитывает удар о кроны. - Трава (
grass.gdshader): класс поверхности по карте — луг/пашня/кустарник (shrub_density); на классе FOREST (1) пучков нет. - Крен на земле (
GroundRun._ground_bank):rate = 10°/с/(м/с)·боковой_ветер + 30°/с·input.roll − bank/0,6 с— A/D на земле одновременно поворачивают курс (_turn) и кренят крыло (установившийся крен 18° при полном A/D) — это «неваляшка»; боковой ветер 3 м/с даёт 18°. Нет ограничения силы пилота и нет зависимости от разгрузки ног; A/D на разбеге поворачивают курс с постоянной угловой скоростью, а не по дуге. - Касание крылом: в режиме GROUND столкновения крыла с землёй нет — это визуальное проникновение. Кандидаты (проверить числами): крен от A/D/ветра (половина размаха ~5 м на высоте ~1,5–1,8 м → касание при ~19°); тангаж
theta = gamma + носпри стоянии (v < min_v → gamma = 0, нос вверх относительно горизонта) на склоне вниз — задняя часть киля в склон; поворот базиса вокруг ступней (Telemetry.basisв точкеposition= ступни); сглаживание позы вGliderVisual.
Физика и границы модели (для SF-3)
Крыло на плечах пилота вращается по крену вокруг оси киля у плеч. Моменты по крену:
- аэродинамический от бокового ветра (поперечная устойчивость, V-образность/стреловидность:
M = q·S·b·C_lβ·β, β — угол скольжения относительно набегающего потока), плюс несимметрия ветра по размаху, если есть; - вес крыла при крене (плечо — высота ЦТ крыла над осью вращения);
- рука пилота: восстанавливающий момент (пилот как регулятор «пропорционально крену + демпфирование»), ограниченный
M_max · (N/W), гдеN/W— доля веса на ногах (1 − L_верт/W, 0..1).M_max— из силы рук на стойках трапеции и опоры стоп (оценка с числами в документе, порядок сотни Н·м); - инерция крыла по крену и аэродемпфирование (порядок величин — из массы/размаха крыла).
Не учитываем: подвижность ног, шаг в сторону, сложную позу тела — это граница модели (в
docs/guide/flight.md).
Задачи
| # | Задача | Тип | Оценка | Зависит |
|---|---|---|---|---|
| SF-1 | Пустырь вокруг точки старта | dp-engineer | 0,5–1 дн | — |
| SF-2 | Трава на подложке «лес» | dp-engineer | 0,5 дн | — |
| SF-3 | Рука пилота: крен на земле, поворот по дуге, переход в полёт | dp-engineer | 1 дн | — |
| SF-4 | Касание крылом земли: причина с доказательством + исправление | dp-engineer | 0,5–1 дн | — (параллельно SF-3; с учётом, что SF-3 уберёт крен от A/D) |
Все четыре — одна волна, параллельно (≤ 4 исполнителей).
SF-1. Пустырь вокруг точки старта
- Скоуп: при старте с карты (и в любом другом «произвольном» старте) — лес и кустарник в круге радиуса R вокруг точки старта → луг, как у встроенных стартов (та же
replace_in_circle), с обновлением всего, что читает карту поверхности: деревья, импостеры, кусты, маска леса 10 м (forest_at, столкновения с кронами), текстура поверхности у рельефа/травы. R — не меньше 2× длины разбега (решение пользователя); длина разбега L_run — горизонтальная дистанция от старта с места до отрыва в штиль на склонеstart_search.min_slope_deg(самый длинный из пригодных) для самого «длинного» крыла линейки при эталонной массе; если в штиль на этом склоне взлёт не удаётся — дистанция заtakeoff.max_run_time_s. R — в конфиге с обоснованием в_doc(числа); встроенные старты —max(start_clearing_radius_m, R). - Ключевые файлы:
scripts/terrain/terrain.gd,scripts/game/game.gd,scripts/game/start_placement.gd,configs/game.json(start_search), при необходимостиtree_placer.gd/terrain_tree_models.gd/forest_impostors.gd/shrub_scatter.gd(только перестроение/обновление),docs/guide/terrain.md/docs/guide/game.md. - Не трогать:
grass.gdshader,grass_field.gd(кроме вызова уже существующего обновления текстуры поверхности, если нужно — через координатора),configs/vegetation.json(SF-2);scripts/flight/*(SF-3/SF-4). - Условия приёмки:
- Headless-тест: L_run измеряется в тесте живым прогоном
GroundRun(синтетический склонmin_slope_deg, штиль, все крылья линейки) и проверяетсяR ≥ 2·L_run. - Headless-тест: после старта в точке, где лес/кустарник, в круге R нет деревьев (экземпляры
TerrainTreeModels/TreePlacer), кустов (ShrubScatter),forest_at = 0иsurface_at ∉ {FOREST, SHRUB}(сетка проверки ≤ 5 м); за пределами круга лес остаётся. - Разбег с этой точки не кончается
crash_trees(CollisionCheckпо пути разбега и первых ~100 м полёта над поляной). - Скриншот: старт с карты в лесу — поляна вокруг, вид с земли и сверху (
build/screenshots/sf1_pustyr/). - Старые тесты рельефа/деревьев/кустов/травы — зелёные.
- Headless-тест: L_run измеряется в тесте живым прогоном
SF-2. Трава на подложке «лес»
- Скоуп: травинки растут и на классе FOREST — плотность и высота из конфига (
configs/vegetation.json → grass, новые ключи с_doc: доля пучков под пологом, высота — трава под лесом ниже и реже луга; обоснование — тень полога), цвет — как у луга в этой точке или темнее (палитра рельефа); у ствола не обязательно выкашивать. Дальняя трава/подложка (far_layer, если он рисует луг) — то же правило. Визуальные мелочи — не больше 2 попыток. - Ключевые файлы:
scripts/terrain/grass.gdshader,scripts/terrain/grass_field.gd,configs/vegetation.json,docs/guide/vegetation.md, тестtests/vegetation/test_grass_field.gd. - Не трогать:
terrain.gd,game.gd, деревья/кусты (SF-1),scripts/flight/*. - Условия приёмки:
- Тест: параметр плотности на классе FOREST доходит до материала травы (
grass_field→ uniform), по умолчанию > 0; при 0 — как раньше. - Скриншоты с земли: лес на Аскарово (или другой встроенной локации с лесом у старта) — до/после, трава между стволами (
build/screenshots/sf2_trava_les/). - Замер кадра на стенде/в игре: время кадра до/после при виде на лес с земли (число; рост — в пределах нескольких %; если больше — сказать).
- Старые тесты травы — зелёные.
- Тест: параметр плотности на классе FOREST доходит до материала травы (
SF-3. Рука пилота: крен на земле, поворот по дуге, переход в полёт
- Скоуп: заменить
GroundRun._ground_bankна динамику крена крыла на плечах (см. «Физика»): моменты ветра, веса крыла, рука пилота с пределомM_max·(N/W); A/D на земле не кренят крыло: стоя/шагом — поворот на месте вокруг вертикали, на разбеге — по дуге (угловая скорость = скорость / радиус, радиус ограничен снизу физически: боковая сила ног/сцепление, число с обоснованием). Крен — относительно горизонта (не склона). Опрокидывание (крен ≥fail_bank_degдольшеfail_time_s) — срыв «crosswind», как сейчас (итог есть в интерфейсе; новых подсказок/сообщений нет). Переход в полёт: крен и скорость крена непрерывны на отрыве; в воздухе — прежняя модель крена. Параметры —configs/flight.json → ground_bank(новые ключи с_docи единицами, старыеcrosswind_roll_dps_per_ms/pilot_roll_rate_dps/level_time_s— убрать); описание, законы и границы —docs/guide/flight.md. - Ключевые файлы:
scripts/flight/ground_run.gd,scripts/flight/flight_model.gd(только стык перехода GROUND→AIR,roll_rateна отрыве),configs/flight.json,configs/pilot.json(если поворот/сила пилота там),docs/guide/flight.md,tests/flight/test_takeoff.gd+ новыйtests/flight/test_ground_bank.gd. - Не трогать:
glider_visual.gd_ground_pose,flight_telemetry.gd(SF-4), рельеф/растительность. - Условия приёмки (headless):
- Штиль, стоит 10 с с зажатой A (и D): |крен| < 0,5°, курс поворачивается (> 30°).
- Боковой ветер слабый (2,5 м/с, как в старом
test_crosswind_corrected, без ввода крена): установившийся |крен| < 5°, взлёт удаётся. - Боковой порыв выше порога (порог M_aero = M_max — посчитан и назван в документе): крыло опрокидывается → FAILED «crosswind» за разумное время; порыв на 20 % ниже порога — держит.
- Разбег с зажатой A: траектория — дуга, радиус ≥ R_min (число), крен на бегу < 3° при штиле.
- Переход: на отрыве |Δкрен| за шаг и скачок скорости крена < порога (например < 1°/с) при штиле и при слабом боковом ветре; удержание пропорционально доле веса на ногах (тест: при искусственной разгрузке 50 % предельный момент — вдвое меньше).
- Косой склон (поперечный уклон 15°): крен ≈ 0 относительно горизонта (|крен| < 0,5°).
- Все тесты
flight/иgame/зелёные (test_crosswind_*переписаны под новую модель).
SF-4. Касание крылом земли: причина и исправление
- Скоуп: найти, почему крыло проходит в землю на старте (сначала — на ровном; затем — на встроенных стартах локаций): замер нижней точки крыла (концы консолей, задний и передний конец киля, трапеция) над рельефом по кадрам стоя/шагом/на разбеге — отдельно вклад крена, тангажа, точки поворота базиса (ступни), высоты подвески/модели, сглаживания позы, выборки высоты рельефа. Доказательство — таблица чисел и кадры. Исправить, если причина локальна (постановка на рельеф, тангаж стоя, ось поворота, поза визуала). Если причина требует менять модель столкновений шире задачи — шлюз: остановиться и вернуть варианты координатору.
- Ключевые файлы:
scripts/flight/glider_visual.gd(_ground_pose, поза крыла на земле),scripts/flight/flight_telemetry.gd(basis на земле),scripts/flight/ground_run.gd(_aero_force— только тангаж стоя; крен — SF-3),scripts/flight/glider.gd, инструмент замера (вtools/или тест),docs/guide/flight.md(раздел о позе на земле — согласовать слияние с SF-3 через координатора). - Не трогать:
_ground_bank,_turnвground_run.gd(SF-3); рельеф/растительность. - Условия приёмки:
- Отчёт о причине: таблица (старт, фаза, крен, тангаж, уклон, нижняя точка крыла над землёй, м) до и после, 2–4 кадра до/после (
build/screenshots/sf4_kasanie/). - Headless-тест: на ровном старте при штиле (стоит, шагом, разбег до отрыва, крен = 0) нижняя точка крыла ≥ зазора (число с обоснованием, например ≥ 0,3 м) над рельефом; на склоне 20° по курсу вниз — то же.
- На косом склоне 15° тест только фиксирует число (верхняя консоль близко к склону — правда жизни, не исправляется).
- Тесты
flight/иgame/зелёные.
- Отчёт о причине: таблица (старт, фаза, крен, тангаж, уклон, нижняя точка крыла над землёй, м) до и после, 2–4 кадра до/после (
Риски
- SF-3 и SF-4 правят
ground_run.gdв разных функциях — слияние через координатора (сначала принимается первая готовая, вторая —git merge feature/start-fixesперед отчётом). - SF-1: перестройка деревьев/кустов после выбора старта увеличит время загрузки при старте с карты — замерить (число в отчёте).
- Если причина касания крылом — геометрия модели крыла/высота подвески, затрагивающая полёт (камеры, столкновения) — шлюз.