fix(ship): delta-scale drag so it stays correct at any physics tick rate

drag_coefficient/angular_drag/the idle angular-drag multiplier were applied
once per physics tick with no delta scaling, correct only because
project.godot never pins physics/common/physics_ticks_per_second and
Godot's default happens to be 60. _tick_scaled(k, state.step) makes the
decay rate invariant to tick rate instead. Also promotes the previously
hardcoded 0.9 idle angular-drag literal to an export, matching its sibling.
This commit is contained in:
Josh Creek
2026-08-05 09:13:52 +01:00
parent cab2fa6391
commit 0f7603d3cb
+15 -4
View File
@@ -18,6 +18,7 @@ extends RigidBody3D
@export var max_angular_speed = 3.0 # Maximum rotation speed
@export var drag_coefficient = 0.98 # Linear drag (air resistance)
@export var angular_drag = 0.95 # Rotational drag
@export var idle_angular_drag = 0.9 # Rotational drag when no rotation input is held
@export_group("Surface Pull")
@export var wall_pull_strength = 6.0 # Wall grav-plating strength (m/s^2-equivalent)
@@ -279,12 +280,22 @@ func apply_rotation_forces(state: PhysicsDirectBodyState3D, rotation_input: Vect
state.apply_torque(torque)
# Scales a per-tick decay multiplier `k` (defined at a 60 Hz reference rate)
# by the actual elapsed tick time `step`, so `v *= _tick_scaled(k, step)`
# decays at the same rate per second regardless of physics_ticks_per_second.
func _tick_scaled(k: float, step: float) -> float:
return pow(k, step * 60.0)
func apply_drag_and_limits(state: PhysicsDirectBodyState3D, rotation_input: Vector3):
# Linear drag (air resistance)
# Physics: F_drag = -½ * ρ * v² * C_d * A (drag force equation)
# Simplified: v_new = v_old * drag_coefficient (exponential decay)
# This simulates air resistance reducing velocity over time
state.linear_velocity *= drag_coefficient
# This simulates air resistance reducing velocity over time.
# _tick_scaled makes the decay rate invariant to the physics tick rate —
# drag_coefficient/angular_drag/idle_angular_drag are all defined as the
# per-tick multiplier at a 60 Hz reference rate.
state.linear_velocity *= _tick_scaled(drag_coefficient, state.step)
# Angular drag (rotational resistance)
# Physics: Similar to linear drag but for rotational motion
@@ -292,10 +303,10 @@ func apply_drag_and_limits(state: PhysicsDirectBodyState3D, rotation_input: Vect
# Simplified: ω_new = ω_old * angular_drag (exponential decay)
if rotation_input.length() < 0.01:
# More drag when not actively rotating to stop quicker
state.angular_velocity *= 0.9
state.angular_velocity *= _tick_scaled(idle_angular_drag, state.step)
else:
# Normal drag when actively rotating
state.angular_velocity *= angular_drag
state.angular_velocity *= _tick_scaled(angular_drag, state.step)
# Limit maximum speeds
# Physics: Terminal velocity concept - maximum achievable speed