feat: lighter air control (steer + stronger strafe) and 2-charge dash

- Air: direct steering bends existing velocity toward input without changing
  its magnitude (air_steer_rate), on top of Quake accelerate with raised
  gains (air_strafe_accel 55->90, per-tick cap 1.2->2.5). No-input air drag
  nearly removed so held momentum carries. Steering skips opposing input to
  avoid flipping through zero.
- Dash: charge system on the machine — dash_charges (2) spend instantly,
  each recharges in dash_cooldown (2 s). get_dash_cooldown_remaining() now
  reports time to the NEXT charge (0 while one is banked) so existing HUDs
  keep working; get_dash_charges() added for pips.
- Tests: dash tests updated to the charge API + new coverage for
  spend-2-then-recharge; 11/11 pass, spawn smoke clean.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Nicholas Butzke
2026-07-17 13:32:01 -04:00
co-authored by Claude Fable 5
parent 198345177a
commit 2c7b5a1aca
9 changed files with 90 additions and 15 deletions
+33 -3
View File
@@ -48,7 +48,8 @@ var last_ground_time: float = 0.0
var jump_buffer_time: float = 0.0
var coyote_timer: float = 0.0
var jump_cooldown_timer: float = 0.0
var dash_cooldown_timer: float = 0.0
var dash_charges: int = -1 # -1 = initialize from params on first tick
var dash_recharge_timer: float = 0.0
var current_jump_count: int = 0
var chain_timer: float = 0.0
var chain_count: int = 0
@@ -103,10 +104,18 @@ func _physics_process(delta: float) -> void:
# Update timers
jump_cooldown_timer = maxf(jump_cooldown_timer - delta, 0.0)
dash_cooldown_timer = maxf(dash_cooldown_timer - delta, 0.0)
slide_cooldown_timer = maxf(slide_cooldown_timer - delta, 0.0)
wall_cooldown_timer = maxf(wall_cooldown_timer - delta, 0.0)
# Dash charges: regain one per dash_cooldown while below max
if dash_charges < 0:
dash_charges = params.dash_charges
if dash_charges < params.dash_charges:
dash_recharge_timer -= delta
if dash_recharge_timer <= 0.0:
dash_charges += 1
dash_recharge_timer = params.dash_cooldown if dash_charges < params.dash_charges else 0.0
# Update chain timer
if chain_timer > 0.0 and on_ground:
chain_timer -= delta
@@ -401,5 +410,26 @@ func get_crouch_factor() -> float:
/ (original_capsule_height * 0.5), 0.0, 1.0)
func can_dash() -> bool:
return dash_charges != 0 # -1 (uninitialized) counts as ready
## Spend one dash charge and start the recharge clock if it isn't running.
func consume_dash_charge() -> void:
if dash_charges < 0:
dash_charges = params.dash_charges
dash_charges = maxi(dash_charges - 1, 0)
if dash_recharge_timer <= 0.0:
dash_recharge_timer = params.dash_cooldown
## Seconds until the NEXT dash is available (0 when a charge is banked).
## HUDs also read get_dash_charges() to draw pips.
func get_dash_cooldown_remaining() -> float:
return dash_cooldown_timer
if dash_charges != 0:
return 0.0
return maxf(dash_recharge_timer, 0.0)
func get_dash_charges() -> int:
return dash_charges if dash_charges >= 0 else params.dash_charges