From 570f4bb9d090176307a979f0a8a3cf73aa975b6d Mon Sep 17 00:00:00 2001 From: Niko Marmeladkov Date: Tue, 13 Oct 2026 15:20:08 +0300 Subject: [PATCH] =?UTF-8?q?docs:=20=D0=B8=D0=B7=D1=83=D1=87=D0=B5=D0=BD=20?= =?UTF-8?q?CGameMovement=20=D0=B8=D0=B7=20Source=20SDK=202013=20=E2=80=94?= =?UTF-8?q?=20=D1=80=D0=B0=D1=81=D1=85=D0=BE=D0=B6=D0=B4=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=81=20=D0=BD=D0=B0=D1=88=D0=B5=D0=B9=20=D1=84=D0=B8?= =?UTF-8?q?=D0=B7=D0=B8=D0=BA=D0=BE=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Доки Valve закрыты Anubis, читал первоисточник ValveSoftware/source-sdk-2013, src/game/shared/gamemovement.cpp. Записаны точные причины двух симптомов пользователя и ещё трёх расхождений: 1. StepMove у оригинала пробует оба варианта (вниз и вверх на flStepSize) и берёт тот, что ушёл дальше по горизонтали; вертикальную скорость берёт из движения ВНИЗ (m_vecVelocity.z = vecDownVel.z). У нас подшаг всегда идёт путём «поднять 18 и уронить», режет путь на рампе и добавляет паразитную вертикаль — отсюда и потеря скорости вверх, и «прыжки с рампы» вниз. 2. Friction() вызывается каждый кадр на земле безусловно, формула control = max(speed, sv_stopspeed), drop = control*friction*frametime. У нас трение только при отсутствии ввода — по рампе на бегу его нет. 3. Гравитация делится 0.5 до и 0.5 после движения (StartGravity/FinishGravity). 4. Ускорение — Quake-формула accelspeed = accel*frametime*wishspeed, а в воздухе ещё и ограничение sv_air_max_wishspeed = 30. Отсюда «вязкое» управление в воздухе, которого у нас нет. 5. Прыжок по фронту: в оригинале при выходе из воды ставится m_nOldButtons |= IN_JUMP с комментарием «don't jump again until released» — наша защёлка воспроизводит штатный приём движка. Код не менял: сначала фиксирую расхождения, чтобы не подгонять тесты. --- docs/PARITY.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/docs/PARITY.md b/docs/PARITY.md index cae66fa..df4dab8 100644 --- a/docs/PARITY.md +++ b/docs/PARITY.md @@ -783,3 +783,114 @@ Source делает наоборот: при `FL_ONGROUND` вертикальн в Source движение просто останавливается. - `sv_maxspeed = 320`, `sv_maxvelocity = 350` как отдельные ограничения. - Ускорение/торможение поворота камеры, инерция при стрейфе. + +## Часть B6. Изучение `CGameMovement` (Source SDK 2013) + +Источник: `src/game/shared/gamemovement.cpp` из `ValveSoftware/source-sdk-2013` +(доки Valve под Anubis, читался исходник). Ниже — расхождения нашей реализации +с оригиналом, найденные **чтением первоисточника**, а не догадками. + +### B6.1. Почему теряется скорость на рампе вверх + +`CGameMovement::StepMove` устроен принципиально иначе, чем наш подшаг: + +```cpp +// Try sliding forward both on ground and up 16 pixels +// take the move that goes farthest +TryPlayerMove( &vecEndPos, &trace ); // сначала ВНИЗ — получаем vecDownPos/vecDownVel +...подъём на flStepSize... +TryPlayerMove(); // потом ВВЕРХ — получаем vecUpPos +// decide which one went farther +float flDownDist = ( vecDownPos.x - vecPos.x )^2 + ( vecDownPos.y - vecPos.y )^2; +float flUpDist = ( vecUpPos.x - vecPos.x )^2 + ( vecUpPos.y - vecPos.y )^2; +if ( flDownDist > flUpDist ) { едем вниз; } +else { /* copy z value from slide move */ mv->m_vecVelocity.z = vecDownVel.z; } +``` + +Три отличия от нашего кода, каждое даёт симптом: + +1. **Пробуются оба варианта, побеждает тот, что ушёл дальше по горизонтали.** + Мы всегда идём путём «поднять на 18 и уронить». На рампе вверх путь вниз + (без подшага) уходит дальше — значит оригинал его и берёт, а подшаг не + выполняется вовсе. У нас он выполняется всегда и режет путь. +2. **Вертикальная скорость берётся из движения ВНИЗ** + (`m_vecVelocity.z = vecDownVel.z`), а не из движения вверх. Иначе на + рампе появляется паразитная вертикальная составляющая. +3. Отказ от подшага при `trace.plane.normal[2] < 0.7` — то есть на крутых + поверхностях подшаг не применяется вовсе. + +### B6.2. Почему при спуске «прыгает с рампы» + +Наш подшаг выполняется по признаку «движение заблокировано» и всегда с +подъёмом на `STEP_HEIGHT = 18`. На рампе вниз игрок кадр за кадром +подскакивает на 18 юнитов и падает обратно — визуально это «прыжки с +рампы». Оригинал при спуске идёт по пути `flDownDist`, который дальше, и +подшага не делает. + +### B6.3. Трение: у нас не так + +`CGameMovement::Friction()` вызывается **каждый кадр на земле** безусловно +(проверка только `GetGroundEntity() != NULL`), а не «если нет ввода». +Формула оригинала: + +```cpp +control = (speed < sv_stopspeed) ? sv_stopspeed : speed; // 100 по умолчанию +drop += control * friction * frametime; // sv_friction = 4 +newspeed = max(0, speed - drop); +``` + +У нас трение срабатывает только при `wish.length_squared() < 1.0`, то есть +при беге по рампе его нет вовсе. + +### B6.4. Гравитация делится пополам до и после движения + +```cpp +void CGameMovement::StartGravity( void ) { + mv->m_vecVelocity[2] -= ent_gravity * GetCurrentGravity() * 0.5 * frametime; +} +void CGameMovement::FinishGravity( void ) { + mv->m_vecVelocity[2] -= ent_gravity * GetCurrentGravity() * frametime * 0.5; +} +``` + +В комментарии Valve: «yes, this 0.5 looks wrong, but it's not». Суммарно +гравитация та же, но момент приложения другой — это и даёт правильное +ощущение веса на дуге. + +### B6.5. Ускорение: Quake-алгоритм, а не линейная интерполяция + +```cpp +currentspeed = velocity.Dot(wishdir); +addspeed = wishspeed - currentspeed; +if (addspeed <= 0) return; +accelspeed = accel * frametime * wishspeed * friction; // НЕ accel*dt! +if (accelspeed > addspeed) accelspeed = addspeed; +velocity += accelspeed * wishdir; +``` + +`AirAccelerate` отличается одной строкой — ограничением желаемой скорости: + +```cpp +if ( wishspd > GetAirSpeedCap() ) wishspd = GetAirSpeedCap(); // sv_air_max_wishspeed = 30 +``` + +Вот почему в воздухе управление «вязкое»: прирост скорости за кадр +ограничен, а ускорение не накапливается. У нас `AIR_ACCEL = 12` при +`GROUND_ACCEL = 10` и линейная интерполяция к целевой скорости — в воздухе +у нас **быстрее**, чем на земле. + +### B6.6. Прыжок по фронту + +В чистом Source `IN_JUMP` проверяется по уровню, но при выходе из воды +ставится `mv->m_nOldButtons |= IN_JUMP; // Don't jump again until released`. +То есть «не прыгать, пока не отпустят» — штатный приём движка, а не +изобретение: наша защёлка `jump_latched` воспроизводит именно его. + +### B6.7. Порядок работы на будущее + +1. `StepMove` по оригиналу: оба варианта, побеждает дальний, `z` из + вниз. Чинит и потерю скорости, и «прыжки с рампы». +2. `Friction()` безусловно на земле. +3. `StartGravity` / `FinishGravity` пополам. +4. `Accelerate` / `AirAccelerate` квото-формулой с `sv_air_max_wishspeed`. +5. `CheckVelocity` — ограничиватель `sv_maxvelocity = 350`.