Have Sud report its own tick-counted elapsed time to clients

Window._elapsed_min() (client/brewpi_gui.py) reconstructed the dynamic
forecast's x-axis from wall-clock time times the server's nominal
configured sim_warp_factor. But that warp factor is only nominal -
real asyncio/Python scheduling overhead across the 7 concurrent tasks
means the actually-achieved speedup runs measurably below it (~80x
instead of 100x, confirmed by timing HoldRemaining countdowns against
real elapsed time), especially under heavy message/print load. Since
the GUI's reconstruction assumed a precise, constant mapping, the live
measured trace visibly drifted behind the forecast - most visible on
short, transition-dense test schedules where the timing slip is a
large fraction of the total duration.

components/sud.py: Sud now tracks 'elapsed' (tick-counted simulated
seconds since the run started, frozen while PAUSED/IDLE/DONE, reset on
start()) - the ground truth for run progress, immune to real-world
pacing jitter. tasks/sud.py broadcasts it as {'Sud': {'Elapsed': ...}}.

client/brewpi_gui.py: replaced sud_start_time/sud_paused_total/
sud_pause_started_at and all the wall-clock reconstruction in
_elapsed_min() with sud_elapsed_seconds, set directly from the
server's 'Elapsed' broadcasts - simpler too, since pause-freezing is
now handled server-side rather than needing separate client-side
bookkeeping.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhiQe64F74uHV8jzuoSa5K
This commit is contained in:
2026-06-22 11:21:39 +02:00
co-authored by Claude Sonnet 4.6
parent 76d3a6047c
commit 44b1b0c705
3 changed files with 39 additions and 34 deletions
+12
View File
@@ -106,6 +106,7 @@ class Sud(AttributeChange):
self.step = None
self.user_message = None
self._paused_from = None
self.elapsed = 0.0
def save(self):
"""Returns the currently running sud.json document (EMPTY_SUD if
@@ -161,6 +162,7 @@ class Sud(AttributeChange):
if self.state not in (SudState.IDLE, SudState.DONE):
return
self.index = -1
self.elapsed = 0.0
self._advance()
def pause(self):
@@ -195,6 +197,16 @@ class Sud(AttributeChange):
self._finish_step()
def tick(self, dt):
"""Advances the run by dt simulated seconds. 'elapsed' is the tick-
counted ground truth for how far the run has actually progressed -
clients used to reconstruct this themselves from wall-clock time
times the configured warp factor, but the warp factor is only
nominal (real asyncio/Python scheduling overhead means the actual
achieved speedup runs measurably below it, especially under heavy
message/print load), making that reconstruction drift from the
truth. Frozen while PAUSED/IDLE/DONE, same as hold_remaining."""
if self.state not in (SudState.IDLE, SudState.DONE, SudState.PAUSED):
self.elapsed += dt
if self.state == SudState.HOLDING:
self.hold_remaining -= dt
if self.hold_remaining <= 0: