diff --git a/src/engine/PlayerController.js b/src/engine/PlayerController.js index 783e75f..d3669b9 100644 --- a/src/engine/PlayerController.js +++ b/src/engine/PlayerController.js @@ -3384,8 +3384,29 @@ export class PlayerController { && now < this._jumpAnticipateUntil && this._jumpPendingImpulse; const inJumpLand = this._jumpLandUntil && now < this._jumpLandUntil; + // Coyote-фильтр для микро-полётов на ступеньках. При спуске по + // лестнице из блоков персонаж 30-700мс физически в воздухе, и + // jump_air мигает между шагами walk. Критерий — ВЫСОТА падения + // от последней наземной позиции (а не время — полёт может быть + // длинным при спуске лицом к камере). Опустился <1.3 блока И не + // прыгал → ступенька, играем walk/run. + if (result.onGround) { + this._lastGroundY = this._pos.y; + } + const dropFromGround = (this._lastGroundY != null) + ? (this._lastGroundY - this._pos.y) : Infinity; + const microAir = !result.onGround + && !this._jumpHeld // не прыжок со Space + && !this._wasAirborne // не продолжение реального прыжка + && dropFromGround < 1.3 // опустился меньше 1.3 блока + && this._vy < 4; // не подлетает вверх (степ-ап импульс) if (inAnticipate) { mState = stAnticipate; + } else if (microAir) { + // Микро-полёт между ступеньками — наземная анимация. + mState = this._crouching + ? (isMoving ? 'crouch_walk' : 'crouch_idle') + : (isMoving ? (isSprinting ? 'run' : 'walk') : 'idle'); } else if (!result.onGround) { mState = stAir; this._wasAirborne = true;