diff --git a/Game/scenes/HUD.tscn b/Game/scenes/HUD.tscn index 6228ac29..c38b96ca 100644 --- a/Game/scenes/HUD.tscn +++ b/Game/scenes/HUD.tscn @@ -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 diff --git a/Game/scripts/hud_attitude_indicator.gd b/Game/scripts/hud_attitude_indicator.gd index 6aea1420..b19dfba3 100644 --- a/Game/scripts/hud_attitude_indicator.gd +++ b/Game/scripts/hud_attitude_indicator.gd @@ -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: diff --git a/Game/scripts/hud_gauge.gd b/Game/scripts/hud_gauge.gd index 4aba4fc9..55cc9290 100644 --- a/Game/scripts/hud_gauge.gd +++ b/Game/scripts/hud_gauge.gd @@ -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: diff --git a/Game/scripts/hud_heading_tape.gd b/Game/scripts/hud_heading_tape.gd index df5a811c..858b6151 100644 --- a/Game/scripts/hud_heading_tape.gd +++ b/Game/scripts/hud_heading_tape.gd @@ -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: diff --git a/Game/scripts/hud_instrument.gd b/Game/scripts/hud_instrument.gd index 4b2c022f..4817c976 100644 --- a/Game/scripts/hud_instrument.gd +++ b/Game/scripts/hud_instrument.gd @@ -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 diff --git a/TODO.md b/TODO.md index 94f2528d..36ce1c23 100644 --- a/TODO.md +++ b/TODO.md @@ -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.