Контракты модуля «control-fix»
Внутренний документ. План — docs/archive/plan/control-fix.md, журнал — docs/archive/plan/control-fix-progress.md. Контрактный тест — tests/game/test_control_fix_contracts.gd (форма стыков; ломается при смене формата без правки контракта). v1 — то, что есть в коде на v1.0.0 (3820bda). Менять интерфейс — только через координатора: версия +1, что изменилось, потребители правятся в том же шаге.
С1. ControlInput — управление пилота за шаг — v2
Владелец: scripts/core/control_input.gd. Источники: InputController, Autopilot, BotPilot, сеть. Потребители: FlightModel.step, GroundRun.step, Glider, визуал.
v1 (до CF-3):
pitch: float ∈ [−1, 1]— +1 трапеция от себя (нос вверх), −1 на себя; на земле — угол носа крыла (+ нос вверх).roll: float ∈ [−1, 1]— +1 вправо. В полёте — крен (режим поweight_shift); на земле — только курс (крыло не кренит), К3 v2.weight_shift: bool;run: bool— разбег;walk: float ∈ [−1, 1]— шаг.
v2 (CF-3, решение пользователя 01.10, вариант В):
- Новое поле
turn: float ∈ [−1, 1], по умолчанию 0 — поворот курса на земле стоя и шагом (+ вправо, по часовой); в полёте и на бегу не используется. pitchна земле (стоя, шагом, на бегу) — нос крыла, тот же смысл и знак, что в полёте (+ от себя = нос вверх), полный ход [−1, 1].rollна земле (стоя, шагом, на бегу) — заданный крен крыла «рукой пилота», + вправо; полный ход = предельный крен руки (flight.json → ground_bank, ключ с физическим обоснованием — CF-3). Курс roll на земле больше не задаёт.run,walk,weight_shift— без изменений.
С2. InputController → ControlInput — v3
Владелец: scripts/game/input_controller.gd. Потребитель: Game.tick (input_controller.update(dt) → glider.set_input).
Не меняется: update(dt: float) -> ControlInput; enabled, hands_off, on_ground; set_mouse_captured(on), mouse_captured; правая кнопка — осмотреться (mouse_mode() — до v3) (трапеция держит положение).
v1: мышь (bar) — только в воздухе; на земле W/S — шаг, Shift+W — разбег, A/D — поворот, ↑/↓ — подстройка носа ±0,3 поверх run_nose_neutral + auto_nose; защёлка клавиш при отрыве (controls.json → takeoff_latch, is_latched). Мышь по умолчанию look. CF-2 (без смены версии): в bar смещение мыши на земле не копится.
v2 (CF-3, решение пользователя 01.10):
controls.json → mouse.modeпо умолчаниюbar.- Стоя и шагом (Shift не зажат): W/S →
walk, A/D →turn; нос и крен крыла (pitch,roll) — стрелки, мышь вbar, стик геймпада. Клавиши крыла на земле — те же действия, что в полёте (pitch_pull_in/pitch_push_out,roll_left/roll_right,invert_pitch); какой клавише какой знак — только карта вводаcontrols.json → keys(её раскладку по умолчанию меняет модуль feature/ui-controls: W — от себя, S — на себя), в наземной логике знак клавиш не закладывается. Стоя W/S/A/D из этих действий исключаются (они — шаг/поворот), на бегу — входят. - Действия ввода (CF-3): новые
turn_left[A],turn_right[D] — поворот стоя/шагом; удаленыnose_up,nose_down,takeoff_latch; стоя трапецию не двигают клавиши, занятыеwalk_*/turn_*(InputController.GROUND_MOVE_ACTIONS). - Разбег: зажат Shift →
run = true(W не нужен; блокировкаrun_blocked— как была); W/S, A/D, стрелки, мышь, стик →pitch,rollкак в полёте;walk = 0,turn = 0. - Мышь в
barна земле управляетpitch/rollи стоя, и на бегу; смещение мыши на отрыве непрерывно (правка CF-2 «на земле не копится» заменяется этим). - Защёлки при отрыве нет (
takeoff_latch,is_latchedубраны): на отрывеpitch/rollнепрерывны. - Базовый нос без ввода (CF-3):
pitch = 0— угол разбега самого крыла (wing.launch.alpha_neutral_deg, 16–17°), тот же ноль, что трим в полёте; ввод — полный ход ±1 (≈ ±20° α).ground.run_nose_neutral,ground.auto_nose, подстройка ↑/↓ игрока убраны (автоматика носа у ботов —configs/bots.json → launch.run_nose/launch.auto_nose). Трапеция одна на земле и в полёте (клавиши + мышь, затем стик); «в центр» (X) — только в полёте.
v3 (02.10, правка модуля ui-controls — У1 v3 / У2 v2 в docs/contracts/ui-controls.md, решение пользователя «крыло через WASD не нужно, настройку режима мыши убрать»):
mouse_mode()иcontrols.json → mouse.modeубраны: мышь всегда крыло (бывшийbar; захват — как был; правая кнопка — осмотреться).- W/S/A/D сняты с действий трапеции (
pitch_*/roll_*— только стрелки): на земле W/S →walk, A/D →turn(стоя и шагом); на разбеге (Shift) W/S/A/D ничего не делают (walk = turn = 0, крыло — мышь, стрелки, стик); в полёте — обзор головой (look_*). Исключение клавиш шага из трапеции (GROUND_MOVE_ACTIONS) больше не нужно. - Остальное v2 — без изменений.
С3. Пилот на земле — GroundRun (start-fixes К3) — К3 v3
Базовые контракты — docs/contracts/start-fixes.md → К3 v2 (крен на земле, переход в полёт), К4 v1 (поза крыла); их тест — tests/game/test_start_fixes_contracts.gd. Владелец в этом модуле — CF-1 (до CF-3), затем CF-3.
К3 v3 (CF-3):
- Стоя/шагом курс — от
input.turn(pilot.walk.turn_rate_dps), крыло этим не кренится; на бегу курс — от крена крыла (дуга: боковое ускорение от боковой составляющей силы крыла, |a| ≤pilot.run.turn_accel_max_ms2),input.turnне используется. input.rollна земле — заданный кренφ_задрегулятора «руки пилота»: момент руки−I/τ²·(φ − φ_зад) − 2I/τ·φ̇, пределpilot_moment_max_nm · feet_load— как в v2. Без вводаφ_зад = 0(поведение v2).input.pitchна земле — нос крыла на полный ход (смысл как в v2).- Заданный крен:
φ_зад = roll · ground_bank.command_max_deg(15°: ход рук на стойках ≈ ±0,1 м при плече 0,35 м → atan(0,2/0,7) ≈ 16°; меньшеfail_bank_deg20°).pilot_moment(m, bank, rate, load_frac, inertia)— сигнатура прежняя, вторым аргументом передаётся φ − φ_зад. - Дуга на бегу: a = a_кр + clamp(a_зад − a_кр, ±N/W·a_max), |a| ≤ a_max =
turn_accel_max_ms2; a_зад = g·tg φ_зад (наклон в поворот вместе с крылом), a_кр = (1 − N/W)·g·sin φ (боковая составляющая подъёмной силы); ω = a/v, не больше поворота на месте. К отрыву (N/W → 0) курс — от фактического крена, как вираж в воздухе. - Порогов по времени в старте и взлёте нет (решение пользователя 01.10, план п. 4): отрыв — только L ≥ W при несорванном крыле.
GroundRun.failure ∈ {"wingtip", ""}—wingtip: конец опущенной креном консоли на земле (геометрия: полуразмах от киля наtakeoff.wing_height_m);nose_high,nose_low,tailwind,crosswind,weak_runубраны (сорванное/опущенное крыло и попутный ветер просто не дают отрыва; бросил бежать — стоит). Изflight.json → takeoffубраныgrace_s,fail_time_s,check_min_airspeed_ms,nose_high_margin_deg,nose_low_alpha_deg,tailwind_max_ms,weak_run_min_time_s,max_run_time_s; изground_bank—fail_bank_deg. Локаль —launch_fail_wingtip.FlightStats: полёт засчитан по геометрии (≥ 10 м над землёй или ≥ 50 м от точки отрыва), без 10 с. Автопилот бежит сразу после замера ветра стоя (безstand_s). - Переход земля → полёт (CF-3, вместо
takeoff.grace_s): после отрыва пилот ещё на ногах, пока ступни нижеtakeoff.upright_clear_m(1,0 м, длина ноги); касание в это время — не посадка, сноваGroundRun(разбег продолжается, отрыв опять по L ≥ W); угол атаки — как на разбеге (launch.alpha_neutral_deg+ pitch·range). Автопилот и боты бегут, пока ступни ниже этой высоты. GroundRun.step(...),Result,phase,failure,feet_load,wind_moment_nm,hold_limit_nm,roll_inertia— без изменений;pilot_momentможет получить заданный крен — тогда новая сигнатура записывается сюда.