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
+15 -3
View File
@@ -45,9 +45,21 @@ func update(delta: float) -> void:
# Quake's trick: cap per-tick projection so sharp turns give the gain
accel_speed = minf(accel_speed, params.air_wish_speed_cap)
hvel += wish_dir * accel_speed
# Direct steering: bend existing velocity toward the input WITHOUT
# changing its magnitude, so airborne direction changes feel light.
# Skipped when input opposes travel (braking is the accel path's job,
# and lerping through zero would flip the heading unstably).
var speed_now := hvel.length()
if speed_now > 1.0:
var travel_dir := hvel / speed_now
if travel_dir.dot(wish_dir) > -0.7:
var steered := hvel.lerp(wish_dir * speed_now, params.air_steer_rate * delta)
if steered.length_squared() > 0.01:
hvel = steered.normalized() * speed_now
else:
# No input: slight air drag (very subtle)
hvel *= (1.0 - 0.5 * delta)
# No input: barely any drag — held momentum should carry
hvel *= (1.0 - 0.15 * delta)
vel.x = hvel.x
vel.z = hvel.z
@@ -127,6 +139,6 @@ func update(delta: float) -> void:
return
# ── Dash ──────────────────────────────────────────────────────────────
if machine.input_dash and machine.dash_cooldown_timer <= 0.0:
if machine.input_dash and machine.can_dash():
machine.switch_to("dash")
return