perf(hud): stop flight instruments redrawing when values have settled

hud_gauge, hud_attitude_indicator, and hud_heading_tape now skip
queue_redraw() when the newly-lerped value hasn't moved past a small
epsilon, instead of redrawing every frame forever. Angle-wrapping
values (heading, attitude roll) use a new shared angle_delta_deg
helper on HudInstrument so the wrap boundary doesn't read as a false
jump.

HUD.tscn's Instruments node now sets process_mode = 1 (PAUSABLE),
overriding the inherited ALWAYS mode from the HUD root so instruments
stop processing during the post-match pause freeze, while sibling
ResultOverlay keeps running its win-screen tween.
This commit is contained in:
Josh Creek
2026-08-04 17:33:52 +01:00
parent fbb783b947
commit bb56f69edc
6 changed files with 36 additions and 9 deletions
+1
View File
@@ -171,6 +171,7 @@ text = "0 - 0"
horizontal_alignment = 1
[node name="Instruments" type="Control" parent="Control"]
process_mode = 1
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
+11 -3
View File
@@ -26,11 +26,19 @@ func set_attitude(pitch_deg: float, roll_deg: float) -> void:
_target_roll = roll_deg
# Below this, both axes have visually settled — skip the redraw.
const REDRAW_EPSILON_DEG := 0.05
func _process(delta: float) -> void:
var t := _smoothing_weight(delta)
_pitch = lerpf(_pitch, _target_pitch, t)
_roll = lerp_angle_deg(_roll, _target_roll, t)
queue_redraw()
var new_pitch := lerpf(_pitch, _target_pitch, t)
var new_roll := lerp_angle_deg(_roll, _target_roll, t)
var changed := absf(new_pitch - _pitch) > REDRAW_EPSILON_DEG \
or absf(angle_delta_deg(_roll, new_roll)) > REDRAW_EPSILON_DEG
_pitch = new_pitch
_roll = new_roll
if changed:
queue_redraw()
func _draw() -> void:
+8 -2
View File
@@ -26,8 +26,14 @@ func set_value(value: float) -> void:
func _process(delta: float) -> void:
var t := _smoothing_weight(delta)
_value = lerpf(_value, _target, t)
queue_redraw()
var new_value := lerpf(_value, _target, t)
# Redraw threshold scales with the gauge's own range so it stays
# imperceptible whether this is a 0-12 altitude gauge or a 0-100 thrust
# bar, while skipping the redraw once the lerp has visually settled.
var changed := absf(new_value - _value) > max_value * 0.001
_value = new_value
if changed:
queue_redraw()
func _draw() -> void:
+8 -2
View File
@@ -26,10 +26,16 @@ func set_heading(heading_deg: float) -> void:
_target_heading = heading_deg
# Below this, the tape has visually settled — skip the redraw.
const REDRAW_EPSILON_DEG := 0.05
func _process(delta: float) -> void:
var t := _smoothing_weight(delta)
_heading = fmod(lerp_angle_deg(_heading, _target_heading, t) + 360.0, 360.0)
queue_redraw()
var new_heading := fmod(lerp_angle_deg(_heading, _target_heading, t) + 360.0, 360.0)
var changed := absf(angle_delta_deg(_heading, new_heading)) > REDRAW_EPSILON_DEG
_heading = new_heading
if changed:
queue_redraw()
func _draw() -> void:
+8
View File
@@ -13,5 +13,13 @@ static func lerp_angle_deg(from: float, to: float, weight: float) -> float:
return rad_to_deg(lerp_angle(deg_to_rad(from), deg_to_rad(to), weight))
# Shortest signed distance from `from` to `to` in degrees, wrapped to
# [-180, 180]. Use this (never a plain subtraction) to decide whether an
# angle that wraps around (heading, roll) has moved enough to redraw —
# a naive delta gives a false-large jump at the 0/360 boundary.
static func angle_delta_deg(from: float, to: float) -> float:
return wrapf(to - from, -180.0, 180.0)
func _smoothing_weight(delta: float) -> float:
return 1.0 - exp(-SMOOTHING * delta) # frame-rate independent
-2
View File
@@ -25,8 +25,6 @@ Bugs found in an adversarial review. None are gameplay- or physics-affecting, so
## Performance
- [x] Gate `Ship._emit_telemetry_data()` — it runs `get_euler()` + trig per ship per physics tick for every ship, including AI ships nobody displays and `--headless` training where no HUD exists. Gate on having signal connections, and `set_physics_process(false)` when headless (`arena_boundary.gd` already does this correctly for its `_process`).
- [ ] Stop the HUD instruments redrawing once settled: all five `queue_redraw()` every frame forever, re-recording canvas items with `draw_string` glyph work, and `HUD.tscn` sets `process_mode = 3` so it continues while paused.
- [ ] Shared per-team materials instead of `Ship._apply_team_color()` allocating a fresh `StandardMaterial3D` and assigning it as `material_override` (and running at least twice per ship — once from `_ready`, once from the `team` setter). Removes the allocation and lets same-team ships batch.
- [ ] Merge each goal's visuals into one `ArrayMesh` with a hull surface and a net surface — currently 11 `MeshInstance3D`s and 4 materials per goal, ~22 draw calls for a static prop. `arena_boundary.gd:333` already demonstrates the `SurfaceTool` technique in this codebase.
- [ ] Merge the four non-tinted ship meshes (Hull/Canopy/EngineGlowL/R) into one — 6 draw calls per ship down to 3. Irrelevant at 1v1; 36 calls before VFX at 3v3.