Called once per frame from `EngineState::begin_frame` after `delta_time` is updated. `dt_seconds` is wall-clock frame time.
(&mut self, dt_seconds: f64, renderer: &mut Renderer)
| 75 | /// Called once per frame from `EngineState::begin_frame` after |
| 76 | /// `delta_time` is updated. `dt_seconds` is wall-clock frame time. |
| 77 | pub fn tick(&mut self, dt_seconds: f64, renderer: &mut Renderer) { |
| 78 | if !self.enabled { |
| 79 | return; |
| 80 | } |
| 81 | |
| 82 | let frame_ms = (dt_seconds * 1000.0) as f32; |
| 83 | // EMA bootstrap — first frame after enable, seed the buffer |
| 84 | // with the observed value so we don't spend 10 frames climbing |
| 85 | // from 0 toward the steady state. |
| 86 | if self.ema_ms == 0.0 { |
| 87 | self.ema_ms = frame_ms; |
| 88 | } else { |
| 89 | self.ema_ms += EMA_ALPHA * (frame_ms - self.ema_ms); |
| 90 | } |
| 91 | |
| 92 | self.frames_since_step = self.frames_since_step.saturating_add(1); |
| 93 | if self.frames_since_step < COOLDOWN_FRAMES { |
| 94 | return; |
| 95 | } |
| 96 | |
| 97 | let target = self.target_frame_ms; |
| 98 | if self.ema_ms > target * HYSTERESIS_DOWN && self.idx > 0 { |
| 99 | self.idx -= 1; |
| 100 | renderer.set_render_scale(STEPS[self.idx]); |
| 101 | self.frames_since_step = 0; |
| 102 | } else if self.ema_ms < target * HYSTERESIS_UP && self.idx + 1 < STEPS.len() { |
| 103 | self.idx += 1; |
| 104 | renderer.set_render_scale(STEPS[self.idx]); |
| 105 | self.frames_since_step = 0; |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | pub fn current_scale(&self) -> f32 { STEPS[self.idx] } |
| 110 | } |
no test coverage detected