---
title: "Player movement abilities dash sprint and double jump"
description: "The player already walks and jumps (see third-person-and-platformer-movement or first-person-controls) and now needs an extra move: a dash burst, a sprint that speeds them up while a key is held, or a second jump in the air. The engine has no DashComponent, SprintComponent, or DoubleJumpComponent; each ability is a script layered on the real `CharacterMovementComponent` motor, and the burst itself"
engineVersion: v1.0.234
date: 2026-09-28
license: "(c) Gessa, proprietary. Cite with attribution to https://gessa.ai/docs/. Terms: https://gessa.ai/terms/."
canonical: https://gessa.ai/docs/knowledge/playbooks/player-movement-abilities/
---

# Player movement abilities dash sprint and double jump

## When to use

The player already walks and jumps (see third-person-and-platformer-movement or
first-person-controls) and now needs an extra move: a dash burst, a sprint that
speeds them up while a key is held, or a second jump in the air. The engine has
no DashComponent, SprintComponent, or DoubleJumpComponent; each ability is
a script layered on the real `CharacterMovementComponent` motor, and the burst
itself is the same `ctx.physics.command` `launch` that jump pads use.

## The recipe

Prerequisite: the player carries `CharacterMovementComponent`
(`enableKcc: true`, a `maxSpeed`, a `jumpSpeed`, a `groundY`) and an
`InputProfileComponent`. Add one action per ability
(see input-mapping-actions-bindings): a `sprint` boolean and a `dash` (or
`doubleJump`) button. Author the behavior with
`project_apply_script_semantic_patch` using the `ai_safe` op pack.

### Sprint (faster while held)

1. `addActionHandler` `{ "actionKey":"sprint", "handlerKey":"onSprint" }`.
2. In the handler, branch on the `InputActionEvent` `phase`: on `pressed`,
   `ctx.entity.patchComponent` the player's `CharacterMovementComponent` to a
   higher `maxSpeed` (for example 11); on `released`, patch it back to the walk
   value (for example 7). Read the walk value once with `ctx.entity.readComponent`
   and stash it in state if you want it dynamic.
3. Optional stamina: seed a `stamina` number with `writeTypedStateValue`, drain it
   each tick while sprinting with `incrementNumericState` `{ "by": -1 }`, and force
   `released` behavior when it hits 0.

### Dash (a burst in the facing direction)

1. `addActionHandler` `{ "actionKey":"dash", "handlerKey":"onDash" }`.
2. Keep the current facing fresh: `addTickHandler`
   `{ "handlerKey":"onMoveSample" }` that, in a `replaceScriptFromGessaScript`
   body, reads the `move` action value (the `InputActionEvent` `value`, a
   `vector2d`) and, when it is non-zero, `writeTypedStateValue` a `facing`
   vector. This gives the dash a direction even at a standstill (last-moved).
3. In `onDash` (whole-body GessaScript), read `facing`, scale it to a dash speed
   (for example 18), and fire
   `ctx.physics.command(playerId, { type: "launch", velocity: { x: fx*18, y: 0, z: fz*18 } })`
   where `playerId` is the entity the handler runs on (`ctx.entity` is the
   self-accessor for reads and patches; pass the player's own id to the command).
   `launch` SETS the kinematic character's velocity for this step, which is the
   one sanctioned way to fling a `CharacterMovementComponent` body; do not patch
   `TransformComponent` position to fake the dash.
4. Cooldown: `setBooleanState` `{ "field":"dashReady", "value": false }` on dash,
   and flip it back after a delay with a `TimerComponent`
   (see timers-countdowns-and-waves). Gate the dash on `dashReady` so it cannot
   be spammed.

### Double jump (a second jump before landing)

1. Track jumps used: seed `jumpsUsed` to 0 with `writeTypedStateValue`.
2. Ground reset: in an `addTickHandler` body, `ctx.entity.readComponent` the
   `TransformComponent` `position.y` and the `CharacterMovementComponent`
   `groundY`; when the player is at (or near) `groundY`, `writeTypedStateValue`
   `jumpsUsed` back to 0. This is the grounded check without a hidden velocity
   lane.
3. Jump action: `addActionHandler` `{ "actionKey":"jump", "handlerKey":"onJump" }`.
   `readTypedStateValue` `jumpsUsed`; if it is less than the allowed count (2 for
   a double jump), `ctx.physics.command(playerId, { type:"launch",
   velocity:{ x:0, y: jumpSpeed, z:0 } })` and `incrementNumericState`
   `jumpsUsed` `{ "by": 1 }`. When `jumpsUsed` reaches the cap, ignore the press.

## Pitfalls

- The launch `y` for a jump should match the movement `jumpSpeed` so the double
  jump feels like the first; read it with `ctx.entity.readComponent` rather than
  hardcoding a second number that can drift from the motor.
- `launch` REPLACES velocity, it does not add to it: a dash issued mid-fall wipes
  the fall speed for that step. That is usually what you want for a crisp dash;
  for a momentum-preserving dash, compose the desired resultant yourself.
- Do NOT author a `VelocityComponent` or `ForceComponent` to move the character;
  both are hidden internal runtime lanes. Every burst goes through
  `ctx.physics.command`, every speed change through
  `CharacterMovementComponent.maxSpeed`.
- Ground the double-jump reset on position vs `groundY`, not on a landing event
  you cannot subscribe to; there is no grounded-state flag on the movement
  component to read.
- Sprint changes `maxSpeed`; it does not multiply an existing velocity. If sprint
  feels like it does nothing, confirm you patched the same
  `CharacterMovementComponent` the mover reads and that `enableKcc` is true.

## Verify

- `engine_get_component_schema` on `CharacterMovementComponent` to confirm
  `maxSpeed`/`jumpSpeed`/`groundY` before patching.
- `project_get_graph_snapshot` to confirm the sprint/dash/jump handlers and the
  `ScriptComponent` binding exist on the player.
- `simulation_run` (`qa.run.start`) to prove sprint raises speed while held, the
  dash launches in the move direction, and a second jump fires in the air but a
  third does not.
